こんちにわ!ランサーズエンジニアのsatoshiです。本日は、2013年2月にリリースした、「ランサーズポートフォリオ」で使っているPHPでの色検出アルゴリズムについて紹介します。この機能は、ランサーズを卒業した ウーカシュ・クラフチック により作られました。とても良い機能なので、是非紹介したいと思います。
ランサーズポートフォリオについて
みなさん、「ランサーズポートフォリオ」という機能をご存知でしょうか?
ランサーズポートフォリオは、ランサーズに登録しているデザイナー様が、自らの作品を公開することができるサービスです。現在5万点近い作品が投稿されており、仕事を依頼したい方はこれらの作品を通じて、デザイナーを探すことが出来ます。
そんなランサーズポートフォリオですが、登録されたポートフォリオ作品を「色」から検索する機能がついています。今回は、この「色検索」を PHP を使ってどのように実装するか、ご紹介したいと思います。
画像のどの部分を判定するか?
まず始めに考えるべきは、「画像のどの部分を検出の対象とすべきか?」という問題です。対象となるエリアは ROI (Range Of Interest) とも呼ばれ、対象画像の中心部を長方形に切り取った形をしています。
なぜこのように画像の中心部を抽出するのでしょうか? それは、「顔」や「ロゴ」といった、画像の主となる構成要素が大抵画像の中心部に配置されているためです。
どの部分を色検出の対象にするか決めたら、今度はどのくらいの粒度で検出を行うかを決めなければなりません。もちろん、粒度を細かくすれば色検出の精度は上がりますが、同時に処理時間が長くなってしまいます。今回、ランサーズでは縦解像度・横解像度それぞれの 1/10 の領域について色検出を行うことにしました。
... open image ... for ($x = $roi['x']['min']; $x <= $roi['x']['max']; $x += $pointDistance['x']) { for ($y = $roi['y']['min']; $y <= $roi['y']['max']; $y += $pointDistance['y']) { // get color vector of current pixel $colorVector = imageColorsForIndex($img, imageColorAt($img, $x, $y)); ... do something with colour ... } }
色検出アルゴリズム
ランサーズポートフォリオでは、「赤」「オレンジ」「黄」「緑」「青緑」「青」「紫」「ピンク」「白」「グレー」「黒」「茶」の 12 種類に作品を分類しています。各作品をこれらのいずれかに分類する際に必要となるのが、「色の近似度の計算」になります。
ただ単に色情報を取得したいのであれば、先ほど示したコードから得られた色の情報をそのまま使えば問題ありません。ただし、今回のように、検出した色と特定の色の「近さ」を測りたい場合(色の近似度を調べたい場合)、いくつかのアルゴリズムを検討する必要があります。
RGB
最もよく知られており、かつ簡単なのが RGB 色空間を用いた近似度の検出でしょう。この場合、RGB で表現された 3 次元空間において 2 つの色を点で表現し、その幾何的距離によって近似度を計算することができます。
ほとんどの画像に対しいて、このアルゴリズムはうまくいきます。しかし、RGB 色空間には「淡い色をうまく表現できない」といった弱点があります。今回の例で言えば、「茶」「グレー」といった色がうまく識別されません。人間の目には「ピンク」と「茶」はだいぶ違って見えますが、RGB 空間においては、「ピンク」は「赤」よりも「茶」に近いと判定されてしまうのです。
function colorDistance($a, $b) { return sqrt(pow($a['red'] - $b['red'], 2) + pow($a['green'] - $b['green'], 2) + pow($a['blue'] - $b['blue'], 2)); }
HSV
HSV 色空間を使えば、この問題は解決できます。HSV 色空間は上記画像のような円錐形をしています。RGB 色空間を HSV 色空間へと変換するには、次のようにします。
function rgbToHsv($rgb) { $h = 0; $s = 0; $v = 0; $r = $rgb[0] / 255; $g = $rgb[1] / 255; $b = $rgb[2] / 255; $max = max( $r, $g, $b ); $min = min( $r, $g, $b ); if ( $max === $min ) $h = 0; else if ( $max === $r ) $h = ( 60 * ($g - $b) / ( $max - $min ) + 360 ) % 360; else if ( $max === $g ) $h = 60 * ( $b - $r ) / ( $max - $min ) + 120; else if ( $max === $b ) $h = 60 * ( $r - $g ) / ( $max - $min ) + 240; if ( $max === 0 ) $s = 0; else $s = 1 - $min / $max; $v = $max; $hsv = array($h, $s * 100, $v * 100); return $hsv; }
HSV 色空間においては、最も明るい色が円錐の底面に、最も暗い色が円錐の頂点にきます。また、淡い色は円錐の中心部に集まるといった特徴があります。ここで注意すべきは、HSV のうち H (Hue) は底面の円周にそって、0-360 の値を取るということです(上図参照)。したがって、HSV 色空間において近似度を計算するには、以下のような処理が必要になります。
function colorDistance($a, $b) { $hueDiff = 0; // folding "H" dimension if ($a[0] > $b[0]) { $hueDiff = min($a[0] - $b[0], $b[0] - $a[0] + 360); } else { $hueDiff = min($b[0] - $a[0], $a[0] - $b[0] + 360); } return sqrt(pow($hueDiff, 2) + pow($a[1] - $b[1], 2) + pow($a[2] - $b[2], 2)); }
まとめ
今回は RGB, HSV といった 2 つの方法で 2 つの色の間の近似度を計算する方法をご紹介しました。実際には、このようなコードで得られた 2 色間の距離を比較し、もっとも近い色を先述した 12 色から選んでいます。
ご存じの方もいるかもしれませんが、実際には RGB, HSV 以外にも LAB といった色空間があります。色検出においては LAB の方が良い結果を出すことが多いですが、RGB <=> HSV のように簡単に色情報を変換できません。もし機会があれば、LAB に関しても記事を書いてみようと思います。
参考文献
英語版の記事 をウーカシュがブログで公開してくれているので、こちらも参考にしてください。
Color difference algorithm - Emanuele Feronato
http://www.emanueleferonato.com/2009/08/28/color-differences-algorithm/
Fun with Colour Difference - Steve Hanov's Programming Blog
http://stevehanov.ca/blog/index.php?id=116
Color difference - Wikipedia, the free encyclopedia
http://en.wikipedia.org/wiki/Color_difference
Change Detection in Color Images
http://homepages.inf.ed.ac.uk/rbf/PAPERS/iccv99.pdf
LARGE SCALE IMAGE-BASED ADULT-CONTENT FILTERING
http://www.cs.cmu.edu/~har/visapp2006.pdf