OpenCV2学习笔记之视频流读取与处理

目录

  • 前言
  • 一. 读取视频序列
  • 二. 处理视频帧
  • OpenCV:打开摄像头获取视频流
  • 总结

前言 由于项目需要,计划实现九路视频拼接,因此必须熟悉OpenCV对视频序列的处理。视频信号处理是图像处理的一个延伸,所谓的视频序列是由按一定顺序进行排放的图像组成,即帧(Frame)。在这里,主要记录下如何使用Qt+OpenCV读取视频中的每一帧,之后,在这基础上将一些图像处理的算法运用到每一帧上(如使用Canny算子检测视频中的边缘)。

一. 读取视频序列 OpenCV提供了一个简便易用的框架以提取视频文件和USB摄像头中的图像帧,如果只是单单想读取某个视频,你只需要创建一个cv::VideoCapture实例,然后在循环中提取每一帧。新建一个Qt控制台项目,直接在main函数添加:
#include #include #include #include #include int main(int argc, char *argv[]){QCoreApplication a(argc, argv); // 读取视频流cv::VideoCapture capture("e:/BrokeGirls.mkv"); // 检测视频是否读取成功if (!capture.isOpened()){qDebug() << "No Input Image"; return 1; }// 获取图像帧率double rate= capture.get(CV_CAP_PROP_FPS); bool stop(false); cv::Mat frame; // 当前视频帧cv::namedWindow("Extracted Frame"); // 每一帧之间的延迟int delay= 1000/rate; // 遍历每一帧while (!stop){// 尝试读取下一帧if (!capture.read(frame))break; cv::imshow("Extracted Frame",frame); // 引入延迟if (cv::waitKey(delay)>=0)stop= true; }return a.exec(); }

(注意:要正确打开视频文件,计算机中必须安装有对应的解码器,否则cv::VideoCapture无法理解视频格式!)运行后,将出现一个窗口,播放选定的视频(需要在创建cv::VideoCapture对象时指定视频的文件名)。
OpenCV2学习笔记之视频流读取与处理
文章图片


二. 处理视频帧 为了对视频的每一帧进行处理,这里创建自己的类VideoProcessor,其中封装了OpenCV的视频获取框架,该类允许我们指定每帧调用的处理函数。
首先,我们希望指定一个回调处理函数,每一帧中都将调用它。该函数接受一个cv::Mat对象,并输出处理后的cv::Mat对象,其函数签名如下:
void processFrame(cv::Mat& img, cv::Mat& out);

作为这样一个处理函数的例子,以下的Canny函数计算图像的边缘,使用时直接添加在mian文件中即可:
// 对视频的每帧做Canny算子边缘检测void canny(cv::Mat& img, cv::Mat& out) {// 先要把每帧图像转化为灰度图cv::cvtColor(img,out,CV_BGR2GRAY); // 调用Canny函数cv::Canny(out,out,100,200); // 对像素进行翻转cv::threshold(out,out,128,255,cv::THRESH_BINARY_INV); }

现在我们需要创建一个VideoProcessor类,用来部署视频处理模块。而在此之前,需要先另外创建一个类,即VideoProcessor内部使用的帧处理类。这是因为在面向对象的上下文中,更适合使用帧处理类而不是一个帧处理函数,而使用类可以给程序员在涉及算法方面有更多的灵活度(书上介绍的)。将这个内部帧处理类命名为FrameProcessor,其定义如下:
#ifndef FRAMEPROCESSOR_H#define FRAMEPROCESSOR_H#include #include class FrameProcessor{public:virtual void process(cv:: Mat &input, cv:: Mat &output)= 0; }; #endif // FRAMEPROCESSOR_H

