Jetson CSI/USB 摄像头配置与多路视频流处理
1. Jetson 摄像头接口概览
Jetson 支持两种摄像头接口:
摄像头接口对比:
┌─────────────┬──────────────────┬──────────────────┐
│ 特性 │ CSI 摄像头 │ USB 摄像头 │
├─────────────┼──────────────────┼──────────────────┤
│ 接口 │ MIPI CSI-2 │ USB 2.0/3.0 │
│ 带宽 │ 高(4 Lane) │ 中等 │
│ 延迟 │ 极低(<10ms) │ 中等(30-100ms) │
│ CPU 占用 │ 低(ISP 硬件加速)│ 高(软解码) │
│ 多路支持 │ 最多 6 路 │ 受 USB 带宽限制 │
│ 热插拔 │ ❌ 不支持 │ ✅ 支持 │
│ 价格 │ 中等 │ 低 │
│ 推荐场景 │ 实时检测、机器人 │ 通用开发、调试 │
└─────────────┴──────────────────┴──────────────────┘
2. CSI 摄像头配置
2.1 常见 CSI 摄像头型号
支持的 CSI 摄像头:
├── Raspberry Pi Camera Module v2 (IMX219)
│ ├── 分辨率:3280×2464 @ 15fps / 1920×1080 @ 30fps
│ ├── 接口:15-pin FPC
│ └── 价格:~$25
├── Raspberry Pi HQ Camera (IMX477)
│ ├── 分辨率:4056×3040 @ 30fps / 1920×1080 @ 60fps
│ ├── 接口:22-pin FPC(需转接板)
│ └── 价格:~$50
├── ArduCam IMX219 双目摄像头
│ ├── 双路 IMX219
│ └── 适合立体视觉
└── Leopard Imaging LI-IMX477
├── 工业级 IMX477
└── 适合工业部署
2.2 硬件连接
CSI 摄像头连接步骤:
1. 断开 Jetson 电源
2. 找到载板上的 CSI 接口(通常标有 CAM0/CAM1)
3. 拉起 FPC 连接器的卡扣
4. 将排线金手指朝下插入(蓝色面朝 PCB 板边)
5. 按下卡扣固定
6. 接通电源
注意:
- Orin NX Developer Kit 有 2 个 CSI 接口(CAM0 + CAM1)
- 第三方载板 CSI 接口位置可能不同
- IMX477 需要 22-pin 转 15-pin 转接板
2.3 检测 CSI 摄像头
bash
# 检查摄像头是否被系统识别
ls /dev/video*
# 应该看到 /dev/video0 等设备
# 使用 v4l2 工具检查
v4l2-ctl --list-devices
# 输出示例:
# vi-output, imx219 9-0010 (platform:tegra-capture-vi:0):
# /dev/video0
# 查看摄像头支持的格式
v4l2-ctl -d /dev/video0 --list-formats-ext
# 输出示例:
# [0]: 'RG10' (10-bit Bayer RGRG/GBGB)
# Size: Discrete 3280x2464
# Interval: Discrete 0.067s (15.000 fps)
# Size: Discrete 1920x1080
# Interval: Discrete 0.033s (30.000 fps)
2.4 测试 CSI 摄像头
bash
# 使用 GStreamer 测试 CSI 摄像头
# IMX219 测试
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
'video/x-raw(memory:NVMM), width=1920, height=1080, framerate=30/1' ! \
nvvidconv ! 'video/x-raw, format=BGRx' ! videoconvert ! \
'video/x-raw, format=BGR' ! autovideosink
# IMX477 测试
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! \
'video/x-raw(memory:NVMM), width=4032, height=3040, framerate=20/1' ! \
nvvidconv ! 'video/x-raw, format=BGRx' ! videoconvert ! \
'video/x-raw, format=BGR' ! autovideosink
# 截图
gst-launch-1.0 nvarguscamerasrc num-buffers=1 ! \
'video/x-raw(memory:NVMM), width=1920, height=1080' ! \
nvvidconv ! 'video/x-raw, format=BGRx' ! videoconvert ! \
'video/x-raw, format=BGR' ! pngenc ! filesink location=camera_test.png
3. USB 摄像头配置
3.1 检测 USB 摄像头
bash
# 列出 USB 设备
lsusb | grep -i camera
# 输出示例:
# Bus 002 Device 003: ID 046d:0825 Logitech, Inc. Webcam C270
# 查看摄像头信息
v4l2-ctl -d /dev/video0 --all
# 输出:设备名、驱动、格式、分辨率等
# 测试摄像头
v4l2-ctl -d /dev/video0 --list-formats-ext
3.2 测试 USB 摄像头
bash
# 方式一:GStreamer(推荐,低延迟)
gst-launch-1.0 v4l2src device=/dev/video0 ! \
'video/x-raw, width=1280, height=720, framerate=30/1' ! \
videoconvert ! 'video/x-raw, format=BGR' ! autovideosink
# 方式二:OpenCV(简单)
python3 -c "
import cv2
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
while True:
ret, frame = cap.read()
if ret:
cv2.imshow('USB Camera', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
"
# 方式三:FFmpeg
ffmpeg -f v4l2 -video_size 1280x720 -i /dev/video0 -f sdl2 preview
3.3 USB 摄像头性能优化
python
#!/usr/bin/env python3
"""usb_camera_optimized.py - USB 摄像头优化采集"""
import cv2
import threading
import queue
import time
class OptimizedUSBCamera:
"""优化的 USB 摄像头采集"""
def __init__(self, device_id=0, width=1280, height=720, fps=30):
self.device_id = device_id
self.width = width
self.height = height
self.fps = fps
self.frame_queue = queue.Queue(maxsize=2)
self.running = False
# 使用 GStreamer 管道(降低 CPU 占用)
self.pipeline = (
f"v4l2src device=/dev/video{device_id} ! "
f"video/x-raw, width={width}, height={height}, framerate={fps}/1 ! "
f"videoconvert ! video/x-raw, format=BGR ! appsink"
)
def start(self):
"""启动采集线程"""
self.running = True
self.thread = threading.Thread(target=self._capture_loop, daemon=True)
self.thread.start()
return self
def _capture_loop(self):
"""采集循环"""
cap = cv2.VideoCapture(self.pipeline, cv2.CAP_GSTREAMER)
if not cap.isOpened():
print(f"❌ 无法打开摄像头 /dev/video{self.device_id}")
return
while self.running:
ret, frame = cap.read()
if ret:
# 丢弃旧帧,保持队列最新
if self.frame_queue.full():
try:
self.frame_queue.get_nowait()
except queue.Empty:
pass
self.frame_queue.put(frame)
else:
time.sleep(0.001)
cap.release()
def read(self, timeout=1.0):
"""获取最新帧"""
try:
return self.frame_queue.get(timeout=timeout)
except queue.Empty:
return None
def stop(self):
"""停止采集"""
self.running = False
if hasattr(self, 'thread'):
self.thread.join(timeout=2)
if __name__ == "__main__":
camera = OptimizedUSBCamera(device_id=0, width=1280, height=720, fps=30)
camera.start()
fps_counter = 0
fps_start = time.time()
while True:
frame = camera.read()
if frame is not None:
fps_counter += 1
if time.time() - fps_start >= 1.0:
print(f"FPS: {fps_counter}")
fps_counter = 0
fps_start = time.time()
cv2.imshow("Optimized USB Camera", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
camera.stop()
cv2.destroyAllWindows()
4. 多路视频流处理
4.1 双 CSI 摄像头
python
#!/usr/bin/env python3
"""dual_csi.py - 双 CSI 摄像头实时显示"""
import cv2
import threading
class CSICamera:
def __init__(self, sensor_id, width=1280, height=720, fps=30):
self.sensor_id = sensor_id
self.pipeline = (
f"nvarguscamerasrc sensor-id={sensor_id} ! "
f"video/x-raw(memory:NVMM), width={width}, height={height}, "
f"format=NV12, framerate={fps}/1 ! "
f"nvvidconv flip-method=0 ! "
f"video/x-raw, width={width}, height={height}, format=BGRx ! "
f"videoconvert ! video/x-raw, format=BGR ! appsink"
)
self.cap = cv2.VideoCapture(self.pipeline, cv2.CAP_GSTREAMER)
self.frame = None
self.running = True
self.thread = threading.Thread(target=self._capture, daemon=True)
self.thread.start()
def _capture(self):
while self.running:
ret, frame = self.cap.read()
if ret:
self.frame = frame
def read(self):
return self.frame
def release(self):
self.running = False
self.cap.release()
def main():
cam0 = CSICamera(sensor_id=0, width=1280, height=720)
cam1 = CSICamera(sensor_id=1, width=1280, height=720)
print("🎯 按 'q' 退出")
while True:
frame0 = cam0.read()
frame1 = cam1.read()
if frame0 is not None and frame1 is not None:
# 水平拼接
combined = cv2.hconcat([frame0, frame1])
cv2.imshow("Dual CSI Camera", combined)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cam0.release()
cam1.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
4.2 四路 USB 摄像头
python
#!/usr/bin/env python3
"""quad_usb.py - 四路 USB 摄像头"""
import cv2
import threading
import numpy as np
import time
class USBCameraThread(threading.Thread):
def __init__(self, device_id, width=640, height=480):
super().__init__(daemon=True)
self.device_id = device_id
self.frame = None
self.running = True
self.pipeline = (
f"v4l2src device=/dev/video{device_id} ! "
f"video/x-raw, width={width}, height={height}, framerate=30/1 ! "
f"videoconvert ! video/x-raw, format=BGR ! appsink"
)
def run(self):
cap = cv2.VideoCapture(self.pipeline, cv2.CAP_GSTREAMER)
while self.running:
ret, frame = cap.read()
if ret:
self.frame = frame
else:
time.sleep(0.01)
cap.release()
def main():
# 启动 4 路摄像头
cameras = []
for i in range(4):
cam = USBCameraThread(device_id=i, width=640, height=480)
cam.start()
cameras.append(cam)
print(f"摄像头 {i} 已启动")
time.sleep(2) # 等待摄像头初始化
while True:
frames = []
for i, cam in enumerate(cameras):
if cam.frame is not None:
frame = cam.frame.copy()
cv2.putText(frame, f"Camera {i}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
frames.append(frame)
else:
# 黑色占位
frames.append(np.zeros((480, 640, 3), dtype=np.uint8))
# 2x2 拼接
top = cv2.hconcat([frames[0], frames[1]])
bottom = cv2.hconcat([frames[2], frames[3]])
grid = cv2.vconcat([top, bottom])
cv2.imshow("Quad USB Cameras", grid)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
for cam in cameras:
cam.running = False
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
4.3 混合 CSI + USB 摄像头
python
#!/usr/bin/env python3
"""hybrid_cameras.py - CSI + USB 混合多路"""
import cv2
import threading
import queue
import time
class HybridCameraSystem:
"""混合摄像头系统"""
def __init__(self):
self.cameras = {}
self.frame_queues = {}
def add_csi_camera(self, name, sensor_id, width=1280, height=720, fps=30):
"""添加 CSI 摄像头"""
pipeline = (
f"nvarguscamerasrc sensor-id={sensor_id} ! "
f"video/x-raw(memory:NVMM), width={width}, height={height}, "
f"format=NV12, framerate={fps}/1 ! "
f"nvvidconv ! video/x-raw, format=BGRx ! videoconvert ! "
f"video/x-raw, format=BGR ! appsink"
)
self.cameras[name] = pipeline
self.frame_queues[name] = queue.Queue(maxsize=2)
def add_usb_camera(self, name, device_id, width=1280, height=720, fps=30):
"""添加 USB 摄像头"""
pipeline = (
f"v4l2src device=/dev/video{device_id} ! "
f"video/x-raw, width={width}, height={height}, framerate={fps}/1 ! "
f"videoconvert ! video/x-raw, format=BGR ! appsink"
)
self.cameras[name] = pipeline
self.frame_queues[name] = queue.Queue(maxsize=2)
def _capture_thread(self, name, pipeline, frame_queue):
"""采集线程"""
cap = cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER)
while True:
ret, frame = cap.read()
if ret:
if not frame_queue.full():
frame_queue.put(frame)
else:
time.sleep(0.01)
cap.release()
def start(self):
"""启动所有摄像头"""
threads = []
for name, pipeline in self.cameras.items():
t = threading.Thread(
target=self._capture_thread,
args=(name, pipeline, self.frame_queues[name]),
daemon=True
)
t.start()
threads.append(t)
print(f"✅ 摄像头 '{name}' 已启动")
return threads
def get_frame(self, name, timeout=1.0):
"""获取指定摄像头帧"""
try:
return self.frame_queues[name].get(timeout=timeout)
except queue.Empty:
return None
if __name__ == "__main__":
system = HybridCameraSystem()
# 添加 CSI 摄像头
system.add_csi_camera("前视", sensor_id=0, width=1280, height=720)
system.add_csi_camera("后视", sensor_id=1, width=1280, height=720)
# 添加 USB 摄像头
system.add_usb_camera("侧视", device_id=0, width=640, height=480)
system.start()
time.sleep(2)
while True:
frames = {}
for name in system.cameras:
frame = system.get_frame(name)
if frame is not None:
cv2.imshow(name, frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
5. RTSP 视频流推流
5.1 摄像头 → RTSP 推流
bash
# 使用 GStreamer 推流到 RTSP 服务器
# 需要安装 RTSP 服务器
sudo apt install -y gstreamer1.0-rtsp libgstrtspserver-1.0-dev
# 启动 RTSP 推流(CSI 摄像头)
gst-launch-1.0 -v nvarguscamerasrc sensor-id=0 ! \
'video/x-raw(memory:NVMM), width=1280, height=720, framerate=30/1' ! \
nvvidconv ! 'video/x-raw(memory:NVMM), format=NV12' ! \
nvv4l2h264enc bitrate=4000000 ! \
h264parse ! rtph264pay ! \
udpsink host=127.0.0.1 port=5400
# 播放 RTSP 流
ffplay rtsp://127.0.0.1:8554/camera0
5.2 Python RTSP 推流服务器
python
#!/usr/bin/env python3
"""rtsp_server.py - 基于 GStreamer 的 RTSP 推流"""
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstRtspServer', '1.0')
from gi.repository import Gst, GstRtspServer, GLib
Gst.init(None)
class CSIStreamFactory(GstRtspServer.RTSPMediaFactory):
"""CSI 摄像头流工厂"""
def __init__(self, sensor_id=0, **kwargs):
super().__init__(**kwargs)
self.sensor_id = sensor_id
def do_create_element(self, url):
pipeline_str = (
f"( nvarguscamerasrc sensor-id={self.sensor_id} ! "
f"video/x-raw(memory:NVMM), width=1280, height=720, "
f"framerate=30/1, format=NV12 ! "
f"nvv4l2h264enc bitrate=4000000 ! "
f"h264parse ! rtph264pay name=pay0 pt=96 )"
)
return Gst.parse_launch(pipeline_str)
def main():
server = GstRtspServer.RTSPServer()
server.set_service("8554")
# 多路摄像头
for i, name in enumerate(["camera0", "camera1"]):
factory = CSIStreamFactory(sensor_id=i)
factory.set_shared(True)
server.get_mount_points().add_factory(f"/{name}", factory)
print(f"✅ RTSP 流: rtsp://0.0.0.0:8554/{name}")
server.attach(None)
print("🚀 RTSP 服务器已启动")
loop = GLib.MainLoop()
loop.run()
if __name__ == "__main__":
main()
6. 录像与截图
python
#!/usr/bin/env python3
"""recorder.py - 视频录制与截图"""
import cv2
import os
import time
from datetime import datetime
class VideoRecorder:
"""视频录制器"""
def __init__(self, output_dir="recordings"):
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
self.writer = None
self.recording = False
def start_recording(self, width, height, fps=30, codec='mp4v'):
"""开始录制"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filepath = os.path.join(self.output_dir, f"rec_{timestamp}.mp4")
fourcc = cv2.VideoWriter_fourcc(*codec)
self.writer = cv2.VideoWriter(filepath, fourcc, fps, (width, height))
self.recording = True
self.start_time = time.time()
print(f"🔴 开始录制: {filepath}")
return filepath
def write_frame(self, frame):
"""写入帧"""
if self.recording and self.writer:
self.writer.write(frame)
def stop_recording(self):
"""停止录制"""
if self.writer:
self.writer.release()
self.writer = None
self.recording = False
duration = time.time() - self.start_time
print(f"⏹ 停止录制: {duration:.1f} 秒")
def take_screenshot(self, frame, prefix="screenshot"):
"""截图"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
filepath = os.path.join(self.output_dir, f"{prefix}_{timestamp}.png")
cv2.imwrite(filepath, frame)
print(f"📸 截图: {filepath}")
return filepath
if __name__ == "__main__":
recorder = VideoRecorder()
# CSI 摄像头
pipeline = (
"nvarguscamerasrc sensor-id=0 ! "
"video/x-raw(memory:NVMM), width=1280, height=720, framerate=30/1 ! "
"nvvidconv ! video/x-raw, format=BGRx ! videoconvert ! "
"video/x-raw, format=BGR ! appsink"
)
cap = cv2.VideoCapture(pipeline, cv2.CAP_GSTREAMER)
print("按键说明:")
print(" r - 开始/停止录制")
print(" s - 截图")
print(" q - 退出")
while True:
ret, frame = cap.read()
if not ret:
break
# 显示录制状态
if recorder.recording:
elapsed = time.time() - recorder.start_time
cv2.circle(frame, (30, 30), 10, (0, 0, 255), -1)
cv2.putText(frame, f"REC {elapsed:.0f}s", (50, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.imshow("Camera", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('r'):
if recorder.recording:
recorder.stop_recording()
else:
recorder.start_recording(1280, 720)
elif key == ord('s'):
recorder.take_screenshot(frame)
if recorder.recording:
recorder.write_frame(frame)
recorder.stop_recording()
cap.release()
cv2.destroyAllWindows()
总结
| 场景 | 推荐方案 | 预期 FPS |
|---|---|---|
| 单路 CSI 实时检测 | nvarguscamerasrc + TensorRT | 30-60 |
| 双路 CSI 立体视觉 | 双线程采集 + 单线程推理 | 15-30 每路 |
| 4 路 USB 监控 | GStreamer + 多线程 | 15-30 每路 |
| 混合 CSI+USB | 统一采集框架 | 按需配置 |
| RTSP 推流 | nvv4l2h264enc 硬编码 | 30 |
核心要点:
- CSI 优先:低延迟、低 CPU 占用
- GStreamer 管道:Jetson 上最高效的视频处理方式
- 多线程采集:采集和推理分离,避免丢帧
- 硬件编码 :推流使用
nvv4l2h264enc