画像２値化の前処理に凹凸係数も試す

# 凹凸係数による明るさムラ除去
2年ほど前、CLAHE（コントラスト制限付き適応的ヒストグラム平均化）を利用し、明るさムラで思惑どおりに2値化が出来ない画像への対処方法について記事を書きました。

https://qiita.com/ydclab_0003/items/3bfaaa364d9e9e1664a5

今回は、明るさムラ除去に「凹凸係数」を使うとどうなるか確認してみたいと思います。
具体的な処理ロジックについては以下の記事を参考にさせていただきました。
[背景ムラの除去 - 2値化の前処理として](https://kakasi.hatenablog.com/entry/2020/03/02/151053)
[OpenCVSharp4で凹凸係数法による影の除去](https://negizoku.hatenablog.jp/entry/2020/09/20/231519)

> 凹凸係数は画像中のそれぞれの画素の輝度値を、その周辺画素の平均輝度値で除算することで求められます。これにより画像全体における明るさを、各画素周辺の局所領域における相対的な明るさに変換する考え方のようです。

# CLAHEとの前処理結果の比較
では、[前回記事](https://qiita.com/ydclab_0003/items/3bfaaa364d9e9e1664a5)でも使った以下の画像で前処理結果を比較してみたいと思います。

![キャプチャ01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/0bd09d0d-82a7-00b1-a55d-42493f53aebb.jpeg)
> 前回記事の試みでは、オリジナル画像のままではこのリング部分の2値化が上手くいかず、CLAHEで前処理後に2値化すると上手くいきました。

早速、前処理の結果をみていきます。
左から順に、オリジナル、CLAHE適用後、そして凹凸係数法適用後の画像となります。

![キャプチャ01.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/cbbb8e10-18f7-9105-c85a-43b3dafe0208.jpeg)

CLAHEは、画像を一旦細かい区画に分けて個別調整し再合成するため、少しぎくしゃくした仕上がりになりますが、凹凸係数法の方はかなり滑らかな仕上がりになっています。
それでは、これら3つの画像をそれぞれ2値化してみたいと思います。

# オリジナル画像2値化
先ずは、オリジナルのムラ画像に対し閾値（thresh）を段階的に変化させたものです。
どの閾値を採用しても、リング部分を捉えることは出来ませんでした。

![キャプチャ02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/05a077ac-0276-c827-f414-18ce409d4535.jpeg)

# CLAHE適用画像の2値化
続いて、CLAHE適用画像に対し閾値（thresh）を段階的に変化させたものです。
閾値160~180 あたりでリングの2値化が出来ています。

![キャプチャ02.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/f2726a31-425f-8835-088c-7bc5a86d5171.jpeg)

# 凹凸係数法適用画像の2値化
同様に、凹凸係数法適用画像に対し閾値（thresh）を段階的に変化させたものです。
閾値（thresh）が200~210 あたりでリング2値化が出来ました。

![キャプチャ03.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626665/fbe86dd0-5dfe-32d4-7da3-3a0913b40bc0.jpeg)



# サンプルコード
本記事のサンプルコードです。

```python:CLAHE_Ototsu_try.ipynb
import math
import numpy as np
import matplotlib.pyplot as plt
import cv2
%matplotlib inline

# ２値化閾値を変化させplot
def plot_threshold(img, threshBegin, threshEnd, threshStep):
    imgs=[]
    for thresh in range(threshBegin, 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',vmin=0, vmax=255)
        plt.title(f'thresh={threshBegin+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, x] += add

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

# 凹凸係数法適用
blur = cv2.blur(im_org, (31, 31))
im_ototsu = im_org*1.0 / (blur + 1e-6)
im_ototsu = cv2.normalize(im_ototsu, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

# Originalと前処理後の画像を表示
plt.figure(figsize=(16,8), facecolor='white')
plt.subplots_adjust(wspace=0.2, hspace=0.2)
plt.subplot(1, 3, 1)
plt.imshow(im_org, cmap='gray', vmin=0, vmax=255)
plt.title('Orignal')
plt.subplot(1, 3, 2)
plt.imshow(im_clahe, cmap='gray', vmin=0, vmax=255)
plt.title('After CLAHE')
plt.subplot(1, 3, 3)
plt.imshow(im_ototsu, cmap='gray', vmin=0, vmax=255)
plt.title('After Ototsu')
plt.show()

# Original画像の2値化を試行
plot_threshold(im_org, 80, 190, 10)
# CLAHE適用後画像の2値化を試行
plot_threshold(im_clahe, 110, 220, 10)
# 凹凸係数法適用後画像の2値化を試行
plot_threshold(im_ototsu, 140, 250, 10)
```
以上、画像に明るさムラがあり思惑どおりに2値化出来なかったときのヒントになれば幸いです。
最後までお読みいただき、ありがとうございました。

::: note 
動作確認環境：
 ・ *python 3.11.6 + numpy 1.26.0 + opencv 4.8.1*