見出し画像

Webカメラと動画でYOLO11の推論速度を測ってみた


概要

  • 下記の記事を拝見して気になったのでYOLO11(物体検出タスク)の推論速度を測ってみました。

  • 入力は最大60fpsのWebカメラと動画ファイル(mp4)を使用しました。

  • RTX 2070 SUPERの環境で使用した動画ファイルの場合、Lサイズモデルまでは30fps以上で推論することができました。

  • Webカメラは最大60fpsですが、物体検出を行わずにカメラ映像を描画するだけの処理でも35fps弱しか出ませんでした。

拝見した記事

前置き

動機

前述の記事を拝見して気になったのは「平均のFPSを求めてみる」の結果で、nサイズ ~ xサイズの結果がほぼ同等の 31~32 fps程となっていた点です。
これまで試した経験からnサイズとxサイズの推論速度にはそこそこ差があると感じていました。
そこでYOLO11の推論速度の個人的な再確認を主目的としつつ、記事投稿者の方やYOLO11の速度感が気になっている方の情報の足しになればと思い本記事を作成します。

PyTorch+GPUでの処理時間計測の注意点

今回の件とは直接関係しませんが、速度に関する話題なので取り上げておきたいと思います。

Pythonで処理時間を測る場合、 time.time() を利用することが多いかと思います。
しかし、GPUを使用してPyTorchのモデルの推論速度を測る場合は単純に time.time() を実行するのではなく、先に torch.cuda.synchronize() を実行してCPUとGPUのタイミングを同期させてあげる必要があります。

# 例
torch.cuda.synchronize()
t = time.time()  # または time.perf_counter()

これはPyTorchがGPUを非同期で実行していることに起因するようです。
詳しい解説については以下の記事をご確認ください。

ただし、冒頭でご紹介した記事で示されたYOLO11の推論時間(fps)の件と上記の torch.cuda.synchronize() の件は関係ないと考えています。
というのも、YOLO11の実装の中で torch.cuda.synchronize() は使用されているためです。

ultralytics.utils.ops.Profile.time
ultralytics.utils.torch_utils.time_sync

そのため、記事内のfpsを求めるコードで torch.cuda.synchronize() は使用されていませんが、GPUを使用するYOLO側でタイミング同期が行われているようです。

YOLO11の推論時間取得

Ultralytics公式のサンプルコードを実行すると下記のように処理時間が表示されます。

# 公式のサンプルコード

from ultralytics import YOLO

# Load a pretrained YOLO11n model
model = YOLO("yolo11n.pt")

# Run inference on an image
results = model("bus.jpg")  # results list

# View results
for r in results:
    print(r.boxes)  # print the Boxes object containing the detection bounding boxes
# 実行結果(抜粋)

image 1/1 /content/bus.jpg: 640x480 4 persons, 1 bus, 9.0ms
Speed: 2.1ms preprocess, 9.0ms inference, 1.2ms postprocess per image at shape (1, 3, 640, 480)

この処理時間は model("bus.jpg") 内で実行される 前処理、推論、後処理の時間です。

コード内でこれらの時間を取得したい場合は、上記サンプルコードのfor文で「r.speed」とすると辞書型で取得することができます。
この「r」は ultralytics.engine.results.Results クラスのインスタンスで、推論結果や元画像などの情報も保持しています。
また、上記の処理時間の2行を表示させたくない場合は推論実行時に verbose=False を指定することで非表示にできます。

# verbose=False と処理時間取得

# Run inference on an image
results = model("bus.jpg", verbose=False)

# View results
for r in results:
    print(r.speed)
# 実行結果

{'preprocess': 2.302408218383789, 'inference': 8.151769638061523, 'postprocess': 1.2218952178955078}

前置きのつもりが思いのほか長くなってしまいすみません。
本題に移ります!

実施内容

確認項目は以下の通りです。

  • Webカメラでフレームレート確認

  • 動画ファイルでフレームレート確認

  • YOLO無しでフレームレート確認

実行環境のスペックを簡潔にまとめると下記となります。

$$
\begin{array}{|c|c|} \hline
\rm{OS} & \rm{Windows\ 10} \\ \hline
\rm{CPU} & \rm{Intel\ Core\ i7-9700KF} \\ \hline
\rm{GPU} & \rm{GeForce\ RTX \ 2070\ SUPER} \\ \hline
\end{array}
$$

