色変換行列のうんちく

色変換行列、または色行列(Color Matrix)のうんちくです。

# 色変換行列とは？

行列と RGB値を掛け算する事で色の変換が行える。そんな便利な行列の応用例です。行列なので色空間に対して任意の線形変換が出来ます。
行列の形には色々ありますが、簡単なのは以下の 4x3 行列です。　(なお、輝度は 0.0〜1.0 の範囲とします)

```math
\begin{pmatrix}
R' \\
G' \\
B'
\end{pmatrix}
=
\begin{pmatrix}
m_{11} & m_{21} & m_{31} & m_{41} \\
m_{12} & m_{22} & m_{32} & m_{42} \\
m_{13} & m_{23} & m_{33} & m_{43} \\
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B \\
1 
\end{pmatrix}
```

つまり、R,G,B を以下の方程式で計算するだけです。

```math
\begin{array}{l}
R' = m_{11} R + m_{21} G + m_{31} B + m_{41} \\
G' = m_{12} R + m_{22} G + m_{32} B + m_{42} \\
B' = m_{13} R + m_{23} G + m_{33} B + m_{43} \\
\end{array}
```

幾何変換のアフィン行列を連想させる方程式で、実際計算自体は同じなので似た要領でプログラミング実装できます。

# 行列のサイズ

行列としては正方行列の方が便利なので、以下のように表現される事が多いです。先程の 4x3 行列はこれの省略形とも考えられます。

```math
\begin{pmatrix}
R' \\
G' \\
B' \\
1
\end{pmatrix}
=
\begin{pmatrix}
m_{11} & m_{21} & m_{31} & m_{41} \\
m_{12} & m_{22} & m_{32} & m_{42} \\
m_{13} & m_{23} & m_{33} & m_{43} \\
0 & 0 & 0 & 1 \\
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B \\
1 
\end{pmatrix}
```

RGBA だと更に増えて 5x5 です。A(alpha) まで一緒に処理する事はそう多くありませんが、紹介します。

```math
\begin{pmatrix}
R' \\
G' \\
B' \\
A' \\
1
\end{pmatrix}
=
\begin{pmatrix}
m_{11} & m_{21} & m_{31} & m_{41} & m_{51} \\
m_{12} & m_{22} & m_{32} & m_{41} & m_{52} \\
m_{13} & m_{23} & m_{33} & m_{43} & m_{53} \\
m_{14} & m_{24} & m_{34} & m_{44} & m_{54} \\
0 & 0 & 0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B \\
A \\
1 
\end{pmatrix}
```

# 色変換行列のメリット

- 数字の羅列で変換を表現できる
- 線形変換なら何でもできる
- 複数の処理を一度に処理できる

等があります。これらの詳細は後述します。
線形変換の具体例を見ないとメリットは理解しづらいので、まずは具体例から紹介します。

# 具体例

## 変化なし

まずは基本形として、R,G,B 値が変化しないマトリックスを紹介します。

```math
\begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0
\end{pmatrix}
```

実際に計算すると以下のようになるので、変化しないのは明白ですね。

```math
\begin{array}{l}
R' = 1 R + 0 G + 0 B + 0 = R \\
G' = 0 R + 1 G + 0 B + 0 = G \\
B' = 0 R + 0 G + 1 B + 0 = B
\end{array}
```

なお、単位行列(identity matrix) としては、以下の形をとります。

```math
\begin{pmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
```

## 特定の色味強調

加算と乗算で赤味を増やす例です。

### 赤の加算

```math
\begin{pmatrix}
1 & 0 & 0 & 0.4 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0
\end{pmatrix}
```
```math
\begin{array}{l}
R' = 1 R + 0 G + 0 B + 0.4 = R + 0.4 \\
G' = 0 R + 1 G + 0 B + 0 = G \\
B' = 0 R + 0 G + 1 B + 0 = B
\end{array}
```

| 元画像 | R + 0.4 |
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/16717160-4fd6-de8c-4bc3-5b0b885241df.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/cc27adac-11fa-c3b0-ced1-1bbb34287fa8.png) |

