LoginSignup
19
13

More than 1 year has passed since last update.

【画像処理】PythonでOpenCVを使ったラベリング処理

Last updated at Posted at 2021-02-17

PythonでOpenCVを使ったラベリング処理
前回の講座ではPythonでOpenCVを使わずにラベリング処理を学び、ラベリングの仕組みを理解できたかと思います。

今回は「PythonでOpenCVを使ったラベリング処理」を学んでいきます。
それぞれのバージョンはPython 3.8.2、OpenCV 4.2.0になります。また、今回の記事の内容はOpenCVの公式ドキュメントを参考にしています。

OpenCVでラベリング処理

OpenCVに実装されているラベリング処理は大きく分けて、
①簡易版ラベリング(connectedComponents)
②詳細版ラベリング(connectedComponentsWithStats)

以上の2種類があります。
それぞれの違いと具体的な使い方について説明します。

簡易版ラベリング処理

簡易版は、戻り値がラベル数入力画像と同じ大きさで、ラベル番号が振られた配列の2つのみです。

connectedComponents.py

# 簡易版ラベリング処理
retval, labels = cv2.connectedComponents(image)

# 簡易版ラベリング処理(アルゴリズムを指定できる)
retval, labels = cv2.connectedComponentsWithAlgorithm(image, connectivity, ltype, ccltype)

Parameters 説明
image 8ビットの2値化画像(CV_8U)
retval ラベル数
labels ラベル番号が振られた配列(入力画像と同じ大きさ)
connectivity 近傍指定(8 or 4)
ltype 戻り値labelsの型を指定(CV_32S or CV_16U)
ccltype ラベリングアルゴリズムを指定(SAUF or BBDT)

実装

実際の実装はこのようになります。

connectedComponents_easy.py

import sys
import numpy as np
import cv2
import random

# 2値化
def binarize(src_img, thresh, mode):
    gray_img = cv2.cvtColor(src_img, cv2.COLOR_RGB2GRAY)
    bin_img = cv2.threshold(gray_img, thresh, 255, mode)[1]
    return bin_img

# ラベルテーブルの情報を元に入力画像に色をつける
def put_color_to_objects(src_img, label_table):
    label_img = np.zeros_like(src_img)
    for label in range(label_table.max()+1):
        label_group_index = np.where(label_table == label)
        label_img[label_group_index] = random.sample(range(255), k=3)
    return label_img

if __name__ == "__main__":
    
    src_img = cv2.imread(sys.argv[1])
    bin_img = binarize(src_img, 180, cv2.THRESH_BINARY_INV)
    
    retval, labels = cv2.connectedComponents(bin_img)
    cv2.imwrite("labels.png", put_color_to_objects(src_img, labels))

1.1 入力画像 1.2 2値化画像 1.3 処理画像
spc_logo.pngsrc_img.png binarize.pngbin_img.png label.pnglabels.png

「1.3 処理画像」のようにラベリング処理ができたことが確認できると思います。
単純にラベリング処理された結果のみが必要な場合はこれで十分ですが、物体の位置や面積が必要な場合は次の詳細版ラベリング処理を使うことで、簡単に取得できます。

詳細版ラベリング処理

簡易版の戻り値2つに加えて、物体ごとの座標面積物体の中心座標が戻り値に含まれています。


# 詳細版ラベリング処理
retval, labels, stats, centroids = cv.connectedComponentsWithStats(image)

# 詳細版ラベリング処理(アルゴリズムを指定できる)
retval, labels, stats, centroids = cv.connectedComponentsWithStatsWithAlgorithm(image, connectivity, ltype, ccltype)

Parameters 説明
image 8ビットの2値化画像(CV_8U)
retval ラベル数
labels ラベル番号が振られた配列(入力画像と同じ大きさ)
stats 物体ごとの座標と面積(ピクセル数)
centroids 物体ごとの中心座標
connectivity 近傍指定(8 or 4)
ltype 戻り値labelsの型を指定(CV_32S or CV_16U)
ccltype ラベリングアルゴリズムを指定(SAUF or BBDT)

実装

実際の実装はこのようになります。

connectedComponentsWithStats.py

import sys
import numpy as np
import cv2
import random

# 2値化
def binarize(src_img, thresh, mode):
    gray_img = cv2.cvtColor(src_img, cv2.COLOR_RGB2GRAY)
    bin_img = cv2.threshold(gray_img, thresh, 255, mode)[1]
    return bin_img

