2009-07-24 39 views
38

Tôi có một BitmapImage WPF mà tôi nạp từ một file .JPG, như sau:Tìm màu điểm ảnh cụ thể của một BitmapImage

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

tôi muốn truy vấn như những gì màu sắc là tại các điểm cụ thể. Ví dụ, giá trị RGB ở điểm ảnh (65,32) là bao nhiêu?

Tôi làm cách nào để thực hiện việc này? Tôi đã thực hiện cách tiếp cận này:

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); 

Mặc dù tôi sẽ thú nhận có một chút khỉ, con khỉ đang thực hiện với mã này. Dù sao, có cách nào đơn giản để xử lý mảng byte này để chuyển đổi sang giá trị RGB không?

+0

Vì mục đích gì? Và tại sao bạn thêm 7 và chia cho 8 trong tính toán nStride? – Jviaches

+3

@Jviaches Thêm 7 và chia cho 8 để chính xác vòng thành đủ byte (f.i. 10 bit sẽ cần 2 byte.) – erikH

Trả lời

13

Việc giải thích mảng byte kết quả phụ thuộc vào định dạng pixel của bitmap nguồn, nhưng trong trường hợp đơn giản nhất là 32 bit, hình ảnh ARGB, mỗi pixel sẽ bao gồm bốn byte trong mảng byte. Pixel đầu tiên sẽ được giải thích thusly:

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

Để xử lý mỗi điểm ảnh trong hình ảnh, bạn có lẽ muốn tạo vòng lồng nhau để đi bộ các hàng và các cột, incrementing một biến chỉ số bằng số byte trong mỗi pixel.

Một số loại bitmap kết hợp nhiều pixel thành một byte. Ví dụ, một hình ảnh đơn sắc gói tám pixel vào mỗi byte. Nếu bạn cần xử lý các hình ảnh khác với 24/32 bit trên mỗi pixel (những hình ảnh đơn giản), thì tôi khuyên bạn nên tìm một cuốn sách hay bao gồm cấu trúc nhị phân cơ bản của bitmap.

+4

Cảm ơn, nhưng ... tôi cảm thấy như.NET framework nên có một số giao diện tóm tắt tất cả các phức tạp mã hóa. Rõ ràng là thư viện System.Windows.Media biết cách giải mã một bitmap, vì nó hiển thị nó trên màn hình. Tại sao tôi phải biết tất cả các chi tiết triển khai? Tôi không thể sử dụng logic kết xuất đã được triển khai bằng cách nào đó? –

+2

Tôi cảm thấy đau đớn của bạn. Trong các dạng cửa sổ, lớp Bitmap có các phương thức GetPixel và SetPixel để lấy và thiết lập giá trị của từng pixel riêng lẻ, nhưng các phương thức này nổi tiếng chậm và tất cả trừ vô dụng trừ các trường hợp rất đơn giản. Bạn có thể sử dụng RenderTargetBitmap và các đối tượng cấp cao hơn để tạo và bitmap tổng hợp, nhưng theo ý kiến ​​của tôi, xử lý hình ảnh tốt nhất còn lại trong miền của C++, nơi truy cập trực tiếp vào các bit phù hợp hơn. Chắc chắn, bạn CÓ THỂ làm xử lý hình ảnh trong C# /. NET, nhưng theo kinh nghiệm của tôi, nó luôn luôn cảm thấy như cố gắng để đặt một hình vuông peg thông qua một lỗ tròn. –

+11

Tôi không đồng ý. Theo tôi, C# là lựa chọn tốt hơn C++ cho 90% nhu cầu xử lý ảnh. Ngôn ngữ C# có nhiều ưu điểm so với C++ và từ khóa "không an toàn" và "cố định" cho phép bạn truy cập trực tiếp vào các bit a la C++ khi bạn cần chúng. Trong hầu hết các trường hợp, việc triển khai C# của thuật toán xử lý hình ảnh sẽ rất giống với tốc độ của C++, nhưng đôi khi nó sẽ chậm hơn đáng kể. Tôi sẽ chỉ sử dụng C++ trong vài trường hợp mà C# sẽ thực hiện kém do tối ưu hóa bị bỏ qua bởi trình biên dịch JIT. –

53

Sau đây là cách tôi sẽ thao tác pixel trong C# sử dụng mảng đa chiều:

[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; 
} 

sử dụng:

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

Nếu bạn muốn cập nhật pixel, rất giống mã số hoạt động ngoại trừ bạn sẽ tạo ra một WritableBitmap và sử dụng điều này:

public void PutPixels(WritableBitmap 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); 
} 

do đó:

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

PutPixels(bitmap, pixels, 7, 7); 

Lưu ý rằng mã này chuyển đổi bitmap thành Bgra32 nếu chúng đến định dạng khác. Điều này nói chung là nhanh, nhưng trong một số trường hợp có thể là một nút cổ chai hiệu suất, trong trường hợp này, kỹ thuật này sẽ được sửa đổi để phù hợp với định dạng đầu vào cơ bản chặt chẽ hơn.

