OpenCV3.0から、画像処理の中でも非常に重要な処理の一つである、ラベリングが利用できるようになりました。この機能がOpenCVにも実装されたことで、ラベリングの使い勝手が向上したことは間違いありません。それでは具体的に使い方を紹介させていただきます。

ラベリングとは

ラベリングとは、二値化した画像内の連続した点の集まりごとに、個別の番号を割り当てる処理です。ラベリングにより、画像内にいくつのオブジェクトが存在しているのか、そしてそのオブジェクトがどのような特徴(形、大きさなど)を持ち、画像内のどこに存在しているのかを知ることができます。

それでは簡単にラベリングのイメージを説明します。

ラベリングのイメージ

元画像
02_color.png
二値化(注)
02_mono06.png
ナンバリング
01_number.png
サイズ ※外接矩形の高さと幅
01_size.png
面積
01_area.png
位置 ※外接矩形の左上座標
01_pos.png
中心(重心)座標 ※実際の座標は、浮動小数点数で出力されますが、下図では整数に丸めたものを表示しています
01_center.png

上図のように、二値化した画像に対しラベリングを行うことで、オブジェクトごとの各種情報を得ることができます。Pythonは非常に扱いやすい言語ですが、すべてPythonでコーディングをしようとすると、処理速度の面ではあまり有利ではありません。Pythonには、OpenCVのようにC言語などで書かれた高速で動作する各種ライブラリーやモジュールが豊富に用意されています。今回取り上げたラベリングではループ処理が多用されるため、ここでOpenCVが利用できるメリットは非常に大きいと思います。コードの簡素化と高速化が同時に実現できるわけです。

注)ラベリングは二値画像に対して行いますので、処理に適した二値画像生成のためのノウハウも必要となります。OpenCVを利用した二値化については、こちらの記事を参考にしてください。

2種類のラベリングと取得パラメータ

それでは具体的に、OpenCVに実装されたラベリング関数の使い方について説明いたします。

1)簡易版

とりあえずラベル情報だけを取得したい場合はこちらを利用します。

nLabels, labelImages = cv2.connectedComponents(image_src)

1)nLabelsは、ラベル数(ただし、背景のラベル番号0もカウントされているため、オブジェクトの数は nLabels - 1 となります)

2)labelImagesは、ラベル番号が入った配列データ(座標 (x, y) のラベル番号は、labelImages[x, y] で参照することができます)

3)image_srcは、二値画像の入力配列

2)詳細版

ラベル番号と同時に、そのオブジェクトの複数の情報を取得することができます。

nLabels, labelImages, data, center = cv2.connectedComponentsWithStats(image_src)

1)nLabelsは、ラベル数(ただし、背景のラベル番号0もカウントされているため、オブジェクトの数は nLabels - 1 となります)

2)labelImagesは、ラベル番号が入った配列データ(座標 (x, y) のラベル番号は、labelImages[x, y] で参照することができます)

3)dataは、オブジェク詳細の配列データ(x, y, w, h, size = data[ラベル番号] で参照することができます)
※x, y, w, h は、オブジェクトの外接矩形の左上のx座標、y座標、高さ、幅
※size は、面積(pixcel)

3)centerは、オブジェクの中心点(オブジェクトの重心座標 (x, y) が、浮動小数点数で出力されます。コーディングの際には、数値の型に注意してください)

3)image_srcは、二値画像の入力配列

サンプルコード

必要に応じ、入力画像のファイル名や使用する関数を差し替えてください。

サンプルプログラムの動作確認環境

1)OS: ubuntu16.04LTS(64bit)
2)Python: 3.4.1

ラベリング(簡易版)

