|
写在之前的
在初学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(&#39;M&#39;, &#39;J&#39;, &#39;P&#39;, &#39;G&#39;));//视频流格式
cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
if (!cap.isOpened())
{
cout << &#34;打开失败&#34; << endl;
return 0;
}
cv::Mat frame;
cv::namedWindow(&#34;frame&#34;);//创建显示窗口
while (1)
{
cap >> frame;//bool judge=cap.read(frame);
if (frame.empty()) break;
cv::imshow(&#34;frame&#34;, frame);
if (27 == cv::waitKey(33))//&#34;ESC&#34;
break;
}
cv::destroyWindow(&#34;frame&#34;);
cap.release();
return 0;
}
很明显,这是个单线程,cap先获取帧,然后再显示出来,其中waitKey(33)有3个作用:
1、触发GUI窗口&#34;frame&#34;显示图片;
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(&#39;M&#39;, &#39;J&#39;, &#39;P&#39;, &#39;G&#39;));//视频流格式
cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
if (!cap.isOpened())
{
cout << &#34;打开失败&#34; << endl;
return 0;
}
cv::Mat frame;
cv::namedWindow(&#34;frame&#34;);//创建显示窗口
//************************************************************************
//设置字体格式
//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 = &#34;FPS:&#34; + to_string(FPS);
cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
cv::imshow(&#34;frame&#34;, frame);
if (27 == cv::waitKey(33)) break;
QueryPerformanceCounter(&nEndTime);//停止计时
time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
FPS = 1 / time;
}
cv::destroyWindow(&#34;frame&#34;);
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 << &#34;正在打开摄像头&#34; << 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(&#39;M&#39;, &#39;J&#39;, &#39;P&#39;, &#39;G&#39;));//视频流格式
cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
if (!cap.isOpened())
{
cout << &#34;打开失败&#34; << endl;
return;
}//意思意思,以防万一
cout << &#34;打开成功&#34; << endl;
cv::Mat frame2;
while (key != 27)
{
cap >> frame2;//bool judge=cap.read(frame2);//先别管帧有没有读取成功
g_mutex.lock();//修改公共资源,用进程锁锁定
frames.push(frame2);
g_mutex.unlock();
}//被主进程通知结束循环
cout << &#34;正在解除摄像头占用&#34; << endl;
cap.release();
cout << &#34;成功解除摄像头占用&#34; << 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(&#34;frame&#34;);//创建显示窗口
while (key != 27)
{
QueryPerformanceCounter(&nBeginTime);//开始计时
if (!frames.empty())//队列里面有帧
{
g_mutex.lock();
frame = frames.front();
frames.pop();
g_mutex.unlock();
string s = &#34;FPS:&#34; + to_string(FPS);
cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
cv::imshow(&#34;frame&#34;, frame);
}
cv::waitKey(33);
QueryPerformanceCounter(&nEndTime);//停止计时
time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
FPS = 1 / time;
}
cv::destroyWindow(&#34;frame&#34;);
}三、写完两个关键步骤后,在主进程中,我们将这两个函数分别添加到两个子进程中去:
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(&#34;frame&#34;);
这两个步骤至关总要,容器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 << &#34;正在打开摄像头&#34; << 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(&#39;M&#39;, &#39;J&#39;, &#39;P&#39;, &#39;G&#39;));//视频流格式
cap.set(cv::CAP_PROP_FPS, 30);//帧率 帧/秒
if (!cap.isOpened())
{
cout << &#34;打开失败&#34; << endl;
return;
}//意思意思,以防万一
cout << &#34;打开成功&#34; << endl;
cv::Mat frame2;
while (key != 27)
{
cap >> frame2;//bool judge=cap.read(frame2);//先别管帧有没有读取成功
g_mutex.lock();//修改公共资源,用进程锁锁定
frames.push(frame2);
g_mutex.unlock();
}//被主进程通知结束循环
cout << &#34;正在解除摄像头占用&#34; << endl;
cap.release();
cout << &#34;成功解除摄像头占用&#34; << 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(&#34;frame&#34;);//创建显示窗口
while (key != 27)
{
QueryPerformanceCounter(&nBeginTime);//开始计时
if (!frames.empty())//队列里面有帧
{
g_mutex.lock();
frame = frames.front();
frames.pop();
g_mutex.unlock();
string s = &#34;FPS:&#34; + to_string(FPS);
cv::putText(frame, s.c_str(), origin, font_face, font_scale, cv::Scalar(0, 255, 255), thickness, 8, 0);
cv::imshow(&#34;frame&#34;, frame);
}
cv::waitKey(33);
QueryPerformanceCounter(&nEndTime);//停止计时
time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;
FPS = 1 / time;
}
cv::destroyWindow(&#34;frame&#34;);
}
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) |
|