Warning: Undefined variable $position in /home/pystyles/pystyle.info/public_html/wp/wp-content/themes/lionblog/functions.php on line 4897

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

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

概要

画像処理の2値化の仕組みと OpenCV の cv2.threshold() の使い方を解説します。

Advertisement

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)$ の画素値を表します。

2値化の例

OpenCV で2値化を行う方法

2値化は全画素を同じ閾値で変換する大域的2値化 (global thresholding) と領域ごとに異なる閾値で変換する適応的2値化 (adaptive thresholding) の2つの方法があります。 明るさが画像全体で均一でない場合、大域的2値化では上手く2値化ができないかもしれません。その場合は、適応的2値化を検討してください。

OpenCV では、2値化は以下の関数で行えます。

  • 大域的2値化
    • threshold(): ある閾値以下かどうかで2値化します。
    • inRange(): 2つの閾値の範囲内かどうかで2値化します。
  • 適応的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値画像
Advertisement

基本的な使い方

In [1]:
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))

sample.jpg

カラー画像なので、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値化の方法 thresholdTypecv2.THRESH_BINARY に設定しました。

In [2]:
# 画像を読み込む。
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
$$ \mathrm{dst}(x, y) = \begin{cases} 0 & \mathrm{src}(x, y) \le \mathrm{threshold} \\ \mathrm{maxValue} & otherwise \end{cases} $$
  • cv2.THRESH_BINARY_INV
$$ \mathrm{dst}(x, y) = \begin{cases} \mathrm{maxValue} & \mathrm{src}(x, y) \le \mathrm{threshold} \\ 0 & otherwise \end{cases} $$
  • cv2.THRESH_TRUNC
$$ \mathrm{dst}(x, y) = \begin{cases} \mathrm{src}(x, y) & \mathrm{src}(x, y) \le \mathrm{threshold} \\ \mathrm{threshold} & otherwise \end{cases} $$
  • cv2.THRESH_TOZERO
$$ \mathrm{dst}(x, y) = \begin{cases} 0 & \mathrm{src}(x, y) \le \mathrm{threshold} \\ \mathrm{src}(x, y) & otherwise \end{cases} $$
  • cv2.THRESH_TOZERO_INV
$$ \mathrm{dst}(x, y) = \begin{cases} \mathrm{src}(x, y) & \mathrm{src}(x, y) \le \mathrm{threshold} \\ 0 & otherwise \end{cases} $$

横軸が入力の値、縦軸が変換後の値を表します。

cv2.THRESH_OTSU、cv2.THRESH_TRIANGLE を指定すると、閾値を画像から自動的に決めます。 この2つのフラグは cv2.THRESH_BINARY + cv2.THRESHOLD_OTSU のように上記5つのいずれかのフラグと組み合わせて指定します。 自動的に決めた閾値は返り値 ret として返します。

In [3]:
# 大津の手法
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
In [4]:
# トライアングルアルゴリズム
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値化が綺麗にできていません。

Advertisement

ipywidgets を使用したパラメータ調整

Jupyter Notebook で利用できる ipywidgets を使ってパラメータを調整するためのコードを記載します。

ipywidgets

In [5]:
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)