分析图像的颜色 [英] Analyze colors of an Image
问题描述
我裁剪了一部分图像,并通过12个轨迹栏定义了2个颜色范围(H/S/L).我还有一个精度/速度"滑块,范围从1到10.
I have part of an Image cropped out, and 2 Color Ranges (H/S/L) defined via 12 trackbars. I also have a "Precision/Speed" slider ranging from 1 to 10.
我需要分析多少个图像像素落入每个指定的颜色范围.
基于精度/速度滑块,我跳过了一些行/像素.
I need to analyze how many pixels of the Image fall into each of the specified Color Ranges.
Based on the precision/speed slider, I skip some rows/pixels.
它运作良好,但速度太慢.高精度(跟踪条值= 1)大约需要550毫秒.
精度低但速度快(跟踪条值= 10)时,大约需要5毫秒.
Its working great but its too slow. With high precision (trackbar value = 1), it takes about 550 ms.
With low precision but high speed (trackbar value = 10) it takes about 5 ms.
有没有办法加快此代码的速度?理想情况下,我需要将其速度提高5倍.
Is there a way to speed this code up? Ideally I would need it to be 5 times faster.
For y As Integer = 0 To 395
If y Mod 2 = 0 Then
startpixel = tbval / 2
Else
startpixel = 0
End If
If y Mod tbval = 0 Then
For x As Integer = 0 To 1370
If x Mod tbval - startpixel = 0 Then
analyzedpixels = analyzedpixels + 1
Dim pColor As Color = crop.GetPixel(x, y)
Dim h As Integer = pColor.GetHue
Dim s As Integer = pColor.GetSaturation * 100
Dim l As Integer = pColor.GetBrightness * 100
'verify if it is part of the first color
If h >= h1min And h <= h1max And s >= s1min And s <= s1max And l >= l1min And l <= l1max Then
color1pixels = color1pixels + 1
End If
If h >= h2min And h <= h2max And s >= s2min And s <= s2max And l >= l2min And l <= l2max Then
color2pixels = color2pixels + 1
End If
End If
Next
End If
Next
这是工作代码.
Dim rect As New Rectangle(0, 0, crop.Width, crop.Height)
Dim bdata As Imaging.BitmapData = crop.LockBits(rect, Imaging.ImageLockMode.ReadOnly, crop.PixelFormat)
Dim ptr As IntPtr = bdata.Scan0
Dim bytes As Integer = Math.Abs(bdata.Stride) * crop.Height
Dim rgbValues As Byte() = New Byte(bytes - 1) {}
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)
For i As Integer = 0 To crop.Height - 1
If i Mod 2 = 0 Then
startpixel = tbval / 2
Else
startpixel = 0
End If
If i Mod tbval = 0 Then
For j As Integer = 0 To crop.Width - 1
If j Mod tbval - startpixel = 0 Then
analyzedpixels = analyzedpixels + 1
Dim position = (bdata.Stride * i) + j * 4
Dim c = Color.FromArgb(BitConverter.ToInt32(rgbValues, position))
Dim h As Integer = c.GetHue
Dim s As Integer = c.GetSaturation * 100
Dim l As Integer = c.GetBrightness * 100
If h >= h1min And h <= h1max And s >= s1min And s <= s1max And l >= l1min And l <= l1max Then
color1pixels = color1pixels + 1
End If
If h >= h2min And h <= h2max And s >= s2min And s <= s2max And l >= l2min And l <= l2max Then
color2pixels = color2pixels + 1
End If
End If
stride += 4
Next
End If
Next
crop.UnlockBits(bdata)
推荐答案
When performing sequential operations on a Bitmap's color data, the Bitmap.LockBits method can provide a huge increase in performace, since the Bitmap data needs to be loaded in memory just once, as opposed to sequential GetPixel/SetPixel calls: each call will load a partial section of the Bitmap data in memory and then discard it, to repeat the process when these methods are called again.
如果相反需要单次调用GetPixel/SetPixel,则这些方法可能比Bitmap.LockBits()
具有性能优势.但是,在这种情况下,实际上,性能不是一个因素.
If a single call to GetPixel/SetPixel is needed instead, these methods may have a performace advantage over Bitmap.LockBits()
. But, in this case, performace is not a factor, in practice.
Bitmap.LockBits()
的工作方式:
How Bitmap.LockBits()
works:
这是函数调用:
public BitmapData LockBits (Rectangle rect, ImageLockMode flags, PixelFormat format);
// VB.Net
Public LockBits (rect As Rectangle, flags As ImageLockMode, format As PixelFormat) As BitmapData
-
rect As Rectangle
:此参数指定我们感兴趣的Bitmap数据部分;该部分的字节将被加载到内存中.它可以是位图的整个大小,也可以是它的较小部分.rect As Rectangle
: This parameter specifies the section of the Bitmap data we're interested in; this section's bytes will be loaded in memory. It can be the whole size of the Bitmap or a smaller section of it.flags As
它也可以用来指定-设置ImageLockMode.UserInputBuffer
- BitmapData.Scan0 属性是将指针(IntPtr
)指向存储位图数据的初始内存位置.
该属性允许指定已经存储了预先存在的位图数据缓冲区的存储位置.使用指针在进程之间交换位图数据时,此功能很有用.
请注意,有关ImageLockMode.UserInputBuffer
的MSDN文档令人困惑(如果不是错误的话).flags As
ImageLockMode
: Specifies the type of lock to perform. The access to the memory to can be limited to Read or Write, or concurrent Read/Write operations are allowed.
It can be also used to specify - settingImageLockMode.UserInputBuffer
- that the BitmapData object is provided by the calling code.
TheBitmapData
object defines some of the Bitmap properties (Width
andHeight
of the Bitmap, width of the scan line (theStride
: number of bytes that compose a single line of pixels, represented by theBitmap.Width
multiplied by the number of bytes per pixel, rounded to a 4-bytes boundary. See the note about theStride
).
The BitmapData.Scan0 property is the Pointer (IntPtr
) to the initial memory location where the Bitmap data is stored.
This property allows to specify the memory location where a pre-existing Bitmap data buffer is already stored. It becomes useful when Bitmap data is exchanged between processes using Pointers.
Note that the MSDN documentation aboutImageLockMode.UserInputBuffer
is confusing (if not wrong).关于步幅的重要说明:
如前所述,
Stride
(也称为扫描线)表示组成一行像素的字节数.由于硬件对齐要求,它总是四舍五入到4字节边界(4的整数倍).As mentioned before, the
Stride
(also called scan-line) represents the number of bytes that compose a single line of pixels. Because of harware alignment requirements, it's always rounded up to a 4-bytes boundary (an integer number multiple of 4).Stride = [Bitmap Width] * [bytes per Color] Stride += (Stride Mod 4) * [bytes per Color]
这是我们始终使用通过
PixelFormat.Format32bppArgb
创建的位图的原因之一:位图的Stride
始终已与所需边界对齐.This is one of the reasons why we always work with Bitmaps created with
PixelFormat.Format32bppArgb
: the Bitmap'sStride
is always already aligned to the required boundary.如果位图的格式改为
PixelFormat.Format24bppRgb
(每种颜色3个字节),该怎么办?如果位图的
Width
乘以每像素字节数不是4
的倍数,则Stride
将用0
填充以填补空白.If the Bitmap's
Width
multiplied by the Bytes per Pixels is not a multiple of4
, theStride
will be padded with0
s to fill the gap.大小为
(100 x 100)
的位图在32位和24位格式中都不会填充:A Bitmap of size
(100 x 100)
will have no padding in both 32 bit and 24 bit formats:100 * 3 = 300 : 300 Mod 4 = 0 : Stride = 300 100 * 4 = 400 : 400 Mod 4 = 0 : Stride = 400
大小为
(99 x 100)
的位图将有所不同:It will be different for a Bitmap of size
(99 x 100)
:99 * 3 = 297 : 297 Mod 4 = 1 : Stride = 297 + ((297 Mod 4) * 3) = 300 99 * 4 = 396 : 396 Mod 4 = 0 : Stride = 396
填充24位位图的
Stride
,添加3个字节(设置为0
)以填充边界.The
Stride
of a 24 bit Bitmap is padded adding 3 bytes (set to0
) to fill the boundary.当我们检查/修改通过其坐标访问单个Pixel的内部值时,这不是问题,类似于SetPixel/GetPixel的操作方式:始终可以正确找到Pixel的位置.
It's not a problem when we inspect/modify internal values accessing single Pixels by their coordinates, similar to how SetPixel/GetPixel operate: the position of a Pixel will always be found correctly.
假设我们需要检查/更改大小为
(99 x 100)
的位图中位置(98, 70)
处的像素.
仅考虑每个像素的字节数.缓冲区内的像素位置为:Suppose we need to inspect/change a Pixel at position
(98, 70)
in a Bitmap of size(99 x 100)
.
Considering only the bytes per pixel. The pixel position inside the Buffer is:[Bitmap] = new Bitmap(99, 100, PixelFormat = Format24bppRgb) [Bytes x pixel] = Image.GetPixelFormatSize([Bitmap].PixelFormat) / 8 [Pixel] = new Point(98, 70) [Pixel Position] = ([Pixel].Y * [BitmapData.Stride]) + ([Pixel].X * [Bytes x pixel]) [Color] = Color.FromArgb([Pixel Position] + 2, [Pixel Position] + 1, [Pixel Position])
将像素的垂直位置乘以扫描线的宽度,缓冲区内的位置将始终是正确的:填充大小包含在计算中.
下一个位置(0, 71)
的像素颜色将返回预期的结果:Multiplying the Pixel's vertical position by the width of the scan line, the position inside the buffer will always be correct: the padded size is included in the calculation.
The Pixel Color at the next position,(0, 71)
, will return the expected results:顺序读取颜色字节时会有所不同.
第一条扫描线将返回直到最后一个像素(最后3个字节)的有效结果:接下来的3个字节将返回用于舍入Stride
的字节的值,所有字节均设置为0
.It will be different when reading color bytes sequentially.
The first scan line will return valid results up to the last Pixel (the last 3 bytes): the next 3 bytes will return the value of the bytes used to round theStride
, all set to0
.这也可能不是问题.例如,应用一个滤镜,使用滤镜矩阵的值读取并修改代表像素的每个字节序列:我们只需要修改3个字节的序列即可,在渲染位图时将不考虑该序列.
This might also not be a problem. For example, applying a filter, each sequence of bytes that represent a pixel is read and modifyed using the values of the filter's matrix: we would just modify a sequence of 3 bytes that won't be considered when the Bitmap is rendered.
但是如果我们要搜索特定的像素序列就很重要:读取不存在的像素颜色可能会损害结果和/或使算法失衡.
对位图的颜色执行统计analisys时,情况相同.But it does matter if we are searching for specific sequences of pixels: reading a non-existent pixel Color may compromise the result and/or unbalance an algorithm.
The same when performing statistical analisys on a Bitmap's colors.当然,我们可以在循环中添加一个检查:
if [Position] Mod [BitmapData].Width = 0 : continue
.
但这会为每次迭代添加新的计算.Of course, we could add a check in the loop:
if [Position] Mod [BitmapData].Width = 0 : continue
.
But this adds a new calculation to each iteration.实际操作
一种简单的解决方案(更常见的解决方案)是创建一个格式为
PixelFormat.Format32bppArgb
的新位图,因此Stride
将始终正确对齐:The simple solution (the more common one) is to create a new Bitmap with a format of
PixelFormat.Format32bppArgb
, so theStride
will be always correctly aligned:Imports System.Drawing Imports System.Drawing.Imaging Imports System.Runtime.InteropServices Private Function CopyTo32BitArgb(image As Image) As Bitmap Dim imageCopy As New Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb) imageCopy.SetResolution(image.HorizontalResolution, image.VerticalResolution) For Each propItem As PropertyItem In image.PropertyItems imageCopy.SetPropertyItem(propItem) Next Using g As Graphics = Graphics.FromImage(imageCopy) g.DrawImage(image, New Rectangle(0, 0, imageCopy.Width, imageCopy.Height), New Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel) g.Flush() End Using Return imageCopy End Function
这将生成具有相同DPI定义的字节兼容位图;还会从 Image.PropertyItems 复制源图像.
This generates a byte-compatible Bitmap with the same DPI definition; the Image.PropertyItems are also copied from the source image.
要对其进行测试,我们将棕褐色调滤镜应用于图像,并使用其副本执行对位图数据的所有修改:
To test it, let's apply a sepia tone filter to an Image, using a copy of it to perform all the modifications needed to the Bitmap data:
Public Function BitmapFilterSepia(source As Image) As Bitmap Dim imageCopy As Bitmap = CopyTo32BitArgb(source) Dim imageData As BitmapData = imageCopy.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb) Dim buffer As Byte() = New Byte(Math.Abs(imageData.Stride) * imageCopy.Height - 1) {} Marshal.Copy(imageData.Scan0, buffer, 0, buffer.Length) Dim bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) \ 8; Dim red As Single = 0, green As Single = 0, blue As Single = 0 Dim pos As Integer = 0 While pos < buffer.Length Dim color As Color = Color.FromArgb(BitConverter.ToInt32(buffer, pos)) ' Dim h = color.GetHue() ' Dim s = color.GetSaturation() ' Dim l = color.GetBrightness() red = buffer(pos) * 0.189F + buffer(pos + 1) * 0.769F + buffer(pos + 2) * 0.393F green = buffer(pos) * 0.168F + buffer(pos + 1) * 0.686F + buffer(pos + 2) * 0.349F blue = buffer(pos) * 0.131F + buffer(pos + 1) * 0.534F + buffer(pos + 2) * 0.272F buffer(pos + 2) = CType(Math.Min(Byte.MaxValue, red), Byte) buffer(pos + 1) = CType(Math.Min(Byte.MaxValue, green), Byte) buffer(pos) = CType(Math.Min(Byte.MaxValue, blue), Byte) pos += bytesPerPixel End While Marshal.Copy(buffer, 0, imageData.Scan0, buffer.Length) imageCopy.UnlockBits(imageData) imageData = Nothing Return imageCopy End Function
Bitmap.LockBits
不一定总是 best 可用的最佳选择.
使用Bitmap.LockBits
is not always necessarily the best choice available.
The same procedure to apply a filter could also be performed quite easily using the ColorMatrix class, which allows to apply a5x5
matrix transformation to a Bitmap, using just a simple array of float (Single
) values.例如,让我们使用
ColorMatrix
类和众所周知的5x5
矩阵来应用灰度滤镜:For example, let's apply a Grayscale filter using the
ColorMatrix
class and a well-known5x5
Matrix:Public Function BitmapMatrixFilterGreyscale(source As Image) As Bitmap ' A copy of the original is not needed but maybe desirable anyway ' Dim imageCopy As Bitmap = CopyTo32BitArgb(source) Dim filteredImage = New Bitmap(source.Width, source.Height, source.PixelFormat) filteredImage.SetResolution(source.HorizontalResolution, source.VerticalResolution) Dim grayscaleMatrix As New ColorMatrix(New Single()() { New Single() {0.2126F, 0.2126F, 0.2126F, 0, 0}, New Single() {0.7152F, 0.7152F, 0.7152F, 0, 0}, New Single() {0.0722F, 0.0722F, 0.0722F, 0, 0}, New Single() {0, 0, 0, 1, 0}, New Single() {0, 0, 0, 0, 1} }) Using g As Graphics = Graphics.FromImage(filteredImage), attributes = New ImageAttributes() attributes.SetColorMatrix(grayscaleMatrix) g.DrawImage(source, New Rectangle(0, 0, source.Width, source.Height), 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes) End Using Return filteredImage End Function
这篇关于分析图像的颜色的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!