【Python】OpenCVのMeanShiftとCamShiftによる物体の発見・追跡

Pythonの応用
スポンサーリンク

OpenCVを使ったPythonでの画像処理について、Lucas-Kanade法密なオプティカルフローでの物体の追跡(Object Tracking)について触れてきました。

ここでは画像中にある物体を発見・追跡するためのMeanShift、CamShiftアルゴリズムについて見ていこうと思います。

MeanShiftアルゴリズムは、画像中に点の分布などがあるときに、そこへ小さなウィンドウを移動させて画素の分布密度や画素数が最大になる領域にウィンドウを合わせていく処理になります。

CamShiftアルゴリズムは、MeanShiftアルゴリズムで追跡するウインドウが常に同じサイズなので、追跡対象の見え方によってウィンドウのサイズを調節する処理になります。

ここでは、webカメラで撮影された顔を検出して、その移動を追跡する操作をやっていきます。

スポンサーリンク

OpenCVでのMeanShiftアルゴリズム

では、OpenCvのMeanShiftアルゴリズムを使ってwebカメラでの物体追跡(顔の追跡)をやってきましょう。

import cv2
import numpy as np


cap = cv2.VideoCapture(0)
ret,frame = cap.read()

face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')

OpenCVとNumPyのライブラリーをインポートして、内臓webカメラに接続して映像を読み込みます。CascadeClassifier()を使って顔の検出器を読み込んでおきます。

Haar Cascadesによる顔の検出はすでにあつかったのでこちらを参照してください。

【Python】OpenCVのHaar Cascadesによる顔検出
OpenCVを使ったPythonでの画像処理について、Haar Cascadesという分類器を使って画像からの顔の検出を扱っていきます。顔の検出と顔認識とは違うことに注意しましょう。ここでは画像からの顔検出とwebカメラの顔検出を行います。

while文を使って、キャプチャーを実行して行きます。

while True:

    ret,frame = cap.read()

    face_rects = face_cascade.detectMultiScale(frame)

    (face_x,face_y,w,h) = tuple(face_rects[0])
    track_window = (face_x,face_y,w,h)

    roi = frame[face_y:face_y+h, face_x:face_x+w]

    hsv_roi =  cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

    roi_hist = cv2.calcHist([hsv_roi],[0],None,[180],[0,180])

    cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)

    term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)


    if ret == True:

        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)

        rect, track_window = cv2.meanShift(dst, track_window, term_crit)

        x,y,w,h = track_window
        img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), (0,0,255),5)

        cv2.imshow('img2',img2)

        if cv2.waitKey(60) & 0xff == 27:
            break

    else:
        break

while文を使って、キャプチャーを実行し、read()で読み込みます。retはデータの読み込みの可否のture/falseの取得、frameは映像のパラメータの取得です。

先にCascadeClassifier()で顔の検出器を読み込んでいるので、detectMultiScale()を使って顔検出を行います。

顔検出の最初のパラメータの位置と幅と高さをface_x,face_y,w,hにタプルで渡して、追跡で移動させるウインドウを作ります。

このウインドウは全体画像とは異なるサイズの画像の合成となるので、トラッキングの為の関心領域ROI(Region of Interest)を設定します。フレームの中にオフセットの位置を指定しています。このROIについては画像の合成のところですでに扱った内容です。

【Python】OpenCVで画像を合成する - addWeighted, bitwise演算, ROI
Pythonに画像処理ライブラリのOpenCVを使って、2つの画像を合成したり重ねたりする方法を見ていきたいと思います。addWeighted()での合成や、関心領域(ROI)とbitwise演算を使った合成の方法を見ていきます。

これをHSVカラーマッピングで使用するようにcv2.cvtColor()で変換します。

物体が動くと、その動きはヒストグラムを逆投影した画像中に反映して映されるので、各フレームでターゲットを逆投影するためのヒストグラムをcalcHist()を使って見つけます。

calcHist()についてはこちらで扱いました。

【Python】OpenCVを使ったヒストグラム - calcHist(), equalizeHist()
OpenCVを使ったPythonでの画像処理についてヒストグラムを扱います。ヒストグラムを求めるにはcalcHist()を使います。画像のコントラストを調整するのにヒストグラム平坦化があり、equalizeHist()を使います。

これを、関数cv2.normalize()を使って最小0、最大255の範囲でヒストグラム配列値を正規化してコントラストを低減しています。

