ある色を与えられたときに、それに近似した色の服部をデータベースから探して5つほど表示したい。
近似色のデータを探すには、色同士の差を計算しなければならないので、やり方を調べて丸写ししてきた。
サーバー側での処理なので、Rubyで書いてる。
1. linear RGBに直す
与えられたRGBをlinear RGBに変換する。
普通のRGBとlinear RGBの違いは下の質問のベストアンサーがわかりやすかった。
普通のRGBは人の目に合わせてできてるから、そのまま単純に計算するわけにはいかないみたい。
直し方は、まず範囲が0~255だったら255で割って0~1にスケーリングしてから、以下のようにする。
def to_linear(rgb) rgb.map do |element| if element <= 0.04045 return element / 12.92 else return ((element + 0.055) / 1.055) ** 2.4 end end end
2. XYZに直す
XYZは、RGBとL*a*b*に限らず、いろんな色空間の橋渡し的な存在みたい。
def rgb2xyz(rgb) m = [ [0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505] ] m.map do |m_row| m_row.zip(rgb).inject(0) {|sum, (m_element, rgb_element)| sum + m_element * rgb_element * 100} end end
行列とか意味わからんかったけど、とりあえず掛け算のやり方だけ何とか覚えた。
初めてブロックパラメータに括弧使ったけど、普通に使えて感動した。
3. XYZのスケーリング
def scale_xyz(xyz) xyz.zip([95.047, 100.000, 108.883]).map {|xyz_element, n| xyz_element / n} end
この係数は、L*a*b*の英語版Wikipediaの記事から取ってきた。なぜか日本語版には書いてくれてなかったので。
4. L*a*b*に直す
def xyz2lab(xyz) def f(t) if t > 0.0089 t ** (1.0 / 3) else ((29.0 / 3) ** 3 * t + 16) / 116 end end [116 * f(xyz[1]) - 16, 500 * (f(xyz[0]) - f(xyz[1])), 200 * (f(xyz[1]) - f(xyz[2]))] end
Wikipediaのとはちょっとやり方が違うけど、もちろんどちらも答えは同じ。こっちの方がdeltaとか定義してなくて楽そうだったのでこっちにした。
5. L*a*b*同士で距離を求める
普通にユークリッド距離を計算したらいいらしい。
参考
↑このページの(2)と(3)をめっちゃ読んだ。9割くらい意味わからんかった笑
↑このQiitaの記事にめっちゃまとめてくれてあった。この方は、L*a*b*では飽き足らず、更にレベルの高いことをやってはるみたい(よくわからん)。
できたもの
画像中の色を指定したら、近い色の服部を返してくれる!
今はさくさく動いてくれるけど、これからデータを増やしたらどうなるのかちょっとどきどきしてる。