IE盒子

搜索
查看: 127|回复: 2

C++使用多线程优化opencv获取摄像头图像并实时显示 ...

[复制链接]

2

主题

8

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2023-1-7 17:48:58 | 显示全部楼层 |阅读模式
写在之前的     

在初学opencv的时候,实时显示摄像头的画面帧率往往只有十几帧(使用opencv,很多人估计没有用视频流格式),我网上查找了一些资料也发现了一些解决方法,但大都零零散散,所以为了其他人少走远路,我在这些基础上做出总结。完整的代码在文章最后面(我只是站在前辈们的肩膀上)。
opencv调用摄像头典型用法

以下代码为初学者最常用的,注意查看摄像头参数并设置符合电脑实际硬件要求,别把硬件搞坏了(win10自带的摄像机可以查看):


#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;

int width = 640;
int heigth = 480;
cv::VideoCapture cap;
int main()
{
        cap.open(0);
        cap.set(cv::CAP_PROP_FRAME_WIDTH, width);    //设置宽度
        cap.set(cv::CAP_PROP_FRAME_HEIGHT, heigth);  //设置长度
        cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));//视频流格式
        cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒

        if (!cap.isOpened())
        {
                cout << "打开失败" << endl;
                return 0;
        }
        cv::Mat frame;
        cv::namedWindow("frame");//创建显示窗口


        while (1)
        {
                cap >> frame;//bool judge=cap.read(frame);
                if (frame.empty()) break;
                cv::imshow("frame", frame);
                if (27 == cv::waitKey(33))//"ESC"
                        break;
        }
        cv::destroyWindow("frame");
        cap.release();
        return 0;
}
很明显,这是个单线程,cap先获取帧,然后再显示出来,其中waitKey(33)有3个作用:
1、触发GUI窗口"frame"显示图片;
2、进行时延33ms;
3、非阻塞读取键盘输入。
在这个基础上加上一点点计算帧率的代码(关于如何显示汉字,本文暂不介绍,请移步到文章结尾大佬文章连接),来直观地感受以下速度:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <windows.h>
#include <time.h>
using namespace std;

int width = 640;
int heigth = 480;
cv::VideoCapture cap;
int main()
{
        cap.open(0);
        cap.set(cv::CAP_PROP_FRAME_WIDTH, width);    //设置宽度
        cap.set(cv::CAP_PROP_FRAME_HEIGHT, heigth);  //设置长度
        cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));//视频流格式
        cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒

        if (!cap.isOpened())
        {
                cout << "打开失败" << endl;
                return 0;
        }
        cv::Mat frame;
        cv::namedWindow("frame");//创建显示窗口
//************************************************************************
        //设置字体格式
        //cv::putText()
        int font_face = cv::FONT_HERSHEY_COMPLEX;
        double font_scale = 1;
        int thickness = 2;
        int baseline;
        cv::Point origin;
        origin.x = 10;
        origin.y = 30;

        //用于记录时间
        int FPS = 0;
        double time = 0;
        LARGE_INTEGER nFreq;
        LARGE_INTEGER nBeginTime;
        LARGE_INTEGER nEndTime;
        QueryPerformanceFrequency(&nFreq);
//************************************************************************
        while (1)
        {
                QueryPerformanceCounter(&nBeginTime);//开始计时

                cap >> frame;//bool judge=cap.read(frame);
                if (frame.empty()) break;

                string s = "FPS:" + to_string(FPS);
                cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
                cv::imshow("frame", frame);
                if (27 == cv::waitKey(33)) break;

                QueryPerformanceCounter(&nEndTime);//停止计时  
                time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
                FPS = 1 / time;
        }

        cv::destroyWindow("frame");
        cap.release();

        return 0;
}

附带一张运行结果图片:


实际运行速度根本没有30帧,只会在20帧左右跳动,而且能感受到一卡一卡的。如果在这个过程中加上一点人脸识别等操作,能卡到只有每秒5帧(一个人脸),甚至1帧(多个人脸)(别问我为啥知道,因为就是知道);
用双线程来优化实时显示