term_criteは繰り返し処理の終了条件です。Lucas-Kanade法を扱った時にも触れました。この条件が満たされた時にアルゴリズムの繰り返し計算が終了します。cv2.TERM_CRITERIA_EPSは指定された精度(epsilon)、 cv2.TERM_CRITERIA_COUNTは指定された繰り返しの最大回数(count)です。ここではこれらのどちらかの条件が満たされた時に繰り返し計算の終了を指定しています。ここでは回数を10、精度を1にしています。

各種設定が決まったので、if文を使って画像の読み込みが成立している時の処理を行って行きます。コードの終わりの方でelse文を指定してretがfalseならbreak処理します。

まずHSV形式でストリームのフレームを変換します。

cv2.calcBackProject()を使い、先に作成したroi_histに基づいた逆投影を計算します。パラメータはcalcHist()とほぼ同じ使い方です。

矩形の新しい座標を取得するためにcv2.meanShift()を適用します。

取得した新しい座標から、cv2.rectangle()を使ったframe内に赤い矩形を描いていきます。

これをimshow()で描画します。

if cv2.waitKey(30) & 0xff == 27: で[esc]キーが押された時にbreak処理します。

while文はここまでです。

cap.release()
cv2.destroyAllWindows()

最後に、cap.release()でデバイスを解放し、cv2.destroyAllWindows()で全て終了させます。

コードは以上です。

このコードのwhile文の中の冒頭のface_rectからif文の手前のterm_criteまでの部分は、取り出して外に出し、face_cascadeの下に配置してもいいと思います。私の環境ではカメラの起動に時間がかかって、顔を認知できずに初期の追跡ウインドウが設定できなかったので、こういうコードにしてみました。

このPythonスクリプトを実行すると、webカメラが起動し、検出された顔が赤い四角で囲まれます。ただし、最初に検出した顔で四角の追跡サイズを決めているので、カメラに近ずいたり遠ざかった場合に問題が生じます。ここを調整するのが、次のCamShiftアルゴリズムです。

スポンサーリンク

OpenCVでのCamShiftアルゴリズム

CamShiftアルゴリズムは、MeanShiftアルゴリズムで追跡するウインドウが常に同じサイズなので、追跡対象の見え方によってウィンドウのサイズを調節する処理になります。

上のコードを下記のように書き換えてます。全体の流れは同じで一部分を変更しているだけです。

import cv2
import numpy as np


cap = cv2.VideoCapture(0)
ret,frame = cap.read()

face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')

while True:
    
    ret,frame = cap.read()

    face_rects = face_cascade.detectMultiScale(frame)

    (face_x,face_y,w,h) = tuple(face_rects[0])
    track_window = (face_x,face_y,w,h)

    roi = frame[face_y:face_y+h, face_x:face_x+w]

    hsv_roi =  cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

    roi_hist = cv2.calcHist([hsv_roi],[0],None,[180],[0,180])

    cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)

    term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1)


    if ret == True:

        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)

        ##############################################################
        ########## CamShiftアルゴリズム #################################
        ##############################################################

        rect, track_window = cv2.CamShift(dst, track_window, term_crit)

        pts = cv2.boxPoints(rect)
        pts = np.int0(pts)
        img2 = cv2.polylines(frame,[pts],True, (0,0,255),5)
        cv2.imshow('img2',img2)

        #############################################################
        #############################################################
        #############################################################

        if cv2.waitKey(60) & 0xff == 27:
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

コメントアウトで3段の#で挟まれている部分がMeanShiftのコードと違う部分です。この部分のみについて触れていきます。

cv2.MeanShift()の代わりにcv2.CamShift()で処理して矩形の新しい座標を取得します。

cv2.boxPoints()で矩形データを4点座標に変換し、np.int0で整数にキャストしています。

これをcv2.polylines()を使って多角形として描いています。polylines()については線や図を描く処理ですでに扱いました。

【Python】OpenCVで線や図を描く - rectangle, circle, line, putText, polylines
PythonにOpenCVをインポートして画像に線や図を描画する方法学びます。ここでは矩形のrectangle()、円のcircle()、直線のline()、テキストのputText()、 多角形のpolylines()を扱います。

上との違いはこの部分のみです。

