0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry Pi + USBカメラ + MediaMTX + DeepStream 9.1 + YOLO11でリアルタイム物体検出する

0
Posted at

1. はじめに

Raspberry Piに接続したUSBカメラの映像をネットワーク越しに配信し、NVIDIA GPUを搭載したPC側でYOLO11によるリアルタイム物体検出を行う環境を構築した。

イメージ図:
image.png

2. 全体構成

Raspberry Pi側ではUSBカメラからH.264映像を取得し、MediaMTXをRTSPサーバーとして動作させる。
GPUマシン側ではRTSPをGStreamerで受信し、DeepStreamの nvinfer を使ってYOLO11を実行する。

┌───────────────┐
│ 1. USBカメラ    │
│ 映像を撮影する   │
└───────┬───────┘
        │
        ▼
┌──────────────────────────────┐
│ 2. Raspberry Pi              │
│ 映像を受け取り、RTSPで配信する   │
│                              │
│ 使用ソフト: FFmpeg, MediaMTX   │
└───────┬──────────────────────┘
        │
        │ RTSP
        ▼
┌──────────────────────────────┐
│ 3. NVIDIA GPU搭載PC           │
│ 配信映像を受信し、YOLO11で       │
│ 物体検出を行う                 │
│                              │
│ 使用ソフト:                    │
│ GStreamer, DeepStream, YOLO11 │
└───────┬──────────────────────┘
        │
        ▼
┌───────────────┐
│ 4. ディスプレイ  │
│ 検出結果つき映像 │
│ を表示する      │
└───────────────┘

使用した機器の詳細は以下の通り。

Raspberry Pi:

項目 内容
モデル Raspberry Pi 3 Model B
OS Raspberry Pi OS Lite(64-bit)

NVIDIA GPU搭載PC:

項目 内容
OS Kubuntu 24.04.04
CPU Ryzen 5 7600
メモリ DDR5-5600 64GB
マザーボード MSI MAG B650 TOMAHAWK WIFI
GPU1 ZOTAC GAMING GeForce RTX 5060 Ti 16GB Twin Edge
GPU2 MSI GeForce RTX 3060 VENTUS 2X 12G OC
GPU ドライバーバージョン 580.173.02

USBカメラ:
Anker PowerConf C200

なお、GPU搭載PCは、あらかじめ以下の環境が利用可能な状態であることを前提とする。

  • Dockerがインストールされている
  • NVIDIA GPUドライバーがインストールされている
  • NVIDIA Container Toolkitがインストールされている
  • docker run --gpus ... を使用して、DockerコンテナからNVIDIA GPUを利用できる
  • Raspberry PiとGPU搭載PCがネットワーク経由で相互に通信できる

3. Raspberry Pi側の構築

3.1 必要なパッケージのインストール

V4L2の確認用ツールとFFmpegをインストールする。

sudo apt update
sudo apt install -y v4l-utils ffmpeg

3.2 USBカメラの認識確認

接続されているVideo4Linuxデバイスを確認する。

v4l2-ctl --list-devices

今回の環境では次のように認識された。

bcm2835-codec-decode (platform:bcm2835-codec):
        /dev/video10
        /dev/video11
        /dev/video12
        /dev/video18
        /dev/video31

bcm2835-isp (platform:bcm2835-isp):
        /dev/video13
        /dev/video14
        /dev/video15
        /dev/video16
        /dev/video20
        /dev/video21
        /dev/video22
        /dev/video23
        /dev/media0
        /dev/media1

Anker PowerConf C200: Anker Pow (usb-3f980000.usb-1.2):
        /dev/video0
        /dev/video1
        /dev/media3

bcm2835-codec (vchiq:bcm2835-codec):
        /dev/media2

/dev/video0 の詳細を確認すると、Anker PowerConf C200の情報が出力されているため、
USBカメラが /dev/video0 として認識されていることを確認できる。

v4l2-ctl -d /dev/video0 --all

# 必要部分のみ抜粋
Driver name : uvcvideo
Card type   : Anker PowerConf C200
Video Capture
Streaming
Width/Height : 2560/1440
Pixel Format : 'MJPG'
Frames per second: 30.000

