Python/OpenCVで動画内の物体トラッキングをする方法

  • このエントリーをはてなブックマークに追加

動画の中を移動する物体の動きをトラッキング(追跡)することが出来れば、その物体の変位や速度、加速度といった物理量を計算することが出来ます。ここでは、Python/OpenCVを使って動画内物体トラッキングコードの例を紹介します。

こんにちは。wat(@watlablog)です。本日は動画処理の例題として、物体トラッキングの方法を紹介します

物体トラッキング処理で得る情報

トラッキングとは?

トラッキング(Tracking)とは、日本語で「追跡」を意味する単語です。

Advertisements

言葉の使われ方は分野によって様々ですが、例えばマーケティング分野では、インターネットを閲覧しているユーザがどのページを見てどのリンクを辿ったかを調べることもトラッキングと呼びます。

また信号処理の分野では、回転体のFFT分析でわかる回転次数の変化を追うこともトラッキングと呼びます。

このページでは動画内の物体の移動を追跡する内容を扱い、同じくトラッキングと呼びます。

物体トラッキングでわかること

軌跡(変位)がわかる

動画から物体の動きを追跡するという処理は、そのままで軌跡情報が得られていることになります。

物理や工学の実験では着目している物体がどの方向にどれだけ動いたかを知ることは、事象の理解にとても重要です。

変位センサ、GPSセンサを付けられる規模であれば良いのですが、微小範囲を取り扱ってセンサを付けることが出来なかったり、そもそもセンサを取付けることで事象の変化が懸念される場合等は動画で分析することが多々あります。

速度や加速度がわかる

動画で軌跡や変位が数値としてわかるということは、速度や加速度といった情報も同時に得ることが可能です。

動画撮影や動画ファイルにはFPS(Frames Per Second)、フレームレートとも呼ばれる時間情報を持っています。

変位と速度、加速度はそれぞれ時間による微積分関係にあるため、FPS情報が取得できれば算出可能となります。

物体トラッキング手順の例

ここで、物体トラッキング手順の例を紹介していきます。「~例」としているのは、このトラッキング技術はケースによって様々な手法を使い分ける必要があり、万能な方法がないからです。

ここでは物体の特徴量として輪郭情報を検出し、その情報から位置座標を取得する方法を紹介します

あくまで一例ですので、異なる物体特徴量検出の方法を覚えたらそれは物体トラッキングの手段が増えたことにもなります。

他の例では、ディープニューラルネットワークを使った検出が最新技術でしょうか。CNNやYOLOといったキーワードで検索すると色々出て来ますね。これらはAI技術ですが、ご興味があればAIカテゴリのページも是非見てみて下さい!

動画内物体の輪郭から位置を特定するために必要な画像処理技術

動画のフレーム処理

動画内の物体トラッキングをするためには、まずは動画ファイルを扱う方法を知る必要があります。

動画といってもパラパラ漫画のような多数のフレームで構成されています。そのため、基本はフレームに対する画像処理をする方法を覚えておけば問題ありません。

以下の記事はそれぞれ「動画のフレームに対しある画像処理をする」という内容ですので、必要であれば是非ご覧下さい。
Python/OpenCVで動体検知!動画の動いている部分を検出
Python動画編集!動画に位置制御したテロップを入れる方法
Pythonでブログの広告が目立つかどうか「動的」に評価する方法

グレースケール化と二値化

今回は動画の各フレーム画像に対し、物体の輪郭を検出する手法を使います。

輪郭を始めとする様々な特徴量の抽出には、カラー画像をそのまま扱うのではなく、グレースケール化したり二値化したりといった前処理を行うことが一般的です。

以下の記事はグレースケール化や二値化に関する内容ですので、こちらも必要に応じてご覧下さい。
Python/OpenCVで画像の二値化をする方法
Python/OpenCVの適応的閾値処理で綺麗な二値化!

輪郭検出処理

本題の輪郭抽出方法ですが、輪郭は上記グレースケール化と二値化処理を使って行います。

詳しくは以下の記事に記載しましたので、併せてご覧下さい。
Python/OpenCVで画像内オブジェクトの輪郭抽出をする

輪郭から物体トラッキングをするPythonコード

サンプル動画

今回コードのテストで使用するサンプル動画を以下のYoutube動画に示します。ダウンロードボタンも付けましたので、是非サンプルコードと合わせてお試しください。

…かなり適当感はありますが、当WATLABブログのロゴマークを黒い背景の中で動かしてみました。

ロゴマークは背景の中を振幅100[pix]の正弦波で動いています。

動作環境

本コードは以下のPython環境で動作を確認しました。

Python Python 3.9.6
PyCharm (IDE) PyCharm CE 2020.1
Numpy 1.21.1
matplotlib 3.4.3
opencv-python 4.2.0.34

