R&Dチーム2年目の葉山です。巣ごもりの影響...はあまり関係なく元からですが、もっぱら自宅で映画ばかり見ています。バイオレンスな映画が好きです。最近は業務でMultiple Object Trackingを扱っており、トラッキング処理の実装に四苦八苦しています。
今回はMultiple Object Trackingの基本的な解説と、アルゴリズムや便利なライブラリを紹介していきたいと思います。
Multiple Object Trackingとは?
Multiple Object Tracking(MOT)とは、名前の通り映像に写っている複数の物体を追跡する手法の総称です。MOTではそれぞれの追跡物体にIDを割り振りますが、同じ対象物には可能な限り同じIDを与え続けることを目標とします。
MOTの利用ケースとしては、例えば映像から店舗の入店者数を数えるタスクに利用できます。他にもスポーツの映像でコートの中の選手の動きを分析したり、あるいは動物の群れの行動を分析するなど、多種多様な活用方法が考えられます。*1
MOTの課題設定は非常にシンプルですが、決して簡単なタスクという訳ではありません。
例えば、以下の例のように障害物の奥に一度隠れた物体に対し、再度同じIDを割り振ることを考えます。これには物体の特徴量や、移動量の予測などが必要であることは想像に難くないと思います。
また、以下の例のように物体同士が重なり合う場面では、往々にして奥の物体の追跡が切れたり、二つを合わせて一つの物体として認識してしまいます。この結果、二つの物体に割り振られるIDが、本来のものと異なるIDに変化してしまう場合があります。
MOTにはTracking-by-Detectionというパラダイムがあります。これはその名の通り、物体検出(Detection)した結果を用いて、別の手法によって物体追跡(Tracking)を行うというものです。物体追跡の手法は多種多様ですが、多くはカルマン・フィルタ *2 やパーティクル・フィルタ *3 、SORT *4 やオプティカル・フロー *5 といった複数の手法を組み合わせて実現しています。しかし最近では、TrackingとDetectionをまとめた(end-to-end的)モデルも増えてきています。
新しい手法の方が精度が良い傾向はありますが、処理速度的にはTracking-by-Detectionの方が優っている場合も多いため、どちらが優れているとは一概には言いづらいのが現状です。
以上のようにMOTは、未だ精度的にも速度的にも改善の余地があり、日進月歩で新たな手法の提案がなされています。
MOTの手法やライブラリなど
MOTベンチマークで顕著な成績を残しているモデルたち
MOTのベンチマークには、主にMOT Challengeで提供されているデータセットが利用されています。 Papers With CodeのMOTのベンチマークを見ることで、各ベンチマークデータセットに関するstate-of-the-artなモデルを眺めることができます。
ここではその中のいくつかのモデルについて簡単にご紹介します。
FairMOT
FairMOT は少し前まで多くのベンチマークでstate-of-the-artを獲得していたMOT手法です。 One-Shotなモデルであり、フレームを入力することでDetectionとReID(bbox *6 とIDの関連付け)がまとめて出力されます。
FairMOTのポイントは、それ以前までの学習において精度が低めだったReID部分の精度を強化する工夫を導入している点です。 簡単にまとめると、それ以前のAnchor *7 に則ったモデルでは対象物体の特徴量の計算に"不公平"が生じるため、FairMOTではAnchor-freeなReID計算を実現しています。
TransMOT(STGT)
残念ながらまだ実装は公開されていないのですが、多くのベンチマークでstate-of-the-artを獲得している(2021/07 現在)のがTransMOT(STGT)です。Spatial-Temporal Graph Transformerという独自のTransformer *8 を利用し、end-to-endなトラッキングを実現しています。
その他の手法やライブラリ
FastMOT
FastMOTはTracking-by-Detectionの手法の中では最近注目しているアルゴリズムです。 アルゴリズムの概要は以下の通りです。
- 物体検出(デフォルトでYOLOv4 *9 とSSD *10 が用意されている)
- OSNet :Detectionの各bboxに対する特徴ベクトルを取得する
- KLT Tracker:オプティカル・フローにより特徴点の抽出をし、カメラ自体の運動を推定、追跡物体の位置予測を行う
- カルマン・フィルタ:追跡物体に対するKLT Trackerの結果を利用し、最終的な物体の動き予測を行う
- 上記の計算を元に検出物体と追跡中の物体のマッチングを行う
- (a) 特徴ベクトルと動き予測を基にしたマッチング
- (b) aでマッチしなかったものについて、追跡中の物体(active)とIoU *11 でマッチング
- (c) bでマッチしなかったものについて、まだ一度もマッチしたことが無い(unconfirmed)追跡物体とIoUでマッチング
- (d) cでマッチしなかったものについて、過去にlostしたものと特徴ベクトルによりマッチング
FastMOTの利点としては、精度と速度の両面でバランスが良い点が挙げられると思います。 デフォルトでTensorRTやNumbaを使って高速化・最適化されているため、雑にそのまま動かしても速いのも嬉しい点です。
物体検出の部分は自由な手法で良いため、RefineDet *12 やYOLOv5など他のモデルにつけ替えて試しています。Tracking-by-Detectionの良いところは、このように検出モデルの変更が容易な点もあるかと思います。
motpy
motpyはTracking-by-Detectionの"Tracking"の部分をサクッと実装したいときに便利なライブラリです。独自に用意した物体検出のデータを使って、簡単にMOT機能を実装することができます。
基本的な使い方は以下の通りです。
import numpy as np from motpy import Detection, MultiObjectTracker # 物体検出のデータを次のフォーマットで用意:[xmin, ymin, xmax, ymax] # step timeを秒単位で指定し、Trackerを初期化 tracker = MultiObjectTracker(dt=0.1) # トラッキング処理を走らせる for step in range(len(object_box)): # フレームのbboxを更新する # ここではobject_boxは1フレームの[xmin, ymin, xmax, ymax]のデータが格納されたlistとする tracker.step(detections=[Detection(box=object_box)]) # アクティブな追跡物体を取得する tracks = tracker.active_tracks() # trackはID、bbox、検出スコアの情報を持つ print('MOT tracker tracks %d objects' % len(tracks)) print('first track box: %s' % str(tracks[0].box))
内部的にはIoUによるマッチングやカルマン・フィルタ等を主に使用しているようです。物体追跡のアルゴリズムは一から実装するとなかなか大変なので、motpyのような汎用的なTrackingフレームワークの存在はありがたいですね。
MOTの評価指標
MOTは単一の評価指標で測ることが難しいタスクです。そのため、複数の評価指標(MOT metrics)を算出し総合的に判断する必要があります。
MOT評価指標に関しては日本語でわかりやすく解説されている記事やMOT ChallengeのEvaluation Measuresに解説が載っていますので、ぜひそちらをご参照ください。
評価指標を測るライブラリ
MOT評価指標を測るのに便利なライブラリとしてpy-motmetricsを紹介します。MOTの論文実装のプロジェクトでもよく使用されています。
MOT評価指標を算出するのに必要な情報は、主に
- トラッキングID:追跡中の物体にTrackerが割り当てたID
- 物体どうしの距離情報:bboxから求めたIoUなどから距離を定める
の2種類だけです。py-motmetricsライブラリを利用すると、上に挙げた情報から簡単にMOT評価指標を求めることができます。
py-motmetricsの使い方
実際の使い方を見てみましょう。
まずは、MOTAccumulator
に各フレームにおける情報を追加していきます。
import motmetrics as mm import numpy as np # アキュムレータの作成 acc = mm.MOTAccumulator(auto_id=True) # 各フレームにおける、追跡物体の情報を追加 # acc.update1回につき、1フレーム分のデータを追加 acc.update( [1, 2], # Ground Truth に出現する追跡物体のトラッキングIDのリスト [1, 2, 3], # Tracker に出現する追跡物体のトラッキングIDのリスト [ [0.1, np.nan, 0.3], # Ground Truth の1番の追跡物体と、Tracker の1, 2, 3番の追跡物体との距離 [0.5, 0.2, 0.3] # Ground Truth の2番の追跡物体と、Tracker の1, 2, 3番の追跡物体との距離 ] )
Ground Truth(正解データ)の追跡物体とTrackerが予測した追跡物体の距離の情報(距離行列)を与えています。与えた距離情報から、Ground Truthの何番の物体と、Trackerの何番の物体が対応しているのかを判断してくれます。距離はbboxから求めたIoUを使うのがよいと思われます。
py-motmetricsには2つのbboxのリストからIoUによる距離行列を計算するメソッドが用意されているので、そちらを利用しましょう。
a = np.array([ [0, 0, 1, 2], # [xmin, ymin, width, height]のフォーマット [0, 0, 0.8, 1.5], ]) b = np.array([ [0, 0, 1, 2], [0, 0, 1, 1], [0.1, 0.2, 2, 2], ]) mm.distances.iou_matrix(a, b, max_iou=0.5) # 1 - IoUを距離とする # max_iouを超える距離はnp.nanになる """ [[ 0. 0.5 nan] [ 0.4 0.42857143 nan]] """
全フレームぶんのデータを追加できたら、あとはmetrics.create()
でmetricsの入れ物を作成し、compute()
メソッドで評価指標を計算してくれます。
compute()
の引数は以下を参照ください。
https://github.com/cheind/py-motmetrics/blob/develop/motmetrics/metrics.py#L147-170
例として、以下のようなコードでMOT Challengeと同様の出力を得ることができます。
mh = mm.metrics.create() # データを追加したアキュムレータから評価指標を計算する summary = mh.compute(acc, metrics=['mota', 'motp', 'idf1', 'mostly_tracked', 'mostly_lost', 'num_false_positives', 'num_misses', 'num_switches'], name="Name") strsummary = mm.io.render_summary( summary, formatters={'mota' : '{:.2%}'.format, 'motp' : '{:.2%}'.format, 'idf1' : '{:.2%}'.format}, namemap={'mota': 'MOTA', 'motp' : 'MOTP', 'idf1': 'IDF1', 'mostly_tracked': 'MT', 'mostly_lost': 'ML', 'num_false_positives': 'FP', 'num_misses': 'Misses', 'num_switches': 'ID Sw'} ) print(strsummary)
出力例
MOTA MOTP IDF1 MT ML FP Misses ID Sw Name 73.08% 0.02 0.85 1 1 9 68 0
終わりに
MOTは難しいタスクですが、その分応用上の有用性も高く研究が盛んな分野です。近年はTracking-by-Detectionではない新たなパラダイムの手法もその数を増やしており、今後も新たなMOT手法の動向に注目していきたいです。
オプティムではAI・IoT技術によりビジネス課題・社会的課題の解決を目指しています。農業・医療・土木・サービス業など、様々な産業に変革をもたらすようなチャレンジに興味のあるエンジニアを募集しています。
ぜひ以下の採用情報もご覧ください。
*1:https://arxiv.org/abs/1409.7618
*2:https://qiita.com/MoriKen/items/0c80ef75749977767b43
*3:http://www.allisone.co.jp/html/Notes/DSP/Filter/particle-filter/index.html
*4:https://arxiv.org/abs/1703.07402
*5:https://qiita.com/icoxfog417/items/357e6e495b7a40da14d8
*6:Bounding Boxのことです
*7: https://qiita.com/mshinoda88/items/9770ee671ea27f2c81a9#%E3%82%A2%E3%83%B3%E3%82%AB%E3%83%BC%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E5%B0%8E%E5%85%A5:アンカーボックスのことです
*8:https://qiita.com/omiita/items/07e69aef6c156d23c538
*9:https://qiita.com/cv_carnavi/items/68dcda71e90321574a2b
*10:https://qiita.com/de0ta/items/1ae60878c0e177fc7a3a
*11:https://tech-blog.optim.co.jp/entry/2019/03/18/173000:Intersection over Unionの略。Jaccard係数の名前でもお馴染み