Webカメラでフレームレート確認

撮影環境は屋内になります。
検出される物体数はだいたい0~6個で、nサイズやsサイズでは0~1個程度、lサイズやxサイズでは4~6個程度検出されるような環境です。
実際の画像は載せられず視覚的な説明ができずすみません。

計測はWebカメラからの入力フレーム300枚で行いました。
Webカメラは冒頭の記事で使用されていたものと同じフルHD最大60fpsのカメラです。

結果は以下の通りです。

$$
\begin{array}{|c|c|c|} \hline
\rm{サイズ} & \rm{fps} & \rm{1フレームの平均処理時間} \\ \hline
\rm{n} & \rm{33.54} & \rm{30\ ms} \\ \hline
\rm{s} & \rm{32.90} & \rm{30\ ms} \\ \hline
\rm{m} & \rm{28.16} & \rm{36\ ms} \\ \hline
\rm{l} & \rm{25.11} & \rm{40\ ms} \\ \hline
\rm{x} & \rm{20.17} & \rm{50\ ms} \\ \hline
\end{array}
$$

計測コード

計測で使用したコードは下記です。
冒頭の記事のコードを拝借し、300フレームで終了する処理と最小/最大値を出すための変更を加えました。

import cv2
import time
from ultralytics import YOLO
import torch

# YOLOv11のモデルをロード
model = YOLO("yolo11x.pt") 

# mp4動画ファイルのパス
cap = cv2.VideoCapture(0)

# FPS計算のための変数
frame_count = 0
frame_times = []

while cap.isOpened():
    torch.cuda.synchronize(); start_time = time.time()  # フレーム処理開始時間を記録

    ret, frame = cap.read()
    if not ret:
        break

    # YOLOで物体検出を行う
    results = model(frame)  # model(frame, verbose=False)
    
    # 結果をフレームに描画して表示
    annotated_frame = results[0].plot()

    # ウィンドウに描画した結果を表示
    cv2.imshow("YOLO Detection", annotated_frame)

    # フレーム処理にかかった時間を計算
    torch.cuda.synchronize(); frame_time = time.time() - start_time
    frame_times.append(frame_time)
    frame_count += 1

    # 300 フレーム処理後 または 'q'を押すと終了
    if frame_count >= 300 or (cv2.waitKey(1) & 0xFF == ord('q')):
        break

# 平均FPSを計算
if frame_count > 0:
    average_fps = frame_count / sum(frame_times)
    print(f"Average FPS: {average_fps:.2f}")
    print(f"Min frame time: {min(frame_times):.3f} s")
    print(f"Avg frame time: {sum(frame_times)/frame_count:.3f} s")
    print(f"Max frame time: {max(frame_times):.3f} s")
    print(f"Frame times over 0.050 sec: " + ", ".join([f"{t:.3f}" + f" s @ {i}/{frame_count}" for i, t in enumerate(frame_times) if t >= 0.05]))

cap.release()
cv2.destroyAllWindows()

実行結果の抜粋

実行結果のfps部分を並べます。

# nサイズ
Average FPS: 33.54
Min frame time: 0.013 s
Avg frame time: 0.030 s
Max frame time: 2.272 s
Frame times over 0.050 sec: 2.272 s @ 0/300, 0.086 s @ 3/300, 0.051 s @ 54/300, 0.056 s @ 187/300, 0.052 s @ 220/300, 0.054 s @ 271/300

# sサイズ
Average FPS: 32.90
Min frame time: 0.013 s
Avg frame time: 0.030 s
Max frame time: 2.303 s
Frame times over 0.050 sec: 2.303 s @ 0/300, 0.052 s @ 39/300

# mサイズ
Average FPS: 28.16
Min frame time: 0.019 s
Avg frame time: 0.036 s
Max frame time: 2.411 s
Frame times over 0.050 sec: 2.411 s @ 0/300


# lサイズ
Average FPS: 25.11
Min frame time: 0.023 s
Avg frame time: 0.040 s
Max frame time: 2.491 s
Frame times over 0.050 sec: 2.491 s @ 0/300, 0.058 s @ 12/300, 0.061 s @ 62/300, 0.054 s @ 161/300, 0.053 s @ 257/300