3.3 カメラの対応フォーマット確認

カメラが出力できるコーデック、解像度、フレームレートを確認する。
v4l2-ctl -d /dev/video0 --list-formats-extで確認すると、C200はH264の生成に対応していることが分かる。

v4l2-ctl -d /dev/video0 --list-formats-ext

ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'MJPG' (Motion-JPEG, compressed)
                Size: Discrete 2560x1440
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
        [1]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
        [2]: 'H264' (H.264, compressed)
                Size: Discrete 2560x1440
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)

今回は、以下を使用する。

 [2]: 'H264' (H.264, compressed)
  Size: Discrete 1920x1080
     Interval: Discrete 0.033s (30.000 fps)

3.4 MediaMTXの導入と起動

RTSPサーバーにはMediaMTXを使用する。今回使用したのはARM64版のMediaMTX v1.21.0。

wget https://github.com/bluenviron/mediamtx/releases/download/v1.21.0/mediamtx_v1.21.0_linux_arm64.tar.gz

tar -xvf mediamtx_v1.21.0_linux_arm64.tar.gz

起動する。

./mediamtx

3.5 FFmpegでH.264映像をMediaMTXへ送信

mediamtxを起動したターミナルとは別ターミナルでFFmpegを起動する。
以下のコマンドでUSBカメラの情報をH.264でMediaMTXへ送信できる。

ffmpeg \
  -f v4l2 \
  -input_format h264 \
  -video_size 1920x1080 \
  -framerate 30 \
  -i /dev/video0 \
  -c:v copy \
  -f rtsp \
  rtsp://127.0.0.1:8554/cam

C200はH.264を直接出力できるため、FFmpegでは再エンコードせず -c:v copy でMediaMTXへ転送する。
これにより、Raspberry Pi側でH.264エンコードを行う必要がなく、CPU負荷を抑えることができる。

3.6 ブラウザから配信映像を確認

MediaMTXのWebRTC出力は、ブラウザから次のURLで確認できる。

http://<Raspberry PiのIPアドレス>:8889/cam/

ブラウザでの表示例:
image.png

GPUマシン側から接続する場合は、RTSPを使用するため、URLは以下となる。

rtsp://<Raspberry PiのIPアドレス>:8554/cam

4. GStreamerとDeepStreamについて

4.1 GStreamerとは

GStreamerは、映像や音声を扱うためのオープンソースのマルチメディアフレームワーク。
GStreamerでは、映像処理を行う小さな部品を接続してパイプラインを作る。

例えば、

rtspsrc
   ↓
rtph264depay
   ↓
h264parse
   ↓
avdec_h264
   ↓
videoconvert
   ↓
autovideosink

という構成では、

RTSP受信
↓
RTPからH.264を取り出す
↓
H.264を解析する
↓
H.264をデコードする
↓
映像形式を変換する
↓
画面に表示する

という処理になる。
GStreamerのコマンドでは、Element同士を ! でつなぐ。

gst-launch-1.0 \
  rtspsrc ... ! \
  rtph264depay ! \
  h264parse ! \
  avdec_h264 ! \
  videoconvert ! \
  autovideosink

この一連のつながりがGStreamer Pipelineになる。

4.2 DeepStreamとは

DeepStreamは、NVIDIAが提供している映像AI向けSDK。
DeepStream自体がGStreamerとは別の映像処理エンジンというわけではなく、GStreamerをベースに構築されている。DeepStreamではNVIDIA GPUやTensorRTを利用するためのElementが用意されている。今回使うDeepStreamのElementは以下。

nvvideoconvert
nvstreammux
nvinfer
nvdsosd
nveglglessink

5. NVIDIA GPUマシン側の構築

5.1 DeepStreamコンテナの起動

今回はNVIDIAが配布しているDeepStreamのDockerコンテナを使用する。
最終的に映像をウィンドウ表示するため、Dockerコンテナ起動前にまずホスト側からDockerコンテナのrootユーザーにX11アクセスを許可する。

xhost +SI:localuser:root

そして、DeepStream 9.1コンテナを起動する。

