OpenCV2學(xué)習(xí)筆記之視頻流讀取與處理
前言
由于項(xiàng)目需要,計(jì)劃實(shí)現(xiàn)九路視頻拼接,因此必須熟悉OpenCV對視頻序列的處理。視頻信號處理是圖像處理的一個(gè)延伸,所謂的視頻序列是由按一定順序進(jìn)行排放的圖像組成,即幀(Frame)。在這里,主要記錄下如何使用Qt+OpenCV讀取視頻中的每一幀,之后,在這基礎(chǔ)上將一些圖像處理的算法運(yùn)用到每一幀上(如使用Canny算子檢測視頻中的邊緣)。
一. 讀取視頻序列
OpenCV提供了一個(gè)簡便易用的框架以提取視頻文件和USB攝像頭中的圖像幀,如果只是單單想讀取某個(gè)視頻,你只需要?jiǎng)?chuàng)建一個(gè)cv::VideoCapture實(shí)例,然后在循環(huán)中提取每一幀。新建一個(gè)Qt控制臺(tái)項(xiàng)目,直接在main函數(shù)添加:
#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; // 當(dāng)前視頻幀 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(); }
(注意:要正確打開視頻文件,計(jì)算機(jī)中必須安裝有對應(yīng)的解碼器,否則cv::VideoCapture無法理解視頻格式!)運(yùn)行后,將出現(xiàn)一個(gè)窗口,播放選定的視頻(需要在創(chuàng)建cv::VideoCapture對象時(shí)指定視頻的文件名)。
二. 處理視頻幀
為了對視頻的每一幀進(jìn)行處理,這里創(chuàng)建自己的類VideoProcessor,其中封裝了OpenCV的視頻獲取框架,該類允許我們指定每幀調(diào)用的處理函數(shù)。
首先,我們希望指定一個(gè)回調(diào)處理函數(shù),每一幀中都將調(diào)用它。該函數(shù)接受一個(gè)cv::Mat對象,并輸出處理后的cv::Mat對象,其函數(shù)簽名如下:
void processFrame(cv::Mat& img, cv::Mat& out);
作為這樣一個(gè)處理函數(shù)的例子,以下的Canny函數(shù)計(jì)算圖像的邊緣,使用時(shí)直接添加在mian文件中即可:
// 對視頻的每幀做Canny算子邊緣檢測 void canny(cv::Mat& img, cv::Mat& out) { // 先要把每幀圖像轉(zhuǎn)化為灰度圖 cv::cvtColor(img,out,CV_BGR2GRAY); // 調(diào)用Canny函數(shù) cv::Canny(out,out,100,200); // 對像素進(jìn)行翻轉(zhuǎn) cv::threshold(out,out,128,255,cv::THRESH_BINARY_INV); }
現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)VideoProcessor類,用來部署視頻處理模塊。而在此之前,需要先另外創(chuàng)建一個(gè)類,即VideoProcessor內(nèi)部使用的幀處理類。這是因?yàn)樵诿嫦驅(qū)ο蟮纳舷挛闹?,更適合使用幀處理類而不是一個(gè)幀處理函數(shù),而使用類可以給程序員在涉及算法方面有更多的靈活度(書上介紹的)。將這個(gè)內(nèi)部幀處理類命名為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
現(xiàn)在可以開始定義VideoProcessor類了,以下為videoprocessor.h中的內(nèi)容:
#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: // 創(chuàng)建視頻捕獲對象 cv::VideoCapture capture; // 每幀調(diào)用的回調(diào)函數(shù) void (*process)(cv::Mat&, cv::Mat&); // FrameProcessor接口 FrameProcessor *frameProcessor; // 確定是否調(diào)用回調(diào)函數(shù)的bool信號 bool callIt; // 輸入窗口的名稱 std::string windowNameInput; // 輸出窗口的名稱 std::string windowNameOutput; // 延遲 int delay; // 已處理的幀數(shù) long fnumber; // 在該幀停止 long frameToStop; // 是否停止處理 bool stop; // 當(dāng)輸入圖像序列存儲(chǔ)在不同文件中時(shí),可使用以下設(shè)置 // 把圖像文件名的數(shù)組作為輸入 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: // 默認(rèn)設(shè)置 digits(0), frameToStop(-1), VideoProcessor() : callIt(false), delay(-1), fnumber(0), stop(false), process(0), frameProcessor(0) {} // 創(chuàng)建輸入窗口 void displayInput(std::string wt); // 創(chuàng)建輸出窗口 void displayOutput(std::string wn); // 不再顯示處理后的幀 void dontDisplay(); // 以下三個(gè)函數(shù)設(shè)置輸入的圖像向量 bool setInput(std::string filename); // 若輸入為攝像頭,設(shè)置ID bool setInput(int id); // 若輸入為一組圖像序列時(shí),應(yīng)用該函數(shù) bool setInput(const std::vector<std::string>& imgs); // 設(shè)置幀之間的延遲 // 0意味著在每一幀都等待按鍵響應(yīng) // 負(fù)數(shù)意味著沒有延遲 void setDelay(int d); // 返回圖像的幀率 double getFrameRate(); // 需要調(diào)用回調(diào)函數(shù) void callProcess(); // 不需要調(diào)用回調(diào)函數(shù) void dontCallProcess(); // 設(shè)置FrameProcessor實(shí)例 void setFrameProcessor(FrameProcessor* frameProcessorPtr); // 設(shè)置回調(diào)函數(shù) void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)); // 停止運(yùn)行 void stopIt(); // 判斷是否已經(jīng)停止 bool isStopped(); // 是否開始了捕獲設(shè)備? bool isOpened(); // 返回下一幀的幀數(shù) long getFrameNumber(); // 該函數(shù)獲取并處理視頻幀 void run(); }; #endif // VIDEOPROCESSOR_H
然后,在videoprocessor.cpp中定義各個(gè)函數(shù)的功能:
#include "videoprocessor.h" // 創(chuàng)建輸入窗口 void VideoProcessor::displayInput(std::string wt) { windowNameInput= wt; cv::namedWindow(windowNameInput); } // 創(chuàng)建輸出窗口 void VideoProcessor::displayOutput(std::string wn) { windowNameOutput= wn; cv::namedWindow(windowNameOutput); } // 不再顯示處理后的幀 void VideoProcessor::dontDisplay() { cv::destroyWindow(windowNameInput); cv::destroyWindow(windowNameOutput); windowNameInput.clear(); windowNameOutput.clear(); } // 設(shè)置輸入的圖像向量 bool VideoProcessor::setInput(std::string filename) { fnumber= 0; // 釋放之前打開過的視頻資源 capture.release(); images.clear(); // 打開視頻 return capture.open(filename); } // 若輸入為攝像頭,設(shè)置ID bool VideoProcessor::setInput(int id) { fnumber= 0; // 釋放之前打開過的視頻資源 capture.release(); images.clear(); // 打開視頻文件 return capture.open(id); } // 若輸入為一組圖像序列時(shí),應(yīng)用該函數(shù) bool VideoProcessor::setInput(const std::vector<std::string>& imgs) { fnumber= 0; // 釋放之前打開過的視頻資源 capture.release(); // 輸入將是該圖像的向量 images= imgs; itImg= images.begin(); return true; } // 設(shè)置幀之間的延遲 // 0意味著在每一幀都等待按鍵響應(yīng) // 負(fù)數(shù)意味著沒有延遲 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; } // 需要調(diào)用回調(diào)函數(shù) void VideoProcessor::callProcess() { callIt= true; } // 不需要調(diào)用回調(diào)函數(shù) void VideoProcessor::dontCallProcess() { callIt= false; } // 設(shè)置FrameProcessor實(shí)例 void VideoProcessor::setFrameProcessor(FrameProcessor* frameProcessorPtr) { // 使回調(diào)函數(shù)無效化 process= 0; // 重新設(shè)置FrameProcessor實(shí)例 frameProcessor= frameProcessorPtr; callProcess(); } // 設(shè)置回調(diào)函數(shù) void VideoProcessor::setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&)) { // 使FrameProcessor實(shí)例無效化 frameProcessor= 0; // 重新設(shè)置回調(diào)函數(shù) process= frameProcessingCallback; callProcess(); } // 以下函數(shù)表示視頻的讀取狀態(tài) // 停止運(yùn)行 void VideoProcessor::stopIt() { stop= true; } // 判斷是否已經(jīng)停止 bool VideoProcessor::isStopped() { return stop; } // 是否開始了捕獲設(shè)備? bool VideoProcessor::isOpened() { return capture.isOpened() || !images.empty(); } // 返回下一幀的幀數(shù) long VideoProcessor::getFrameNumber() { if (images.size()==0) { // 得到捕獲設(shè)備的信息 long f= static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES)); return f; } else // 當(dāng)輸入來自一組圖像序列時(shí)的情況 { return static_cast<long>(itImg-images.begin()); } } // 該函數(shù)獲取并處理視頻幀 void VideoProcessor::run() { // 當(dāng)前幀 cv::Mat frame; // 輸出幀 cv::Mat output; // 打開失敗時(shí) if (!isOpened()) { qDebug() << "Error!"; return; } stop= false; while (!isStopped()) { // 讀取下一幀 if (!readNextFrame(frame)) break; // 顯示輸出幀 if (windowNameInput.length()!=0) cv::imshow(windowNameInput,frame); // 調(diào)用處理函數(shù) if (callIt) { // 處理當(dāng)前幀 if (process) process(frame, output); else if (frameProcessor) frameProcessor->process(frame,output); // 增加幀數(shù) fnumber++; } else { output= frame; } // 顯示輸出幀 if (windowNameOutput.length()!=0) cv::imshow(windowNameOutput,output); // 引入延遲 if (delay>=0 && cv::waitKey(delay)>=0) stopIt(); // 檢查是否需要停止運(yùn)行 if (frameToStop>=0 && getFrameNumber()==frameToStop) stopIt(); } }
定義好視頻處理類,它將與一個(gè)回調(diào)函數(shù)相關(guān)聯(lián)。使用該類,可以創(chuàng)建一個(gè)實(shí)例,指定輸入的視頻文件,綁定回調(diào)函數(shù),然后開始對每一幀進(jìn)行處理,要調(diào)用這個(gè)視頻處理類,只需在main函數(shù)中添加:
// 定義一個(gè)視頻處理類處理視頻幀 // 首先創(chuàng)建實(shí)例 VideoProcessor processor; // 打開視頻文件 processor.setInput("e:/BrokeGirls.mkv"); // 聲明顯示窗口 // 分別為輸入和輸出視頻 processor.displayInput("Input Video"); processor.displayOutput("Output Video"); // 以原始幀率播放視頻 processor.setDelay(1000./processor.getFrameRate()); // 設(shè)置處理回調(diào)函數(shù) processor.setFrameProcessor(canny); // 開始幀處理過程 processor.run(); cv::waitKey();
效果:
OpenCV:打開攝像頭獲取視頻流
#include #include using 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】循環(huán)顯示每一幀 Mat frame; //定義一個(gè)Mat變量,用于存儲(chǔ)每一幀的圖像 char key; while (true) { //讀入圖像 capture>> frame; //讀取當(dāng)前幀 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); //顯示當(dāng)前幀 } return 0; }
總結(jié)
到此這篇關(guān)于OpenCV2學(xué)習(xí)筆記之視頻流讀取與處理的文章就介紹到這了,更多相關(guān)OpenCV視頻流讀取與處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
selenium WebDriverWait類等待機(jī)制的實(shí)現(xiàn)
這篇文章主要介紹了selenium WebDriverWait類等待機(jī)制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Python不同目錄間進(jìn)行模塊調(diào)用的實(shí)現(xiàn)方法
這篇文章主要介紹了Python不同目錄間進(jìn)行模塊調(diào)用的實(shí)現(xiàn)方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01python正則表達(dá)式re之compile函數(shù)解析
這篇文章主要介紹了python正則表達(dá)式re之compile函數(shù)解析,介紹了其定義,匹配模式等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10python簡單實(shí)現(xiàn)獲取當(dāng)前時(shí)間
最近項(xiàng)目中經(jīng)常需要python去取當(dāng)前的時(shí)間,雖然不是很難,但是老是忘記,用一次丟一次,為了能夠更好的記住,我今天特意寫下python 當(dāng)前時(shí)間這篇文章,如果你覺的對你有用的話,可以收藏下。2016-08-08Python logging管理不同級別log打印和存儲(chǔ)實(shí)例
這篇文章主要介紹了Python logging管理不同級別log打印和存儲(chǔ)實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01CentOS 6.X系統(tǒng)下升級Python2.6到Python2.7 的方法
今天到新公司發(fā)現(xiàn)用的CentOS 6.X系統(tǒng),默認(rèn)安裝的Python是2.6版本,可是我的程序引用的部分庫需要2.7版本或以上,所以只能升級Python到2.7版本了,現(xiàn)在將升級的步驟分享給大家,有需要的朋友們可以參考借鑒。2016-10-10