全コード

先ほどのサンプル動画に対し、物体トラッキングをする全コードを以下に示します。ポイントは輪郭検出を行っているdef関数です。

輪郭検出で得られたcontoursには輪郭の座標情報が格納されています。この座標情報をx, y軸それぞれで平均をとったものを物体の座標としています。

その他は既に紹介した記事内容と同じです。
しかし、今回はカラー画像を扱っているので、VideoWriterの最後の引数がTrueになっている点が注意点です(これをFalseにすると動画が正常に保存されない)。

実行結果

以下の動画が実行結果です。
移動する物体に対しcv2.circleで円を描画していますが、見事にトラッキング出来ている結果を得ました。

おまけ1:軌跡情報の取得

以下はおまけですが、このコードを先ほどのコードの一番下に追加することで、グラフ表示が出来ます。

以下のグラフが軌跡です。綺麗な正弦波を得ることが出来ました。
縦軸は画像の場合下が正ですが、このグラフは上が正になっていることに注意が必要です。

動画から物体の軌跡を得た結果

このように軌跡情報が数値であれば、後は既に得ているFPS情報を使って時間の関数にすることも可能ですね。

おまけ2:軌跡情報を元動画に描画する

先ほどの軌跡情報(xy座標)を元動画に重ね描きするコードを以下に示します。
#軌跡を残しつつマーカを描画(軌跡を連続とするために新規frameには過去の座標分もforで描画している)」の部分が主な変化点ですが、コードを関数化したりdocstringを使って関数の冒頭に説明を書いたりしてみました。

結果は以下です(YouTube動画視聴で確認できますが、ダウンロードも可)。今回はマーカを使いましたが、cv2には他にも線や円を描いたりできますのでアレンジ可能です。

おまけ3:グラフの横軸を時間軸にする

おまけ1で作ったグラフの横軸を時間軸にしてみます。

既に動画を読み込む時に「# 動画ファイル保存用の設定」部分でfps(1秒当たりのフレーム数)を抽出していますので、動画のコマ間隔は時間に変換可能です。

以下のコードは全コードを示していますが(書き方も関数でまとめたりしています)、def tracking()の最後で時間軸tを作成してみました。抽出した座標情報と同じだけの長さに時間分解能dtをかけているだけです。

結果はこちら。画像はxとyの2軸あるので2つのグラフで書いてみました。x方向はただまっすぐ進み、y方向は振動している特徴が時間で追えるようになりました。

プロットの時間軸化

まとめ

本記事では動画から各フレームを取得し、フレームに対しグレースケール化と二値化処理を施し輪郭抽出を行い、輪郭情報から位置座標を連続的に取得することで軌跡を得るPythonコードを紹介しました。

動画処理は基本を習得すればどれも同じような流れなので、今後も何か画像処理を覚えたら動画へも応用してみたいと思います。

今回は輪郭情報からトラッキングをしてみましたが、他にも色々な手法がありそうですね!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!

SNSでもご購読できます。

