近似色を選ぶ方法に関する実験

 2016/02/27 7M4MON


 とある色と、とある色が似ているか判別する場合、2色の色空間上の距離を計算する必要があります。
 色空間は、「RGB」、「HSB/HSV」、「CIE L*a*b*」の3種類が代表的らしいです。
 そこで、24ビットフルカラーの全色(4096*4096 = 1677216色・ピクセル)の画像を、パレットを固定して減色し、パフォーマンスを比較してみました。
 (画像クリックで原寸となります。firefoxだと画像を開けないときがあるようです。)
 
 元画像

 Full_24bit_RGB_palette.png

 パレットはHTMLで定義されていて、古いブラウザや携帯でもカラーネームを解釈できる(らしい)
 「black, gray, silver, white, maroon, red, purple, fuchsia, green, lime, olive, yellow, navy, blue, teal, aqua」の16色としました。

1.RGB色空間

 赤、青、緑の色成分を3辺とし、その立方体での距離を求めます。
 それぞれの成分は、Color.R、Color.G、Color.B プロパティで参照できます。
 計算する際は、プロパティはBYTE型なので、CInt(Byte)でパースしておきます。
 
 

 149秒
 
 なお、このページによると、RGBに適切な重み付けをすると少しは人間の感覚に近い色差になるようなので試してみました。
 (R:0.299, G:0.587, B:0.114)
 
 Full_24bit_RGB_palette_16_rgb2.png
 
 計算時間に有意な差はありませんでした。

 

2.HSB/HSV色空間

 色相(Hue)、彩度(Saturation・Chroma)、明度(Value・Lightness・Brightness)の三つの成分からなる色空間です。
 color.GetHue()/color.GetSaturation()/color.GetBrightness()メソッドにより簡単に各成分を取得できます。
 最初はこのページの方法でやってみたのですが、うまい具合に行きませんでした。
 
 Full_24bit_RGB_palette_16_hsv_old.png

 175秒

 そこで、参考文献の「Change Detection in Color Images」を参照したところ
 3.3章に「3.3 Euclidean Distance of HSV」とあり、極座標から変換した後に距離を求めるのが正解のようでした。
 よって、hueは hue / 360 として0〜1に変換し、vs * cos(2πh)、 vs * sin(2πh)、 v  の距離を求めました。
 
 Full_24bit_RGB_palette_16_hsv.png

 213秒

 上記の画像は白の領域がほとんど無く、別の画像では近似に違和感がありました。
 そこで、Wikipediaのページを参考にSとBの定義を変えてみました。
   


 Full_24bit_RGB_palette_16_hsv.png

 252秒

3.CIE L*a*b*

 Lab色空間は人間の視覚を近似するよう、国際照明委員会 (CIE) が策定したものらしいです。
 詳しい式はwikipediaなどに書いてありますが、VB.netのコードは下記ページに載っていました。
 http://www.vbforums.com/showthread.php?762919-RGB-XYZ-and-Lab-conversions
 上の方にある Tanner_H氏のコードは問題ありませんが、下の方にある reexre氏のコードは上手く変換できません。

 Full_24bit_RGB_palette_16_lab2.png

 最後の部分
  If cL > 255 Then cL = 255
  If cL < 0 Then cL = 0
  If cA > 255 Then cA = 255
  If cB > 255 Then cB = 255
 で変な値に飛んでいってしまいますが、そもそもこの時点ですでに希望の値でないようでした。
 ので、Tanner_H氏のコードで変換した結果が↓です。
 
 Full_24bit_RGB_palette_16_lab1.png

 354秒
 
 
 先に変換テーブルを作成しておくことにより、Lab色空間への変換を高速にすることが出来るようです。
 http://d.hatena.ne.jp/butyricacid/20080711/1215772093
 こちらページのコードをVB.netに移植したものを下記に示します。
 (作者のbutyricacid氏に公開の許可を頂きました。ありがとうございます!)
#Region "LabFastConvert"
    'http://d.hatena.ne.jp/butyricacid/20080711/1215772093
    'を移植した。

    Private _gammaTable() As Integer = createGammaTable()
    Private _labTable() As Integer = createLabTable()

    Private Function createGammaTable() As Integer()
        Dim table(256) As Integer

        For i = 0 To 255
            Dim d As Double = i / 255
            Dim g As Double
            If d > 0.04045 Then
                g = Math.Pow((d + 0.055) / 1.055, 2.4)
            Else
                g = (d / 12.92)
            End If

            table(i) = CInt(g * 100000)
        Next
        Return table
    End Function

    Private Function createLabTable() As Integer()
        Dim table(100001) As Integer
        For i = 0 To 100000
            Dim d As Double = i / 100001
            Dim g As Double
            If d > 0.008856 Then
                g = Math.Pow(d, 1 / 3.0)
            Else
                g = (7.787 * d) + (16 / 116.0)
            End If
            table(i) = CInt(g * 1000000)
        Next
        Return table
    End Function
    
    Public Function sRGBToLab_fast(pColor As Color) As Double()
        Dim r As Integer = CInt(pColor.R)
        Dim g As Integer = CInt(pColor.G)
        Dim b As Integer = CInt(pColor.B)
        r = _gammaTable(r)
        g = _gammaTable(g)
        b = _gammaTable(b)

        'r,g,b 各値は 0 から 100000 の範囲
        Dim x As Integer = 4124 * r + 3576 * g + 1805 * b
        Dim y As Integer = 2126 * r + 7152 * g + 722 * b
        Dim z As Integer = 193 * r + 1192 * g + 9505 * b
        ' x: 0-950500000
        ' y: 0-1000000000
        ' z: 0-1089000000

        ' 0-1000000000 の範囲へ揃えるのと _labTable のインデックス範囲 0-100000 へのマッピングを同時に行う。
        x = x / 9505
        y = y / 10000
        z = z / 10890

        ' x,y,z 各値は 0 から 100000 の範囲

        x = _labTable(x)
        y = _labTable(y)
        z = _labTable(z)

        ' x,y,z 各値は 0 から 1000000 の範囲
        Dim lab() As Double
        lab = {(116 * y - 16000000) / 1000000, (500 * (x - y)) / 1000000, (200 * (y - z)) / 1000000}
        Return lab
    End Function
#End Region
 
 結果は↓です。

 Full_24bit_RGB_palette_16_lab1_fast.png
 
 172秒

 処理時間が49%減になりました。
 差はごく僅かなので、どちらを使ってもほぼ同じ結果が得られると思われます。

 ということで、アプリを作ってみました。

 

 ダウンロードはこちら

 ビーズ工作の役に立つと良いなぁ。

 戻る