Cập nhật

Kể từ BitmapSource.CopyPixels không chấp nhận một mảng hai chiều nó là cần thiết để chuyển đổi mảng giữa một chiều và hai chiều. Phương pháp mở rộng sau đây sẽ làm các trick:

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 
} 

Có hai triển khai ở đây: Đầu tiên là nhanh chóng nhưng sử dụng mã không an toàn để có được một IntPtr đến một mảng (phải biên dịch với/tùy chọn không an toàn). Cách thứ hai chậm hơn nhưng không yêu cầu mã không an toàn. Tôi sử dụng phiên bản không an toàn trong mã của mình.

WritePixels chấp nhận mảng hai chiều, do đó không có phương pháp mở rộng là bắt buộc.

+2

Khi tôi thử điều này tôi nhận được "mảng đầu vào không phải là một thứ hạng hợp lệ". Ý tưởng nào? – whitehawk

+0

@whitehawk: xem bài đăng này: http://stackoverflow.com/questions/3418494/input-array-is-not-a-valid-rank-error-message-when-using-copypixels-method/3418567#3418567 –

+0

@whitehawk: Cảm ơn vì đã cho tôi biết về lỗi này. Tôi đã cập nhật câu trả lời của mình để hiển thị cách khắc phục. Trong mã của riêng tôi, tôi đã thực sự sử dụng một quá tải CopyPixels khác nhau mà phải mất một IntPtr. –

14

Tôi muốn thêm vào Ray's câu trả lời mà bạn cũng có thể tuyên bố PixelColor struct như một liên minh:

[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; 
} 

Và cách mà bạn cũng sẽ có quyền truy cập vào các UInit32 BGRA (cho điểm ảnh nhanh truy cập hoặc sao chép), bên cạnh các thành phần byte riêng lẻ.

4

Tôi muốn cải thiện câu trả lời của Ray - không đủ đại diện để nhận xét. > :(Phiên bản này có tốt nhất của cả hai an toàn/quản lý, và hiệu quả của phiên bản không an toàn.Ngoài ra, tôi đã thực hiện đi với đi qua trong stride như tài liệu. Net cho CopyPixels nói rằng đó là bước tiến của bitmap, không Mặc dù mảng PixelColor phải là cùng một bước tiến như bitmap (để có thể thực hiện nó như một cuộc gọi một bản sao), nó có ý nghĩa để tạo ra một cái mới mảng trong hàm cũng dễ dàng như chiếc bánh

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; 
    } 
+0

Lưu ý: Tốt nhất của quản lý không có nghĩa là mã này an toàn. Mã này sẽ không chạy an toàn (hoặc ít nhất tôi cũng không thể nhận được mã này). – Peter

+1

Đối với phương pháp này để làm việc mảng phải được [chiều cao, chiều rộng] –

0

Một nhận xét nhỏ:..

Nếu bạn đang cố gắng sử dụng mã này (Edit: được cung cấp bởi Ray Burns), nhưng gặp phải lỗi về mảng của xếp hạng, hãy thử chỉnh sửa các phương thức mở rộng như sau :

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

và sau đó gọi phương thức CopyPixels như thế này:

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

Vấn đề là, khi các phương pháp khuyến nông không khác so với bản gốc, một bản gốc được gọi. Tôi đoán điều này là do PixelColor[,] cũng phù hợp với Array.

Tôi hy vọng điều này sẽ giúp bạn nếu bạn gặp vấn đề tương tự.

2

Nếu bạn muốn chỉ là một màu Pixel:

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; 
    } 
+0

Cảm ơn, rất hữu ích cho việc này trong WPF: http://www.functionx.com/vcsharp/gdi/colorselector.htm – ToastyMallows

3

tôi mất tất cả các ví dụ và tạo ra một tốt hơn một chút một - thử nghiệm nó quá
(các lỗ hổng duy nhất được rằng ma thuật 96 như DPI mà thực sự nghe trộm)

tôi cũng so sánh chiến thuật này WPF so với:

  • GDI bằng cách sử dụng đồ họa (System.Drawing)
  • Interop bằng cách trực tiếp gọi GetPixel từ gdi32.dll

Để supprise tôi,
này hoạt động nhanh hơn so với x10 GDI, và xung quanh lần x15 nhanh hơn sau đó Interop.
Vì vậy, nếu bạn đang sử dụng WPF - tốt hơn nhiều để làm việc với điều này để có được màu pixel của bạn.

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; 
    } 
} 
0

Bạn có thể lấy các thành phần màu trong mảng byte. Đầu tiên sao chép các pixel trong 32 bit vào mảng và chuyển đổi thành mảng 8 bit với kích thước lớn hơn 4 lần

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...] 
+0

Tôi giả sử dòng chính xác phải là: byte [] colorArray = byte mới [pixelArray.Length * 4]; –

Các vấn đề liên quan