一、在上面的代码中,可以很清楚地关键步骤就两个:
1、cap从摄像头数据流中获取帧;
2、imshow()将图片显示出来;
中间的纽带只有cv::Mat frame。我们可以将这两个步骤独立起来,用两个进程来执行,进程的协作则通过容器std::queue<cv::Mat> frames来进行,从而将程序运行的时延控制在容器的读取上面,稳定帧率。
有了以上思想,我们进行如下准备,初始化全局变量后:
#include <iostream>
#include <opencv2/opencv.hpp>
#include<thread>
#include <conio.h>
#include <windows.h>
#include <time.h>
using namespace std;

mutex g_mutex;//进程锁
int width = 640;
int heigth = 480;
int key = 0;//进行简单进程控制
queue<cv::Mat> frames;//先进先出队列二、我们先编写第一个函数 getframe(),我们要求它有如下功能:
1、连续获取视频流帧图像,将其存放在队列frames当中;
2、受主进程控制,能主动退出,并释放摄像头资源,我们通过主进程修改变量key值来控制该进程;
在之前的代码上稍作改动,实现代码如下:
void getframe()
{
        cout << "正在打开摄像头" << endl;
        cv::VideoCapture cap;
        cap.open(0);
        cap.set(cv::CAP_PROP_FRAME_WIDTH, width);    //设置宽度
        cap.set(cv::CAP_PROP_FRAME_HEIGHT, heigth);  //设置长度
        cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));//视频流格式
        cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
        if (!cap.isOpened())
        {
                cout << "打开失败" << endl;
                return;
        }//意思意思,以防万一

        cout << "打开成功" << endl;

        cv::Mat frame2;
        while (key != 27)
        {
                cap >> frame2;//bool judge=cap.read(frame2);//先别管帧有没有读取成功
                g_mutex.lock();//修改公共资源,用进程锁锁定
                frames.push(frame2);
                g_mutex.unlock();

        }//被主进程通知结束循环
        cout << "正在解除摄像头占用" << endl;
        cap.release();
        cout << "成功解除摄像头占用" << endl;
}三、编写第二个函数showframe(),期望功能如下:
1、从frames获取图像帧,并显示出来;
2、受主进程控制,能主动退出,控制方式同上;
在之前代码上稍作修改,如下:
void showframe()
{
        //设置字体格式
        //cv::putText()
        int font_face = cv::FONT_HERSHEY_COMPLEX;
        double font_scale = 1;
        int thickness = 2;
        int baseline;
        cv::Point origin;
        origin.x = 10;
        origin.y = 30;

        //用于记录时间
        int FPS = 0;
        double time = 0;
        LARGE_INTEGER nFreq;
        LARGE_INTEGER nBeginTime;
        LARGE_INTEGER nEndTime;
        QueryPerformanceFrequency(&nFreq);

        cv::Mat frame;//人脸图像
        cv::namedWindow("frame");//创建显示窗口
        while (key != 27)
        {
                QueryPerformanceCounter(&nBeginTime);//开始计时  
                if (!frames.empty())//队列里面有帧
                {
                        g_mutex.lock();
                        frame = frames.front();
                        frames.pop();
                        g_mutex.unlock();

                        string s = "FPS:" + to_string(FPS);
                        cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
                        cv::imshow("frame", frame);
                }
                cv::waitKey(33);
                QueryPerformanceCounter(&nEndTime);//停止计时  
                time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
                FPS = 1 / time;
        }
        cv::destroyWindow("frame");
}三、写完两个关键步骤后,在主进程中,我们将这两个函数分别添加到两个子进程中去:
int main()
{
        thread t1(getframe);
        t1.detach();
        thread t2(showframe);
        t2.detach();

        while (1)
        {
                Sleep(500);
                if (_kbhit())
                        break;
        };
        key = 27;//通知其它进程结束
        Sleep(1000);//等待进程结束
        return 0;
}t1和t2两个独立于主进程之外进行,主进程通过Sleep(500)函数来定时检查用户有没有按下退出键;
按下退出键后,主进程要通知t1和t2释放其占有的资源,即:
1、cap.release();
2、cv::destroyWindow("frame");
这两个步骤至关总要,容器frames有其独立的析构函数,不用考虑。(有人肯定觉得多此一举,主进程结束,t1和t2的资源都会被释放。但是我就是倔);
完整的代码如下:

#include <iostream>
#include <opencv2/opencv.hpp>
#include<thread>
#include <conio.h>
#include <windows.h>
#include <time.h>
using namespace std;

std::mutex g_mutex;
int width = 640;
int heigth = 480;
int key = 0;//进行简单进程控制
std::queue<cv::Mat> frames;//先进先出队列

void getframe()
{
        cout << "正在打开摄像头" << endl;
        cv::VideoCapture cap;
        cap.open(0);
        cap.set(cv::CAP_PROP_FRAME_WIDTH, width);    //设置宽度
        cap.set(cv::CAP_PROP_FRAME_HEIGHT, heigth);  //设置长度
        cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'));//视频流格式
        cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
        if (!cap.isOpened())
        {
                cout << "打开失败" << endl;
                return;
        }//意思意思,以防万一

        cout << "打开成功" << endl;

        cv::Mat frame2;
        while (key != 27)
        {
                cap >> frame2;//bool judge=cap.read(frame2);//先别管帧有没有读取成功
                g_mutex.lock();//修改公共资源,用进程锁锁定
                frames.push(frame2);
                g_mutex.unlock();

        }//被主进程通知结束循环

        cout << "正在解除摄像头占用" << endl;
        cap.release();
        cout << "成功解除摄像头占用" << endl;
}

void showframe()
{
        //设置字体格式
        //cv::putText()
        int font_face = cv::FONT_HERSHEY_COMPLEX;
        double font_scale = 1;
        int thickness = 2;
        int baseline;
        cv::Point origin;
        origin.x = 10;
        origin.y = 30;

        //用于记录时间
        int FPS = 0;
        double time = 0;
        LARGE_INTEGER nFreq;
        LARGE_INTEGER nBeginTime;
        LARGE_INTEGER nEndTime;
        QueryPerformanceFrequency(&nFreq);


        cv::Mat frame;//人脸图像
        cv::namedWindow("frame");//创建显示窗口
        while (key != 27)
        {
                QueryPerformanceCounter(&nBeginTime);//开始计时  

                if (!frames.empty())//队列里面有帧
                {
                        g_mutex.lock();
                        frame = frames.front();
                        frames.pop();
                        g_mutex.unlock();

                        string s = "FPS:" + to_string(FPS);
                        cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
                        cv::imshow("frame", frame);
                }
                cv::waitKey(33);
                QueryPerformanceCounter(&nEndTime);//停止计时  
                time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
                FPS = 1 / time;

        }
        cv::destroyWindow("frame");
}

int main()
{
        thread t1(getframe);
        t1.detach();
        thread t2(showframe);
        t2.detach();

        while (1)
        {
                Sleep(500);
                if (_kbhit())
                        break;
        };
        key = 27;//通知其它进程结束
        Sleep(1000);//等待进程结束
        return 0;
}


稳定在29帧到30帧,比之前流畅了很多。文章的最后附上大神的连接:
1、opencv相关:
OpenCV的简单使用教程与基本函数(C++版本)_Great Bruce Young的博客-CSDN博客_c++中opencv
C++ OpenCV【解决putText不能显示中文】_IT.Husky的博客-CSDN博客_c++ opencv 显示中文
2、c++多线程操作:
c++11 Thread库写多线程程序 - 会飞的斧头 - 博客园 (cnblogs.com)
3、容器使用
C++ queue(STL queue)用法详解 (biancheng.net)
回复

使用道具 举报

3

主题

11

帖子

20

积分

新手上路

Rank: 1

积分
20
发表于 2025-5-18 10:34:06 | 显示全部楼层
顶顶更健康
回复

使用道具 举报

3

主题

11

帖子

22

积分

新手上路

Rank: 1

积分
22
发表于 2025-6-11 18:07:34 | 显示全部楼层
沙发位出租,有意请联系电话:13838384381
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表