# xサイズ
Average FPS: 20.17
Min frame time: 0.034 s
Avg frame time: 0.050 s
Max frame time: 2.656 s
Frame times over 0.050 sec: 2.656 s @ 0/300, 0.056 s @ 81/300

動画ファイルでフレームレート確認

使用した動画は過去に作成したSAM2の記事でも利用した NHKクリエイティブ・ライブラリー が公開している下記の動画です。
SAM2の際は10秒にトリミングしましたが、今回は元の長さ(24秒)のまま使用しています。

 素材名: 横浜 馬車道の風景
 ファイル名: D0002040360_00000_V_000.mp4

動画中のとあるフレームの推論結果は以下のようになっています。

nサイズの推論結果
xサイズの推論結果

結果は以下の通りです。

$$
\begin{array}{|c|c|c|} \hline
\rm{サイズ} & \rm{fps} & \rm{1フレームの平均処理時間} \\ \hline
\rm{n} & \rm{38.58} & \rm{26\ ms} \\ \hline
\rm{s} & \rm{37.53} & \rm{27\ ms} \\ \hline
\rm{m} & \rm{35.22} & \rm{28\ ms} \\ \hline
\rm{l} & \rm{32.95} & \rm{30\ ms} \\ \hline
\rm{x} & \rm{18.87} & \rm{53\ ms} \\ \hline
\end{array}
$$

計測コード

先ほどのWebカメラ用のコードを微修正したものです。

import cv2
import time
from ultralytics import YOLO
import torch

# YOLOv11のモデルをロード
model = YOLO("yolo11x.pt") 

# mp4動画ファイルのパス
cap = cv2.VideoCapture("D0002040360_00000_V_000.mp4")

# FPS計算のための変数
frame_count = 0
frame_times = []

while cap.isOpened():
    torch.cuda.synchronize(); start_time = time.time()  # フレーム処理開始時間を記録

    ret, frame = cap.read()
    if not ret:
        break

    # YOLOで物体検出を行う
    results = model(frame)  # model(frame, verbose=False)
    
    # 結果をフレームに描画して表示
    annotated_frame = results[0].plot()

    # ウィンドウに描画した結果を表示
    cv2.imshow("YOLO Detection", annotated_frame)

    # フレーム処理にかかった時間を計算
    torch.cuda.synchronize(); frame_time = time.time() - start_time
    frame_times.append(frame_time)
    frame_count += 1

    # 'q'を押すと終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 平均FPSを計算
if frame_count > 0:
    average_fps = frame_count / sum(frame_times)
    print(f"Average FPS: {average_fps:.2f}")
    print(f"Min frame time: {min(frame_times):.3f} s")
    print(f"Avg frame time: {sum(frame_times)/frame_count:.3f} s")
    print(f"Max frame time: {max(frame_times):.3f} s")
    print(f"Frame times over 0.050 sec: " + ", ".join([f"{t:.3f}" + f" s @ {i}/{frame_count}" for i, t in enumerate(frame_times) if t >= 0.05]))

cap.release()
cv2.destroyAllWindows()

Webカメラ用のコードとの差分は以下の2箇所です。
① WebカメラのID指定から動画ファイルのパス指定に変更
② Webカメラの場合に300フレーム経過で終了させる処理を動画ファイルの方では撤廃

# mp4動画ファイルのパス
cap = cv2.VideoCapture(0)
↓
cap = cv2.VideoCapture("D0002040360_00000_V_000.mp4")
    # 300 フレーム処理後 または 'q'を押すと終了
    if frame_count >= 300 or (cv2.waitKey(1) & 0xFF == ord('q')):
        break# 'q'を押すと終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

実行結果の抜粋

# nサイズ
Average FPS: 38.58
Min frame time: 0.016 s
Avg frame time: 0.026 s
Max frame time: 1.522 s
Frame times over 0.050 sec: 1.522 s @ 0/739

# sサイズ
Average FPS: 37.53
Min frame time: 0.016 s
Avg frame time: 0.027 s
Max frame time: 1.557 s
Frame times over 0.050 sec: 1.557 s @ 0/739

# mサイズ
Average FPS: 35.22
Min frame time: 0.021 s
Avg frame time: 0.028 s
Max frame time: 1.636 s
Frame times over 0.050 sec: 1.636 s @ 0/739

