色差を求める

ある色を与えられたときに、それに近似した色の服部をデータベースから探して5つほど表示したい。

近似色のデータを探すには、色同士の差を計算しなければならないので、やり方を調べて丸写ししてきた。

サーバー側での処理なので、Rubyで書いてる。

1. linear RGBに直す

与えられたRGBをlinear RGBに変換する。

普通のRGBとlinear RGBの違いは下の質問のベストアンサーがわかりやすかった。

stackoverflow.com

普通の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.com

↑このQiitaの記事にめっちゃまとめてくれてあった。この方は、L*a*b*では飽き足らず、更にレベルの高いことをやってはるみたい(よくわからん)。

できたもの

f:id:YaaMaa:20170205044359p:plain

画像中の色を指定したら、近い色の服部を返してくれる!

今はさくさく動いてくれるけど、これからデータを増やしたらどうなるのかちょっとどきどきしてる。