凹凸係数による明るさムラ除去
2年ほど前、CLAHE(コントラスト制限付き適応的ヒストグラム平均化)を利用し、明るさムラで思惑どおりに2値化が出来ない画像への対処方法について記事を書きました。
今回は、明るさムラ除去に「凹凸係数」を使うとどうなるか確認してみたいと思います。
具体的な処理ロジックについては以下の記事を参考にさせていただきました。
背景ムラの除去 - 2値化の前処理として
OpenCVSharp4で凹凸係数法による影の除去
凹凸係数は画像中のそれぞれの画素の輝度値を、その周辺画素の平均輝度値で除算することで求められます。これにより画像全体における明るさを、各画素周辺の局所領域における相対的な明るさに変換する考え方のようです。
CLAHEとの前処理結果の比較
では、前回記事でも使った以下の画像で前処理結果を比較してみたいと思います。
前回記事の試みでは、オリジナル画像のままではこのリング部分の2値化が上手くいかず、CLAHEで前処理後に2値化すると上手くいきました。
早速、前処理の結果をみていきます。
左から順に、オリジナル、CLAHE適用後、そして凹凸係数法適用後の画像となります。
CLAHEは、画像を一旦細かい区画に分けて個別調整し再合成するため、少しぎくしゃくした仕上がりになりますが、凹凸係数法の方はかなり滑らかな仕上がりになっています。
それでは、これら3つの画像をそれぞれ2値化してみたいと思います。
オリジナル画像2値化
先ずは、オリジナルのムラ画像に対し閾値(thresh)を段階的に変化させたものです。
どの閾値を採用しても、リング部分を捉えることは出来ませんでした。
CLAHE適用画像の2値化
続いて、CLAHE適用画像に対し閾値(thresh)を段階的に変化させたものです。
閾値160~180 あたりでリングの2値化が出来ています。
凹凸係数法適用画像の2値化
同様に、凹凸係数法適用画像に対し閾値(thresh)を段階的に変化させたものです。
閾値(thresh)が200~210 あたりでリング2値化が出来ました。
サンプルコード
本記事のサンプルコードです。
import math
import numpy as np
import matplotlib.pyplot as plt
import cv2
%matplotlib inline
# 2値化閾値を変化させ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値化出来なかったときのヒントになれば幸いです。
最後までお読みいただき、ありがとうございました。
動作確認環境:
・ python 3.11.6 + numpy 1.26.0 + opencv 4.8.1