分析图像的颜色

时间:2019-11-28 08:26:04

标签: .net vb.net image graphics

我裁剪了一部分图像,并通过12个轨迹栏定义了2个颜色范围(H / S / L)。我还有一个“精度/速度”滑块,范围从1到10。

我需要分析多少个图像像素落入每个指定的颜色范围。
基于精度/速度滑块,我跳过了一些行/像素。

它运作良好,但是速度太慢。高精度(跟踪条值= 1)大约需要550毫秒。
精度低但速度快(跟踪条值= 10)时,大约需要5毫秒。

有没有办法加快此代码的速度?理想情况下,我需要将其速度提高5倍。

 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)

1 个答案:

答案 0 :(得分:4)

当对位图的颜色数据执行顺序操作时,Bitmap.LockBits方法可以显着提高性能,因为与顺序的GetPixel / SetPixel调用相反,位图数据仅需要一次加载到内存中:每次调用都会将部分位图数据加载到内存中,然后将其丢弃,以在再次调用这些方法时重复该过程。

如果相反需要单次调用GetPixel / SetPixel,则这些方法可能比Bitmap.LockBits()具有性能优势。但是,在这种情况下,实际上,性能不是一个因素。

Bitmap.LockBits()的工作方式

这是函数调用:

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数据部分;该部分的字节将被加载到内存中。它可以是位图的整体大小,也可以是其较小部分。

  • flags As ImageLockMode:指定要执行的锁定类型。对内存的访问可以限制为读取或写入,或者允许并发读取/写入操作。
    它也可以用来指定-设置 ImageLockMode.UserInputBuffer -BitmapData对象由调用代码提供。
    BitmapData 对象定义了某些位图属性(位图的WidthHeight,即扫描线的宽度( {{1} } :组成单行像素的字节数,用Stride乘以每个像素的字节数,四舍五入到4字节边界。请参见{{1 }})。
    BitmapData.Scan0属性是指向位图数据存储位置的初始存储位置的指针(Bitmap.Width
    该属性允许指定已经存储了预先存在的位图数据缓冲区的存储位置。使用指针在进程之间交换位图数据时,此功能很有用。
    请注意,有关Stride的MSDN文档令人困惑(如果没有错)。

  • IntPtr PixelFormat:用于描述单个像素颜色的格式。实际上,它转换为用于表示颜色的字节数。
    ImageLockMode.UserInputBuffer时,每种颜色都由3个字节(RGB值)表示。使用format As,每种颜色都由4个字节(RGB值+ Alpha)表示。
    索引格式(例如PixelFormat = Format24bppRgb)指定每个字节值是Palette条目的索引。 PixelFormat.Format32bppArgb是位图信息的一部分,除非像素格式为Format8bppIndexed:在这种情况下,每个值都是系统颜色表中的一个条目。
    如果未指定,则新位图对象的默认PalettePixelFormat.IndexedPixelFormat

关于步幅的重要说明:

如前所述, PixelFormat.Format32bppArgb (也称为扫描线)表示组成一行像素的字节数。由于硬件对齐要求,它总是四舍五入为4个字节的边界(4的整数倍)。

PixelFormat.Canonical

这是我们始终使用通过Stride创建的位图的原因之一:位图的Stride = [Bitmap Width] * [bytes per Color] Stride += (Stride Mod 4) * [bytes per Color] 始终已与所需边界对齐。

如果位图格式改为 PixelFormat.Format32bppArgb (每种颜色3个字节),该怎么办?

如果位图的Stride乘以每像素字节数不是PixelFormat.Format24bppRgb的倍数,则将用Width填充4来填补空白。

大小为Stride的位图在32位和24位格式中都不会填充:

0

大小为(100 x 100)的位图将有所不同:

100 * 3 = 300 : 300 Mod 4 = 0 : Stride = 300
100 * 4 = 400 : 400 Mod 4 = 0 : Stride = 400

填充24位位图的(99 x 100),添加3个字节(设置为99 * 3 = 297 : 297 Mod 4 = 1 : Stride = 297 + ((297 Mod 4) * 3) = 300 99 * 4 = 396 : 396 Mod 4 = 0 : Stride = 396 )以填充边界。

当我们检查/修改通过其坐标访问单个Pixel的内部值时,这不是问题,类似于SetPixel / GetPixel的操作方式:始终可以正确找到Pixel的位置。

假设我们需要检查/更改大小为Stride的位图中0位置的像素。
仅考虑每个像素的字节数。缓冲区内的像素位置为:

(98, 70)

将像素的垂直位置乘以扫描线的宽度,缓冲区内的位置将始终是正确的:填充大小包含在计算中。
下一位置(99 x 100)的像素颜色将返回预期的结果:

顺序读取颜色字节时会有所不同。
第一条扫描线将返回直到最后一个像素(最后3个字节)的有效结果:接下来的3个字节将返回用于舍入[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)

这也可能不是问题。例如,应用一个过滤器,使用过滤器矩阵的值读取并修改代表像素的每个字节序列:我们只需要修改3个字节的序列即可,在渲染位图时将不考虑该序列。

但是如果我们要搜索特定的像素序列就很重要:读取不存在的像素颜色可能会损害结果和/或使算法失衡。
在对位图的颜色执行统计analisys时相同。

当然,我们可以在循环中添加一个检查:Stride
但这会为每次迭代添加新的计算。

实际操作

一种简单的解决方案(较常见的解决方案)是创建一种格式为0的新位图,因此if [Position] Mod [BitmapData].Width = 0 : continue将始终正确对齐:

PixelFormat.Format32bppArgb

这将生成具有相同DPI定义的字节兼容位图; Image.PropertyItems也会从源图像中复制。

要对其进行测试,我们将棕褐色调滤镜应用于图像,并使用其副本执行对位图数据的所有修改:

Stride

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 不一定总是 best 的最佳选择。
使用ColorMatrix类也可以很容易地执行应用过滤器的相同过程,该类允许仅使用简单的float({{1} })值。

例如,让我们使用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矩阵来应用灰度滤镜:

5x5