source

비트맵 이미지의 특정 픽셀 색상 찾기

itover 2023. 4. 17. 21:45
반응형

비트맵 이미지의 특정 픽셀 색상 찾기

에서 로드한 WPF 비트맵이미지가 있어요다음과 같은 JPG 파일:

this.m_image1.Source = new BitmapImage(new Uri(path));

나는 특정 지점에서의 색상에 대해 질문하고 싶다.예를 들어 픽셀(65,32)에서의 RGB 값은 얼마입니까?

이거 어떻게 해야 되지?저는 다음과 같은 접근방식을 취했습니다.

ImageSource ims = m_image1.Source;
BitmapImage bitmapImage = (BitmapImage)ims;
int height = bitmapImage.PixelHeight;
int width = bitmapImage.PixelWidth;
int nStride = (bitmapImage.PixelWidth * bitmapImage.Format.BitsPerPixel + 7) / 8;
byte[] pixelByteArray = new byte[bitmapImage.PixelHeight * nStride];
bitmapImage.CopyPixels(pixelByteArray, nStride, 0);

원숭이를 보는 눈이 조금 있긴 하지만 원숭이는 이 코드를 계속 사용합니다.어쨌든, 이 바이트 배열을 RGB 값으로 변환하는 간단한 방법이 있을까요?

다차원 배열을 사용하여 C#의 픽셀을 조작하는 방법은 다음과 같습니다.

[StructLayout(LayoutKind.Sequential)]
public struct PixelColor
{
  public byte Blue;
  public byte Green;
  public byte Red;
  public byte Alpha;
}

public PixelColor[,] GetPixels(BitmapSource source)
{
  if(source.Format!=PixelFormats.Bgra32)
    source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);

  int width = source.PixelWidth;
  int height = source.PixelHeight;
  PixelColor[,] result = new PixelColor[width, height];

  source.CopyPixels(result, width * 4, 0);
  return result;
}

사용방법:

var pixels = GetPixels(image);
if(pixels[7, 3].Red > 4)
{
  ...
}

는, 「」를 작성하는 것 에는, 한 코드가 합니다.WriteableBitmap , 으면 안 돼요.

public void PutPixels(WriteableBitmap bitmap, PixelColor[,] pixels, int x, int y)
{
  int width = pixels.GetLength(0);
  int height = pixels.GetLength(1);
  bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width*4, x, y);
}

따라서 다음과 같이 됩니다.

var pixels = new PixelColor[4, 3];
pixels[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 };

PutPixels(bitmap, pixels, 7, 7);

이 코드는 비트맵이 다른 형식으로 도착하면 Bgra32로 변환됩니다.이것은 일반적으로 빠르지만 경우에 따라서는 성능 병목현상이 발생할 수 있습니다.이 경우 이 기술은 기본 입력 형식에 더 가깝게 수정될 수 있습니다.

갱신하다

★★BitmapSource.CopyPixels2번입니다.배열을 1차원과 2차원으로 변환해야 합니다.다음 확장 메서드로 문제를 해결할 수 있습니다.

public static class BitmapSourceHelper
{
#if UNSAFE
  public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset)
  {
    fixed(PixelColor* buffer = &pixels[0, 0])
      source.CopyPixels(
        new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
        (IntPtr)(buffer + offset),
        pixels.GetLength(0) * pixels.GetLength(1) * sizeof(PixelColor),
        stride);
  }
#else
  public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset)
  {
    var height = source.PixelHeight;
    var width = source.PixelWidth;
    var pixelBytes = new byte[height * width * 4];
    source.CopyPixels(pixelBytes, stride, 0);
    int y0 = offset / width;
    int x0 = offset - width * y0;
    for(int y=0; y<height; y++)
      for(int x=0; x<width; x++)
        pixels[x+x0, y+y0] = new PixelColor
        {
          Blue  = pixelBytes[(y*width + x) * 4 + 0],
          Green = pixelBytes[(y*width + x) * 4 + 1],
          Red   = pixelBytes[(y*width + x) * 4 + 2],
          Alpha = pixelBytes[(y*width + x) * 4 + 3],
        };
  }
#endif
}

여기에는 다음 두 가지 구현이 있습니다.첫 번째는 빠르지만 안전하지 않은 코드를 사용하여 IntPtr을 어레이로 가져옵니다(/unsafe 옵션을 사용하여 컴파일해야 함).두 번째는 속도가 느리지만 안전하지 않은 코드가 필요하지 않습니다.내 코드에 안전하지 않은 버전을 사용합니다.

