はじめに
Xilinx KV260のDPU(Deep Learning Processing Unit)を使って、USBカメラの映像をYOLOv7でリアルタイム物体検出し、HDMIモニタに結果を表示します。
前回の記事で静止画のYOLOv7推論(190ms, 5.3FPS相当)を実現しましたが、今回はそのxmodelをそのまま流用し、カメラ入力→リアルタイム表示まで仕上げます。
構成
| 項目 | 内容 |
|---|---|
| ボード | Kria KV260 |
| OS | Ubuntu 22.04 |
| DPU | DPUCZDX8G_ISA1_B4096 (300MHz) |
| モデル | YOLOv7(PTQ量子化済み) |
| カメラ | USB Webカメラ (UVC, 640x480) |
| 表示 | HDMI (cv2.imshow, X11) |
| Docker | vai-yolov7:v2 (Vitis AI runtime + OpenCV GUI) |
システム構成図
USB Camera (640x480)
│
▼
┌──────────────────────────────────────────┐
│ Docker (vai-yolov7:v2) │
│ │
│ CameraThread ──→ 前処理(resize,正規化) │
│ │ │
│ ▼ │
│ DPU推論 (185ms) │
│ │ │
│ ▼ │
│ 後処理(decode+NMS, 90ms) │
│ │ │
│ ▼ │
│ 描画+表示 │
└──────────────────────────────────────────┘
│
▼
HDMI Monitor (バウンディングボックス + FPS表示)
後処理の高速化が鍵
DPU推論は185msで固定ですが、YOLOv7の後処理(アンカーデコード + NMS)がPythonでは非常に重いです。
初期実装: 0.6 FPS
3重forループで全グリッド(80x80 + 40x40 + 20x20 = 25,200セル x 3アンカー)を走査:
# これだとARM Cortex-A53で約1400ms
for ay in range(grid_h):
for ax in range(grid_w):
for a in range(3):
det = out[ay, ax, a]
obj_conf = sigmoid(det[4])
if obj_conf < conf_thresh:
continue
...
最終実装: 3.6 FPS(6倍高速化)
2つの最適化を適用:
1. sigmoid事前フィルタ
sigmoid(x) > threshold は x > log(threshold / (1 - threshold)) と数学的に等価。生の値で比較し、通過した検出のみsigmoidを計算する:
raw_thresh = np.log(conf_thresh / (1.0 - conf_thresh))
mask = raw_obj > raw_thresh # sigmoid計算なしでフィルタ
ays, axs, ans = np.where(mask) # 数百個程度に絞られる
det = out[ays, axs, ans] # 必要な検出だけ取り出す
obj_conf = 1.0 / (1.0 + np.exp(-det[:, 4])) # ここで初めてsigmoid
25,200 x 3 = 75,600回のsigmoid計算が、数百回に削減されます。
2. NumPyベクトル化
ループを廃止し、座標計算をNumPyの一括演算に:
cx = (sxy[:, 0] * 2 - 0.5 + axs) * stride
cy = (sxy[:, 1] * 2 - 0.5 + ays) * stride
w = (swh[:, 0] * 2) ** 2 * anchor[ans, 0]
h = (swh[:, 1] * 2) ** 2 * anchor[ans, 1]
最適化結果まとめ
| 最適化 | 後処理時間 | 全体FPS |
|---|---|---|
| Python 3重forループ | 1400ms | 0.6 |
| NumPyベクトル化 | 270ms | 2.2 |
| + sigmoid事前フィルタ | 90ms | 3.6 |
パフォーマンス内訳
| 処理 | 時間 |
|---|---|
| カメラ取得 | ~0ms(別スレッド) |
| 前処理(resize+正規化) | ~15ms |
| DPU推論 | ~185ms |
| 後処理(デコード+NMS) | ~90ms |
| 描画+表示 | ~10ms |
| 合計 | ~275ms(3.6 FPS) |
試行して効果がなかったもの
| 最適化 | 結果 | 理由 |
|---|---|---|
| カメラ取得スレッド化 | 効果なし | DPUがボトルネック |
| DPU推論+後処理パイプライン化 | 効果なし | execute_asyncが実際にはブロック |
VARTのexecute_asyncは名前に反して同期的に動作するようで、パイプラインによるDPUと後処理の並列実行は実現できませんでした。
実行手順
前提
- [前回の記事]でYOLOv7のxmodelとDockerイメージ(vai-yolov7:v1)を作成済み
- vai-yolov7:v1にOpenCV GUI(GTK)を追加してvai-yolov7:v2を作成:
sudo docker run -it vai-yolov7:v1 bash -c \
"apt-get update && apt-get install -y libgtk2.0-dev && pip install opencv-python --force-reinstall"
sudo docker commit <container_id> vai-yolov7:v2
KV260で実行
注意: SSHではなく、KV260のHDMIモニタ前で直接操作すること。
# 1. スクリプトを配置
scp yolov7_cam.py run_yolo_cam.sh ubuntu@<KV260_IP>:~/
# 2. DPUファームウェアロード
sudo xmutil unloadapp
sudo xmutil loadapp kv260-benchmark-b4096
# 3. X11アクセス許可(KV260のデスクトップターミナルで)
export DISPLAY=:1
xhost +local:
# 4. 実行
DISPLAY=:1 bash ~/run_yolo_cam.sh
HDMIモニタにカメラ映像とバウンディングボックスが表示されます。qキーで終了。
ハマりポイント
| 問題 | 原因 | 解決 |
|---|---|---|
cv2.error: The function is not implemented |
opencv-python-headless(GUI無し)がインストールされていた |
apt install libgtk2.0-dev + pip install opencv-python --force-reinstall
|
could not connect to display :0 |
SSHセッションからX11にアクセスできない | KV260のHDMIモニタ前で直接操作する |
xhost: unable to open display ":0" |
X11ソケットが:1だった(/tmp/.X11-unix/X1) |
export DISPLAY=:1 |
| DPU controller empty factory | XCLBINが未ロード | sudo xmutil loadapp kv260-benchmark-b4096 |
ソースコード
まとめ
- KV260のDPU B4096でYOLOv7のリアルタイムカメラ検出を実現(3.6 FPS)
- 後処理のNumPyベクトル化 + sigmoid事前フィルタで0.6FPS → 3.6FPSに改善
- DPU推論185msが支配的で、これが現在の上限(理論上限5.4FPS)
- パイプライン化はVARTのexecute_asyncがブロックするため効果なし