ImageMagick でコントラスト補正

# はじめに

-auto-level や -normalize は画像が明るすぎたり暗すぎたりする際、見やすくする為によく使われます。それらが具体的に何をしてるかのメモです。以下のオプションを紹介します。

```
-level, -auto-level, -normalize, -contrast-stretch, -linear-stretch, -contrast
```

# コントラスト補正

コントラスト補正は特定区間の輝度レベルを輝度 Min-Max まで広げる処理です。一次式(ax+b)で線形に広げる事が多く、その事を linear contrast stretch とも呼びます。

例えば、曇り空でどんより暗い景色の画像は、輝度ヒストグラムの分布が左側(暗い方)に偏っている為、輝度レベルを広げて右側(明るい方)を有効活用するだけで見やすくなります。

|画像|補正後|
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/c669ee22-2bbd-1031-ab13-6055d58c3849.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/f2aceaa5-defd-6f0f-e736-a82467930086.png)|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/8ea17f01-52e8-0d72-9cb7-35001e7cb8b9.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/469b1735-e11d-e2c5-b344-c919aa758db2.png)|

コントラスト補正はこのような処理ですが、オプションによってこの広げ方が微妙に違います。

# ImageMagick オプション

## -level

具体的にどこの輝度レベルを広げるのか割合で指定します。

| |
|---|
| ![640-histのlevel解説.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/39f054fa-0ed7-37d5-609c-df654cc9c8f1.png) |

0%〜70% の輝度レベルを ０%〜100% に広げるコマンドラインです。結果的に右側の30%の領域まで広がります。

```
% convert 640.png -level 0%,70% level.png
```

|640.png|auto-level.png|
|---|---|
| ![640-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/38f898e4-5d04-ca8a-474a-626a4ae80c38.png) | ![level-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4936e4a7-5160-e01e-aaa3-f625cf091f32.png) |
| ![640.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4443d52f-056b-7b09-1efe-ddd864951796.png) |  ![level.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/15f93de5-01b1-f8ca-922f-43c72b0bb05a.png)|

ヒストグラムをみれば最大輝度が分かるはずで、70% は ImageMagick に自動で算出して欲しいですよね。
-auto-level はその要求に応えてくれます。

## -auto-level

-auto-level は、一番低い(暗い)輝度と、一番高い(明るい)輝度を自動で探して、そこを min-max として輝度レベルを引き延ばします。-level のように割合を指定する必要がありません。

| |
|---|
|![640-histのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/490351ab-b612-99bf-9756-eb880382fd72.png)|

```
convert 640.png -auto-level auto-level.png
```
|640.png|auto-level.png|
|---|---|
| ![640-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/38f898e4-5d04-ca8a-474a-626a4ae80c38.png) | ![auto-level-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/83c6b6c5-8c25-de06-fef0-ca6dac34420c.png) |
| ![640.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4443d52f-056b-7b09-1efe-ddd864951796.png) | ![auto-level.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/a89518d5-2f4b-b6dc-cb42-a1c9e65b35c7.png)|

実はこの画像は上手くいきすぎる例で、1 pixel でも輝度 0 と 255 があると何も変換しなくなります。実際の写真では結構そういうケースがあります。

極端な例ですが、LED付きスキーポールなんてものがあったとして、その先っぽが目一杯白く光ってるとします。

| |
|---|
|![640-2のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/5640ce62-3206-9085-fca2-2bd53d664357.png)|

この画像は auto-level だと何も変換されません。
1 pixel とはいえ最大値の255輝度があるので、そこが max となり引き伸す余地がなくなるからです。

| |
|---|
|![640-2-histのコピー2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/55d64430-6857-cfdc-a55d-2ce559df3e3a.png) |

```
% convert 640-2.png -auto-level auto-level-2.png
```

|640-2.png | auto-level-2.png|
|---|---|
|![640-2-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/93b01b8e-6a04-e174-8903-b718db245dc0.png) | ![auto-level-2-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/46dfbfcc-ffa2-7d62-5013-94ae7f429d4c.png)|
|![640-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/41cab2e0-ca28-cb1f-7410-22abc40f6736.png) | ![auto-level-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/d6783bdd-715d-210f-ccd3-5de106dd64be.png)|

-normalize はこのケースでも、うまく変換します。

## -normalize

-auto-level と似ていますが、暗い方からいくつかのピクセルと、明るい方からもいくつかのピクセルをスルーして、min, max を決めます。感覚としては極端に暗かったり明るかった位するピクセルを無視してくれます。

その為、先ほどのように 1 pixel 程度 FFFFFF(白) が入っていても、それがなかった場合の -auto-level に近い動作をします。

| |
|---|
|![normalizeの図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/80631232-99c0-3c68-47c9-28cc15907098.png)|

```
% convert 640-2.png -normalize normalize-2.png
```

|640-2.png | normalize-2.png|
|---|---|
|![640-2-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/93b01b8e-6a04-e174-8903-b718db245dc0.png) | ![normalize-2-hist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/032d4731-4378-c3e4-f434-3d133f44093e.png) |
|![640-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/41cab2e0-ca28-cb1f-7410-22abc40f6736.png) | ![normalize-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4a8a15c0-c3f6-7373-7d3c-ca5d344eb894.png)|