# ラベルテーブルの情報を元に入力画像に色をつける
def put_color_to_objects(src_img, label_table):
    label_img = np.zeros_like(src_img)
    for label in range(label_table.max()+1):
        label_group_index = np.where(label_table == label)
        label_img[label_group_index] = random.sample(range(255), k=3)
    return label_img

# 各ラベルの座標と面積を描画する
def draw_stats(src_img, stats):
    stats_img = src_img.copy()
    for coordinate in stats[1:]:
        left_top = (coordinate[0], coordinate[1])
        right_bottom = (coordinate[0] + coordinate[2], coordinate[1] + coordinate[3])
        stats_img = cv2.rectangle(stats_img, left_top, right_bottom, (0, 0, 255), 1)
        stats_img = cv2.putText(stats_img, str(coordinate[4]), left_top, cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), 1)
    return stats_img

# 物体の中心座標を描画する
def draw_centroids(src_img, centroids):
    centroids_img = src_img.copy()
    for coordinate in centroids[1:]:
        center = (int(coordinate[0]), int(coordinate[1]))
        centroids_img = cv2.circle(centroids_img, center, 1, (0, 0, 255))
    return centroids_img

if __name__ == "__main__":
    
    src_img = cv2.imread(sys.argv[1])
    bin_img = binarize(src_img, 180, cv2.THRESH_BINARY_INV)
    
    retval, labels, stats, centroids = cv2.connectedComponentsWithStats(bin_img)
    stats_img = draw_stats(src_img, stats)
    centroids_img = draw_centroids(stats_img, centroids)

    cv2.imwrite("labels.png", put_color_to_objects(src_img, labels))
    cv2.imwrite("centroids_img.png", centroids_img)

2.1 入力画像 2.2 2値化画像 2.3 処理画像
spc_logo.pngsrc_img.png binarize.pngbin_img.png label.pngcentroids_img.png

「2.3 処理画像」のようにラベリング処理に加えて、各物体の位置と面積、中心座標が取得できたことが確認できると思います。

ラベリングアルゴリズムの違い(SAUF or BBDT)

OpenCVで指定できるアルゴリズムは「SAUF」または「BBDT」の2つで、指定の方法が以下の3つあります。公式ドキュメントはこちら

8近傍 4近傍
cv2.CCL_WU SAUF SAUF
cv2.CCL_DEFAULT BBDT SAUF
cv2.CCL_GRANA BBDT SAUF

この2つのアルゴリズムの違いは、ラベル割り当ての順番です。
SAUFは、必ず上の行からラベルが割り当てられるのに対して、BBDTは、必ずしも上の行からラベルが割り当てられるとは限りません。

今回は、SAUFBBDTの違いを確認するために、実験を行いました。
実験方法としては、「それぞれのアルゴリズムで同じ画像からラベリングされたオブジェクトを小さいラベル番号から1つずつ出力し、その順番が異なれば、そこに差分がある」としました。
そして実際に実験した結果、同じ8近傍でも複雑な画像は特にSAUFBBDFの差分が出ることがわかりました。(今回の実験では500個以上のオブジェクトを比較したため、途中結果は載せずに結論のみ記載しています。)

以下、各アルゴリズムでの速度比較になります。比較に使用した画像は、「1.1 入力画像(3264×2448)」です。

簡易版(connectedComponentsWithAlgorithm)

8近傍 4近傍
SAUF 10.1053ms 10.2545ms
BBDT 8.1517ms -

詳細版(connectedComponentsWithStatsWithAlgorithm)

8近傍 4近傍
SAUF 12.1041ms 12.4613ms
BBDT 9.6318ms -

上記の結果から、BBDTのほうが実行速度の面では有利なので、通常はアルゴリズムを意識する必要はないと言えます。(デフォルト設定がcv2.CCL_DEFAULTのため)しかし、ラベル割り当ての順番を保証する必要があるといった場合には、8近傍でもSAUFアルゴリズムを使うために、cv2.CCL_WUの設定にするべきでしょう。

さいごに

今回は、「PythonでOpenCVを使ったラベリング処理」について解説しました。

それでは引き続きよろしくお願いいたします。

目次は以下の記事からご覧になれます。

19
13
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
13