OpenCV视频流C++多线程处理方法详细分析

目录
  • 为什么需要多线程处理视频流
  • C++的多线程处理方式
  • 函数封装的实现方式
  • 类封装的实现方式
  • 可能遇到的问题

为什么需要多线程处理视频流

在之前有写过一篇文章Python环境下OpenCV视频流的多线程处理方式,上面简单记录了如何使用Python实现对OpenCV视频流的多线程处理。简单来说,在目标检测等任务中,如果视频流的捕获、解码以及检测都在同一个线程中,那么很可能出现目标检测器实时性不高导致的检测时延问题。使用多线程处理,将视频帧的捕获和解码放在一个线程,推理放在一个线程,可以有效缓解时延的问题,使得目标检测的实时性看似有所提升。

C++的多线程处理方式

C++的处理方式与Python大致相同,但却可能遇到一些问题,如使用OpneCV多线程时X11库报错、OpenCV显示卡死等问题,这些问题可能的解决方法会在后面简单提一下。在本文中,使用的多线程是c++11中引入的thread标准库,实现方式则包括函数封装和类封装两种。

函数封装的实现方式

函数封装的实现方式相比类封装要更为简洁,当然可复用性也会降低。简单的示例代码如下:

// video_test.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
static std::mutex mutex;
static std::atomic_bool isOpen;
static void cameraThreadFunc(int camId, int height, int width, cv::Mat* pFrame)
{
    cv::VideoCapture capture(camId);
    capture.set(cv::CAP_PROP_FOURCC, CV_FOURCC('M', 'J', 'P', 'G'));
    capture.set(cv::CAP_PROP_FRAME_WIDTH, width);
    capture.set(cv::CAP_PROP_FRAME_HEIGHT, height);
    capture.set(cv::CAP_PROP_FPS, 30);
    if (!capture.isOpened()) {
        isOpen = false;
        std::cout << "Failed to open camera with index " << camId << std::endl;
    }
    cv::Mat frame;
    while (isOpen) {
        capture >> frame;
        if (mutex.try_lock()) {
            frame.copyTo(*pFrame);
            mutex.unlock();
        }
        cv::waitKey(5);
    }
    capture.release();
}
int main(int argc, char* argv[])
{
    isOpen = true;
    cv::Mat frame(480, 640, CV_8UC3), gray;
    std::thread thread(cameraThreadFunc, 0, 480, 640, &frame);
    while (isOpen) {
        mutex.lock();
        frame.copyTo(gray);
        mutex.unlock();
        if (gray.empty()) {
            break;
        }
        cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
        cv::blur(gray, gray, cv::Size(3, 3));
        cv::Canny(gray, gray, 5 , 38 , 3);
        cv::waitKey(100);
        cv::imshow("video", gray);
        if (cv::waitKey(1) == 'q') {
            break;
        }
    }
    isOpen = false;
    thread.join();
    return 0;
}

在上面的代码中,摄像头的打开、帧捕获及解码都在cameraThreadFunc线程函数中进行。在c++11中,有关pthread的线程操作都封装在thread标准库中,线程的开启方式也由执行pthread_create()函数变为对thread类的操作。使用thread类时,第一个参数为线程函数的指针,后续的参数为传入线程函数的参数。需要注意的是:如果要传入参数引用,则需要使用std::ref()对参数进行包装;如果传入类成员函数时,则thread类构造函数的第二个参数必须为this

使用多线程时还需要考虑线程之间的同步问题,在上面的程序中,两个线程会同时访问pFrame指向的缓存空间,使用mutex可确保同一时刻下仅有一个线程能访问到缓存空间。另外,使用atomic_bool在多线程中进行状态切换也是必要的,原子操作使得对布尔变量的赋值在临界区中进行,可消除线程之间竞争访问或访问结果不一致的情况。

在上面的程序中,由于主线程会先访问pFrame变量,因此需要预先为pFrame申请空间,不然程序开始执行时出现pFrame为空的情况。在Ubuntu中使用g++编译的方法如下:

g++ video_test.cpp -std=c++11 -I/usr/local/include/ -lpthread -L/usr/local/lib -lopencv_highgui -lopencv_core -lopencv_imgproc -lopencv_videoio -o video_test

根据OpenCV版本和安装位置的不同,需要相应修改头文件和库文件的位置,例如对于OpenCV4,头文件目录应修改为/usr/local/include/opencv4。在Jetson平台上,头文件的位置在/usr/include/opencv4,库文件则在/usr/lib/aarch64-linux-gnu。如果有配置pkg-config,那么还可以使用如下方式进行编译:

g++ video_test.cpp -std=c++11 `pkg-config --cflags opencv` -pthread `pkg-config --libs opencv` -o video_test

类封装的实现方式

同函数封装的方式相似,类封装的方式仅是将线程函数和线程同步变量变为类成员,从而提升程序的可复用性。简单的示例代码如下:

// video_test.cpp
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <atomic>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class VideoCaptureMT {
public:
	VideoCaptureMT(int index, int height=480, int width=640);
	VideoCaptureMT(std::string filePath, int height=480, int width=640);
	~VideoCaptureMT();
	bool isOpened() {
		return m_IsOpen;
	}
	void release() {
		m_IsOpen = false;
	}
	bool read(cv::Mat& frame);
private:
	void captureInit(int index, std::string filePath, int height, int width);
	void captureFrame();
	cv::VideoCapture* m_pCapture;
	cv::Mat* m_pFrame;
	std::mutex* m_pMutex;
	std::thread* m_pThread;
	std::atomic_bool m_IsOpen;
};
VideoCaptureMT::VideoCaptureMT(int index, int height, int width)
{
	captureInit(index, std::string(), height, width);
}
VideoCaptureMT::VideoCaptureMT(std::string filePath, int height, int width)
{
	captureInit(0, filePath, height, width);
}
VideoCaptureMT::~VideoCaptureMT()
{
	m_IsOpen = false;
	m_pThread->join();
	if (m_pCapture->isOpened()) {
		m_pCapture->release();
	}
	delete m_pThread;
	delete m_pMutex;
	delete m_pCapture;
	delete m_pFrame;
}
void VideoCaptureMT::captureInit(int index, std::string filePath, int height, int width)
{
	if (!filePath.empty()) {
		m_pCapture = new cv::VideoCapture(filePath);
	}
	else {
		m_pCapture = new cv::VideoCapture(index);
	}
	m_pCapture->set(cv::CAP_PROP_FRAME_WIDTH, width);
	m_pCapture->set(cv::CAP_PROP_FRAME_HEIGHT, height);
	m_pCapture->set(cv::CAP_PROP_FPS, 30);
	m_IsOpen = true;
	m_pFrame = new cv::Mat(height, width, CV_8UC3);
	m_pMutex = new std::mutex();
	m_pThread = new std::thread(&VideoCaptureMT::captureFrame, this);
}
void VideoCaptureMT::captureFrame()
{
	cv::Mat frameBuff;
	while (m_IsOpen) {
		(*m_pCapture) >> frameBuff;
		if (m_pMutex->try_lock()) {
			frameBuff.copyTo(*m_pFrame);
			m_pMutex->unlock();
		}
		cv::waitKey(5);
	}
}
bool VideoCaptureMT::read(cv::Mat& frame)
{
	if (m_pFrame->empty()) {
		m_IsOpen = false;
	}
	else {
		m_pMutex->lock();
		m_pFrame->copyTo(frame);
		m_pMutex->unlock();
	}
	return m_IsOpen;
}
int main(int argc, char* argv[])
{
	VideoCaptureMT capture(0);
	cv::Mat frame, gray;
	while (capture.isOpened()) {
		if (!capture.read(frame)) {
			break;
		}
		cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
		cv::blur(gray, gray, cv::Size(3, 3));
		cv::Canny(gray, gray, 5 , 38 , 3);
		cv::waitKey(100);
		cv::imshow("image", gray);
        if (cv::waitKey(5) == 'q') {
			break;
		}
	}
	capture.release();
	return 0;
}

在上面的代码中,线程函数和线程间同步变量都是类成员,不同的地方在于:摄像头是在主线程中打开,在子线程中捕获和解码帧,但实际效果和函数封装的方式没有区别。

可能遇到的问题

使用C++编写OpenCV的多线程程序时可能会遇到一些问题,例如我在Jetson AGX上运行时会报错,提示需要进行XInitThreads的初始化。出现这样的情况时,需要在cpp文件中添加#include <X11/Xlib.h>头文件,并在main函数开头添加XInitThreads()函数调用,在编译时还需要添加-lX11链接库。我在Jetson Nano上运行时还遇到显示窗口卡死的情况,既imshow函数出现问题,点击关闭窗户后又会重新打开新窗口正常显示。遇到这样的情况,可在main函数开头添加一行代码cv::setNumThreads(1),设置OpenCV在单线程的模式下运行可缓解窗口卡死的情况。

到此这篇关于OpenCV视频流C++多线程处理方法详细分析的文章就介绍到这了,更多相关OpenCV视频流内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • OpenCV视频流Python多线程处理方法详细分析

    目录 前言 Python多线程编程 OpenCV视屏流的多线程处理 结语 前言 最近在功能性测试的过程中,需要在Python环境下用OpenCV读取网络摄像头的视频流,接着用目标检测器进行视屏帧的后续处理.在测试过程中发现如果是单线程的情况,会出现比较严重的时延,如果目标检测模型稍微大一点,像YOLOv4这类的,那么情况更加严重. 后面考虑到演示效果,从单线程改为了多线程,即单独用一个线程实时捕获视频帧,主线程在需要时从子线程拷贝最近的帧使用即可.通过这样的修改,不仅时延基本消失,整个流程的实时

  • OpenCV视频流C++多线程处理方法详细分析

    目录 为什么需要多线程处理视频流 C++的多线程处理方式 函数封装的实现方式 类封装的实现方式 可能遇到的问题 为什么需要多线程处理视频流 在之前有写过一篇文章Python环境下OpenCV视频流的多线程处理方式,上面简单记录了如何使用Python实现对OpenCV视频流的多线程处理.简单来说,在目标检测等任务中,如果视频流的捕获.解码以及检测都在同一个线程中,那么很可能出现目标检测器实时性不高导致的检测时延问题.使用多线程处理,将视频帧的捕获和解码放在一个线程,推理放在一个线程,可以有效缓解时

  • Javascript访问html页面的控件的方法详细分析第1/2页

    下面切入正题:    访问控件的主要对象是:document对象.分别对应当前文档所有的(子对象)个人观点.并且已经提供的几个主要方法来访问对象. 1.       document.getElementById 2.       document.getElementsByName  3           document.getElementsByTagName 4           document.all 下面我主要谈谈以上几个方法的具体用法:   一.首先我来谈谈document.

  • Jquery getJSON方法详细分析

    准备工作·Customer类 复制代码 代码如下: public class Customer{    public int Unid { get; set; }    public string CustomerName { get; set; }    public string Memo { get; set; }    public string Other { get; set; }} ·服务端处理(Json_1.ashx) 复制代码 代码如下: Customer customer =

  • C++双线程调用网络摄像头与多线程调用多摄像头同步执行方法详细讲解

    目录 一.使用双线程调用网络摄像头并执行算法 方法一 方法二 二.使用多线程调用多路摄像头并同步执行多个算法 在调用网络摄像头处理自己的算法时,当解码的速度与算法运行的速度差太多时,会出现类似下面的错误 error while decoding MB 148 4, bytestream 所以需要使用两个线程,一个线程调用摄像头,一个线程用来处理图像. 一.使用双线程调用网络摄像头并执行算法 方法一 #include <iostream> #include <thread> #inc

  • 详细分析mysql视图的原理及使用方法

    前言: 在MySQL中,视图可能是我们最常用的数据库对象之一了.那么你知道视图和表的区别吗?你知道创建及使用视图要注意哪些点吗?可能很多人对视图只是一知半解,想详细了解视图的同学看过来哟,本篇文章会详细介绍视图的概念.创建及使用方法. 1.视图定义及简单介绍 视图是基于 SQL 语句的结果集的可视化的表,即视图是一个虚拟存在的表,可以包含表的全部或者部分记录,也可以由一个表或者多个表来创建.使用视图就可以不用看到数据表中的所有数据,而是只想得到所需的数据.当我们创建一个视图的时候,实际上是在数据

  • C语言详细分析讲解struct与union使用方法

    目录 一.struct 的小秘密 二.结构体与柔性数组 三.C语言中的 union 四.小结 一.struct 的小秘密 C语言中的 struct 可以看作变量的集合 struct 的问题:空结构体占用多大内存?下面编写程序看一下吧: #include <stdio.h> struct TS { }; int main() { struct TS t1; struct TS t2; printf("sizeof(struct TS) = %d\n", sizeof(stru

  • Java详细分析String类与StringBuffer和StringBuilder的使用方法

    目录 String类基本概念 String字符串的存储原理 String类的常用构造方法 String类中常用方法 StringBuffer类 StringBuilder类 String类基本概念 String类属于引用数据类型,不属于基本数据类型. 在Java中只要是" "(双引号)中的,都是String对象. java中规定,双引号中的字符串是不可变的,也就是说"abc"自出生到死亡都不可能变成"abcd",也不能变成"ab&quo

  • Java详细分析sleep和wait方法有哪些区别

    目录 一.sleep和wait方法的区别 二.wait方法 wait方法的使用 wait结束等待的条件 三.notify和notifyAll方法 一.sleep和wait方法的区别 根本区别:sleep是Thread类中的方法,不会马上进入运行状态,wait是Object类中的方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程 释放同步锁:sleep会释放cpu,但是sleep不会释放同步锁的资源,wait会释放同步锁资源 使用范围: slee

  • Java详细分析Lambda表达式与Stream流的使用方法

    目录 Lambda Stream流 Lambda Lambda 表达式是一个匿名函数,我们可以把 lambda 表达式理解为一段可以传递的代码(将代码段像数据一样传递).使用它可以写出更简洁, 更灵活的代码.作为一种更紧凑的代码风格,使 java 语言的表达式能力得到的提升. 我们可以知道, Lambda表达式是为简化语法而存在的 ArrayList<String> list = new ArrayList<>(); list.add("a"); list.ad

随机推荐