# lサイズ
Average FPS: 32.95
Min frame time: 0.023 s
Avg frame time: 0.030 s
Max frame time: 1.806 s
Frame times over 0.050 sec: 1.806 s @ 0/739, 0.055 s @ 120/739

# xサイズ
Average FPS: 18.87
Min frame time: 0.039 s
Avg frame time: 0.053 s
Max frame time: 1.905 s
Frame times over 0.050 sec: 1.905 s @ 0/739, 0.055 s @ 8/739, (多いため中略), 0.054 s @ 737/739, 0.054 s @ 738/739

YOLO無しでフレームレート確認

結果は以下の通りです。

$$
\begin{array}{|c|c|c|} \hline
\rm{サイズ} & \rm{fps} & \rm{1フレームの平均処理時間} \\ \hline
\rm{Webカメラ} & \rm{34.61} & \rm{29\ ms} \\ \hline
\rm{動画ファイル} & \rm{492.69} & \rm{2\ ms} \\ \hline
\end{array}
$$

上記の通り最大60fpsのWebカメラを使用しても35fps弱しか出ていないことがわかりました。
動画ファイルの方は動画本来の30fpsを遥かに超えた速度で処理されていることから、コード自体がfpsを低迷させているわけでもなさそうです。

計測コード

import cv2
import time

# Webカメラの起動
cap = cv2.VideoCapture(0)

# FPS計算のための変数
frame_count = 0
frame_times = []

while cap.isOpened():
    start_time = time.time()  # フレーム処理開始時間を記録

    ret, frame = cap.read()
    if not ret:
        break

    # ウィンドウにカメラ映像をそのまま表示
    cv2.imshow("Camera Stream", frame)

    # フレーム処理にかかった時間を計算
    frame_time = time.time() - start_time
    frame_times.append(frame_time)
    frame_count += 1

    # 300 フレーム処理後 または 'q'を押すと終了
    if frame_count >= 300 or (cv2.waitKey(1) & 0xFF == ord('q')):
        break

# 平均FPSを計算
if frame_count > 0:
    average_fps = frame_count / sum(frame_times)
    print(f"Average FPS: {average_fps:.2f}")
    print(f"Min frame time: {min(frame_times):.3f} s")
    print(f"Avg frame time: {sum(frame_times)/frame_count:.3f} s")
    print(f"Max frame time: {max(frame_times):.3f} s")
    print(f"Frame times over 0.050 sec: " + ", ".join([f"{t:.3f}" + f" s @ {i}/{frame_count}" for i, t in enumerate(frame_times) if t >= 0.05]))

cap.release()
cv2.destroyAllWindows()

実行結果の抜粋

# Webカメラの描画のみ
Average FPS: 34.61
Min frame time: 0.005 s
Avg frame time: 0.029 s
Max frame time: 0.627 s
Frame times over 0.050 sec: 0.627 s @ 0/300
# 動画ファイルの描画のみ
Average FPS: 492.69
Min frame time: 0.001 s
Avg frame time: 0.002 s
Max frame time: 0.041 s
Frame times over 0.050 sec:

冒頭の記事に結果に対する推測

先述のWebカメラでの結果で、n、sサイズが32~33fpsになっていましたが、もしかするとこの35fps弱引っ張られた値なのかもしれません。

冒頭の記事には実行環境(GPU)はRTX 3060と記載されており、私の使用したRTX 2070 SuperよりもYOLO11の推論速度は速いと思われます。
そのため、もっと高いfpsを出すポテンシャルがあったもののWebカメラの映像取得がボトルネックになってしまい、最終的に31~32fps程になってしまったのかもしれません。

まとめ

  • Webカメラと動画でYOLO11(物体検出タスク)の推論速度を測ってみました。

  • RTX 2070 SUPERの環境でも、Webカメラで約20~34fps、動画ファイルで約19~39fpsという結果になりました。

  • 詳細な原因は特定できていませんが、最大60fpsのWebカメラを使用かつ単純なカメラ映像取得のコードでも約35fpsしか得られないことがわかりました。

  • 冒頭の記事の結果で各モデルサイズの結果に差がほとんど出なかったのは、RTX 3060で十分な推論速度出ており、Webカメラの取得の部分がボトルネックになったのではないかと推測しています。

※推論速度は実行環境や入力映像で異なりますのであくまで参考程度とお考え下さい。

いいなと思ったら応援しよう!