OpenCVで簡単に実装できる画像前処理

調べて実装して、評価してみた。あとアイデアをひとつだけ。

# 概要

1. OpenCV で簡単に実装できる画像前処理を調べて実装したよ
1. 実際に Object Detection する前に適用して評価したよ
1. 結果は・・・微妙だったよ

# モチベーション

Object Detection モデルは開発者が公開している pre-trained weight を使って遊んでいるだけの私だけど、モデルに入れる前の画像を前処理してモデルが判断しやすいようにすること、そしてモデルの出力を選別したり綺麗に整形したりするのは、使う側の責任だろうと。

ということで今回は画像前処理について実際にやってみたので、そのまとめをば。（要するに備忘録）
私は画像が専門じゃないので、OpenCV を使って簡単に実装できたものだけを。

画像は全て COCO 2017val に含まれていたものです。使っている Object Detection モデルは yolov5m で、ONNX で動かしています。もちろん使っているのはコレ。

https://github.com/tetutaro/yolo_various_framework

# 昼白色が強い画像の場合（ホワイトバランス調整）

特に室内の画像の場合、照明の昼白色が強くて「黄色いなオイ！」と思うことが時々ある。
そういう場合はホワイトバランスの調整をして、なるべく自然色に戻してあげた方が良いんじゃないかと。

```python
import numpy as np
import cv2

def adjust_white_balance(image: np.ndarray) -> np.ndarray:
    # white balance adjustment for strong neutral white
    image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
    avg_a = np.average(image[:, :, 1])
    avg_b = np.average(image[:, :, 2])
    image[:, :, 1] = image[:, :, 1] - (
        (avg_a - 128) * (image[:, :, 0] / 255.0) * 1.1
    )
    image[:, :, 2] = image[:, :, 2] - (
        (avg_b - 128) * (image[:, :, 0] / 255.0) * 1.1
    )
    image = cv2.cvtColor(image, cv2.COLOR_LAB2BGR)
    return image
```

