YOLOv8で物体追跡を実践|サンプルコードで学ぶ追跡手法
![](https://tech.aru-zakki.com/wp-content/uploads/2023/09/yolov8-traking.001.jpeg)
YOLOv8では、物体の検出だけでなく物体追跡(Object Tracking)することも可能です。この記事では、物体検出と物体追跡の違いと、物体追跡の方法についてサンプルコードを提示しながら解説します。物体追跡タスクに興味がある方は是非読んでみてください。
YOLOv8での物体追跡(Object Tracking)
YOLOv8は、Ultralytics社が開発した物体検出モデルです。物体検出以外にも色々なタスクに対応していて、セグメンテーション、姿勢推定なども可能になっています。
![YOLOv8 github page](https://tech.aru-zakki.com/wp-content/uploads/2023/07/image-29-1024x579.png)
公式のGithubのリポジトリを見ていると、”Track
“の文字があります。と言うことで、YOLOv8は物体追跡にも対応しているようです。
![YOLOv8が対応しているタスク](https://tech.aru-zakki.com/wp-content/uploads/2023/09/image-14-1024x273.png)
動画でオブジェクト検出する場合、移動する物体を追跡をできた方が断然便利です。対応していると言うことなので、物体追跡のやり方を調べ、実際に動作させてみました。
参考までに検出と追跡の違い
物体検出(Detect)タスクとは、画像中に存在する物体の位置と種類を特定するタスクです。
物体追跡(Tracking)タスクは、連続する画像データ(動画)中で動く物体を追跡するタスクです。
物体検出では、例えば動画中で歩いている人物に対して「人」と言うことはわかっても、動画の中の中の人物が同じ人であるかを識別していません(追跡していません)。一方、物体追跡では、動く物体を追跡(トラッキング)するので、同じ人が動いていると識別することができます。
追跡を利用すれば、右から左に移動する人(例えば、入場する人)と、左から右に移動する人(例えば、退場する人)の数をカウントするなどのアプリを簡単に作ることができます。
インストール
まずは、YOLOv8のインストールです。
YOLOv8のインストール
Python(3.8以上)とPyTorch(1.8以上)がインストールされていれば、以下のコマンドでインストールされます。
pip install ultralytics
YOLOv8の学習
追跡の前に、追跡するターゲットを学習させる必要があります。追跡するターゲットの学習は物体検出タスクなどで行う必要があります。学習手順については、ここでは解説しません。以下の記事を参考にしてください。
![YOLO v8/YOLO v9で物体検出|独自(カスタム)データの学習と推論を実践](https://tech.aru-zakki.com/wp-content/uploads/2023/07/yolov8v9.001-320x180.jpeg)
物体追跡の具体的なコード
調べてみると、YOLOv8で物体追跡を行うのはかなり簡単です。
以下、サンプルです。
yolov8n.pt
がなければ自動ダウンロードされるみたいなので、モデルのダウンロードも不要っぽいです。なお、独自に学習させたモデルを使う場合には、YOLO('yolov8n.pt')
の部分に学習させたモデルへのパスを指定します。
キャプチャしながら追跡
PCにWebカメラなどのカメラを接続していて、OpenCVで使える状態であれば、カメラからキャプチャしながら追跡させることが可能です。コードは以下のようになります。
import cv2
from ultralytics import YOLO
# 学習済みのモデルをロード
model = YOLO('yolov8n.pt')
# 動画ファイル(or カメラ)を開く
cap = cv2.VideoCapture(0)
# キーが押されるまでループ
while cap.isOpened():
# 1フレーム読み込む
success, frame = cap.read()
if success:
# YOLOv8でトラッキング
results = model.track(frame, persist=True)
# 結果を画像に変換
annotated_frame = results[0].plot()
# OpenCVで表示&キー入力チェック
cv2.imshow("YOLOv8 Tracking", annotated_frame)
key = cv2.waitKey(1)
if key != -1 :
print("STOP PLAY")
break
動画ファイルで追跡
ファイルから動画ファイル(.mp4)を読み込んで追跡する場合は、VideoCapture
の引数をファイルパスに変更するだけです。コードは、キャプチャとほとんど同じになります。
import cv2
from ultralytics import YOLO
# 学習済みのモデルをロード
model = YOLO('yolov8n.pt')
# 動画ファイル(or カメラ)を開く
video_path = "動画ファイル.mp4"
cap = cv2.VideoCapture(video_path)
# キーが押されるまでループ
while cap.isOpened():
# 1フレーム読み込む
success, frame = cap.read()
if success:
# YOLOv8でトラッキング
results = model.track(frame, persist=True)
# 結果を画像に変換
annotated_frame = results[0].plot()
# OpenCVで表示&キー入力チェック
cv2.imshow("YOLOv8 Tracking", annotated_frame)
key = cv2.waitKey(1)
if key != -1 :
print("STOP PLAY")
break
![](https://tech.aru-zakki.com/wp-content/uploads/2023/06/tabbycat.png)
ワンポイント
model.track
のパラメータにverbose=False
を追加すると、コンソールへのログの出力を止めることが可能です。
また、show=True
パラメータを追加すると、cv2.imshow()
で画像を表示しなくても画像を表示することが可能です。
YouTube動画などを再生して追跡
URLを指定することで直接追跡を行うことも可能です。
from ultralytics import YOLO
model = YOLO('yolov8n.pt')
results = model.track(source="YouTubeの動画のURL", show=True)
追跡アルゴリズムの設定
YOLOv8では、現在、追跡アルゴリズムとしてBoT-SORT
とByteTrack
をサポートしているようです。
![](https://tech.aru-zakki.com/wp-content/uploads/2023/06/tabbycat.png)
それぞれについては、以下のURLを参考にしてください
BoT-SORT : https://github.com/NirAharon/BoT-SORT
ByteTrack : https://github.com/ifzhang/ByteTrack
一応調べた結果を別記事にしていますのでそちらも参考に
![YOLOv8の物体追跡(MoT)の手法を調べてみた(パラメータなど)](https://tech.aru-zakki.com/wp-content/uploads/2023/09/yolov8-mot.001-320x180.jpeg)
追跡方法を指定するには、track()
呼び出し時にtracker=’xxx.yaml’と言う形でyamlファイルを指定します。
results = model.track(frame, tracker='xxx.yaml')
xxx.yamlは、例えば以下のようになります。パラメータについては、それぞれのトラッカーのページを参照してください。トラッカーはいくつか触ったことがありますが、パラメータチューニングは結構面倒です。
BoT-SORTを利用する場合
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Default YOLO tracker settings for BoT-SORT tracker https://github.com/NirAharon/BoT-SORT
tracker_type: botsort # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.5 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association
new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks
match_thresh: 0.8 # threshold for matching tracks
# min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now)
# mot20: False # for tracker evaluation(not used for now)
# BoT-SORT settings
gmc_method: sparseOptFlow # method of global motion compensation
# ReID model related thresh (not supported yet)
proximity_thresh: 0.5
appearance_thresh: 0.25
with_reid: False
ByteTrackを利用する場合
# Ultralytics YOLO 🚀, AGPL-3.0 license
# Default YOLO tracker settings for ByteTrack tracker https://github.com/ifzhang/ByteTrack
tracker_type: bytetrack # tracker type, ['botsort', 'bytetrack']
track_high_thresh: 0.5 # threshold for the first association
track_low_thresh: 0.1 # threshold for the second association
new_track_thresh: 0.6 # threshold for init new track if the detection does not match any tracks
track_buffer: 30 # buffer to calculate the time when to remove tracks
match_thresh: 0.8 # threshold for matching tracks
# min_box_area: 10 # threshold for min box areas(for tracker evaluation, not used for now)
# mot20: False # for tracker evaluation(not used for now)
![](https://tech.aru-zakki.com/wp-content/uploads/2023/07/cat2-e1688625674820.png)
トラッカーの調整には、それぞれの追跡アルゴリズムの理解が必要なるかと思います。これらについては、また別の記事にしたいと思います。
結果の出力について
動画(.mp4)ファイルに対して追跡を行っている場合について、トラッカーの出力についても調べてみました。具体的には、以下のコードのresults
です。
results = model.track(frame, persist=True)
results
はリストです。画像を1枚づつ渡しているので、resultsは長さ1のリストとなります。なので、results[0]
だけアクセスできます。
results[0]
は、ultralytics.engine.results
型のオブジェクトです。物体検出モデルを使った場合、物体追跡の結果は、results[0].boxes
に格納されています。
以下の画像の場合、results[0].boxes
は次のようになります。
![](https://tech.aru-zakki.com/wp-content/uploads/2023/09/image-15-773x1024.png)
ultralytics.engine.results.Boxes object with attributes:
boxes: tensor([[2.8825e+02, 1.3936e-01, 4.7949e+02, 2.2639e+02, 1.0000e+00, 3.6112e-01, 1.6000e+01]])
cls: tensor([16.])
conf: tensor([0.3611])
data: tensor([[2.8825e+02, 1.3936e-01, 4.7949e+02, 2.2639e+02, 1.0000e+00, 3.6112e-01, 1.6000e+01]])
id: tensor([1.])
is_track: True
orig_shape: (640, 480)
shape: torch.Size([1, 7])
xywh: tensor([[383.8676, 113.2671, 191.2385, 226.2555]])
xywhn: tensor([[0.7997, 0.1770, 0.3984, 0.3535]])
xyxy: tensor([[2.8825e+02, 1.3936e-01, 4.7949e+02, 2.2639e+02]])
xyxyn: tensor([[6.0052e-01, 2.1775e-04, 9.9893e-01, 3.5374e-01]])
この中の、'id
‘が追跡している物体のIDになります。idが同じ場合は、同じ物体として追跡していることになります。
また、物体の位置はxywh, xywhn, xyxy, xyxynに格納されています。位置の見方は、x,yが座標、w,hが幅と高さ、nが正規化されているかどうかを示しています。例えば、xywhnの場合は、x座標、y座標、幅、高さを正規化(画像の幅と高さを1.0とした値)した値であることを表しています。
![](https://tech.aru-zakki.com/wp-content/uploads/2023/06/tabbycat.png)
なお、boxesの各要素にアクセスしたい場合は、results[0].boxes.id
などと指定します。
複数個のオブジェクトが画像内にある場合の出力は以下のようになります。オブジェクト数だけ、クラス、ID、座標などの情報が増える感じです(ちなみに、id:5は猫ですが検出結果が間違ってます😅)。
![](https://tech.aru-zakki.com/wp-content/uploads/2023/09/image-17-769x1024.png)
ultralytics.engine.results.Boxes object with attributes:
boxes: tensor([[3.4826e+02, 7.3290e+01, 4.8000e+02, 2.7381e+02, 4.0000e+00, 4.6047e-01, 1.5000e+01],
[6.6458e+01, 4.5299e+01, 1.9723e+02, 3.0847e+02, 5.0000e+00, 5.4958e-01, 1.6000e+01],
[1.2354e+02, 1.8989e+01, 2.7287e+02, 1.2105e+02, 6.0000e+00, 6.1618e-01, 4.5000e+01]])
cls: tensor([15., 16., 45.])
conf: tensor([0.4605, 0.5496, 0.6162])
data: tensor([[3.4826e+02, 7.3290e+01, 4.8000e+02, 2.7381e+02, 4.0000e+00, 4.6047e-01, 1.5000e+01],
[6.6458e+01, 4.5299e+01, 1.9723e+02, 3.0847e+02, 5.0000e+00, 5.4958e-01, 1.6000e+01],
[1.2354e+02, 1.8989e+01, 2.7287e+02, 1.2105e+02, 6.0000e+00, 6.1618e-01, 4.5000e+01]])
id: tensor([4., 5., 6.])
is_track: True
orig_shape: (640, 480)
shape: torch.Size([3, 7])
xywh: tensor([[414.1283, 173.5520, 131.7433, 200.5232],
[131.8447, 176.8860, 130.7730, 263.1747],
[198.2036, 70.0170, 149.3353, 102.0569]])
xywhn: tensor([[0.8628, 0.2712, 0.2745, 0.3133],
[0.2747, 0.2764, 0.2724, 0.4112],
[0.4129, 0.1094, 0.3111, 0.1595]])
xyxy: tensor([[348.2567, 73.2904, 480.0000, 273.8136],
[ 66.4582, 45.2986, 197.2312, 308.4734],
[123.5360, 18.9886, 272.8713, 121.0455]])
xyxyn: tensor([[0.7255, 0.1145, 1.0000, 0.4278],
[0.1385, 0.0708, 0.4109, 0.4820],
[0.2574, 0.0297, 0.5685, 0.1891]])
追跡する動画(gifアニメーションに変換)
![キャプチャ画像](https://tech.aru-zakki.com/wp-content/uploads/2024/03/org.gif)
YOLOv8-worldモデルを使うと、学習なしで物体検出・追跡が可能になります。YOLOv8-worldについては以下の記事を参考にしてください。
![ゼロショットで物体検出可能なYOLOv8-Worldモデルを試す](https://tech.aru-zakki.com/wp-content/uploads/2024/04/yolov8-world.001-320x180.jpeg)
MacBook Air M2で物体追跡をした場合の処理速度について(参考)
Macbook Air M2のCPUで実行して48-50ms/frameでした。このスペックのCPUだと、秒20枚程度は処理できるようです。
0: 640x480 1 cat, 48.1ms
Speed: 0.8ms preprocess, 48.1ms inference, 0.4ms postprocess per image at shape (1, 3, 640, 480)
また、M2のGPUを使うと10ms/frame前後になりました。YOLOv8ではMacのGPUの恩恵が結構あるようです。10msだと100fpsとなり動画の再生(デコード)が追いつかない感じでした。
0: 640x480 1 cat, 1 bowl, 1 tv, 10.5ms
Speed: 0.9ms preprocess, 10.5ms inference, 4.3ms postprocess per image at shape (1, 3, 640, 480)
GPUを使う場合は、model = YOLO('yolov8n.pt').to("mps")
とto
をつけるだけです。
まとめ
YOLOv8で物体追跡ができることに気づいて、使い方をまとめてみました。かなり使いやすいと思います。これまでは、物体検出モデルと物体検出を組み合わせる必要がありましたが、これが統合されたと言うのは驚きです。
![](https://tech.aru-zakki.com/wp-content/uploads/2023/06/tabbycat.png)
実は、YOLOv8と物体追跡を組み合わせてコードを書こうとしていて気づきました。ドキュメントはちゃんと読むべきですね。
ところで、動作させていて気づいたのですが、物体のクラスが変化してもIDは同じ、つまり追跡は続けているようです。このあたりの動作も調べると面白そうです。