近似色を選ぶ方法に関する実験
2016/02/27 7M4MON
とある色と、とある色が似ているか判別する場合、2色の色空間上の距離を計算する必要があります。
色空間は、「RGB」、「HSB/HSV」、「CIE L*a*b*」の3種類が代表的らしいです。
そこで、24ビットフルカラーの全色(4096*4096 = 1677216色・ピクセル)の画像を、パレットを固定して減色し、パフォーマンスを比較してみました。
(画像クリックで原寸となります。firefoxだと画像を開けないときがあるようです。)
元画像
パレットは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)
計算時間に有意な差はありませんでした。
2.HSB/HSV色空間
色相(Hue)、彩度(Saturation・Chroma)、明度(Value・Lightness・Brightness)の三つの成分からなる色空間です。
color.GetHue()/color.GetSaturation()/color.GetBrightness()メソッドにより簡単に各成分を取得できます。
最初はこのページの方法でやってみたのですが、うまい具合に行きませんでした。
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 の距離を求めました。
213秒
上記の画像は白の領域がほとんど無く、別の画像では近似に違和感がありました。
そこで、Wikipediaのページを参考にSとBの定義を変えてみました。
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氏のコードは上手く変換できません。
最後の部分
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氏のコードで変換した結果が↓です。
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
結果は↓です。
172秒
処理時間が49%減になりました。
差はごく僅かなので、どちらを使ってもほぼ同じ結果が得られると思われます。
ということで、アプリを作ってみました。
ダウンロードはこちら。
ビーズ工作の役に立つと良いなぁ。
戻る