OpenCV – 画像処理の2値化の仕組みと cv2.threshold() の使い方

概要
画像処理の2値化の仕組みと OpenCV の cv2.threshold() の使い方を解説します。
2値化
画像をある閾値を基準にして、2つの階調で表現される2値画像 (binary image) に変換する処理のことを2値化 (Thresholding) といいます。
以下の例を見てみましょう。 左側のグレースケール画像の各画素は0から255の256階調の値で表現されています。 これを値が10より大きいなら1、10以下なら0と変換します。 すると、各画素が $0, 255$ の2階調で表される2値画像が得られます。
$$ \mathrm{dst}(x, y) = \begin{cases} 0 & \mathrm{src}(x, y) \le 10 \\ 1 & \mathrm{src}(x, y) > 10 \end{cases} $$ただし、$\mathrm{src}(x, y)$ は入力画像の位置 $(x, y)$ の画素値、$\mathrm{dst}(x, y)$ は出力画像の位置 $(x, y)$ の画素値を表します。
OpenCV で2値化を行う方法
2値化は全画素を同じ閾値で変換する大域的2値化 (global thresholding) と領域ごとに異なる閾値で変換する適応的2値化 (adaptive thresholding) の2つの方法があります。 明るさが画像全体で均一でない場合、大域的2値化では上手く2値化ができないかもしれません。その場合は、適応的2値化を検討してください。
OpenCV では、2値化は以下の関数で行えます。
- 大域的2値化
- threshold(): ある閾値以下かどうかで2値化します。
- inRange(): 2つの閾値の範囲内かどうかで2値化します。
- 適応的2値化
- adaptiveThreshold(): 領域ごとに適応的に閾値を決めて、2値化します。
このうち、cv2.threshold() を使った2値化のやり方を以下で紹介します。
cv2.threshold()
cv2.threshold() では、ある1つの閾値を決めて、2値化を行います。
ret, dst = cv2.threshold(src, dst, threshold, maxValue, thresholdType)
- 引数
- src: 入力画像 (uint8 または float 型の numpy 配列)
- threshold: 閾値
- maxValue: cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV の場合に、使用する。
- thresholdType: 2値化の方法
- 返り値
- retval: 閾値 (cv2.THRESH_OTSU、cv2.THRESH_TRIANGLE を使用した場合に自動的に決まった閾値を知るための返り値)
- dst: 2値画像
基本的な使い方
import cv2
import numpy as np
from IPython.display import display, Image
def imshow(img):
"""ndarray 配列をインラインで Notebook 上に表示する。
"""
ret, encoded = cv2.imencode(".jpg", img)
display(Image(encoded))
カラー画像なので、cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
でグレースケール画像に変換します。
今回は、次のように2値化します。
$$ \mathrm{dst}(x, y) = \begin{cases} 0 & \mathrm{src}(x, y) \le 10 \\ 255 & \mathrm{src}(x, y) > 10 \end{cases} $$そのため、閾値 threshold
を10、閾値より大きい場合に変換する値 maxValue
を255、2値化の方法 thresholdType
を cv2.THRESH_BINARY
に設定しました。
# 画像を読み込む。
img = cv2.imread("sample.jpg")
# グレースケール形式に変換する。
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2値化する。
ret, binary = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)
imshow(binary)

