【Python/OpenCV】カラートラッキング(色追跡)で移動物体の検出

Python版OpenCVでカラートラッキング(色追跡)を実装し、移動物体を検出する方法をソースコード付きで解説します。

【色追跡とは】動画から特定の色を検出

カラートラッキング(色追跡)は、その名の通り、特定の色のみを検出して追跡します。
カラートラッキングには、通常RGB色空間ではなく、同系統の色の範囲を数値で指定しやすいHSV色空間を用います。

関連記事
HSVの原理 HSV色空間の原理・特徴・計算式
色追跡の原理 HSV色空間とカラートラッキングによる物体追跡の原理

今回は、PythonとOpenCVでカラートラッキング(色追跡)を実装してみました。

動画解説

本ページの内容は動画でも紹介しています。

HSV色空間の赤色

赤色の範囲 赤色の範囲(OpenCVのHSV色空間)
H 0~60, 300~360[度] 0~30, 150~179
S 50~100[%] 128~255
V 00~100[%] 0~255

サンプルコード

サンプルプログラムのソースコードです。

# -*- coding: utf-8 -*-
import cv2
import numpy as np

def red_detect(img):
    # HSV色空間に変換
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 赤色のHSVの値域1
    hsv_min = np.array([0,127,0])
    hsv_max = np.array([30,255,255])
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # 赤色のHSVの値域2
    hsv_min = np.array([150,127,0])
    hsv_max = np.array([179,255,255])
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)
    
    return mask1 + mask2


def main():
    videofile_path = "C:/github/sample/python/opencv/video/color_tracking/red_pendulum.mp4"

    # カメラのキャプチャ
    cap = cv2.VideoCapture(videofile_path)
    
    while(cap.isOpened()):
        # フレームを取得
        ret, frame = cap.read()

        # 赤色検出
        mask = red_detect(frame)

        # 結果表示
        cv2.imshow("Frame", frame)
        cv2.imshow("Mask", mask)

        # qキーが押されたら途中終了
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

実行結果

サンプルプログラムの実行結果です。

【ブロブ解析】最も大きい赤色領域の座標を取得して追跡

画像をラベリング処理し、ラベル付けされた領域の特徴を解析することをブロブ解析といいます。
Python版OpenCVでは、cv2.connectedComponentsWithStats()で2値画像のブロブ解析ができます。
つまり、マスク画像を与えてやると、面積が最大の領域のみを取り出したりできます。
これを応用すれば、動画に複数の赤色物体が映っていても、一番大きな赤色物体だけを追跡したりできます。

ブロブ解析の詳細はこちら
1 【Python/OpenCV】最大面積のブロブ解析(座標・大きさなど)

サンプルコード

# -*- coding: utf-8 -*-
import cv2
import numpy as np

def red_detect(img):
    # HSV色空間に変換
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 赤色のHSVの値域1
    hsv_min = np.array([0,127,0])
    hsv_max = np.array([30,255,255])
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # 赤色のHSVの値域2
    hsv_min = np.array([150,127,0])
    hsv_max = np.array([179,255,255])
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)
    
    return mask1 + mask2

# ブロブ解析
def analysis_blob(binary_img):
    # 2値画像のラベリング処理
    label = cv2.connectedComponentsWithStats(binary_img)

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

    # ブロブ面積最大のインデックス
    max_index = np.argmax(data[:, 4])

    # 面積最大ブロブの情報格納用
    maxblob = {}

    # 面積最大ブロブの各種情報を取得
    maxblob["upper_left"] = (data[:, 0][max_index], data[:, 1][max_index]) # 左上座標
    maxblob["width"] = data[:, 2][max_index]  # 幅
    maxblob["height"] = data[:, 3][max_index]  # 高さ
    maxblob["area"] = data[:, 4][max_index]   # 面積
    maxblob["center"] = center[max_index]  # 中心座標
    
    return maxblob

def main():
    videofile_path = "C:/github/sample/python/opencv/video/color_tracking/red_pendulum.mp4"

    # カメラのキャプチャ
    cap = cv2.VideoCapture(videofile_path)
    
    while(cap.isOpened()):
        # フレームを取得
        ret, frame = cap.read()

        # 赤色検出
        mask = red_detect(frame)

        # マスク画像をブロブ解析(面積最大のブロブ情報を取得)
        target = analysis_blob(mask)

        # 面積最大ブロブの中心座標を取得
        center_x = int(target["center"][0])
        center_y = int(target["center"][1])

        # フレームに面積最大ブロブの中心周囲を円で描く
        cv2.circle(frame, (center_x, center_y), 30, (0, 200, 0),
                   thickness=3, lineType=cv2.LINE_AA)

        # 結果表示
        cv2.imshow("Frame", frame)
        cv2.imshow("Mask", mask)

        # qキーが押されたら途中終了
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main() 

