1. はじめに
Raspberry Piに接続したUSBカメラの映像をネットワーク越しに配信し、NVIDIA GPUを搭載したPC側でYOLO11によるリアルタイム物体検出を行う環境を構築した。
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/
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
成功すると動画 + 物体検出のバウンディボックスが表示されるウィンドウが起動する。
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でリアルタイム物体検出できるところまで構築した。


