当前位置: 首页 > news >正文

OpenCV多线程编程:从单线程到多线程的视频处理

一、最简单的摄像头显示程序

让我们从最基础的版本开始:一个单线程程序,直接从摄像头读取并显示画面。

基础版本代码

#include <iostream> #include <opencv2/opencv.hpp> using namespace std; int main() { // 打开摄像头(默认摄像头编号0) cv::VideoCapture cap(0); if (!cap.isOpened()) { cerr << "Error: Could not open camera!" << endl; return -1; } cv::Mat frame; while (true) { cap >> frame; // 读取一帧 if (frame.empty()) { cerr << "Error: Empty frame!" << endl; break; } cv::imshow("Camera", frame); // 显示画面 char key = cv::waitKey(1); if (key == 'q' || key == 'Q') { break; // 按q键退出 } } cap.release(); cv::destroyAllWindows(); return 0; }

基础版本的特点

  • 优点:简单直接,易于理解
  • 缺点:所有操作都在一个线程中执行,如果添加复杂的图像处理,会导致画面卡顿

二、尝试使用线程

初学者可能会尝试将摄像头读取放入单独的线程

#include <iostream> #include <opencv2/opencv.hpp> #include <thread> using namespace std; void captureThread() { cv::VideoCapture cap(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera!" << std::endl; return; } cv::Mat frame; while (true) { cap >> frame; if (frame.empty()) break; cv::imshow("Camera", frame); // 错误:在子线程中显示 char key = cv::waitKey(1); if (key == 'q' || key == 'Q') break; } cap.release(); cv::destroyAllWindows(); } int main() { thread captureVideo(captureThread); captureVideo.join(); return 0; }

三、再进一步使用双线程实现

#include <iostream> #include <opencv2/opencv.hpp> #include <thread> #include <mutex> #include <atomic> using namespace std; // 共享数据 cv::Mat sharedFrame; mutex mtx; atomic<bool> running(true); // 线程1:负责捕获视频帧 void captureThread() { cv::VideoCapture cap(0); if (!cap.isOpened()) { cerr << "Error: Could not open camera!" << endl; running = false; return; } cv::Mat frame; while (running) { cap >> frame; if (frame.empty()) { cerr << "Error: Empty frame!" << endl; break; } // 使用互斥锁保护共享数据 lock_guard<mutex> lock(mtx); frame.copyTo(sharedFrame); } cap.release(); } // 线程2:负责处理和显示 void displayAndProcessThread() { cv::namedWindow("Camera", cv::WINDOW_AUTOSIZE); while (running) { cv::Mat frame; // 获取最新的帧 { lock_guard<mutex> lock(mtx); if (!sharedFrame.empty()) { sharedFrame.copyTo(frame); } } if (!frame.empty()) { // ===== 在这里添加你的图像处理代码 ===== // 示例1:添加文字 string text = "Hello OpenCV!"; cv::putText(frame, text, cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2); // 示例2:添加时间戳信息 cv::putText(frame, "Press 'q' to quit", cv::Point(10, frame.rows - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 1); // 显示处理后的画面 cv::imshow("Camera", frame); } // 检查退出条件 char key = cv::waitKey(30); if (key == 'q' || key == 'Q') { running = false; break; } } cv::destroyAllWindows(); } int main() { cout << "Program started. Press 'q' to quit." << endl; // 创建两个线程 thread capture(captureThread); thread display(displayAndProcessThread); // 等待线程结束 display.join(); capture.join(); cout << "Program terminated." << endl; return 0; }

四、代码解析

1. 线程同步机制

mutex mtx; // 互斥锁,防止数据竞争 atomic<bool> running; // 原子变量,控制线程结束
  • 互斥锁:确保同一时刻只有一个线程访问共享数据
  • 原子变量:安全地在多线程间传递状态信息

2. 线程分工

线程职责说明
captureThread捕获视频帧持续从摄像头读取,存入共享变量
displayAndProcessThread处理和显示获取帧,添加特效,显示画面

3. 关键代码说明

// 保护共享数据的访问 { lock_guard<mutex> lock(mtx); // 自动加锁解锁 frame.copyTo(sharedFrame); // 安全的拷贝 }

五、进阶:添加更多图像处理效果

你可以在显示线程中添加各种OpenCV特效:

// 在显示线程的处理部分添加 // 1. 转为灰度图 cv::Mat gray; cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY); // 2. 边缘检测 cv::Mat edges; cv::Canny(gray, edges, 50, 150); // 3. 人脸检测(需要haar cascade文件) // cv::CascadeClassifier faceCascade; // faceCascade.load("haarcascade_frontalface_default.xml"); // vector<cv::Rect> faces; // faceCascade.detectMultiScale(gray, faces); // 4. 添加帧率显示 static int frameCount = 0; static auto startTime = chrono::steady_clock::now(); frameCount++; auto currentTime = chrono::steady_clock::now(); auto elapsed = chrono::duration_cast<chrono::seconds>(currentTime - startTime).count(); if (elapsed >= 1) { double fps = frameCount / elapsed; cout << "FPS: " << fps << endl; frameCount = 0; startTime = currentTime; }

六、总结

单线程 vs 双线程对比

特性单线程双线程
实现复杂度简单中等
响应性极好
处理复杂任务会卡顿流畅
CPU利用率一般更好
代码可维护性简单良好

