一、二値画像の基本概念
-
二値画像の定義 二値画像(バイナリ画像)とは、画像の各ピクセルが2つの値しか持たない特殊な画像形式を指します。一般的に白黒、B&W、モノクロ画像として表現されます。 二値画像では、各ピクセルの輝度値は0または255のいずれかであり、それぞれ黒と白を表現します。
-
二値画像の特性
- データ量が少ない
- 処理速度が速く、計算コストが低い
- 実用性が高い
- 幾何学的概念を定義できる
- 二値画像の長所と短所 長所:ストレージ容量が少ない 短所:人物や風景などの画像を表現する場合、輪郭情報のみを表現でき、内部のテクスチャ特徴が明確に表現されない。このような場合、より豊富なテクスチャ特徴を持つグレースケール画像を使用する必要があります。
二、二値画像の接続性と距離
- 近傍( Neighborhood ) 任意のピクセル(x,y)に対して、{(x+p,y+q)}(p,qは適切な整数の組)のピクセル集合をそのピクセルの近傍と呼びます。つまり、ピクセル(x,y)の周囲のピクセルによって形成される領域のことです。
-
4-近傍 ピクセル(x,y)に対し、上下左右の4つのピクセル:(x-1,y)、(x,y-1)、(x+1,y)、(x,y+1)
-
D-近傍(対角近傍) ピクセル(x,y)に対し、4つの対角線上のピクセル:(x-1,y-1)、(x+1,y-1)、(x-1,y+1)、(x+1,y+1)
-
8-近傍 ピクセル(x,y)に対し、4-近傍の4ピクセルとD-近傍の4ピクセルを合わせた8つのピクセル
- 二値画像の隣接
- 4-隣接:ピクセルqがピクセルpの4-近傍に存在する場合、pとqは4-隣接関係にある
- 8-隣接:ピクセルqがピクセルpの8-近傍に存在する場合、pとqは8-隣接関係にある
- D-隣接:ピクセルqがピクセルpのD-近傍に存在する場合、pとqはD-隣接関係にある
- m-隣接:ピクセルqがpの4-近傍にある、またはqがpのD-近域にあり、かつqの4-近域とpの4-近域の共通部分が空の場合、pとqはm-隣接関係にある
m-隣接の目的は8-隣接における二義性を解消することです。
-
連結性 集合Sにおいて、連結成分が存在する条件は、隣接するピクセルが特定の隣接関係を満たすピクセルの並びとして存在することです。例えば、ピクセルpからqへA1,A2,A3…An個のピクセル点があり、隣接ピクセルが特定の隣接関係を満たす場合、pとqの間に経路が存在します。経路が首尾で繋がっている場合、閉経路と呼びます。
-
ピクセルの連結 二値画像において、同じ値を持つ2つのピクセルa1とa2について、それらと同じ値を持つすべてのピクセルが4-近傍または8-近囲内でa1からa2への隣接ピクセル列を構成できる場合、a1とa2は4-連結または8-連結であると呼びます。そのピクセル列は4-パスまたは8-パスと呼ばれます。
-
連結成分 二値画像の連結成分を研究する際、1ピクセルの連結成分を4-連結または8-連結で定義する必要があります。0ピクセルの連結成分は、反対の8-連結または4-連結を使用すると矛盾が生じます。
三、画像タイプの比較
-
二値画像(バイナリ画像) 各ピクセルが2つの値しか持たない画像形式。
-
グレースケール画像 各ピクセルが1つの輝度値を持つ画像。通常、最も暗い黒から最も明るい白までの輝度で表示されます。理論的には、任意の色の異なる濃淡、または異なる輝度の異なる色で表現可能です。グレースケール画像は、一般的に8ビットの非線形スケールで保存され、256階調の輝度を表現できます(16ビットの場合は65536階調)。
-
カラー画像 各ピクセルが赤(R)、緑(G)、青(B)の3つの成分で表現され、各成分は0から255の値を取ります。CMYKでは、シアン、マゼンタ、イエロー、ブラックの4成分で表現されます。
四、画像の二値化
画像の二値化とは、画像の各ピクセルの輝度値を0または255に設定し、画像全体を明確な白黒効果に変換するプロセスです。二値化により、画像データ量が大幅に減少し、対象物の輪郭が強調されます。
二値化画像を得るには、まず画像をグレースケールに変換し、256階調のグレースケール画像から適切なしきい値を選択して、画像の全体的および局所的特徴を保持した二値化画像を取得します。しきい値以上の輝度を持つピクセルは特定の物体とみなし、輝度値を255に設定します。それ以外のピクセルは物体領域外とみなし、輝度値を0(背景または例外領域)に設定します。
1. 画像二値化の方法とPython実装
一般的な二値化方法には、単純二値化法、平均値法、二峰値法、OTSU法などがあります。
1.1 単純二値化法
画像をグレースケールに変換後、127(輝度値範囲の中央)をしきい値として選択し、127より大きいピクセル値を255に、127以下を0に設定します。
def simple_threshold(image_path):
# 画像の読み込み
input_image = cv.imread(image_path)
# グレースケール変換
gray_image = cv.cvtColor(input_image, cv.COLOR_BGR2GRAY)
# 127をしきい値として二値化
binary_result = np.where(gray_image > 127, 255, 0)
# 結果の表示
plt.imshow(binary_result, cmap='gray')
plt.title('Simple Thresholding')
plt.show()
1.2 平均値法
各画像の輝度値が大きく異なる場合に対応するため、画像自体の平均値をしきい値として使用します。
def mean_threshold(image_path):
# 画像の読み込み
input_image = cv.imread(image_path)
# グレースケール変換
gray_image = cv.cvtColor(input_image, cv.COLOR_BGR2GRAY)
# 平均値を計算
threshold_value = np.mean(gray_image)
print(f"Calculated threshold: {threshold_value}")
# 平均値をしきい値として二値化
binary_result = np.where(gray_image > threshold_value, 255, 0)
# 結果の表示
plt.imshow(binary_result, cmap='gray')
plt.title('Mean Thresholding')
plt.show()
1.3 二峰値法
ヒストグラムは画像の重要な特性であり、画像内の輝度変化を分析するのに役立ちます。したがって、物体と背景の輝度値のコントラストが明確な場合、ヒストグラムには物体と背景を表す二つのピークが含まれ、その間の谷底が最適な二値化の分離点となります。
def bimodal_threshold(image_path):
# 画像の読み込み
input_image = cv.imread(image_path)
# グレースケール変換
gray_image = cv.cvtColor(input_image, cv.COLOR_BGR2GRAY)
# ヒストグラムのフラット化
hist_data = gray_image.flatten()
# ヒストグラムの表示
plt.subplot(121)
plt.hist(hist_data, 256)
# 最も頻度の高い2つの値を取得
from collections import Counter
hist_counter = Counter(hist_data)
peak1, peak2 = hist_counter.most_common(2)[0][0], hist_counter.most_common(2)[1][0]
# 2つのピーク間の最小値を見つける
if peak1 > peak2:
peak1, peak2 = peak2, peak1
min_count = np.iinfo(np.int16).max
optimal_threshold = 0
for value in range(peak1, peak2 + 1):
if hist_counter[value] < min_count:
min_count = hist_counter[value]
optimal_threshold = value
print(f"Peak values: {peak1}, {peak2}")
print(f"Optimal threshold: {optimal_threshold}")
# 最適なしきい値で二値化
binary_result = np.where(gray_image > optimal_threshold, 255, 0)
# 結果の表示
plt.subplot(122)
plt.imshow(binary_result, cmap='gray')
plt.show()
1.4 OTSU法
二峰値法には明らかな欠点があります。ヒストグラムは不連続であり、多くの尖峰と揺れがあるため、正確な極値点を見つけることが困難です。日本の学者大津展之はこの谷底に適切な数学的表現を見出し、1979年に発表しました。この二値化方法は大津アルゴリズム(Otsu's method)として知られています。
OTSU法は最大クラス間分散法とも呼ばれ、大津法で求められたしきい値で画像を二値化分割すると、前景と背景の間のクラス間分散が最大になるためです。これは画像分割におけるしきい値選択の最適アルゴリズムと見なされ、計算が簡単で画像の輝度とコントラストの影響を受けないため、デジタル画像処理で広く応用されています。
def otsu_threshold(image_path):
# 画像の読み込み
input_image = cv.imread(image_path)
# グレースケール変換
gray_image = cv.cvtColor(input_image, cv.COLOR_BGR2GRAY)
# 画像の高さと幅を取得
height, width = gray_image.shape[:2]
# 最適なしきい値を探索
max_variance = 0
best_threshold = 0
for threshold in range(256):
# しきい値で前景と背景に分割
foreground = gray_image[gray_image < threshold]
background = gray_image[gray_image >= threshold]
# 各クラスの重みと平均輝度を計算
if len(foreground) == 0 or len(background) == 0:
continue
weight_foreground = len(foreground) / (height * width)
weight_background = len(background) / (height * width)
mean_foreground = np.mean(foreground)
mean_background = np.mean(background)
# クラス間分散を計算
between_class_variance = weight_foreground * weight_background * \
((mean_foreground - mean_background) ** 2)
# 最大分散を更新
if between_class_variance > max_variance:
max_variance = between_class_variance
best_threshold = threshold
print(f"Optimal threshold: {best_threshold}")
# 最適なしきい値で二値化
binary_result = np.where(gray_image < best_threshold, 0, 255)
# 結果の表示
plt.imshow(binary_result, cmap='gray')
plt.title('Otsu Thresholding')
plt.show()
2. OpenCV-Pythonでの二値化の応用
OpenCVでは、単純なしきい値処理と適応的しきい値処理の2つの方法が提供されています。
2.1 単純しきい値処理(Simple Thresholding)
関数のプロトタイプは以下の通りです:
retval, dst = cv.threshold(src, thresh, maxval, type[, dst])
パラメータ説明:
- 第1引数src:入力画像(グレースケールである必要があります)
- 第2引数thresh:しきい値(通常127)
- 第3引数maxval:最大値(通常255)
- 第4引数type:しきい値処理のタイプ(5種類)
しきい値処理のタイプ:
- cv.THRESH_BINARY: 二値化
- cv.THRESH_BINARY_INV: 逆二値化
- cv.THRESH_TRUNC: しきい値で打ち切る
- cv.THRESH_TOZERO: しきい値以下を0に
- cv.THRESH_TOZERO_INV: しきい値より大きい値を0に
テストコード:
# 画像の読み込みと前処理
source_image = cv.imread('lenna.jpg')
rgb_image = cv.cvtColor(source_image, cv.COLOR_BGR2RGB)
gray_image = cv.cvtColor(rgb_image, cv.COLOR_RGB2GRAY)
# 各種しきい値処理の実行
ret, binary = cv.threshold(gray_image, 127, 255, cv.THRESH_BINARY)
ret, binary_inv = cv.threshold(gray_image, 127, 255, cv.THRESH_BINARY_INV)
ret, trunc = cv.threshold(gray_image, 127, 255, cv.THRESH_TRUNC)
ret, tozero = cv.threshold(gray_image, 127, 255, cv.THRESH_TOZERO)
ret, tozero_inv = cv.threshold(gray_image, 127, 255, cv.THRESH_TOZERO_INV)
# 結果の表示
image_titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
processed_images = [gray_image, binary, binary_inv, trunc, tozero, tozero_inv]
plt.figure(figsize=(12, 8))
for idx in range(6):
plt.subplot(2, 3, idx + 1)
plt.imshow(processed_images[idx], 'gray')
plt.title(image_titles[idx])
plt.axis('off')
plt.tight_layout()
plt.show()
2.2 適応的しきい値処理(Adaptive Thresholding)
関数のプロトタイプは以下の通りです:
dst = cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst])
パラメータ説明:
- src, maxValue, thresholdType:単純しきい値処理と同じ
- adaptiveMethod:しきい値の計算方法(2種類)
- blockSize:近傍領域のサイズ(奇数である必要あり)
- C:計算からの減算値
adaptiveMethodの種類:
- cv.ADAPTIVE_THRESH_MEAN_C: 近傍領域の平均値からCを引く
- cv.ADAPTIVE_THRESH_GAUSSIAN_C: 近傍領域のガウス加重和からCを引く
テストコード:
# 画像の読み込みと前処理
source_image = cv.imread('lenna.jpg')
rgb_image = cv.cvtColor(source_image, cv.COLOR_BGR2RGB)
gray_image = cv.cvtColor(rgb_image, cv.COLOR_RGB2GRAY)
# 単純しきい値処理の実行
ret, simple_thresh = cv.threshold(gray_image, 127, 255, cv.THRESH_BINARY)
# 適応的しきい値処理の実行
adaptive_mean = cv.adaptiveThreshold(gray_image, 255, cv.ADAPTIVE_THRESH_MEAN_C,
cv.THRESH_BINARY, 11, 2)
adaptive_gauss = cv.adaptiveThreshold(gray_image, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C,
cv.THRESH_BINARY, 11, 2)
# 結果の表示
image_titles = ['Original Image', 'Simple Thresholding',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
processed_images = [gray_image, simple_thresh, adaptive_mean, adaptive_gauss]
plt.figure(figsize=(10, 8))
for idx in range(4):
plt.subplot(2, 2, idx + 1)
plt.imshow(processed_images[idx], 'gray')
plt.title(image_titles[idx])
plt.axis('off')
plt.tight_layout()
plt.show()
適応的しきい値処理は、画像の局所的な特性に基づいてしきい値を決定するため、テキストのような画像の処理に特に適しています。