程序员人生 网站导航

OpenCV2学习笔记(九):视频流读取与处理

栏目:综合技术时间:2015-03-30 08:13:57

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

1. 读取视频序列

OpenCV提供了1个简便易用的框架以提取视频文件和USB摄像头中的图象帧,如果只是单单想读取某个视频,你只需要创建1个cv::VideoCapture实例,然后在循环中提取每帧。新建1个Qt控制台项目,直接在main函数添加:

#include <QCoreApplication> #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <QDebug> 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) { // 尝试读取下1帧 if (!capture.read(frame)) break; cv::imshow("Extracted Frame",frame); // 引入延迟 if (cv::waitKey(delay)>=0) stop= true; } return a.exec(); }

(注意:要正确打开视频文件,计算机中必须安装有对应的解码器,否则cv::VideoCapture没法理解视频格式!)运行后,将出现1个窗口,播放选定的视频(需要在创建cv::VideoCapture对象时指定视频的文件名)。

这里写图片描述

2. 处理视频帧

为了对视频的每帧进行处理,这里创建自己的类VideoProcessor,其中封装了OpenCV的视频获得框架,该类允许我们指定每帧调用的处理函数。

首先,我们希望指定1个回调解理函数,每帧中都将调用它。该函数接受1个cv::Mat对象,并输出处理后的cv::Mat对象,其函数签名以下:

void processFrame(cv::Mat& img, cv::Mat& out);

作为这样1个处理函数的例子,以下的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); }

现在我们需要创建1个VideoProcessor类,用来部署视频处理模块。而在此之前,需要先另外创建1个类,即VideoProcessor内部使用的帧处理类。这是由于在面向对象的上下文中,更合适使用帧处理类而不是1个帧处理函数,而使用类可以给程序员在触及算法方面有更多的灵活度(书上介绍的)。将这个内部帧处理类命名为FrameProcessor,其定义以下:

#ifndef FRAMEPROCESSOR_H #define FRAMEPROCESSOR_H #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> 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 <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <QDebug> #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<std::string> images; // 图象向量的迭加器 std::vector<std::string>::const_iterator itImg; // 得到下1帧 // 可能来自:视频文件或摄像头 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(⑴), 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(); // 以下3个函数设置输入的图象向量 bool setInput(std::string filename); // 若输入为摄像头,设置ID bool setInput(int id); // 若输入为1组图象序列时,利用该函数 bool setInput(const std::vector<std::string>& 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(); // 返回下1帧的帧数 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); } // 若输入为摄像头,设置ID bool VideoProcessor::setInput(int id) { fnumber= 0; // 释放之前打开过的视频资源 capture.release(); images.clear(); // 打开视频文件 return capture.open(id); } // 若输入为1组图象序列时,利用该函数 bool VideoProcessor::setInput(const std::vector<std::string>& 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(); } // 返回下1帧的帧数 long VideoProcessor::getFrameNumber() { if (images.size()==0) { // 得到捕获装备的信息 long f= static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES)); return f; } else // 当输入来自1组图象序列时的情况 { return static_cast<long>(itImg-images.begin()); } } // 该函数获得并处理视频帧 void VideoProcessor::run() { // 当前帧 cv::Mat frame; // 输出帧 cv::Mat output; // 打开失败时 if (!isOpened()) { qDebug() << "Error!"; return; } stop= false; while (!isStopped()) { // 读取下1帧 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(); } }

定义好视频处理类,它将与1个回调函数相干联。使用该类,可以创建1个实例,指定输入的视频文件,绑定回调函数,然后开始对每帧进行处理,要调用这个视频处理类,只需在main函数中添加:

// 定义1个视频处理类处理视频帧 // 首先创建实例 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();

效果:

这里写图片描述

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