现在可以开始定义VideoProcessor类了,以下为videoprocessor.h中的内容:
#ifndef VIDEOPROCESSOR_H#define VIDEOPROCESSOR_H#include #include #include #include #include "frameprocessor.h"class VideoProcessor{private:// 创建视频捕获对象cv::VideoCapture capture; // 每帧调用的回调函数void (*process)(cv::Mat&, cv::Mat&); // FrameProcessor接口FrameProcessor *frameProcessor; // 确定是否调用回调函数的bool信号bool callIt; // 输入窗口的名称std::string windowNameInput; // 输出窗口的名称std::string windowNameOutput; // 延迟int delay; // 已处理的帧数long fnumber; // 在该帧停止long frameToStop; // 是否停止处理bool stop; // 当输入图像序列存储在不同文件中时,可使用以下设置// 把图像文件名的数组作为输入std::vector images; // 图像向量的迭加器std::vector::const_iterator itImg; // 得到下一帧// 可能来自:视频文件或摄像头bool readNextFrame(cv::Mat &frame){if (images.size()==0)return capture.read(frame); else {if (itImg != images.end()){frame= cv::imread(*itImg); itImg++; return frame.data != 0; }}}public:// 默认设置 digits(0), frameToStop(-1),VideoProcessor() : callIt(false), delay(-1),fnumber(0), stop(false),process(0), frameProcessor(0) {}// 创建输入窗口void displayInput(std::string wt); // 创建输出窗口void displayOutput(std::string wn); // 不再显示处理后的帧void dontDisplay(); // 以下三个函数设置输入的图像向量bool setInput(std::string filename); // 若输入为摄像头,设置IDbool setInput(int id); // 若输入为一组图像序列时,应用该函数bool setInput(const std::vector& imgs); // 设置帧之间的延迟// 0意味着在每一帧都等待按键响应// 负数意味着没有延迟void setDelay(int d); // 返回图像的帧率double getFrameRate(); // 需要调用回调函数void callProcess(); // 不需要调用回调函数void dontCallProcess(); // 设置FrameProcessor实例void setFrameProcessor(FrameProcessor* frameProcessorPtr); // 设置回调函数void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)); // 停止运行void stopIt(); // 判断是否已经停止bool isStopped(); // 是否开始了捕获设备?bool isOpened(); // 返回下一帧的帧数long getFrameNumber(); // 该函数获取并处理视频帧void run(); }; #endif // VIDEOPROCESSOR_H