docker run --rm -it \
  --gpus '"device=0"' \
  -e DISPLAY=$DISPLAY \
  -e NVIDIA_DRIVER_CAPABILITIES=all \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  nvcr.io/nvidia/deepstream:9.1-triton-multiarch \
  bash

DISPLAY と /tmp/.X11-unix をコンテナへ渡しているのは、最後に nveglglessink からホストのディスプレイへ映像を出すため。

5.2 DeepStream-YoloとUltralyticsの取得

起動したDeepStreamのコンテナ内で作業する。
まず、YOLO11をDeepStreamから利用するため、今回は2つのリポジトリをダウンロードする。

ultralytics/ultralytics は、Ultralyticsが公開しているYOLOの公式実装で、YOLOモデルの学習、推論、エクスポートなどを行うためのPythonパッケージが含まれている。今回はこちらを使ってYOLO11のモデルを扱い、DeepStreamで利用できるようONNX形式へ変換する。Ultralyticsのリポジトリでは、物体検出だけでなく、セグメンテーション、分類、姿勢推定、OBBなどのタスクもサポートされている。

marcoslucianops/DeepStream-Yolo は、YOLO系モデルをNVIDIA DeepStreamで動かすための設定ファイルや変換スクリプト、Custom Parserなどをまとめたリポジトリ。READMEではYOLO11を含む複数のYOLO系モデルをDeepStreamで扱うためのONNX変換やGPU post-processingなどをサポートしている。

cd /opt/nvidia/deepstream/deepstream-9.1

git clone https://github.com/marcoslucianops/DeepStream-Yolo.git
git clone https://github.com/ultralytics/ultralytics.git

cd ultralytics

必要なPythonパッケージをインストールする。

pip3 install -e . --break-system-packages

pip3 install \
  onnx \
  onnxslim \
  onnxruntime \
  --break-system-packages

python3 -m pip install -U \
  onnx \
  onnxscript \
  onnxruntime \
  onnxslim

以下は実際に構築した際に実行した手順をそのまま記載しているため、一部のPythonパッケージのインストールが重複している。

5.3 YOLO11sの取得とONNX変換

次に物体検出に用いるYOLO11sの重みを取得する。

wget https://github.com/ultralytics/assets/releases/download/v8.4.0/yolo11s.pt

DeepStream-Yoloに含まれている変換スクリプトを使ってONNXへ変換する。DeepStream/TensorRTで扱うためにはONNXへ変換する必要がある。

python3 \
  /opt/nvidia/deepstream/deepstream-9.1/DeepStream-Yolo/utils/export_yolo11.py \
  -w yolo11s.pt \
  --dynamic \
  --simplify

変換できたか確認する。yolo11s.onnxが存在すればOK。

ls -lh yolo11s.onnx

-rw-r--r-- 1 root root 37M Sep 15 10:33 yolo11s.onnx

DeepStream-Yoloディレクトリへコピーする。

cp yolo11s.onnx \
  /opt/nvidia/deepstream/deepstream-9.1/DeepStream-Yolo/

5.4 DeepStream-Yolo Custom Parserのビルド

YOLO11の出力Tensorを、DeepStreamが扱えるBBox・クラス・confidenceなどの検出結果へ変換するためにCustom Parserのビルドが必要となる。DeepStream-Yoloに含まれるParserをビルドする。

DeepStream-Yoloへ移動する。

cd /opt/nvidia/deepstream/deepstream-9.1/DeepStream-Yolo

CUDAバージョンを確認する。

nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2026 NVIDIA Corporation
Built on Mon_Mar_02_09:52:23_PM_PST_2026
Cuda compilation tools, release 13.2, V13.2.51
Build cuda_13.2.r13.2/compiler.37434383_0
root@7a6c57e57418:/opt/nvidia/deepstream/deep

今回の環境ではCUDA 13.2だったため、環境変数CUDA_VERに13.2を設定する。

export CUDA_VER=13.2

Custom Parserをビルドする。

make -C nvdsinfer_custom_impl_Yolo clean
make -C nvdsinfer_custom_impl_Yolo -j$(nproc)

生成されたライブラリを確認する。存在すればOKである。

ls -lh nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so

