簡體   English   中英

分析圖像的顏色

[英]Analyze colors of an Image

我裁剪了圖像的一部分,並通過 12 個軌跡欄定義了 2 個顏色范圍 (H/S/L)。 我還有一個從 1 到 10 的“精度/速度”滑塊。

我需要分析圖像中有多少像素落入每個指定的顏色范圍。
基於精度/速度滑塊,我跳過了一些行/像素。

它工作得很好,但太慢了。 高精度(trackbar value = 1),大約需要550 ms。
精度低但速度快(軌跡條值 = 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)

當對 Bitmap 的顏色數據執行順序操作時, Bitmap.LockBits方法可以提供巨大的性能提升,因為 Bitmap 數據只需在內存中加載一次,而不是順序 GetPixel/SetPixel 調用:每次調用都會加載一個內存中的Bitmap數據的部分部分然后丟棄它,當再次調用這些方法時重復該過程。

如果需要對 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 ,掃描線的寬度( Stride :組成單行像素的字節數,由Bitmap.Width乘以每個像素的字節數)像素,四舍五入到 4 字節邊界。請參閱有關Stride的注釋)。
    BitmapData.Scan0屬性是指向存儲位圖數據的初始內存位置的指針 ( IntPtr )。
    此屬性允許指定已存儲預先存在的位圖數據緩沖區的內存位置。 當使用指針在進程之間交換位圖數據時,它變得很有用。
    請注意,有關ImageLockMode.UserInputBuffer的 MSDN 文檔令人困惑(如果沒有錯的話)。

  • format As PixelFormat :用於描述單個像素顏色的格式。 在實踐中,它轉換為用於表示顏色的字節數。
    PixelFormat = Format24bppRgb時,每種顏色由 3 個字節(RGB 值)表示。 使用PixelFormat.Format32bppArgb ,每種顏色由 4 個字節(RGB 值 + Alpha)表示。
    索引格式,如Format8bppIndexed ,指定每個字節值是調色板條目的索引。 Palette是位圖信息的一部分,除非像素格式是PixelFormat.Indexed :在這種情況下,每個值都是系統顏色表中的一個條目。
    如果未指定,新 Bitmap 對象的默認PixelFormatPixelFormat.Format32bppArgbPixelFormat.Canonical

關於步幅的重要說明:

如前所述, Stride (也稱為掃描線)表示組成單行像素的字節數。 由於硬件對齊要求,它總是四舍五入到 4 字節邊界(4 的整數倍)。

Stride =  [Bitmap Width] * [bytes per Color]
Stride += (Stride Mod 4) * [bytes per Color]

這就是為什么我們總是使用使用PixelFormat.Format32bppArgb創建的位圖的原因之一:位圖的Stride總是已經與所需的邊界對齊。

如果位圖的格式改為PixelFormat.Format24bppRgb (3 bytes per Color)怎么辦?

如果 Bitmap 的Width乘以 Bytes per Pixels 不是4的倍數,則Stride將用0填充以填補空白。

大小為(100 x 100)的位圖在 32 位和 24 位格式中都沒有填充:

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

大小為(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 )以填充邊界。

當我們檢查/修改通過坐標訪問單個像素的內部值時,這不是問題,類似於 SetPixel/GetPixel 的操作方式:始終可以正確找到像素的位置。

假設我們需要在大小為(99 x 100)的位圖中檢查/更改位置(98, 70)的像素。
僅考慮每個像素的字節數。 Buffer內的像素位置為:

[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])

將 Pixel 的垂直位置乘以掃描線的寬度,緩沖區內的位置將始終正確:填充的大小包含在計算中。
下一個位置(0, 71)的像素顏色將返回預期結果:

順序讀取顏色字節時會有所不同。
第一個掃描行將返回直到最后一個像素(最后 3 個字節)的有效結果:接下來的 3 個字節將返回用於舍入Stride的字節的值,全部設置為0

這也可能不是問題。 例如,應用一個過濾器,每個表示像素的字節序列都被讀取並使用過濾器矩陣的值進行修改:我們只需修改一個 3 個字節的序列,在渲染位圖時不會考慮這些字節序列。

但是,如果我們正在搜索特定的像素序列,這確實很重要:讀取不存在的像素顏色可能會損害結果和/或使算法失衡。
對位圖的顏色執行統計分析時也是如此。

當然,我們可以在循環中添加一個檢查: if [Position] Mod [BitmapData].Width = 0 : continue
但這會為每次迭代增加一個新的計算。

實際操作

簡單的解決方案(更常見的一種)是創建一個格式為PixelFormat.Format32bppArgb的新位圖,因此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

這將生成具有相同 DPI 定義的字節兼容位圖; Image.PropertyItems也從源圖像中復制。

為了測試它,讓我們對圖像應用棕褐色調濾鏡,使用它的副本來執行位圖數據所需的所有修改:

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不一定是可用的最佳選擇。
使用ColorMatrix類也可以很容易地執行應用過濾器的相同過程,該類允許將5x5矩陣變換應用於位圖,只使用一個簡單的浮點 ( Single ) 值數組。

例如,讓我們使用ColorMatrix類和眾所周知的5x5矩陣應用灰度濾鏡:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM