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

前言

多年前刚刚接触Opencv那会还没有AI,第一次处理视频的时候,仅仅通过usb摄像头显示都还可以,但是通过rtsp等网络方式的方法接入,在显示图像的过程再处理点什么,那简直是卡,
通过网上搜索,建议使用多线程处理,然后一堆代码,终于从里面理清了,最近也有同事遇到同样的问题,我说让看代码,他说里面掺杂了太多业务,不太好看明白,所以才有了这篇文章:从最简单的方法实现多线程处理视频流,不参与任何业务。
一个负责获取视频帧,一个负责处理,使用最简单的实践达到目的。同理的思想用于在一个系统之上,我开发了一个QT快速开发系统,也是使用最简单,不掺杂任何业务的实现,避免其他人为了吃顿饭,还要买个锅碗瓢盆
QT快速开发框架

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

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

基础版本代码

#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的显示操作必须在主线程

需要一份无污染的代码,不包含任何业务
QT快速开发框架

posted @ 2026-03-19 10:08  Tlink  阅读(56)  评论(0)    收藏  举报