-rwxr-xr-x 1 root root 1.2M Sep 15 10:37 nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so

5.5 nvinfer設定ファイルの作成

/opt/nvidia/deepstream/deepstream-9.1/DeepStream-Yolo/config_infer_primary_yolo11.txt を次のよう変更する。

[property]
gpu-id=0
net-scale-factor=0.0039215697906911373
model-color-format=0

onnx-file=yolo11s.onnx
model-engine-file=yolo11s_b1_gpu0_fp16.engine

labelfile-path=labels.txt

batch-size=1
network-mode=2
num-detected-classes=80

interval=0
gie-unique-id=1
process-mode=1
network-type=0
cluster-mode=2

maintain-aspect-ratio=1
symmetric-padding=1

parse-bbox-func-name=NvDsInferParseYolo
custom-lib-path=nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so
engine-create-func-name=NvDsInferYoloCudaEngineGet

[class-attrs-all]
nms-iou-threshold=0.45
pre-cluster-threshold=0.25
topk=300

なお、元の設定からの変更点は以下の2行である。

model-engine-file=yolo11s_b1_gpu0_fp16.engine
network-mode=2

5.6 avdec_h264の導入

今回の環境では、H.264デコードにGStreamerの avdec_h264 を使用した。
まず gstreamer1.0-libav を追加する。

apt update
apt install -y gstreamer1.0-libav

以下の依存ライブラリも追加する。

# なぜかreinstallをつけないと、後続の`gst-inspect-1.0 avdec_h264`が失敗する


apt install -y --reinstall  \
  libvpx9 \
  libmp3lame0 \
  libx264-164 \
  libx265-199 \
  libmpg123-0

共有ライブラリ情報を更新し、GStreamerのplugin cacheを削除する。

ldconfig

rm -f ~/.cache/gstreamer-1.0/registry.*

avdec_h264 が利用できるか確認する。

gst-inspect-1.0 avdec_h264
# 以下のような出力が出ればOK
Factory Details:
  Rank                     primary (256)
  Long-name                libav H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 decoder
  Klass                    Codec/Decoder/Video
  Description              libav h264 decoder
  Author                   Wim Taymans <wim.taymans@gmail.com>, Ronald Bultje <rbultje@ronald.bitfreak.net>, Edward Hervey <bilboed@bilboed.com>
  Documentation            https://gstreamer.freedesktop.org/documentation/libav/avdec_h264.html

Plugin Details:
  Name                     libav
  Description              All libav codecs and formats (system install)
  Filename                 /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstlibav.so
  Version                  1.24.1
  License                  LGPL
  Source module            gst-libav
  Documentation            https://gstreamer.freedesktop.org/documentation/libav/
  Source release date      2024-03-21
  Binary package           GStreamer libav Plugins (Ubuntu)
  Origin URL               https://launchpad.net/ubuntu/+source/gst-libav1.0

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstVideoDecoder
                         +----GstFFMpegVidDec
                               +----avdec_h264

Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      video/x-h264
              alignment: au
          stream-format: { (string)avc, (string)byte-stream }
      video/x-h264
              alignment: nal
          stream-format: byte-stream
  
  SRC template: 'src'
  
(略)

本環境では nvv4l2decoder を利用したPipelineでは正常に映像を取得できなかった。今回は原因調査を切り離し、動作確認を優先して avdec_h264 を利用した。そのためデコード処理はCPUで行われる。

5.7 YOLO11を含むDeepStream Pipelineの実行

DeepStream-Yoloディレクトリへ移動する。

cd /opt/nvidia/deepstream/deepstream-9.1/DeepStream-Yolo

最終的に動作したPipelineは次のとおり。

gst-launch-1.0 -v \
  rtspsrc location=rtsp://<Raspberry PiのIPアドレス>:8554/cam \
    protocols=tcp latency=50 drop-on-latency=true ! \
  rtph264depay ! \
  h264parse ! \
  avdec_h264 ! \
  videoconvert ! \
  video/x-raw,format=NV12 ! \
  queue max-size-buffers=1 leaky=downstream ! \
  nvvideoconvert ! \
  "video/x-raw(memory:NVMM),format=NV12" ! \
  mux.sink_0 \
  nvstreammux name=mux \
    batch-size=1 \
    width=1920 \
    height=1080 \
    live-source=1 \
    batched-push-timeout=33333 ! \
  nvinfer \
    batch-size=1 \
    config-file-path=config_infer_primary_yolo11.txt ! \
  nvvideoconvert ! \
  "video/x-raw(memory:NVMM),format=RGBA" ! \
  nvdsosd \
    display-bbox=1 \
    display-text=1 \
    process-mode=0 ! \
  queue max-size-buffers=1 leaky=downstream ! \
  nveglglessink sync=false

成功すると動画 + 物体検出のバウンディボックスが表示されるウィンドウが起動する。

image.png

5.8 Pipelineの処理内容

最終Pipelineは以下のようになっている。

MediaMTX
    │
    │ RTSP / H.264
    ▼
 rtspsrc
    ▼
rtph264depay
    ▼
 h264parse
    ▼
avdec_h264
    │
    │ CPU memory
    ▼
videoconvert
    │
    │ NV12
    ▼
   queue
    ▼
nvvideoconvert
    │
    │ NVMM / NV12
    ▼
nvstreammux
    ▼
 nvinfer
    │
    │ YOLO11 / TensorRT
    ▼
nvvideoconvert
    │
    │ NVMM / RGBA
    ▼
 nvdsosd
    │
    │ BBox / Label
    ▼
   queue
    ▼
nveglglessink
    ▼
  Display

5.8.1 RTSP受信からH.264デコード

最初の部分は通常のGStreamer処理になる。

rtspsrc
↓
rtph264depay
↓
h264parse
↓
avdec_h264

rtspsrc でMediaMTXへ接続する。
rtph264depay はRTPパケットからH.264データを取り出す。
h264parse はH.264ストリームを後段で扱える形へ整える。
最後に avdec_h264 でH.264をRaw Frameへデコードする。

5.8.2 nvvideoconvertとNVMM

avdec_h264 でデコードしたフレームはCPU側にある。
そのため、

videoconvert ! \
video/x-raw,format=NV12 ! \
nvvideoconvert ! \
"video/x-raw(memory:NVMM),format=NV12"

avdec_h264 の出力は通常のCPU側メモリ上のRaw Frameである。
そこで videoconvert でNV12へ変換した後、nvvideoconvert を使ってNVMM上のバッファへ変換し、後段の nvstreammux へ渡している。

5.8.3 nvstreammux

nvstreammux は入力映像をDeepStreamの推論処理へ渡すためにまとめるElement。

nvstreammux name=mux \
  batch-size=1 \
  width=1920 \
  height=1080 \
  live-source=1 \
  batched-push-timeout=33333

今回は1カメラのため、batch-size=1としている。
live-source=1 はライブ映像を入力していることを示す。
batched-push-timeout=33333 は約33.3 msで、30 fpsの1フレーム周期を目安として設定している。

5.8.4 nvinferとYOLO11

nvinfer はDeepStreamの推論Elementである。

nvinfer \
  batch-size=1 \
  config-file-path=config_infer_primary_yolo11.txt

先ほど作成した設定ファイルを読み込み、TensorRTでYOLO11を実行する。
YOLO11は物体検出の結果、例えば、以下のような結果を出力し、後続に受け渡す。

class      : person
confidence : 0.91
bbox       : x, y, width, height

5.8.5 nvdsosd

nvdsosdではYOLO11から受け取った物体検出の結果を映像へ描画する。

nvdsosd \
  display-bbox=1 \
  display-text=1 \
  process-mode=0

display-bbox=1 でBounding Boxを表示し、display-text=1 でクラス名などのテキストを表示する。

5.8.6 nveglglessink

nveglglessink は映像を画面へ表示するためのElement。
これを実行することでウィンドウにDeepStreamで処理した映像を表示することができる。

6. まとめ

Raspberry Piに接続したAnker PowerConf C200の映像をMediaMTXでRTSP配信し、NVIDIA GPU搭載PC上のDeepStream 9.1 + YOLO11でリアルタイム物体検出できるところまで構築した。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?