コメント

  1. madoka より:

    質問です.このプログラムをjuypter notebookで実行したのですが何も表示されません
    どうすれば動画を再生することができますか?
    なお,動画のファイルパスは # 動画ファイルの読み込みでは643771049.410572.mp4
    video = cv2.VideoWriter(‘video_out.mp4’, fourcc, fps, (w, h), True) # 動画の仕様(ファイル名、fourcc, FPS, サイズ, カラー)では/users/名/Downloads/~.mp4としています
    osはmac Catalinaです
    よろしくお願いします

    1. wat より:

      ご訪問ありがとうございます。
      本プログラムはトラッキングを行った結果を描画した動画の保存までなので、
      動画を実際に再生するには保存されたファイルをご自身で実行する必要があります。
      Pythonで再生まで行うには別途コード追加が必要です。
      以上、よろしくお願い致します。

      1. A+ART より:

        コメント失礼します。
        少しお聞きしたいことがあります。
        こちらのサイトを参考にさせて頂いているのですが、ある動画に対してトラッキングをかけたいのですが上手く反応してくれません。その動画自体に問題があるのか、何が問題なのかが分かりません。その点について教えて頂きたくコメントさせて頂きました。よろしければ返信の方、お待ちしております。

        1. wat より:

          ご訪問ありがとうございます。
          この記事では二値化ではっきりとトラッキング対象物が区別できるものしか対応できないと思います。
          動画の背景や対象物によってはなかなか反応悪くなるかも知れません。

          代替案として、以下のようにパターンマッチングを利用する方法もあります。
          「https://watlab-blog.com/2021/02/04/pattern-match-tracking/」

          いつでも万能のトラッキングができるコード、は難しいですので、ケースバイケースで工夫する必要があると思っています。

          以上、何か不明点ございましたらお気軽にコメントください。
          よろしくお願い致します。

  2. 学生C より:

    質問です。
    写経し、このプログラムを実行しようとしたところ、以下のエラーが出ました。

    py:14: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify ‘dtype=object’ when creating the ndarray.
    contours = np.array(contours) # 輪郭情報をndarrayに変換

    たぶんndarrayの形が汚い(3*3,4*4などになっていない)というエラーだと思いますが、あまり自信を持てません。Pythonのヴァージョンアップにより、新たに生じたエラーだと思いますが、修正方法がわからず困っています。
    もしお時間あれば、ご対応ないし教えていただければうれしいです。

    1. wat より:

      ご訪問ありがとうございます。
      こちらではPython3.9.6、PyCharmで実行して特にエラーが発生しませんでした(Macbookで確認)。
      エラーの内容から、14行目を
      contours = np.array(contours, dtype=’int32′)
      と変更するなどはいかがでしょうか?

      また、本記事「サンプル動画」の部分に僕が使った動画へのダウンロードボタンを設置してみました。
      Pythonのバージョン情報を記載してみましたが、何かヒントになるでしょうか?
      こちらではエラーが出なかったので、使っている動画が合っていないのか、コンピュータ、実行環境、ライブラリのバージョン…等が要因ではないかと思いました。

      以上、よろしくお願い致します。

  3. あああ より:

    物体が途中で見切れてしまっているような映像だとこの方法で速度や加速度を求めるのは難しいですよね?

    1. wat より:

      ご訪問ありがとうございます。
      はい。
      見切れていたり、ノイズが激しかったりといった場合には別の手段、追加アルゴリズム、例外処理等を検討する必要があると思います。

  4. ゆき より:

    質問させてください。
    こちらのコードを引用してファイルを作成したのですが、作成されたファイルのサイズが0KBとなっており、再生しようとするとエラーが発生します。

    Pycharm上はエラーが発生していないのですが、どのように改善すればよいのでしょうか?
    よろしくお願いいたします。

    1. ゆき より:

      申し訳ありません。コードを書き換えると上手く保存できました。ただトラッキングは上手くできていないので、また相談させてください。
      よろしくお願いいたします。

      1. wat より:

        ご訪問ありがとうございます。
        保存に関して解決されたようでよかったです。
        トラッキングは全てのケースでうまくいくアルゴリズムというのは難しいかも知れません。
        この記事では輪郭情報を使っていますが、テンプレートマッチングのような方法もあります↓
        https://watlab-blog.com/2021/02/04/pattern-match-tracking/

        是非色々な方法を試してみてください。

  5. てんつば より:

    動画が再生できない場合はどうすればよいでしょうか?

    1. wat より:

      ご訪問ありがとうございます。
      「動画が再生できない」とは、当ブログでダウンロード可能にしているmp4ファイルが再生できないということでしょうか?
      それとも、記事内で紹介しているYouTube動画が再生できないということでしょうか?

      こちらの環境では両方正常に再生されていますので、
      前者であればmp4の再生環境、後者であればブラウザに問題がないか調べる必要があると思います。

      以上、よろしくお願いいたします。

  6. 理系学生 より:

    コメント失礼いたします.
    すでにコメントされている方もいらっしゃいましたが,こちらのコードを引用してファイルを作成しました.結果,作成されたファイルのサイズが0KBとなり,再生しようとすると「0xc10100be」のエラーが発生します

    Pycharm上はエラーが発生していません.改善方法をご教授いただけると幸いです.
    よろしくお願いいたします。

    1. wat より:

      ご訪問ありがとうございます。
      どうやらこちらの環境では再現しないようです。
      先の質問者様は解決されたようなので、もう少し詳しい情報を知ることで解決の可能性があります。

      例えば、
      ・この記事に添付されている動画をダウンロードして、コードをそのまま実行して再生できないのか。
      ・それとも自前で用意した動画でのみ起こる現象なのか。
      ・PCでmp4はそもそも再生できるのか(コーデックがあるか)
      ・Python環境(バージョン等)がこの記事のものと同じか。

      記載頂いたエラーは再生時に発生するものとのことで、エラーコードをGoogleで検索すると以下のような記事がヒットしました。
      ↓0xC10100BEビデオエラーで動画を再生できない場合の対処方法をご紹介!
      https://recoverit.wondershare.jp/video-repair/fix-0xc10100be-video-error.html

      お使いのPCが動画フォーマットに対応していない可能性があり、mp4以外のエンコードを試すか動画再生ソフトを見直すか…かも知れません。

      以上、よろしくお願い致します。

  7. 理系四年生 より:

    コメント失礼します.
    座標のプロットに関してなのですが、
    こちらを横軸の単位を時間に変更し、時間に対する変位にする事は可能でしょうか。
    もし可能でしたらご教授いただけますと幸いです。

    1. wat より:

      いつもご訪問ありがとうございます。
      可能です。

      以下のコードでFPS(1秒当たりのフレーム数)でコマ画像が1つ進む時間を取得しています。
          fps = int(movie.get(cv2.CAP_PROP_FPS))
      つまり横軸にこの情報を使うことで時間に変更することが可能です。

      そして縦軸を変位([mm]や[m])にするのは、1ピクセル当たりの長さを考慮することで変換可能です。
      通常、商用の画像処理ソフトではキャリブレーションを行って物理単位の長さ情報に変換しています。
      スケールやグリッドを使って撮影画角における長さを別途計算するプログラムにすることで変位に変換することができます。

      以上、ご検討よろしくお願い致します。

      1. 理系四年生 より:

        ご回答ありがとうございます。グラフの横軸の値を指定するのはどちらのコードなのでしょうか。

        1. wat より:

          matplotlibで無理やり軸の設定を変更するよりは、抽出した座標値に対して処理を行う方が良いと思います。
          x, y = contours(frame)
          でx, y座標を計算していますので、この次の行にxとyそれぞれに対して適切な値に校正することで目的のグラフとなります。
          または、whileループの外でx_list, y_listに対して一括に校正係数をかける処理をしても良いと思います。

  8. 理系四年生 より:

    ご回答ありがとうございます。pythonのプログラミングは初めてなのでご教授いただければ幸いです。早速ですが、今回の提案はx, y = contours(frame)で出力した値に校正値を代入してx座標
    をfps値に変換するということでしょうか。もしくはx, y = contours(frame)はそのままに新たな関数を導入した方がよいでしょうか。

    1. wat より:

      すみません、こちらの方で勘違いしていました。
      本処理は2次元の画像の処理でしたので、x, yは共に変位に変換することができますが、時間にすることはできません。
      変位への変換は
      x, y = contours(frame) * 校正係数
      で良いと思いますが、横軸を時間にするというのは、今回のx,yの変位にさらに軸を追加して3次元グラフを作りたいということでしょうか?
      例えば、横軸となる時間波形を新たにnp.linspaceやnp.arangeで作り、x,yの変位をz=np.sqrt(x**2 + y**2)等でひとまとめにするという案もあります。
      2次元の位置情報を時間とどう関連付けたいか、という部分があれば色々やりようがありそうです。

      最初勘違いしており申し訳ございませんでしたが、ご検討お願いします。

      1. 理系四年生 より:

        ご回答ありがとうございます。私が作成したいのは横軸が時間で縦軸が変位のグラフです。
        時間はfpsから求めることが出来るとご教授いただきましたが、そちらの情報をどこに挿入すればグラフの横軸を時間に変更できるかが理解できていません。球の減衰の様子をこちらのプログラムを応用して計測したいと考えています。

        1. wat より:

          なるほど球の減衰ですか、何となくですがイメージできました。
          例えば反発する球の振動をトラッキングして後ほど減衰カーブにフィットさせ、減衰率を計算する…ようなことをイメージして
          「おまけ3」を記事に追加してみました。
          このようなコードで時間軸に対する挙動を分析することが可能です。
          ただ、縦軸変位に変換するためには、1ピクセルが何mmなのかといった情報を別途x_list, y_listに考慮してあげることが必要です。

  9. amy より:

    失礼します。
    OpenCV: Couldn’t read video stream from file “video.mp4”
    のエラーコードが出て解決策がわかりません。今video.mp4はデスクトップに置いています。ご教授願います。

    1. amy より:

      エディタはvscordを使用しています。

      1. wat より:

        ご訪問ありがとうございます。
        そのエラーはファイルパスが見つからない時にでてくるようです。
        動画読み込み部分「# 動画読み込み」の前に以下のコードを書いて実行してみてください。
        ファイルパスが存在するとTrue, 存在しないとFalseになります。

        # Debug
        print(“file exists?”, os.path.exists(filename))

        もしFalseが出る場合、おそらくデスクトップにファイルを置いていることが原因と思います。
        その場合は.pyファイルと同じ場所に動画ファイルを置いてみてください。

        以上、よろしくお願い致します。

  10. H.I より:

    質問です。
    本記事では、動画ファイルからベクトル軌跡を描いていると思いますが、カメラの映像を取り込みベクトル軌跡を描くことは可能でしょうか?(カメラ映像は決められた動きではない)

    また可能であれば、どのような方法が適切で、やり方まで教えていただけると幸いです。

  11. kouyagen より:

    *僕の悩んだポイント*

    x = np.mean(contours[0].T[0, 0]) # 輪郭のx方向平均値を算出
    のT[0,0]って[[]]になるからわざわざ[0,0]おいている。

コメントを残す

*