WritePixels는 2차원 배열을 지원하므로 확장 방식은 필요하지 않습니다.

편집: Jerry가 코멘트에서 지적했듯이 메모리 레이아웃 때문에 2차원 배열은 먼저 수직 좌표를 가집니다.즉, 픽셀로 치수를 지정해야 합니다.높이, 폭은 픽셀[폭, 높이]가 아니며 픽셀[y,x]로 지정됩니다.

PixelColor 구조를 유니언으로 선언할 수도 있다는 Ray의 답변을 덧붙이고 싶습니다.

[StructLayout(LayoutKind.Explicit)]
public struct PixelColor
{
    // 32 bit BGRA 
    [FieldOffset(0)] public UInt32 ColorBGRA;
    // 8 bit components
    [FieldOffset(0)] public byte Blue;
    [FieldOffset(1)] public byte Green;
    [FieldOffset(2)] public byte Red;
    [FieldOffset(3)] public byte Alpha;
}

그러면 개별 바이트 구성 요소 외에도 UInit32 BGRA(빠른 픽셀 액세스 또는 복사)에 액세스할 수 있습니다.

결과 바이트 배열의 해석은 소스 비트맵의 픽셀 형식에 따라 다르지만, 가장 단순한 32비트 ARGB 이미지의 경우 각 픽셀은 바이트 배열의 4바이트로 구성됩니다.첫 번째 픽셀은 다음과 같이 해석됩니다.

alpha = pixelByteArray[0];
red   = pixelByteArray[1];
green = pixelByteArray[2];
blue  = pixelByteArray[3];

이미지 내의 각 픽셀을 처리하려면 행과 열을 이동하는 중첩된 루프를 생성하여 각 픽셀의 바이트 수만큼 인덱스 변수를 증가시킬 수 있습니다.

일부 비트맵 유형은 여러 픽셀을 하나의 바이트로 결합합니다.예를 들어, 흑백 이미지는 각 바이트에 8개의 픽셀을 채웁니다.픽셀당 24/32비트 이외의 이미지(간단한 이미지)를 취급해야 한다면 비트맵의 기본 바이너리 구조를 다루는 좋은 책을 추천합니다.

나는 Ray의 답변을 개선하고 싶다. - 코멘트가 충분하지 않다.>:(이 버전은 안전한/관리된 버전과 안전하지 않은 버전의 효율성 중 가장 좋은 점을 모두 갖추고 있습니다.또한, 나는 더 보폭으로 패스하는 것을 없앴다.CopyPixels의 넷 문서에는 버퍼가 아닌 비트맵의 스트라이드라고 되어 있습니다.오해의 소지가 있어 함수 내부에서 계산할 수 있습니다.PixelColor 어레이는 비트맵과 같은 스트라이드여야 하기 때문에 (단일 복사 호출로 실행할 수 있도록) 함수에 새로운 어레이를 작성하는 것도 의미가 있습니다.식은 죽 먹기다.

    public static PixelColor[,] CopyPixels(this BitmapSource source)
    {
        if (source.Format != PixelFormats.Bgra32)
            source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
        PixelColor[,] pixels = new PixelColor[source.PixelWidth, source.PixelHeight];
        int stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7) / 8);
        GCHandle pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned);
        source.CopyPixels(
          new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
          pinnedPixels.AddrOfPinnedObject(),
          pixels.GetLength(0) * pixels.GetLength(1) * 4,
              stride);
        pinnedPixels.Free();
        return pixels;
    }

는 모든 를 다 더 예를 . - . - 시험해 보다.
로 하는 (Magic 96 DPI)

또한 이 WPF 전술과 다음을 비교했습니다.

  • 그래픽스를 사용한 GDI(system.drawing)
  • GDI32에서 직접 GetPixel을 호출하여 상호 운용합니다.DLL

을 위하여
GDI 10 10 、 Interop 다 15 、
WPF 를 사용하고 있는 경우는, 이것을 사용해 픽셀의 색을 얻는 것이 훨씬 좋습니다.

public static class GraphicsHelpers
{
    public static readonly float DpiX;
    public static readonly float DpiY;

    static GraphicsHelpers()
    {
        using (var g = Graphics.FromHwnd(IntPtr.Zero))
        {
            DpiX = g.DpiX;
            DpiY = g.DpiY;
        }
    }