然后,在videoprocessor.cpp中定义各个函数的功能:
#include "videoprocessor.h"// 创建输入窗口void VideoProcessor::displayInput(std::string wt){windowNameInput= wt; cv::namedWindow(windowNameInput); }// 创建输出窗口void VideoProcessor::displayOutput(std::string wn){windowNameOutput= wn; cv::namedWindow(windowNameOutput); }// 不再显示处理后的帧void VideoProcessor::dontDisplay(){cv::destroyWindow(windowNameInput); cv::destroyWindow(windowNameOutput); windowNameInput.clear(); windowNameOutput.clear(); }// 设置输入的图像向量bool VideoProcessor::setInput(std::string filename){fnumber= 0; // 释放之前打开过的视频资源capture.release(); images.clear(); // 打开视频return capture.open(filename); }// 若输入为摄像头,设置IDbool VideoProcessor::setInput(int id){fnumber= 0; // 释放之前打开过的视频资源capture.release(); images.clear(); // 打开视频文件return capture.open(id); }// 若输入为一组图像序列时,应用该函数bool VideoProcessor::setInput(const std::vector& imgs){fnumber= 0; // 释放之前打开过的视频资源capture.release(); // 输入将是该图像的向量images= imgs; itImg= images.begin(); return true; }// 设置帧之间的延迟// 0意味着在每一帧都等待按键响应// 负数意味着没有延迟void VideoProcessor::setDelay(int d){delay= d; }// 返回图像的帧率double VideoProcessor::getFrameRate(){if (images.size()!=0) return 0; double r= capture.get(CV_CAP_PROP_FPS); return r; }// 需要调用回调函数void VideoProcessor::callProcess(){callIt= true; }// 不需要调用回调函数void VideoProcessor::dontCallProcess(){callIt= false; }// 设置FrameProcessor实例void VideoProcessor::setFrameProcessor(FrameProcessor* frameProcessorPtr){// 使回调函数无效化process= 0; // 重新设置FrameProcessor实例frameProcessor= frameProcessorPtr; callProcess(); }// 设置回调函数void VideoProcessor::setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)){// 使FrameProcessor实例无效化frameProcessor= 0; // 重新设置回调函数process= frameProcessingCallback; callProcess(); }// 以下函数表示视频的读取状态// 停止运行void VideoProcessor::stopIt(){stop= true; }// 判断是否已经停止bool VideoProcessor::isStopped(){return stop; }// 是否开始了捕获设备?bool VideoProcessor::isOpened(){return capture.isOpened() || !images.empty(); }// 返回下一帧的帧数long VideoProcessor::getFrameNumber(){if (images.size()==0){// 得到捕获设备的信息long f= static_cast(capture.get(CV_CAP_PROP_POS_FRAMES)); return f; }else // 当输入来自一组图像序列时的情况{return static_cast(itImg-images.begin()); }}// 该函数获取并处理视频帧void VideoProcessor::run(){// 当前帧cv::Mat frame; // 输出帧cv::Mat output; // 打开失败时if (!isOpened()){qDebug() << "Error!"; return; }stop= false; while (!isStopped()){// 读取下一帧if (!readNextFrame(frame))break; // 显示输出帧if (windowNameInput.length()!=0)cv::imshow(windowNameInput,frame); // 调用处理函数if (callIt){// 处理当前帧if (process)process(frame, output); else if (frameProcessor)frameProcessor->process(frame,output); // 增加帧数fnumber++; }else{output= frame; }// 显示输出帧if (windowNameOutput.length()!=0)cv::imshow(windowNameOutput,output); // 引入延迟if (delay>=0 && cv::waitKey(delay)>=0)stopIt(); // 检查是否需要停止运行if (frameToStop>=0 && getFrameNumber()==frameToStop)stopIt(); }}

定义好视频处理类,它将与一个回调函数相关联。使用该类,可以创建一个实例,指定输入的视频文件,绑定回调函数,然后开始对每一帧进行处理,要调用这个视频处理类,只需在main函数中添加:
// 定义一个视频处理类处理视频帧// 首先创建实例VideoProcessor processor; // 打开视频文件processor.setInput("e:/BrokeGirls.mkv"); // 声明显示窗口// 分别为输入和输出视频processor.displayInput("Input Video"); processor.displayOutput("Output Video"); // 以原始帧率播放视频processor.setDelay(1000./processor.getFrameRate()); // 设置处理回调函数processor.setFrameProcessor(canny); // 开始帧处理过程processor.run(); cv::waitKey();

效果:
OpenCV2学习笔记之视频流读取与处理
文章图片


OpenCV:打开摄像头获取视频流
#include#includeusing namespace cv; using namespace std; int main(){//【1】从摄像头读入视频VideoCapture capture(1); if (!capture.isOpened()){cout<< "open camera fail ..." << endl; return -1; }capture.set(CAP_PROP_FRAME_WIDTH, 640); capture.set(CAP_PROP_FRAME_HEIGHT, 480); char filename[200]; int count =0; //【2】循环显示每一帧Mat frame; //定义一个Mat变量,用于存储每一帧的图像char key; while (true){//读入图像capture>> frame; //读取当前帧key = waitKey(20); if(key ==27)//esc键退出break; if(key ==32)//空格键保存图像{sprintf(filename, "Picture_%d.png", ++count); imwrite(filename, frame); //namedWindow("[frame]", WINDOW_NORMAL); imshow("[frame]",frame); }imshow("image", frame); //显示当前帧}return 0; }


总结 【OpenCV2学习笔记之视频流读取与处理】到此这篇关于OpenCV2学习笔记之视频流读取与处理的文章就介绍到这了,更多相关OpenCV视频流读取与处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    推荐阅读