画像２値化の前処理にCLAHEを使う


# ２値化は画像処理の基本だが･･･
オープンソース化が進み、高度な画像処理が誰でも簡単に試せるようになった昨今ですが、
２値化処理は画像処理の基本として必ずおさえておきたい技術です。

>画像の２値化処理とは、所定の閾値をもって画像を白と黒の2色に変換する処理です

- ２値化画像の入力を前提とする画像処理アルゴリズムが多くある
- ２値化画像を題材とした加工事例が豊富にある
- 通常画像の特定部位を２値化画像でマスキングすることで、領域を限定した加工もできる

思い通りの２値化画像を手にすれば、その後の画像加工のアイディアもグンと広がります。

しかし・・・ **「期待したような２値化が上手く出来ない！」**
これもまたよくあります（苦笑）

例えば、こんな画像で考えてみましょう。
![キャプチャ01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/0bd09d0d-82a7-00b1-a55d-42493f53aebb.jpeg)
さて、このリング部分を２値化で捉えることを考えてみます。
画像２値化の閾値（thresh）を段階的に調整してみました。
![キャプチャ02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/05a077ac-0276-c827-f414-18ce409d4535.jpeg)



しかし、どの閾値で２値化しても上手くリングを捉えることができなかったようです。
- リングの左上部を残す閾値だとリング右下部が残らない
- 逆に右下部を残そうとすると左上部が残らない


# CLAHEによる明るさムラの調整
思惑どおりの２値化が出来なかったのは、画像内での明るさにムラがあり変化しているためです。
>実践においては「あるある」ですね！

こんな時にはCLAHEを試してみましょう。
CLAHEは「Contrast Limited Adaptive Histogram Equalization」の略称で、
日本語にすると「コントラスト制限付き適用的ヒストグラム平均化」です。
[CLAHE - Wikipedia](https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE)
[OpneCVチュートリアル：CLAHE (Contrast Limited Adaptive Histogram Equalization)](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_histograms/py_histogram_equalization/py_histogram_equalization.html#clahe-contrast-limited-adaptive-histogram-equalization)


さあ、では早速試してみましょう。
左がCLAHE適用前、右が適用後です。
![キャプチャ03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/e5b95c12-f78e-c8df-bfa6-ee1a2e5bcb4a.jpeg)
CLAHEは、一旦画像を細かい区画に分け、個別に明るさ調整した後に再合成するため、
加工結果は少しぎくしゃくとした状態にはなりますが、リングの部分はより鮮明になったように見えます。
 
# ２値化に再チャレンジ
さあ、CLAHE適用後の画像で改めて２値化をしてみましょう。
![キャプチャ04.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/a802823b-7f61-e9f4-8157-776fbc7e1a05.jpeg)


今度は閾値（thresh）が160~180 あたりで見事にリングの２値化に成功しました！

以上、画像に明るさムラがあり２値化が上手くいかなかったときのヒントとなれば幸いです。
最後までお読みいただき、ありがとうございました。

# サンプルコード
最後に本記事のサンプルコードも載せておきます。

```python:CLAHE_try.ipynb

%matplotlib inline
import math
import numpy as np
import matplotlib.pyplot as plt
import cv2

# ２値化閾値を変化させplot
def plot_threshold(img, threshStart, threshEnd, threshStep):
    imgs=[]
    for thresh in range(threshStart, threshEnd+1, threshStep):  
        _, dst = cv2.threshold(img, thresh, maxval=255, type=cv2.THRESH_BINARY)
        imgs.append(dst)

    col = 4
    row = (len(imgs)-1)//col + 1
    plt.figure(figsize=(col*3,row*3), facecolor='white')
    plt.subplots_adjust(wspace=0.0, hspace=0.2)

    for i, img in enumerate(imgs):
        plt.subplot(row, col, i+1)
        plt.imshow(img, cmap='gray')
        plt.title(f'thresh={threshStart+threshStep*i}')
        ax = plt.gca()
        ax.axes.xaxis.set_ticks([])
        ax.axes.yaxis.set_ticks([])

    plt.show()

# 検証用のoriginal画像の生成
im_org = np.zeros((256,256), dtype=np.uint8)
h, w = im_org.shape
im_org = cv2.circle(im_org, (w//2, h//2), w//3, 32, thickness=10)
for x in range(h):
    for y in range(w):
        add = int(math.sqrt(x**2+y**2)/1.5)
        im_org[y:y+1, x:x+1] += add 

# CLAHE適用
clahe = cv2.createCLAHE(clipLimit=8.0, tileGridSize=(8,8))
im_clahe = clahe.apply(im_org)   

# OriginalとCLAHE適用後の画像を表示
plt.figure(figsize=(15,18), facecolor='white')
plt.subplots_adjust(wspace=0.1, hspace=0.2)
plt.subplot(1, 2, 1)
plt.imshow(im_org, cmap='gray')
plt.title(f'Orignal')
plt.subplot(1, 2, 2)
plt.imshow(im_clahe, cmap='gray')
plt.title(f'After CLAHE')
plt.show()

# Original画像の2値化を試行
plot_threshold(im_org, 80, 190, 10)

# CLAHE適用後画像の2値化を試行
plot_threshold(im_clahe, 80, 190, 10)
```

::: note 
動作確認環境：
 ・ *python 3.6.9 + numpy 1.16.5 + opencv 3.4.4*
 ・ *python 3.9.7 + numpy 1.22.3 + opnecv 4.5.5*