多线程编程要点

  1. 正确使用互斥锁保护共享数据
  2. 避免死锁:注意加锁顺序
  3. 使用原子变量控制线程状态
  4. 确保主线程等待子线程结束
  5. OpenCV的显示操作必须在主线程

改进点

有朋友说我这个会导致使用的那个线程空转,然后上面那个如果挂了,底下一直阻塞。的确是有这个问题,我的出发点是最简单的实现,既然这么说了,那就优化一下

#include <iostream> #include <opencv2/opencv.hpp> #include <thread> using namespace std; cv::Mat shareFrame; atomic<bool> running(true); mutex mtx; condition_variable condin_v; bool frameReady = false; void captureThread() { cv::VideoCapture cap(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera!" << std::endl; running = false; condin_v.notify_all(); return; } cv::Mat frame; cap.set(cv::CAP_PROP_FPS, 30); while (running) { cap >> frame; if (frame.empty()) { std::cerr << "Error: Empty frame!" << std::endl; break; } { lock_guard<mutex> lock(mtx); frame.copyTo(shareFrame); frameReady = true; } condin_v.notify_one(); this_thread::sleep_for(chrono::microseconds(33)); } cap.release(); condin_v.notify_all(); } void displayAndProcessThread() { cv::namedWindow("Camera", cv::WINDOW_AUTOSIZE); cv::Mat frame; while (running) { { unique_lock<mutex> lock(mtx); condin_v.wait(lock, [] {return frameReady || !running; }); if (!running) break; if (!shareFrame.empty()) { shareFrame.copyTo(frame); frameReady = false; } } if (!frame.empty()) { string text = "Hello Opencv!"; cv::putText(frame, text, cv::Point(50, 50), cv::FONT_HERSHEY_DUPLEX, 1.0, cv::Scalar(0, 255, 0), 2); cv::imshow("Camera", frame); } char key = cv::waitKey(30); if (key == 'Q' || key == 'q') { running = false; condin_v.notify_all(); break; } } cv::destroyAllWindows(); } int main() { thread captureVideo(captureThread); thread display(displayAndProcessThread); captureVideo.join(); display.join(); return 0; }
时间 → 生产者线程 | 消费者线程 --------------------|-------------------- 获取锁 | 生产帧 | (可能正在等待) frameReady = true | 释放锁 | notify_one() ------→ 被唤醒 | 尝试获取锁 | 获取锁成功 | 检查 frameReady = true | 消费帧 | frameReady = false | 释放锁 | 处理并显示帧
http://www.jsqmd.com/news/597568/

相关文章:

  • 效率倍增:将matlab算法思路在快马平台秒级转化为可运行web应用
  • Realtek 8922AE WiFi 7网卡驱动固件版本不匹配实战指南:从问题诊断到长效维护
  • 实战应用:基于快马平台构建红目香薰物联网数据监控与分析平台
  • Linux服务器部署ComfyUI与Flux:从环境配置到高效出图实战
  • Asian Beauty Z-Image Turbo 硬件需求详解:从消费级到专业级GPU配置
  • Padavan固件无外挂硬盘也能玩转opkg?手把手教你用tmpfs空间安装插件(附断电恢复技巧)
  • 高效解决Windows缩略图加载卡顿:一键智能预加载工具WinThumbsPreloader
  • 告别死记硬背:用GitHub笔记和实战思维重新理解电路与电子学
  • AMD GPU加速AI推理全流程:ROCm环境配置与Ollama性能调优实战
  • 数据结构之B树、B+树、B-树详解
  • 动态字体破解与智能反爬:大众点评数据采集系统的全方位解决方案
  • 快马平台一键生成:基于Python antigravity彩蛋的趣味演示原型
  • Xilinx Aurora 8B/10B IP核(5):GT资源规划实战——从PCB引脚到IP核Lane的映射法则
  • 老牌工具RIPS在2024年还能打吗?实测对比汉化版与官方版,附PHPStudy避坑指南
  • FlowState Lab实现JavaScript动态数据可视化:实时波动模拟前端实战
  • 产品经理必看!如何用时序图说清业务流程?附Draw.io操作指南
  • Pixel Aurora Engine效果展示:支持‘CRT荧光余晖’‘像素溢出’‘色阶压缩’高级滤镜
  • 赛马娘DMM版汉化优化终极指南:三分钟打造完美中文体验
  • WaveTools鸣潮工具箱技术解析:游戏效能突破的底层逻辑与实践路径
  • 雪花算法实战避坑指南:时钟回拨怎么办?数据中心ID如何分配?
  • NomNom终极指南:完全掌控《无人深空》存档编辑的免费神器
  • 保姆级教程:用wstunnel+WebSocket隧道,在家也能SSH连接公司内网电脑(含systemd服务配置)
  • SQL 入门 9:SQL 高级子查询:ANY、EXISTS 与多位置应用
  • Windows下PyTorch训练内存爆满?别急着加内存,试试升级PyTorch 1.13+这个隐藏优化
  • LingBot-Depth-ViT-L14效果展示:深度图导出为STL格式用于3D打印可行性验证
  • 如何3步完成QQ空间数据完整导出:GetQzonehistory终极备份指南
  • MinIO避坑指南:Docker部署常见问题与Java客户端最佳实践
  • 【KiCad实战】从设计到嘉立创下单:Gerber文件生成与检查全流程解析
  • 本地AI助手怎么选?DeepSeek-R1与ChatGLM轻量版对比评测实战
  • 从模拟信号到干净方波:用施密特触发器CD40106改造你的传感器信号(附Multisim仿真文件)