使用Lockbits在位图中获取RGB值的.NET方法

3

我正在使用以下代码从图像中提取RGB值,有时这个方法可以正常工作,但是在某些文件中(似乎Stride不能被位图的宽度整除)它会返回混乱的值:

Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(rect, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format24bppRgb)
Dim ptr As IntPtr = bmpData.Scan0
Dim cols As New List(Of Color)
Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmp.Height
Dim rgbValues(bytes - 1) As Byte
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)

' Retrieve RGB values
For i = modByte To rgbValues.Length Step 3
     cols.Add(Color.FromArgb(rgbValues(i + 2), rgbValues(i + 1), rgbValues(i)))
Next

bmp.UnlockBits(bmpData)
bmp.Dispose()
Dim colsCnt As List(Of RgbPixels) = cols.GroupBy(Function(g) New With {Key .R = g.R, Key .G = g.G, Key .B = g.B}).Select(Function(s) New RgbPixels With {.Colour = Color.FromArgb(s.Key.R, s.Key.G, s.Key.B), .Amount = s.Count()}).ToList()

将结果颜色分组后,其值类似于:

R    G    B
255  255  255
255  255  0
255  0    0
0    0    255
0    255  255

有些情况下会出现这种变形,但实际上它们应该是:

R    G    B
255  255  255
0    0    0

请指导我正确的方向,顺便说一下,我的源bmp也是PixelFormat.Format24bppRgb格式,所以我不认为这是问题。如果您只能用C#回答,那也没问题。

1个回答

5
问题在于您没有考虑步长值。步长总是填充的,使得每个图像行的字节数组宽度可被4整除。这是与内存复制和CPU工作方式相关的优化措施,可以追溯几十年之前,至今仍然很有用。
例如,如果一幅图像的宽度为13个像素,则步长将如下所示(简化为一个分量):
=============    (width 13 pixels = 13 bytes when using RGB)
================ (stride would be 16)

对于一个包含14个像素的图片,它将会是这样的:

==============   (width 14 pixels = 14 bytes when using RGB)
================ (stride would still be 16)

因此,在您的代码中,您需要处理一个跨度行而不是一个字节数组,除非您正在使用固定和定义的图像宽度。

我修改了您的代码,使其通过跨度跳过行:

Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(rect, Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format24bppRgb)
Dim ptr As IntPtr = bmpData.Scan0
Dim cols As New List(Of Color)
Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmp.Height
Dim rgbValues(bytes - 1) As Byte
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)

Dim x, y, dx, l as Integer

For y = 0 To rect.Height - 1

    l = y * bmpData.Stride 'calulate line based on stride

    For x = 0 To rect.Width - 1

        dx = l + x * 3  '3 for RGB, 4 for ARGB, notice l is used as offset

        cols.Add(Color.FromArgb(rgbValues(dx + 2), _
                                rgbValues(dx + 1), _
                                rgbValues(dx)))
    Next
Next

' Retrieve RGB values
'For i = modByte To rgbValues.Length Step 3
'     cols.Add(Color.FromArgb(rgbValues(i + 2), rgbValues(i + 1), rgbValues(i)))
'Next

bmp.UnlockBits(bmpData)
bmp.Dispose()

谢谢,那个微调似乎解决了问题。不过有一个小问题需要确认,我注意到你正在以BGR的方式读取数组中的值,我看到有些人对此持有不同意见;有些人说它肯定是RGB,请确认一下。 - Azza
在这种情况下,BGR是正确的。这与Intel/Win平台相关的小端/大端/字节顺序和指针与整数之间的转换有关。 - user1693593

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接