You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程调用cv::imshow展示画面,运行一段时间后出现挂起

多线程调用OpenCV imshow导致窗口挂起的解决方案

问题场景

在Jetson平台上,通过两个独立线程分别调用cv::imshow("Live")和cv::imshow("ROI"),其中Live窗口展示相机捕获的实时画面,ROI窗口展示对焦用的感兴趣区域。运行一段时间后,两个窗口均出现挂起无响应现象。

问题根源

  1. HighGUI非线程安全:OpenCV的HighGUI模块(包含imshow、waitKey、namedWindow等函数)并非线程安全,多线程同时操作GUI会打乱事件循环,引发窗口死锁或挂起。
  2. 共享帧数据竞争:主线程持续更新frame对象,ROI线程直接引用该对象创建roiFrame,二者对frame的读写无同步机制,导致内存访问冲突,触发未定义行为。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 03:47:04