##視頻流讀取與處理
由于項目需要,計劃實現九路視頻拼接,因此必須熟悉OpenCV對視頻序列的處理。視頻信號處理是圖像處理的一個延伸,所謂的視頻序列是由按一定順序進行排放的圖像組成,即幀(Frame)。在這里,主要記錄下如何使用Qt+OpenCV讀取視頻中的每一幀,之后,在這基礎上將一些圖像處理的算法運用到每一幀上(如使用Canny算子檢測視頻中的邊緣)。
一. 讀取視頻序列
OpenCV提供了一個簡便易用的框架以提取視頻文件和USB攝像頭中的圖像幀,如果只是單單想讀取某個視頻,你只需要創建一個cv::VideoCapture實例,然后在循環中提取每一幀。新建一個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)
{
// 嘗試讀取下一幀
if (!capture.read(frame))
break;
cv::imshow("Extracted Frame",frame);
// 引入延遲
if (cv::waitKey(delay)>=0)
stop= true;
}
return a.exec();
}
~~~
(注意:要正確打開視頻文件,計算機中必須安裝有對應的解碼器,否則cv::VideoCapture無法理解視頻格式!)運行后,將出現一個窗口,播放選定的視頻(需要在創建cv::VideoCapture對象時指定視頻的文件名)。

二. 處理視頻幀
為了對視頻的每一幀進行處理,這里創建自己的類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 <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;
// 得到下一幀
// 可能來自:視頻文件或攝像頭
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);
// 若輸入為攝像頭,設置ID
bool setInput(int id);
// 若輸入為一組圖像序列時,應用該函數
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();
// 返回下一幀的幀數
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);
}
// 若輸入為一組圖像序列時,應用該函數
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();
}
// 返回下一幀的幀數
long VideoProcessor::getFrameNumber()
{
if (images.size()==0)
{
// 得到捕獲設備的信息
long f= static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES));
return f;
}
else // 當輸入來自一組圖像序列時的情況
{
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())
{
// 讀取下一幀
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();
~~~
效果:

- 前言
- Win8.1下OpenCV2.4.9+Qt5.3.2開發環境搭建
- OpenCV2學習筆記(一)
- OpenCV2學習筆記(二)
- OpenCV2學習筆記(三)
- OpenCV2學習筆記(四)
- OpenCV2學習筆記(五)
- OpenCV2學習筆記(六)
- OpenCV2學習筆記(七)
- OpenCV2學習筆記(八)
- OpenCV2學習筆記(九)
- OpenCV2學習筆記(十)
- OpenCV2學習筆記(十一)
- OpenCV2學習筆記(十二)
- OpenCV2學習筆記(十三)
- OpenCV2學習筆記(十四)
- OpenCV2學習筆記(十五)
- OpenCV2學習筆記(十六)
- OpenCV2學習筆記(十七)
- OpenCV2學習筆記(十八)
- OpenCV2學習筆記(十九)
- OpenCV2學習筆記(二十)
- OpenCV2學習筆記(二十一)
- OpenCV2學習筆記(二十二)