多线程调用cv::imshow展示画面,运行一段时间后出现挂起
多线程调用OpenCV imshow导致窗口挂起的解决方案
问题场景
在Jetson平台上,通过两个独立线程分别调用cv::imshow("Live")和cv::imshow("ROI"),其中Live窗口展示相机捕获的实时画面,ROI窗口展示对焦用的感兴趣区域。运行一段时间后,两个窗口均出现挂起无响应现象。
问题根源
- HighGUI非线程安全:OpenCV的HighGUI模块(包含
imshow、waitKey、namedWindow等函数)并非线程安全,多线程同时操作GUI会打乱事件循环,引发窗口死锁或挂起。 - 共享帧数据竞争:主线程持续更新
frame对象,ROI线程直接引用该对象创建roiFrame,二者对frame的读写无同步机制,导致内存访问冲突,触发未定义行为。 - ROI帧未实时更新:原代码中
roiFrame = frame(roi)仅执行一次,后续循环始终显示初始ROI区域,未随frame更新同步刷新,同时加剧了线程安全问题。
修复方案
核心原则
所有HighGUI操作统一放在单个线程中执行,通过线程安全的队列传递帧数据,隔离相机捕获线程与GUI显示线程的耦合。
修复后代码
main.cpp
#include <opencv2/core.hpp> #include <opencv2/videoio.hpp> #include <opencv2/highgui.hpp> #include <iostream> #include <thread> #include <queue> #include <mutex> #include <condition_variable> #include <atomic> using namespace std::chrono_literals; // 线程安全的帧队列,仅保留最新帧避免积压 class FrameQueue { public: void push(cv::Mat frame) { std::lock_guard<std::mutex> lock(mtx); while (!queue.empty()) { queue.pop(); } queue.push(frame.clone()); // 克隆帧确保数据独立 } bool pop(cv::Mat& frame) { std::lock_guard<std::mutex> lock(mtx); if (queue.empty()) { return false; } frame = queue.front(); queue.pop(); return true; } private: std::queue<cv::Mat> queue; std::mutex mtx; }; FrameQueue frameQueue; std::atomic<bool> isRunning{true}; // 原子变量控制线程退出 // 统一GUI处理线程:所有窗口显示和事件处理在此执行 void guiThread() { cv::namedWindow("Live", cv::WINDOW_NORMAL); cv::namedWindow("ROI", cv::WINDOW_NORMAL); const cv::Rect roi(100, 100, 200, 200); // 固定ROI区域 while (isRunning) { cv::Mat frame; if (frameQueue.pop(frame)) { cv::imshow("Live", frame); cv::Mat roiFrame = frame(roi); cv::imshow("ROI", roiFrame); } // 必须在GUI线程中调用waitKey处理事件 if (cv::waitKey(5) >= 0) { isRunning = false; break; } } cv::destroyAllWindows(); } int main(int, char**) { cv::Mat frame; Camera cap; if (!cap.isOpened()) { std::cerr << "Failed to open camera\n"; return 1; } std::thread guiWorker(guiThread); std::cout << "Start grabbing" << std::endl << "Press any key to terminate" << std::endl; while (isRunning) { bool success = cap.getFrame(frame); if (frame.empty()) { std::cerr << "ERROR! blank frame grabbed\n"; break; } frameQueue.push(frame); } isRunning = false; guiWorker.join(); return 0; }
JetsonCamera.cpp(无修改)
#include "JetsonCamera.hpp" #include <iostream> Camera::Camera() : cap_( "nvarguscamerasrc sensor-id=0 ! " "video/x-raw(memory:NVMM),width=(int)4656,height=(int)3496,format=(string)NV12,framerate=(fraction)9/1 ! " "nvvidconv flip-method=0 ! " "video/x-raw,width=(int)1280,height=(int)1280,format=(string)BGRx ! " "videoconvert ! " "video/x-raw,format=(string)BGR ! " "appsink", cv::CAP_GSTREAMER ) {} bool Camera::isOpened() { return cap_.isOpened(); } bool Camera::getFrame(cv::Mat& frame) { return cap_.read(frame); }
JetsonCamera.hpp(无修改)
#ifndef CAMERA_HPP #define CAMERA_HPP #include <opencv2/core.hpp> #include <opencv2/videoio.hpp> class Camera { public: Camera(); bool isOpened(); bool getFrame(cv::Mat& frame); private: cv::VideoCapture cap_; }; #endif /* CAMERA_HPP */
关键改进点
- 单线程GUI处理:所有
namedWindow、imshow、waitKey操作集中在guiThread,避免多线程操作GUI的冲突。 - 线程安全队列:使用互斥锁保护帧队列,确保帧数据的读写安全,同时仅保留最新帧避免队列积压。
- 帧克隆:通过
frame.clone()创建独立的帧副本,避免原数据被主线程覆盖导致的显示异常。 - 原子变量控制退出:使用
std::atomic<bool>确保线程间退出信号的安全传递。
内容的提问来源于stack exchange,提问作者Jason Rich Darmawan
相关产品推荐
相关产品推荐