赤を 0.4嵩上げます。嵩上げなので全部が赤くなります。

### 赤の乗算

```math
\begin{pmatrix}
1.4 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0
\end{pmatrix}
```
```math
\begin{array}{l}
R' = 1.4 R + 0 G + 0 B + 0 = 1.4 R \\
G' = 0 R + 1 G + 0 B + 0 = G \\
B' = 0 R + 0 G + 1 B + 0 = B
\end{array}
```

| 元画像 | R x 1.4 |
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/16717160-4fd6-de8c-4bc3-5b0b885241df.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/03b78d36-7e95-3262-6553-aa25956534bc.png) |

赤だけ 1.4 倍します。こちらの方が黒が黒のまま残りますし、自然に赤みを帯びます。お勧めです。

## コントラスト強調

```math
\begin{pmatrix}
1.5 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0
\end{pmatrix}
```
```math
\begin{array}{l}
R' = 1.5 R + 0 G + 0 B + 0 = 1.5 R \\
G' = 0 R + 1.5 G + 0 B + 0 = 1.5 G \\
B' = 0 R + 0 G + 1.5 B + 0 = 1.5 B
\end{array}
```

| 元画像 | RGB x 1.5 |
|---|---|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/74f1acea-0ba2-0835-2e80-4b7fc4c0d97a.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6b13c000-170a-ce4c-6e61-4d386af8df36.png) |

輝度を1.5倍します。暗い画像の視認性が大幅に UP します。

## グレースケール化

```math
\begin{pmatrix}
0.2126 & 0.7152 & 0.0722 & 0 \\
0.2126 & 0.7152 & 0.0722 & 0 \\
0.2126 & 0.7152 & 0.0722 & 0
\end{pmatrix}
```
```math
\begin{array}{l}
R' = 0.2126 R + 0.7152 G + 0.0722 B \\
G' = 0.2126 R + 0.7152 G + 0.0722 B \\
B' = 0.2126 R + 0.7152 G + 0.0722 B
\end{array}
```

| 元画像 | grayscale |
|---|---|
|  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9a1fa301-076d-be9a-afa0-b0c288593cf6.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/7ba05ec8-6fbb-55e4-b1b1-17e3e03cb06a.png) |

R,G,B の3つの値を均して全部同じにします。加重平均の係数 {0.2126, 0.7152, 0.0722} は BT.709 を参照しました。BT.601 の方が有名かもしれません。

## R,G,B 入れ替え

```math
\begin{pmatrix}
0 & 0 & 1 & 0 \\
0 & 1 & 0 & 0 \\
1 & 0 & 0 & 0
\end{pmatrix}
```
```math
\begin{array}{l}
R' = 0 R + 0 G + 1 B + 0 = B \\
G' = 0 R + 1 G + 0 B + 0 = G \\
B' = 1 R + 0 G + 0 B + 0 = R
\end{array}
```

| 元画像 | BGR |
|---|---|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/c68f7c35-4261-5f85-ba1b-2ac37659b8b8.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/f4f19f5b-d0d3-003e-7a47-4bd91d71438f.png) |

R(赤)とB(青)を入れ替えます。OpenCV でよくあるうっかりミスで見かける画像です。

## ネガポジ反転

```math
\begin{pmatrix}
-1 & 0 & 0 & 1 \\
0 & -1 & 0 & 1 \\
0 & 0 & -1 & 1
\end{pmatrix}
```
```math
\begin{array}{l}
R' = -1 R + 0 G + 0 B + 1 = 1 - R \\
G' =  0 R - 1 G + 0 B + 1 = 1 - G \\
B' =  0 R + 0 G - 1 B + 1 = 1 - B
\end{array}
```

| 元画像 | negate |
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/dcdeae0f-be1f-3830-d053-53e4b30e5007.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/becd349e-e4c7-d4aa-d48c-11457cfe9e32.png)