BGR を [Lab色空間](https://ja.wikipedia.org/wiki/Lab%E8%89%B2%E7%A9%BA%E9%96%93)に変換して、ゴニョゴニョするだけ。

![white_balance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/6c919dde-0509-2ade-741c-cfbf811c00ae.png)
ホワイトバランス調整の例[^1]（左：調整なし、右：調整あり）
[^1]: [http://farm1.staticflickr.com/147/434840964_a9c5bc3b58_z.jpg](http://farm1.staticflickr.com/147/434840964_a9c5bc3b58_z.jpg)
confidence score 的には大して変わってないけど、真ん中の人やソファーの上の laptop のスコアがちょっと上がってるか。

# ノイズ除去（Gaussian Blur）

性能が悪いカメラだと、ノイズが乗っちゃってることがある。そういう場合はノイズ除去。

```python
import numpy as np
import cv2

def smooth_image(image: np.ndarray) -> np.ndarray:
    # image smoothing for noise removal
    return cv2.GaussianBlur(image, (5, 5), 0)
```

めちゃ簡単。GaussianBlur で、要するにボカしてノイズを分からないようにする。

![smoothing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/6e935a67-d613-f263-42dd-982fbf83fa0e.png)
ノイズ除去の例[^2]（左：除去なし、右：除去あり）
[^2]: [http://farm4.staticflickr.com/3265/5839036384_1ecac4f59a_z.jpg](http://farm4.staticflickr.com/3265/5839036384_1ecac4f59a_z.jpg)
よく見ると分かるけど、元のザラザラしたノイズがすっきりしている。
Object Detection 的にも、上部の大きな bowl がギリギリ認識できるようになった。

ノイズは、カメラの性能だけじゃなくて、例えば雨なんかもノイズの一種と見做すことが出来る。

![smoothing_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/7a796d3e-05c4-0df0-5d97-20146a738fed.png)
ノイズ除去の例[^3]（左：除去なし、右：除去あり）
[^3]: [http://farm1.staticflickr.com/5/7213514_c15cc3c285_z.jpg](http://farm1.staticflickr.com/5/7213514_c15cc3c285_z.jpg)
この場合、車も消火栓も confidence score が上がっている。

# 暗い部分を明るくする（コントラスト調整）

暗くてよく分かんないのは、明るくしてあげたい。

```python
import numpy as np
import cv2

def correct_contrast(image: np.ndarray) -> np.ndarray:
    # contrast correction that brightens dark areas
    b = 10  # HEURISTIC !!
    gamma = 1 / np.sqrt(image.mean()) * b
    g_table = np.array([
        ((i / 255.0) ** (1 / gamma)) * 255
        for i in np.arange(0, 256)
    ]).astype("uint8")
    return cv2.LUT(image, g_table)
```

![contrast.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/9507e928-f803-e291-9f67-54ca34559d44.png)
コントラスト調整の例[^4]（左：調整なし、右：調整あり）
[^4]: [http://farm2.staticflickr.com/1100/538718655_aef41a4977_z.jpg](http://farm2.staticflickr.com/1100/538718655_aef41a4977_z.jpg)
traffic light が１個消えちゃったけど、車の confidence score は上昇している。

暗くなると言えば逆光だが、ちょっとは効果がある。
![contrast_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/037a916f-6901-74b6-b3db-eeab9e02145f.png)
コントラスト調整の例[^5]（左：調整なし、右：調整あり）
[^5]: [http://farm6.staticflickr.com/5184/5694114164_e2a5d24659_z.jpg](http://farm6.staticflickr.com/5184/5694114164_e2a5d24659_z.jpg)
右側では、海に入っているサーファーを頑張って認識しようとしているのが分かる。

## やってみたけど使わないと判断したもの

### ヒストグラム平坦化

```python
import numpy as np
import cv2

def levelize_histogram(image: np.ndarray) -> np.ndarray:
    # make color distributions even
    for i in range(3):
        image[:, :, i] = cv2.equalizeHist(image[:, :, i])
    return image
```

暗い所を明るくするという意味で、色の分布を平坦化すればいいんじゃないの、という方法。
画像全体がすごい真っ暗！という場合は効果があるけど、普通の画像にやっちゃうと色が凄い事になっちゃうので、パス。

### 固定値を使ったコントラスト調整

```python
import numpy as np
import cv2

def correct_contrast_using_lut(image: np.ndarray) -> np.ndarray:
    # contrast correction using look-up-table
    a = 10
    c_table = np.array([
        255.0 / (1 + np.exp(-a * (i - 128) / 255))
        for i in np.arange(0, 256)
    ]).astype("uint8")
    return cv2.LUT(image, c_table)
```

前に出したコントラスト調整は、画像のRGBの値の平均を使っているけど、これは固定値でやるパターン。これも凄いことになっちゃう。

# Super-Resolution を使った鮮明化

COCOデータセットの中には、かなりボケてたりブレてたりする画像がある。こいつらを根本的にどうにかしたいなぁと考えて、思いついた。
「[Super-Resolution](https://www.pyimagesearch.com/2020/11/09/opencv-super-resolution-with-deep-learning/)（超解像）使えばいいんじゃね？」

dnn_superres を使うので、Python Library として普通の `opencv-python` じゃなくて `opencv-contrib-python` をインストールすることが必要です。

```python
import numpy as np
import cv2
from cv2 import dnn_superres, dnn_superres_DnnSuperResImpl

sr = dnn_superres.DnnSuperResImpl_create()
sr.readModel('superres/ESPCN_x4.pb')
sr.setModel('espcn', 4)

def upsample_image(
    image: np.ndarray,
    sr: dnn_superres_DnnSuperResImpl
) -> np.ndarray:
    # increase resolution with super-resolution to make the image clearer
    # and then shrink the image
    prev_height = image.shape[0]
    prev_width = image.shape[1]
    # sr.upsample() (super-resolution) is too slow when the image is big
    # so, do super-resolution after shrinking image
    image = cv2.resize(
        image, (prev_width // 2, prev_height // 2),
        interpolation=cv2.INTER_AREA
    )
    image = sr.upsample(image)
    # restore image size
    image = cv2.resize(
        image, (prev_width, prev_height),
        interpolation=cv2.INTER_AREA
    )
    return image
```
適当なサンプルプログラムで済まない。

1. SuperResolutionのモデルとしては、一番高速なESPCNを使う
1. ESPCNと言えども、画像のサイズが大きいと時間がかかるので、まずは半分に縮小する
1. ESPCN で４倍に拡大する
1. それをまた半分に縮小して、元のサイズに戻す

意図としては

1. 画像を縮小することでそもそもノイズを減らす効果を期待
1. 拡大に単なる補間ではない SuperResolution を使うことで、さらに滑らかになることを期待

![superres.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/f8e1e4d4-fcdc-10bc-0807-15b8de970888.png)
SuperResolutionをやった例[^6]（左：やってない、右：やった）
[^6]: [http://farm3.staticflickr.com/2258/2269567997_08065fe404_z.jpg](http://farm3.staticflickr.com/2258/2269567997_08065fe404_z.jpg)
こんなブレブレのやつでも、ちょっとはマシになるかな、と。

# 性能

coco dataset 2017val からランダムに1000枚を選んで計測した。

* `no_image_clarify`: 画像前処理なし
* `white_balance`: ホワイトバランス調整のみ
* `smoothing`: ノイズ除去のみ
* `contrast`: コントラスト調整のみ
* `superres`: SuperResolutionのみ
* `all`: 上記全部入り（画像に適用した順番は上の通り）
* `all2`: コントラスト調整すると、ノイズが乗ったみたいになるので、コントラスト調整したあとにノイズ除去した
* `all2_no_superres`: `all2` から SuperResolution だけやめる

## 所要時間

画像１枚あたりの平均
![time.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/b3b36aec-7ca1-07f5-0de2-cf9f00f4b6d9.png)
なんで `all` が前処理単体よりも低いんや・・・まぁ、Object Detection モデルの inference に要する時間に比べると、どれも誤差の範囲内ってことなんだろう。

## 精度

![map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84885/e139e5ed-1ffa-74f2-8b79-81beb2de24fc.png)
入れれば入れるほど落ちてるやんけ・・・

# まとめ

- 前処理を入れれば入れるほど精度が落ちてるのは、結局、特殊な画像だと良くなったように見えるけど、全体に対して一律に適用しちゃうと悪影響を及ぼすということなんだろうな
- 作っては見たものの、デフォルトで OFF にするよう修正します・・・
- ちゃんと評価して良かった（今日の良かった探し）