実行結果

【応用①】振り子の運動を観測

赤色の振り子を色追跡し、その中心座標を記録してグラフ化してみます。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time

def red_detect(img):
    # HSV色空間に変換
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 赤色のHSVの値域1
    hsv_min = np.array([0,127,0])
    hsv_max = np.array([30,255,255])
    mask1 = cv2.inRange(hsv, hsv_min, hsv_max)

    # 赤色のHSVの値域2
    hsv_min = np.array([150,127,0])
    hsv_max = np.array([179,255,255])
    mask2 = cv2.inRange(hsv, hsv_min, hsv_max)
    
    return mask1 + mask2

# ブロブ解析
def analysis_blob(binary_img):
    # 2値画像のラベリング処理
    label = cv2.connectedComponentsWithStats(binary_img)

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

    # ブロブ面積最大のインデックス
    max_index = np.argmax(data[:, 4])

    # 面積最大ブロブの情報格納用
    maxblob = {}

    # 面積最大ブロブの各種情報を取得
    maxblob["upper_left"] = (data[:, 0][max_index], data[:, 1][max_index]) # 左上座標
    maxblob["width"] = data[:, 2][max_index]  # 幅
    maxblob["height"] = data[:, 3][max_index]  # 高さ
    maxblob["area"] = data[:, 4][max_index]   # 面積
    maxblob["center"] = center[max_index]  # 中心座標
    
    return maxblob


def main():
    # データ格納用のリスト
    data = []

    # 動画ファイルのパス
    videofile_path = "C:/github/sample/python/opencv/video/color_tracking/red_pendulum.mp4"

    # 記録データの保存先パス
    csvfile_path = "C:/github/sample/python/opencv/video/color_tracking/data.csv"

    # カメラのキャプチャ
    cap = cv2.VideoCapture(videofile_path)

    # 開始時間
    start = time.time()

    while(cap.isOpened()):
        # フレームを取得
        ret, frame = cap.read()

        # カラートラッキング(赤色)
        mask = red_detect(frame)

        # マスク画像をブロブ解析(面積最大のブロブ情報を取得)
        target = analysis_blob(mask)

        # 面積最大ブロブの中心座標を取得
        center_x = int(target["center"][0])
        center_y = int(target["center"][1])

        # フレームに面積最大ブロブの中心周囲を円で描く
        cv2.circle(frame, (center_x, center_y), 30, (0, 200, 0),
                   thickness=3, lineType=cv2.LINE_AA)

        # 経過時間, x, yをリストに追加
        data.append([time.time() - start, center_x, center_y])

        # ウィンドウ表示
        cv2.imshow("Frame", frame)
        cv2.imshow("Mask", mask)

        # qキーが押されたら途中終了
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break

    # CSVファイルに保存
    np.savetxt(csvfile_path, np.array(data), delimiter=",")

    # キャプチャ解放・ウィンドウ廃棄
    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

■記録データをグラフ化

# -*- coding: utf-8
import numpy as np
import matplotlib.pyplot as plt

def main():
    # CSVのロード
    data = np.genfromtxt(
        "C:/github/sample/python/opencv/video/color_tracking/data.csv", delimiter=",", dtype='float')

    # 2次元配列を分割(経過時間t, x座標, y座標の1次元配列)
    t = data[:,0]
    x = data[:,1]
    y = data[:,2]

    # グラフにプロット
    plt.rcParams["font.family"] = "Times New Roman" # フォントの種類
    plt.plot(t, x, "r-", label="x")
    plt.plot(t, y, "b-", label="y")
    plt.xlabel("Time[sec]", fontsize=16)     # x軸ラベル
    plt.ylabel("Position[px]", fontsize=16)    # y軸ラベル
    plt.grid()         # グリッド表示
    plt.legend(loc=1, fontsize=16)       # 凡例表示
    plt.show()


if __name__ == "__main__":
    main()

実行結果

サンプルプログラムの実行結果です。

■カメラ映像

■グラフ化

グラフの横軸は経過時間[sec]、縦軸は赤色物体の位置座標(x,y)[px]です。
振り子の周期運動を観測できていることがわかります。

関連記事
1 PythonでOpenCV入門 サンプル集
2 【Python】画像処理プログラミング入門
3 【画像処理入門】アルゴリズム&プログラミング
Python画像処理
技術雑記

コメント

タイトルとURLをコピーしました