    public static Color WpfGetPixel(double x, double y, FrameworkElement AssociatedObject)
    {
        var renderTargetBitmap = new RenderTargetBitmap(
            (int)AssociatedObject.ActualWidth,
            (int)AssociatedObject.ActualHeight,
            DpiX, DpiY, PixelFormats.Default);
        renderTargetBitmap.Render(AssociatedObject);

        if (x <= renderTargetBitmap.PixelWidth && y <= renderTargetBitmap.PixelHeight)
        {
            var croppedBitmap = new CroppedBitmap(
                renderTargetBitmap, new Int32Rect((int)x, (int)y, 1, 1));
            var pixels = new byte[4];
            croppedBitmap.CopyPixels(pixels, 4, 0);
            return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
        }
        return Colors.Transparent;
    }
}

소견:

이 코드(Edit: by Ray Burns)를 사용하려고 하는데 어레이의 등급에 대한 오류가 나타나면 다음과 같이 확장 방법을 편집해 보십시오.

public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset, bool dummy)

후, 「」를 호출합니다.CopyPixels하다

source.CopyPixels(result, width * 4, 0, false);

문제는 확장 방식이 기존 방식과 다르지 않은 경우 원래 방식이 호출된다는 것입니다.이거는 아무래도PixelColor[,]Array뿐만 아니라.

같은 문제가 생겼다면 이게 도움이 됐으면 좋겠어요.

픽셀 색상을 하나만 원하는 경우:

using System.Windows.Media;
using System.Windows.Media.Imaging;
...
    public static Color GetPixelColor(BitmapSource source, int x, int y)
    {
        Color c = Colors.White;
        if (source != null)
        {
            try
            {
                CroppedBitmap cb = new CroppedBitmap(source, new Int32Rect(x, y, 1, 1));
                var pixels = new byte[4];
                cb.CopyPixels(pixels, 4, 0);
                c = Color.FromRgb(pixels[2], pixels[1], pixels[0]);
            }
            catch (Exception) { }
        }
        return c;
    }

훨씬 더 간단하죠.데이터를 복사할 필요가 없습니다. 을 사용하다하지만 여기에는 대가가 따른다: 포인터와unsafe특정 상황에서 빠르고 쉽게 할 가치가 있는지 여부를 판단합니다(단, 이미지 조작을 다른 안전하지 않은 클래스에 넣으면 나머지 프로그램은 영향을 받지 않습니다).

var bitmap = new WriteableBitmap(image);
data = (Pixel*)bitmap.BackBuffer;
stride = bitmap.BackBufferStride / 4;
bitmap.Lock();

// getting a pixel value
Pixel pixel = (*(data + y * stride + x));

bitmap.Unlock();

어디에

[StructLayout(LayoutKind.Explicit)]
protected struct Pixel {
  [FieldOffset(0)]
  public byte B;
  [FieldOffset(1)]
  public byte G;
  [FieldOffset(2)]
  public byte R;
  [FieldOffset(3)]
  public byte A;
}

에러 체크(포맷이 실제로 BGRA인지 아닌지의 여부 및 그렇지 않은 경우의 처리)는 독자에게 맡겨집니다.

색 구성 요소는 바이트 배열로 가져올 수 있습니다.먼저 32비트의 픽셀을 어레이에 복사하여 4배 큰 크기의 8비트 어레이로 변환합니다.

int[] pixelArray = new int[stride * source.PixelHeight];

source.CopyPixels(pixelArray, stride, 0);

// byte[] colorArray = new byte[pixelArray.Length];
// EDIT:
byte[] colorArray = new byte[pixelArray.Length * 4];

for (int i = 0; i < colorArray.Length; i += 4)
{
    int pixel = pixelArray[i / 4];
    colorArray[i] = (byte)(pixel >> 24); // alpha
    colorArray[i + 1] = (byte)(pixel >> 16); // red
    colorArray[i + 2] = (byte)(pixel >> 8); // green
    colorArray[i + 3] = (byte)(pixel); // blue
}

// colorArray is an array of length 4 times more than the actual number of pixels
// in the order of [(ALPHA, RED, GREEN, BLUE), (ALPHA, RED...]

언급URL : https://stackoverflow.com/questions/1176910/finding-specific-pixel-colors-of-a-bitmapimage

반응형