#!/usr/bin/env python
        # -*- coding: utf-8 -*-

        import cv2
        import numpy as np
        import random
        import sys

        if __name__ == '__main__':

            # 対象画像を指定
            input_image_path = '<file path>/<file name>'

            # 画像をグレースケールで読み込み
            gray_src = cv2.imread(input_image_path, 0)

            # 前処理(平準化フィルターを適用した場合)
            # 前処理が不要な場合は下記行をコメントアウト
            blur_src = cv2.GaussianBlur(gray_src, (5, 5), 2)

            # 二値変換
            # 前処理を使用しなかった場合は、blur_srcではなくgray_srcに書き換えるする
            mono_src = cv2.threshold(blur_src, 48, 255, cv2.THRESH_BINARY_INV)[1]

            # ラベリング処理
            ret, markers = cv2.connectedComponents(mono_src)

            # ラベリング結果書き出し準備
            color_src = cv2.cvtColor(mono_src, cv2.COLOR_GRAY2BGR)
            height, width = mono_src.shape[:2]
            colors = []

            for i in range(1, ret + 1):
                colors.append(np.array([random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)]))

            # ラベリング結果を画面に表示
            # 各オブジェクトをランダム色でペイント
            for y in range(0, height):
         
                for x in range(0, width):
                    if markers[y, x] > 0:
                        color_src[y, x] = colors[markers[y, x]]
                    else:
                        color_src[y, x] = [0, 0, 0]

            # オブジェクトの総数を黄文字で表示
            cv2.putText(color_src, str(ret - 1), (100, 100), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))

            # 結果の表示
            cv2.imshow("color_src", color_src)

            cv2.waitKey(0)
            cv2.destroyAllWindows()

ラベリング(詳細版)

#!/usr/bin/env python
        # -*- coding: utf-8 -*-

        import cv2
        import numpy as np
        import random
        import sys

        if __name__ == '__main__':

            # 対象画像を指定
            input_image_path = '<file path>/<file name>'

            # 画像をグレースケールで読み込み
            gray_src = cv2.imread(input_image_path, 0)

            # 前処理(平準化フィルターを適用した場合)
            # 前処理が不要な場合は下記行をコメントアウト
            blur_src = cv2.GaussianBlur(gray_src, (5, 5), 2)

            # 二値変換
            # 前処理を使用しなかった場合は、blur_srcではなくgray_srcに書き換えるする
            mono_src = cv2.threshold(blur_src, 48, 255, cv2.THRESH_BINARY_INV)[1]

            # ラベリング結果書き出し用に二値画像をカラー変換
            color_src01 = cv2.cvtColor(mono_src, cv2.COLOR_GRAY2BGR)
            color_src02 = cv2.cvtColor(mono_src, cv2.COLOR_GRAY2BGR)

            # ラベリング処理
            label = cv2.connectedComponentsWithStats(mono_src)

            # オブジェクト情報を項目別に抽出
            n = label[0] - 1
            data = np.delete(label[2], 0, 0)
            center = np.delete(label[3], 0, 0)

            # オブジェクト情報を利用してラベリング結果を画面に表示
            for i in range(n):
         
                # 各オブジェクトの外接矩形を赤枠で表示
                x0 = data[i][0]
                y0 = data[i][1]
                x1 = data[i][0] + data[i][2]
                y1 = data[i][1] + data[i][3]
                cv2.rectangle(color_src01, (x0, y0), (x1, y1), (0, 0, 255))
                cv2.rectangle(color_src02, (x0, y0), (x1, y1), (0, 0, 255))

                # 各オブジェクトのラベル番号と面積に黄文字で表示
                cv2.putText(color_src01, "ID: " +str(i + 1), (x1 - 20, y1 + 15), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))
                cv2.putText(color_src01, "S: " +str(data[i][4]), (x1 - 20, y1 + 30), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))

                # 各オブジェクトの重心座標をに黄文字で表示
                cv2.putText(color_src02, "X: " + str(int(center[i][0])), (x1 - 30, y1 + 15), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))
                cv2.putText(color_src02, "Y: " + str(int(center[i][1])), (x1 - 30, y1 + 30), cv2.FONT_HERSHEY_PLAIN, 1, (0, 255, 255))

            # 結果の表示
            cv2.imshow("color_src01", color_src01)
            cv2.imshow("color_src02", color_src02)

            cv2.waitKey(0)
            cv2.destroyAllWindows()
関連記事