このPythonスクリプトを実行するとMeanShiftで行ったものとは違って、顔を傾けたり、カメラに近づいたり遠ざかったりすると、顔を検出した赤い枠が顔を傾けた方向に合わせてひし形になったり、近寄ると大きくなったり、遠ざかると小さくなって顔をトラッキングすることができます。ただ、ちょっとズレが微妙なトラッキングかもしれません。

スポンサーリンク

カメラの起動時間を考慮したコードの修正

ここまでの処理において、カメラの起動時間の関係について、先ほど次のようなことを述べました。

このコードのwhile文の中の冒頭のface_rectからif文の手前のterm_criteまでの部分は、取り出して外に出し、face_cascadeの下に配置してもいいと思います。私の環境ではカメラの起動に時間がかかって、顔を認知できずに初期の追跡ウインドウが設定できなかったので、こういうコードにしてみました。

このカメラの起動時間を考慮して、ここで触れた部分をwhile文の外に出したコードを用意してみました。

MeanShiftアルゴリズムを使ったコード

先ほどのMeanShiftのコードを書き換えると次のようになります。

import cv2
import numpy as np


cap = cv2.VideoCapture(0)

# カメラが起動するのを待つ
import time
time.sleep(1)

ret,frame = cap.read()

face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
face_rects = face_cascade.detectMultiScale(frame)


(face_x,face_y,w,h) = tuple(face_rects[0])
track_window = (face_x,face_y,w,h)

roi = frame[face_y:face_y+h, face_x:face_x+w]

hsv_roi =  cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

roi_hist = cv2.calcHist([hsv_roi],[0],None,[180],[0,180])

cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)


term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )

while True:
    ret,frame = cap.read()
    if ret == True:


        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)

        ret, track_window = cv2.meanShift(dst, track_window, term_crit)

        x,y,w,h = track_window
        img2 = cv2.rectangle(frame, (x,y), (x+w,y+h), (0,0,255),5)

        cv2.imshow('img2',img2)


        if cv2.waitKey(1) & 0xff == 27:
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

cv2.VideoCapture(0)でカメラを起動した時に、time.sleep(1)で一時中断してから、1フレーム目の画像を取り込んで、顔の検出をさせています。カメラの起動が早い環境では、このtime.sleep(1)の部分が無くても処理できるはずです。

CamShiftアルゴリズムを使ったコード

同様にCamshiftのコードを書き換えて見ます。

import cv2
import numpy as np


cap = cv2.VideoCapture(0)

# カメラが起動するのを待つ
import time
time.sleep(1)

ret,frame = cap.read()

face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
face_rects = face_cascade.detectMultiScale(frame)


(face_x,face_y,w,h) = tuple(face_rects[0])
track_window = (face_x,face_y,w,h)

roi = frame[face_y:face_y+h, face_x:face_x+w]

hsv_roi =  cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)

roi_hist = cv2.calcHist([hsv_roi],[0],None,[180],[0,180])

cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)


term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 )

while True:
    ret,frame = cap.read()
    if ret == True:


        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)

        ##############################################################
        ########## CamShiftアルゴリズム #################################
        ##############################################################

        rect, track_window = cv2.CamShift(dst, track_window, term_crit)

        pts = cv2.boxPoints(rect)
        pts = np.int0(pts)
        img2 = cv2.polylines(frame,[pts],True, (0,0,255),5)
        cv2.imshow('img2',img2)

        #############################################################
        #############################################################
        #############################################################


        if cv2.waitKey(1) & 0xff == 27:
            break

    else:
        break

cap.release()
cv2.destroyAllWindows()

全く同様の考え方での書き換えです。本来ならimport文は一番上に書くべきですが、わかりやすく、time.sleep()を入れてる位置に書いています。

スポンサーリンク

最後に

OpenCVを使ったPythonでの画像処理について、ここでは画像中にある物体を発見・追跡するためのMeanShift、CamShiftアルゴリズムをみてきました。

MeanShiftアルゴリズムは、画像中に点の分布などがあるときに、そこへ規定のウィンドウを移動させて画素の分布密度や画素数が最大になる領域に合わせていくことでトラッキングの処理を行いました。

CamShiftアルゴリズムは、MeanShiftアルゴリズムで使う追跡ウインドウが常に同じサイズなので、追跡対象の傾きや遠近による見え方の大きさの違いなどで不具合が出るので、ウィンドウのサイズを追跡物体に合わせて調節する処理を行いました。

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