树莓派4 8GB双USB摄像头OpenCV C++读取卡顿,JS正常
问题原因分析
- USB总线带宽饱和:树莓派4的USB总线为共享带宽设计,两个Eternico ET101 HD摄像头若以未压缩格式(如YUYV)输出视频,单路720p/30fps的YUYV流带宽约207Mbps,两路总和会超出USB总线实际可用带宽(USB 2.0实际可用约300Mbps以内)。而浏览器JS默认调用摄像头的MJPEG压缩输出格式,带宽仅需几十Mbps,因此能正常运行。
- OpenCV默认参数不合理:
CAP_ANY自动选择的V4L2后端默认采用未压缩的YUYV格式读取,未启用摄像头的硬件压缩能力,进一步加剧带宽占用。 - 同步读取阻塞:单线程下依次调用两个
VideoCapture::read()会触发IO阻塞,带宽不足时摄像头硬件会停止输出,最终导致帧冻结。
C++解决方案
1. 启用MJPEG压缩+多线程读取
通过指定摄像头输出MJPEG压缩格式降低带宽占用,同时用多线程分别处理两个摄像头的读取,避免同步阻塞:
#include <opencv2/core.hpp> #include <opencv2/videoio.hpp> #include <opencv2/highgui.hpp> #include <iostream> #include <thread> #include <unistd.h> using namespace cv; using namespace std; void captureLoop(int deviceID, const string& winName) { VideoCapture cap; // 强制使用V4L2后端 if (!cap.open(deviceID, CAP_V4L2)) { cerr << "Failed to open camera " << deviceID << "\n"; return; } // 配置摄像头输出MJPEG格式、分辨率、帧率 cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G')); cap.set(CAP_PROP_FRAME_WIDTH, 1280); cap.set(CAP_PROP_FRAME_HEIGHT, 720); cap.set(CAP_PROP_FPS, 30); // 验证参数是否生效 cout << "Camera " << deviceID << " actual params: " << cap.get(CAP_PROP_FRAME_WIDTH) << "x" << cap.get(CAP_PROP_FRAME_HEIGHT) << ", FPS: " << cap.get(CAP_PROP_FPS) << "\n"; Mat frame; while (true) { if (!cap.read(frame)) { cerr << "Blank frame from camera " << deviceID << "\n"; break; } imshow(winName, frame); // 每个线程需单独处理waitKey,避免窗口无响应 if (waitKey(1) >= 0) { destroyAllWindows(); exit(0); } } } int main() { // 启动两个独立线程分别读取摄像头 thread cam0(captureLoop, 0, "Camera 0"); thread cam1(captureLoop, 1, "Camera 1"); cam0.join(); cam1.join(); return 0; }
2. 网络推流实现(RTSP为例)
结合OpenCV+FFmpeg实现RTSP推流,需确保树莓派已安装FFmpeg开发库:
#include <opencv2/core.hpp> #include <opencv2/videoio.hpp> #include <opencv2/highgui.hpp> #include <iostream> #include <thread> using namespace cv; using namespace std; void streamLoop(int deviceID, const string& winName, const string& rtspUrl) { VideoCapture cap; if (!cap.open(deviceID, CAP_V4L2)) { cerr << "Failed to open camera " << deviceID << "\n"; return; } cap.set(CAP_PROP_FOURCC, VideoWriter::fourcc('M', 'J', 'P', 'G')); cap.set(CAP_PROP_FRAME_WIDTH, 1280); cap.set(CAP_PROP_FRAME_HEIGHT, 720); cap.set(CAP_PROP_FPS, 30); int width = cap.get(CAP_PROP_FRAME_WIDTH); int height = cap.get(CAP_PROP_FRAME_HEIGHT); double fps = cap.get(CAP_PROP_FPS); // 初始化RTSP推流(需提前部署RTSP服务器如EasyDarwin) VideoWriter writer; if (!writer.open(rtspUrl, CAP_FFMPEG, VideoWriter::fourcc('H', '2', '6', '4'), fps, Size(width, height), true)) { cerr << "Failed to open RTSP stream for camera " << deviceID << "\n"; return; } Mat frame; while (true) { if (!cap.read(frame)) { cerr << "Blank frame from camera " << deviceID << "\n"; break; } imshow(winName, frame); writer.write(frame); // 推送帧到RTSP服务器 if (waitKey(1) >= 0) { destroyAllWindows(); exit(0); } } } int main() { // 替换为你的RTSP服务器地址 thread cam0(streamLoop, 0, "Camera 0", "rtsp://your-server:554/stream0"); thread cam1(streamLoop, 1, "Camera 1", "rtsp://your-server:554/stream1"); cam0.join(); cam1.join(); return 0; }
3. 额外优化建议
- 分离USB端口:将两个摄像头分别插入USB 3.0和USB 2.0端口,避免单总线带宽竞争。
- 降低分辨率/帧率:若MJPEG仍出现带宽问题,可将分辨率降至640x480或帧率降至15fps。
- 预配置摄像头:运行程序前用
v4l2-ctl工具锁定参数:v4l2-ctl --device=/dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=MJPG v4l2-ctl --device=/dev/video1 --set-fmt-video=width=1280,height=720,pixelformat=MJPG
内容的提问来源于stack exchange,提问作者Andrej Vyšný
相关产品推荐
相关产品推荐