2値化の目的は、画像の画素を関心がある領域とそうでない領域に分けることにあります。 輪郭抽出を行なう findContours() などの関数は、背景が0、抽出対象の物体が0以外の値をとる2値画像を要求するため、2値化する際は関心がある領域 (今回の場合、ミジンコ) の画素が255となるように閾値を選びます。
thresholdType
thresholdType
は2値化の方法を指定します。
thresholdType | 概要 |
---|---|
cv2.THRESH_BINARY | threshold 以下の値を0、それ以外の値を maxValue にして2値化を行います。 |
cv2.THRESH_BINARY_INV | threshold 以下の値を maxValue 、それ以外の値を0にして2値化を行います。 |
cv2.THRESH_TRUNC | threshold 以下の値はそのままで、それ以外の値を threshold にします。 |
cv2.THRESH_TOZERO | threshold 以下の値を0、それ以外の値はそのままにします。 |
cv2.THRESH_TOZERO_INV | threshold 以下の値はそのままで、それ以外の値を0にします。 |
cv2.THRESH_OTSU | 大津の手法で閾値を自動的に決める場合に指定します。 |
cv2.THRESH_TRIANGLE | ライアングルアルゴリズムで閾値を自動的に決める場合に指定します。 |
- cv2.THRESH_BINARY
- cv2.THRESH_BINARY_INV
- cv2.THRESH_TRUNC
- cv2.THRESH_TOZERO
- cv2.THRESH_TOZERO_INV

横軸が入力の値、縦軸が変換後の値を表します。
cv2.THRESH_OTSU、cv2.THRESH_TRIANGLE を指定すると、閾値を画像から自動的に決めます。
この2つのフラグは cv2.THRESH_BINARY + cv2.THRESHOLD_OTSU
のように上記5つのいずれかのフラグと組み合わせて指定します。
自動的に決めた閾値は返り値 ret
として返します。
# 大津の手法
ret, binary = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(f"threshold: {ret}") # threshold: 88.0
imshow(binary)
threshold: 88.0

# トライアングルアルゴリズム
ret, binary = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY + cv2.THRESH_TRIANGLE)
print(f"threshold: {ret}") # threshold: 3.0
imshow(binary)
threshold: 4.0

綺麗に2値化できていますね。
閾値の決め方について
cv2.THRESH_OTSU または cv2.THRESH_TRIANGLE で自動で決めた閾値でうまく2値化ができるればそれでよいですが、いつも上手くいくとは限りません。 その場合は手動で閾値を決める必要があります。 2値化の結果を確認しながら、関心がある領域が0以外の値になるように閾値を調整しましょう。

threshold=10
の結果 (左) 及び threshold=170
の結果 (右) です。
左のようになるのが理想であり、右は2値化が綺麗にできていません。
ipywidgets を使用したパラメータ調整
Jupyter Notebook で利用できる ipywidgets を使ってパラメータを調整するためのコードを記載します。
import cv2
import numpy as np
from IPython.display import Image, display
from ipywidgets import widgets
from matplotlib import pyplot as plt
def imshow(img):
"""画像を Notebook 上に表示する。
"""
encoded = cv2.imencode(".png", img)[1]
display(Image(encoded))
def process(thresh, type_, auto):
"""2値化処理を行い、結果を表示する。
"""
type_ = eval(type_)
auto = eval(auto)
if auto:
type_ += auto
ret, binary = cv2.threshold(img, thresh, maxval=255, type=type_)
imshow(binary)
param_widgets = {}
# パラメータ thresh を設定するスライダー
param_widgets["thresh"] = widgets.IntSlider(
min=0, max=255, step=1, value=0, description="thresh: "
)
# パラメータ type を設定するプルダウンメニュー
options = [
"cv2.THRESH_BINARY",
"cv2.THRESH_BINARY_INV",
"cv2.THRESH_TRUNC",
"cv2.THRESH_TOZERO",
"cv2.THRESH_TOZERO_INV",
]
param_widgets["type_"] = widgets.Dropdown(options=options, description="type: ")
# 閾値自動化の方法を使用するかどうか
options = [
"None",
"cv2.THRESH_OTSU",
"cv2.THRESH_TRIANGLE",
]
param_widgets["auto"] = widgets.Dropdown(options=options, description="auto: ")
for x in param_widgets.values():
x.layout.width = "400px"
# 画像を読み込む。
img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)
# ウィジェットを表示する。
widgets.interactive(process, **param_widgets)
-
前の記事
記事がありません
-
次の記事
OpenCV – 画像をグリッド上に分割する、複数の画像をグリッド上に結合する方法 2020.03.11