色差アルゴリズムの違いをビジュアル化してみた

２つの色の知覚的な差を定量化するアルゴリズムがいつくか公開されていて、このアルゴリズムを使って計算すると色の違いを数量化することができます。値が小さければ小さいほど似て（又は同じ色）に見え、大きければ大きいほど異なって見えるということらしいです。

以前[Color difference（Wikipedia）](https://en.wikipedia.org/wiki/Color_difference)で公開されていた色差を求める計算式をJavaScriptで作ってみたことがあったので、それぞれの結果の違いをビジュアル化してみました。

色差計算およびカラーサンプル作成に使ったHSVからRGBに変換するスクリプトはページの最後にリンク先を載せていますので、間違っている等、お気づきのところがあったら教えてくれると助かります。

## どうやってビジュアル化する？
以下のようなHSVカラーサンプルを作って、それを変換することで確認してみたいと思います。
![colorsample.png](https://qiita-image-store.s3.amazonaws.com/0/128670/3e29cc44-62e3-8dec-ab14-3b6476c233b7.png)

変換先の色として私でもイメージできそうな以下の13色（赤、橙、黄、黄緑、緑、青、紫、桃、白、黒、灰、茶、水）を用意しました。
![tocolor3.png](https://qiita-image-store.s3.amazonaws.com/0/128670/ba4355d6-9d63-6ec7-1f1d-e980709eb9d8.png)

それをJavaScriptで定義するとこんな感じ。

```js:色の指定
let colorNames = [
	{name:"赤", reading:"あか", hex:"#e60033"},
	{name:"橙", reading:"だいだい", hex:"#ee7800"},
	{name:"黄", reading:"きい", hex:"#ffd900"},
	{name:"黄緑", reading:"きみどり", hex:"#b8d200"},
	{name:"緑", reading:"みどり", hex:"#3eb370"},
	{name:"青", reading:"あお", hex:"#0095d9"},
	{name:"紫", reading:"むらさき", hex:"#884898"},
	{name:"桃", reading:"もも", hex:"#f09199"},
	{name:"白", reading:"しろ", hex:"#ffffff"},
	{name:"黒", reading:"くろ", hex:"#2b2b2b"},
	{name:"灰", reading:"はい", hex:"#7d7d7d"},
	{name:"茶", reading:"ちゃ", hex:"#965042"},
	{name:"水", reading:"みず", hex:"#bce2e8"}
]
```

プログラムとしては用意した13色の中で最も近い（差が小さい）色を探し、その色に置き換えていくという感じです。以下はそのサンプルです。サンプルなのでrgbからLabへの変換や色差を求めるプログラムは含めていませんので、このままでは動きません。それらの情報はページの最後に載せているリンクを参考にしてください。またテキストからPNGへの変換については「[ImageMagickで画像を座標＋RGB値のテキストに変換する、またその逆](31facab9691fa356880d)」を参考にしてください。

```js:変換（サンプル）
//rgbとlabを計算
for (let o of colorNames) {
	o.rgb = [
			parseInt(o.hex.substring(1,3), 16),
			parseInt(o.hex.substring(3,5), 16),
			parseInt(o.hex.substring(5,7), 16)
	];
	o.lab = rgbToLab(...o.rgb);
}

//カラーサンプルを作成
let colorSample = [];
for(let s=1;s<=100;s++) {
	for(let h=0; h<360; h++){
		let o = {x:h, y:s-1, rgb:hsvToRgb(h, s/100, 1)};
		o.lab = rgbToLab(...o.rgb);
		colorSample.push(o);
	}
}

//最も近い色で変換
for (let o of colorSample) {
    o.euclidean = colorNames.map(p=>[p, euclidean(...p.rgb, ...o.rgb)]).reduce((a,b)=>a[1]<b[1]?a:b)[0];
	o.cie76 = colorNames.map(p=>[p, cie76(...p.lab, ...o.lab)]).reduce((a, b)=>a[1]<b[1]?a:b)[0];
    o.cie94 = colorNames.map(p=>[p, cie94(...p.lab, ...o.lab)]).reduce((a, b)=>a[1]<b[1]?a:b)[0];
    o.ciede2000 = colorNames.map(p=>[p, ciede2000(...p.lab, ...o.lab)]).reduce((a, b)=>a[1]<b[1]?a:b)[0];
}

//保存
let fs = require('fs');
let imageText = []
imageText.push("# ImageMagick pixel enumeration: 360,500,255,srgb");

for (let o of colorSample) {
	imageText.push(`${o.x},${o.y      }: (${o.rgb.join(",")})`);
	imageText.push(`${o.x},${o.y + 100}: (${o.euclidean.rgb.join(",")})`);
	imageText.push(`${o.x},${o.y + 200}: (${o.cie76.rgb.join(",")})`);
	imageText.push(`${o.x},${o.y + 300}: (${o.cie94.rgb.join(",")})`);
	imageText.push(`${o.x},${o.y + 400}: (${o.ciede2000.rgb.join(",")})`);
}

fs.writeFileSync('diff.txt', imageText.join("\n"));

//実行後 convert diff.txt diff.png でPNG形式に変換。
```

## それではGO!

### Euclidean（ユークリッド）
もっとも単純そうな計算式です。３次元での距離を求める計算式と似ています。
> ![Euclidean](https://wikimedia.org/api/rest_v1/media/math/render/svg/f959802c3c7352dbe9ae96468fb188e93de69ef7)
（「[Color difference](https://en.wikipedia.org/wiki/Color_difference#Euclidean)」（5 Feb 2017, at 02:30. UTC）『ウィキペディア』より引用）

![euc.png](https://qiita-image-store.s3.amazonaws.com/0/128670/74c347d0-184c-185e-57c7-994062bc7437.png)

### CIE76
計算式は以下のようになっています。
> ![CIE76](https://wikimedia.org/api/rest_v1/media/math/render/svg/9452dc1872bffcc360663245e22ca32d5a84f3c1)
（「[Color difference](https://en.wikipedia.org/wiki/Color_difference#CIE76)」（5 Feb 2017, at 02:30. UTC）『ウィキペディア』より引用）

式はユークリッドとほとんど同じにみえるのですがRGBではなくLabが使われています。Lab色空間は[Lab color space - Wikipedia](https://en.wikipedia.org/wiki/Lab_color_space)に情報があるのですが、XYZ色空間からの変換方法しか書かれていません。そして[sRGB - Wikipedia](https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation)にはRGBからXYZ色空間への変換方法が書かれています。つまりRGB→XYZ→Labという計算を行わないと計算が行えないことになります。で計算した結果が以下のとおりです。
![cie76.png](https://qiita-image-store.s3.amazonaws.com/0/128670/15894ce4-8555-0fa4-c172-ebbbb313f48e.png)

### CIE94
以下のような計算式なのですが「graphic arts」と「textiles」があったり、比較する色のどちらをL1a1b1にするかによって結果が変わります。
> ![CIE94](https://wikimedia.org/api/rest_v1/media/math/render/svg/3d211e32392967ee23e92b1e3cdc0fe2e0ef4061)
（「[Color difference](https://en.wikipedia.org/wiki/Color_difference#CIE94)」（5 Feb 2017, at 02:30. UTC）『ウィキペディア』より引用）

以下の結果は「graphic arts」の場合です。
![cie94.png](https://qiita-image-store.s3.amazonaws.com/0/128670/d7eb8f94-a65c-53a2-37e9-71530e646257.png)

「textiles」の場合は以下のような感じです。
![cie94t.png](https://qiita-image-store.s3.amazonaws.com/0/128670/99fd974b-ee91-4beb-d2a1-4f6cde68806e.png)

「graphic arts」の場合でL1a1b1とL2a2b2の値を入れ替えると以下のようになります。今回の場合は前者の方が近いようです。
![cie94r.png](https://qiita-image-store.s3.amazonaws.com/0/128670/139e3e5b-1d9b-2003-b7fc-b1ab949282e8.png)

### CIEDE2000
きっと最も新しいと思うんですが、計算式もかなり複雑です。
> ![CIE2000](https://wikimedia.org/api/rest_v1/media/math/render/svg/aec5cdd29cf9fdbd7352935eaee1524d5718340e)
（「[Color difference](https://en.wikipedia.org/wiki/Color_difference#CIEDE2000)」（5 Feb 2017, at 02:30. UTC）『ウィキペディア』より引用）

![cie2000.png](https://qiita-image-store.s3.amazonaws.com/0/128670/26db913a-ac6e-73f9-4b8c-1cfbe9116578.png)

## まとめて比較してみる

|名称|結果|
|:---|:---:|--:|
|元画像|![colorsample.png](https://qiita-image-store.s3.amazonaws.com/0/128670/3e29cc44-62e3-8dec-ab14-3b6476c233b7.png)|
|Euclidean|![euc.png](https://qiita-image-store.s3.amazonaws.com/0/128670/74c347d0-184c-185e-57c7-994062bc7437.png)|
|CIE76|![cie76.png](https://qiita-image-store.s3.amazonaws.com/0/128670/15894ce4-8555-0fa4-c172-ebbbb313f48e.png)|
|CIE94 graphic|![cie94.png](https://qiita-image-store.s3.amazonaws.com/0/128670/d7eb8f94-a65c-53a2-37e9-71530e646257.png)|
|CIE94 textiles|![cie94t.png](https://qiita-image-store.s3.amazonaws.com/0/128670/99fd974b-ee91-4beb-d2a1-4f6cde68806e.png)|
|CIEDE2000|![cie2000.png](https://qiita-image-store.s3.amazonaws.com/0/128670/26db913a-ac6e-73f9-4b8c-1cfbe9116578.png)|

## 使用したスクリプト
今回使用した色差を求めるスクリプトは以下で公開しています。

- [JavaScriptでCIEDE2000色差計算](860f061a2ab7a4f2d06f)
- [JavaScriptでCIE94色差計算](3488df50b7082d4fcffb)
- [JavaScriptでCIE76色差計算](cda9b95395468ee7e0f8)
- [JavaScriptでユークリッド色差計算](28c7b61d6b8ca54c5b94)
- [JavaScriptでHSVからRGBに変換](633d1bf6baf008e82847)
- [JavaScriptでRGBからLab色空間への変換](09caabe6bec46a2a0858)