輝度を反転させます。画像処理で稀にあるうっかりミスで見かける画像です。昭和世代の諸氏であればアナログカメラのネガフィルムで見慣れた映像でしょう。

# 色変換行列のメリット

## 数字の羅列で変換を表現できる

4x3 行列の 12 個の数値だけで様々な画像変換が表現できます。
例えば、赤強調(1.4R) は { 1.4, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 } で良いです。
プログラミングする上で色々と都合が良いです。

## 線形変換なら何でもできる

線形変換なら何でもできます。逆にいうと線形変換しか出来ません。正確には加算で嵩上げすると原点がズレて線形とは言えず、より広い概念の処理になり、これを擬線形(affin)と呼びます。

どんな変換が可能で、どんな変換が無理かわかりやすいのは、プログラミングのみならずサービス仕様を考える上でも、メリットとして大きいです。

例えばガンマ補正みたいな非線形処理は駄目ですし、max/min 操作が含まれるのも無理なので、必要なら別に作り込みましょう。

## 複数の処理を一度に処理できる

行列の掛け算なので、複数の変換行列を適用するのに、あらかじめ掛け算で１つにまとめれば、複数の変換を１度に行えます。
例えば、コントラストを上げた上で、赤強調をしたい場合、順番に処理すると、

```math
\begin{pmatrix}
R' \\
G' \\
B'
\end{pmatrix}
=
\begin{pmatrix}
1.5 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B
\end{pmatrix}
```
```math
\begin{pmatrix}
R'' \\
G'' \\
B''
\end{pmatrix}
=
\begin{pmatrix}
1.4 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0
\end{pmatrix}
\begin{pmatrix}
R' \\
G' \\
B'
\end{pmatrix}
```

これで以下のような画像ができます。

| 元画像(RGB) | RGB x 1.5 (R'G'B') | R x 1.4 (R''G''B'') |
|---|---|---|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/74f1acea-0ba2-0835-2e80-4b7fc4c0d97a.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6b13c000-170a-ce4c-6e61-4d386af8df36.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/b477834f-4810-b678-9aea-50913995b836.png) |

この式をまとめるとこうです。

```math
\begin{pmatrix}
R'' \\
G'' \\
B''
\end{pmatrix}
=
\begin{pmatrix}
1.4 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0
\end{pmatrix}
\begin{pmatrix}
1.5 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B
\end{pmatrix}
```

これを正方行列の形式にすると、以下のようになります。

```math
\begin{pmatrix}
R'' \\
G'' \\
B'' \\
1
\end{pmatrix}
=
\begin{pmatrix}
1.4 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
1.5 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B \\
1
\end{pmatrix}
```

間の2つの色行列は、掛け算で1つにまとめる事ができます。

```math
\begin{pmatrix}
1.4 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
\begin{pmatrix}
1.5 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0\\
0 & 0 & 0 & 1
\end{pmatrix}
=
\begin{pmatrix}
2.1 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0 \\
0 & 0 & 0 & 1
\end{pmatrix}
```

以下のように複数の変換を一度に行っても、最終結果として同じ画像が得られます。

```math
\begin{pmatrix}
R'' \\
G'' \\
B''
\end{pmatrix}
=
\begin{pmatrix}
2.1 & 0 & 0 & 0 \\
0 & 1.5 & 0 & 0 \\
0 & 0 & 1.5 & 0
\end{pmatrix}
\begin{pmatrix}
R \\
G \\
B
\end{pmatrix}
```

この例だと2つですが、これが3つや4つ更に例え100個に増えたとしても、一回の処理にまとめられます。
また、別々に処理すると各処理で小数点を切り捨てたり 0,255 で clamp したりで途中で画像が劣化する悪影響も避けられます。

# 最後に

代表的な線形変換は、以下のURLでだいたい試せるので是非お試しを。

- https://app.awm.jp/image.js/colormat.html

ImageMagick で色行列を扱う方法は、こちらにまとめてます。

- ImageMagick で -color-matrix を使う
  - https://qiita.com/yoya/items/dbd9e201205afb3e3a38