どうやって無視してるのか。それはヒストグラムの面積比でわずかに内側を最小輝度と最大輝度扱いする事によってです。

| min | max |
|---|---|
![normalize-minの図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/edbc64a1-41d9-b197-7f4c-4edad5613644.png) | ![normalize-maxの図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/84236e01-99db-ff1e-277d-0c5b32a243e6.png)|

-contrast-stretch の機能を利用します。

尚、公式サイトの Usage によると -normalize は -contrast-stretch 2%,1% と同じです。ソースコード上は 0.85%,0.05% に見えますが、とにかく異常値を排除すれば十分なだけの小さな値です。

## -contrast-stretch

-constrast-stretch はヒストグラムの面積を指定して -auto-level の min, max から更に範囲を狭める機能です。0%,0% を指定すると -auto-level と同じ挙動になります。

先ほどのスキー場は曇りがちで全体的に画像が暗く、ヒストグラムが偏っていたので MAX 輝度を自動で探せましたが、暗い場所と明るい場所が同時に写っている写真だと困ります。

contrast-stretch はどちらか、もしくは両方を捨てる選択を、そこそこ直感的に出来ます。

| |
|---|
|![IMG-640.P-histのコピー.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2d6c649f-f7a7-29f1-8623-3ce9a8fbe22d.png) |

明るい方からヒストグラム面積50%分を捨てて(白に集約して)、暗い方のコントラストを引き延ばすコマンド。

```
% convert  IMG-640.PNG -contrast-stretch 0%,50% IMG-640-contrast-stretch.PNG
```

|IMG-640.PNG|IMG-640-contrast-stretch.PNG|
|---|---|
|![IMG-640-hist.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/e2b35a80-469c-499b-b542-c071cf953615.png) | ![IMG-640-contrast-stretch-hist.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/22d57250-9c7a-7297-72c5-70fa33ba0c42.png) |
|![IMG-640.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/1d5b032c-37b1-6e5b-1a27-ff38d5510fbf.png) | ![IMG-640-contrast-stretch.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/d1a13be3-fb9d-ce7f-0ee3-232c430f2f86.png) |

-contrast-stretch はヒストグラムの面積を計算する時に R,G,B を同様にカウントするのが欠点になっています。、
RGB の3つは同じ値でも、見た目の明るさが G>R>>B の関係にある為、低輝度は緑が過少評価され、高輝度は青が過少評価されるからです。
どうなるかというと、低輝度では緑が潰れやすく、高輝度では青が潰れやすくなります。

これを改善したものが -linear-stretch です。

## -linear-stretch

-contrast-stretch とパラメータの指定の仕方は同じですが、内部処理では RGB をグレイスケール化した輝度値でヒストグラムを作り、その面積で min, max を決定してコントラストを拡大します。

先ほど紹介した -contrast-stretch の R,G,B で処理が偏る欠点がなくなります。

## -contrast

なお、パラメータを必要としない -contrast 引数も存在します。複数並べて重ね掛けもできます。

```shell
% convert saru.jpg -contrast saru-c1.jpg
% convert saru.jpg +contrast saru+c1.jpg
% convert saru.jpg -contrast -contrast saru-c2.jpg
```

| saru.jpg | saru-c1.jpg |
|---|---|---|---|
|![saru.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/e1d1b3c6-389f-65f6-cd4f-4428c6bc2414.jpeg) | ![saru-c1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/64dccc49-2836-757c-2690-6605e7469eb6.jpeg)|
|saru+c1.jpg | saru-c2.jpg | 
|![saru+c1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/119ba6f7-7c5c-a261-c189-7e4dd870bf19.jpeg) | ![saru-c2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/34989f9a-fad2-c24c-6f87-e1d83655ed80.jpeg) | 

今まで紹介した他のオプションは特定範囲の輝度を全体に広げますが、一方、このオプションは範囲をそのままで、非線形にレベルを歪めます。
具体的には、HSB の B(輝度)を　以下の式で変換します。

```c:MagickCore/enhance.c
ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
                      brightness);
```

グラフにするとこうです。

| -contrast |
|---|
|![im-contrast.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/8cd5136b-eed8-cb09-1a82-cef94dd0bd47.png)|

画像の情報が中輝度に集中してる仮定をおいて、高輝度や低輝度を犠牲にし、中輝度周辺を引き伸ばす戦略です。機械学習でよく見るシグモイド関数による活性化と同じですね。

ちなみに、+contrast はその逆の動作をします。(ImageMagick は +〜 で逆になる事が多い)

| +contrast |
|---|
|![im+contrast.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/fb74f18a-2fa1-21d1-9a59-6a9a493c1b5f.png)|

# さいごに

出来れば明るい方も暗い方も残したいし、50%なんてパラメータも面倒なので自動でいい感じに処理して欲しくなるでしょう。
-equalize のヒストグラム平坦化が、この要望に応えてくれます(勿論デメリットもあります)。そのうち解説を書きます。 (追記。書きました > [ImageMagick で Equalize (ヒストグラム平坦化)](https://qiita.com/yoya/items/979c1099909ce983830d) )

# 参考 URL

- http://www.imagemagick.org/Usage/color_mods/#levels