分析图像的颜色 [英] Analyze colors of an Image

查看:88
本文介绍了分析图像的颜色的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我裁剪了一部分图像,并通过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 - setting ImageLockMode.UserInputBuffer - that the BitmapData object is provided by the calling code.
      The BitmapData object defines some of the Bitmap properties (Width and Height of the Bitmap, width of the scan line (the Stride: number of bytes that compose a single line of pixels, represented by the Bitmap.Width multiplied by the number of bytes per pixel, rounded to a 4-bytes boundary. See the note about the Stride).
      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 about ImageLockMode.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's Stride 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 of 4, the Stride will be padded with 0s 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 to 0) 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 the Stride, all set to 0.

      这也可能不是问题.例如,应用一个滤镜,使用滤镜矩阵的值读取并修改代表像素的每个字节序列:我们只需要修改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 the Stride 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 a 5x5 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-known 5x5 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