本文主要結合《OpenCV2 計算機視覺程式設計手冊》第10章的内容,學習OpenCV 處理視訊圖像的一般方法,包括讀入,處理,寫出。
1.頭檔案
#ifndef HEAD_H_
#define HEAD_H_
#include <iostream>
#include <iomanip>// 控制輸出格式
#include <sstream>// 檔案流控制
#include <string>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#endif // HEAD_H_
2. VideoProcessor頭檔案
#ifndef VPROCESSOR_H_
#define VPROCESSOR_H_
#include "head.h"
void canny(cv::Mat& img, cv::Mat& out)
{
cv::cvtColor(img, out, CV_BGR2GRAY); // 灰階轉換
cv::Canny(out, out, 100, 200); // Canny邊緣檢測
cv::threshold(out, out, 128, 255, cv::THRESH_BINARY_INV); // 二值圖像反轉, 小于128設定為255, 否則為0;即邊緣為黑色
}
// 抽象類FrameProcessor中純虛函數process必須在子類(其繼承類)中重新定義。
// 幀處理器接口
class FrameProcessor
{
public:
// 處理方法,定義為純虛函數, 讓其子類實作具體的接口
virtual void process(cv:: Mat &input, cv:: Mat &output)= 0;
};
class VideoProcessor
{
private:
cv::VideoCapture capture; // OpenCV視訊采集對象(object)
void (*process)(cv::Mat&, cv::Mat&);// 每幀處理的回調函數, 函數指針調用
FrameProcessor *frameProcessor; // 基類(含純虛函數的抽象類)FrameProcessor接口指針, 指向子類的實作
bool callIt; // 啟動回調函數與否的bool判斷, true:調用, false:不調用
std::string windowNameInput; // 輸入顯示視窗名字
std::string windowNameOutput; // 輸出顯示視窗名字
int delay; // 幀間處理延遲
long fnumber; // 已處理幀總數
long frameToStop; // 在該幀停止
bool stop; // 停止處理标志位!
std::vector<std::string> images; // 輸入的圖像集或者圖像向量(vector容器)
std::vector<std::string>::const_iterator itImg;// 圖像集的疊代器
cv::VideoWriter writer; // OpenCV視訊寫出對象
std::string outputFile; // 輸出視訊檔案名字
int currentIndex; // 輸出圖像集的目前索引
int digits; // 輸出圖像檔案名字的數字
std::string extension; // 輸出圖像集的擴充名
// Getting the next frame which could be: video file; camera; vector of images
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;
}
else
{
return false;
}
}
}
// Writing the output frame which could be: video file or images
void writeNextFrame(cv::Mat& frame)
{
if (extension.length())
{ // 輸出圖像檔案
std::stringstream ss;
ss << outputFile << std::setfill('0') << std::setw(digits) << currentIndex++ << extension;
cv::imwrite(ss.str(),frame);
}
else
{ // 輸出視訊檔案
writer.write(frame);
}
}
public:
// 構造函數
VideoProcessor() : callIt(false), delay(-1),
fnumber(0), stop(false), digits(0), frameToStop(-1),
process(0), frameProcessor(0) {}
// 設定視訊檔案的名字
bool setInput(std::string filename)
{
fnumber= 0;
// In case a resource was already
// associated with the VideoCapture instance
capture.release(); // 釋放之前打開過的資源
images.clear(); // 釋放之前打開過的資源
return capture.open(filename);// 打開視訊檔案
}
// 設定相機ID
bool setInput(int id)
{
fnumber= 0;
// In case a resource was already
// associated with the VideoCapture instance
capture.release();
images.clear();
// 打開視訊檔案
return capture.open(id);
}
// 設定輸入的圖像集
bool setInput(const std::vector<std::string>& imgs)
{
fnumber= 0;
// In case a resource was already
// associated with the VideoCapture instance
capture.release();//釋放之前打開過的資源
// 輸入的是圖像集
images= imgs;
itImg= images.begin();
return true;
}
// 設定輸出視訊檔案, 預設參數和輸入的一樣
bool setOutput(const std::string &filename, int codec=0, double framerate=0.0, bool isColor=true)
{
outputFile= filename;
extension.clear();
if (framerate==0.0)
framerate= getFrameRate(); // 與輸入相同
char c[4]; // 使用和輸入相同的編碼格式
if (codec==0)
{
codec= getCodec(c);
}
// 打開輸出視訊
return writer.open(outputFile, // 檔案名
codec, // 使用的解碼格式
framerate, // 幀率
getFrameSize(), // 幀大小
isColor); // 是否為彩色視訊
}
// 設定輸出是圖像集, 字尾必須是".jpg", ".bmp" ...
bool setOutput(const std::string &filename, // 檔案名字首
const std::string &ext, // 圖像檔案字尾
int numberOfDigits=3, // 數字位數
int startIndex=0) // 開始索引000
{
if (numberOfDigits<0) // 數字位數必須是正數
return false;
outputFile = filename; // 檔案名
extension = ext; // 公共字尾名
digits = numberOfDigits; // 檔案名中的數字位數
currentIndex = startIndex; // 開始索引
return true;
}
// 設定每一幀的回調函數
void setFrameProcessor(void (*frameProcessingCallback)(cv::Mat&, cv::Mat&))
{
// invalidate frame processor class instance 使FrameProcessor執行個體無效化
frameProcessor = 0;
process = frameProcessingCallback;
callProcess();
}
// 設定FrameProcessor接口執行個體
void setFrameProcessor(FrameProcessor* frameProcessorPtr)
{
// invalidate callback function 使回調函數無效化
process = 0;
frameProcessor= frameProcessorPtr;
callProcess();
}
// 在frame幀停止
void stopAtFrameNo(long frame)
{
frameToStop= frame;
}
// 處理回調函數
void callProcess()
{
callIt= true;
}
// 不調用回調函數
void dontCallProcess()
{
callIt= false;
}
// 顯示輸入的圖像幀
void displayInput(std::string wn)
{
windowNameInput= wn;
cv::namedWindow(windowNameInput);
}
// 顯示處理的圖像幀
void displayOutput(std::string wn)
{
windowNameOutput= wn;
cv::namedWindow(windowNameOutput);
}
// 不顯示處理的圖像幀
void dontDisplay()
{
cv::destroyWindow(windowNameInput);
cv::destroyWindow(windowNameOutput);
windowNameInput.clear();
windowNameOutput.clear();
}
// 設定幀間延遲時間
// 0 means wait at each frame
// negative means no delay
void setDelay(int d)
{
delay= d;
}
// 處理幀的總數
long getNumberOfProcessedFrames()
{
return fnumber;
}
// 傳回視訊幀的大小
cv::Size getFrameSize()
{
if (images.size()==0)
{
// get size of from the capture device
int w= static_cast<int>(capture.get(CV_CAP_PROP_FRAME_WIDTH));
int h= static_cast<int>(capture.get(CV_CAP_PROP_FRAME_HEIGHT));
return cv::Size(w,h);
}
else
{ // if input is vector of images
cv::Mat tmp= cv::imread(images[0]);
if (!tmp.data) return cv::Size(0,0);
else return tmp.size();
}
}
// 傳回下一幀的幀數
long getFrameNumber()
{
if (images.size()==0)
{
// get info of from the capture device
long f= static_cast<long>(capture.get(CV_CAP_PROP_POS_FRAMES));
return f;
}
else
{ // if input is vector of images
return static_cast<long>(itImg-images.begin());
}
}
// return the position in ms
double getPositionMS()
{
// undefined for vector of images
if (images.size()!=0) return 0.0;
double t= capture.get(CV_CAP_PROP_POS_MSEC);
return t;
}
// 傳回幀率
double getFrameRate()
{
// undefined for vector of images
if (images.size()!=0) return 0;
double r= capture.get(CV_CAP_PROP_FPS);
return r;
}
// 傳回視訊中圖像的總數
long getTotalFrameCount()
{
// for vector of images
if (images.size()!=0) return images.size();
long t= capture.get(CV_CAP_PROP_FRAME_COUNT);
return t;
}
// 擷取輸入視訊的編解碼器
int getCodec(char codec[4])
{
// 未制定的圖像集
if (images.size()!=0) return -1;
union
{// 4-char編碼的資料結果
int value;
char code[4];
} returned;
// 擷取編碼
returned.value= static_cast<int>(capture.get(CV_CAP_PROP_FOURCC));
// 獲得4字元
codec[0]= returned.code[0];
codec[1]= returned.code[1];
codec[2]= returned.code[2];
codec[3]= returned.code[3];
// 傳回對應的整數
return returned.value;
}
// 設定幀位置
bool setFrameNumber(long pos)
{
// for vector of images
if (images.size()!=0)
{
// move to position in vector
itImg= images.begin() + pos;
// is it a valid position?
if (pos < images.size())
return true;
else
return false;
}
else
{ // if input is a capture device
return capture.set(CV_CAP_PROP_POS_FRAMES, pos);
}
}
// go to this position
bool setPositionMS(double pos)
{
// not defined in vector of images
if (images.size()!=0)
return false;
else
return capture.set(CV_CAP_PROP_POS_MSEC, pos);
}
// go to this position expressed in fraction of total film length
bool setRelativePosition(double pos)
{
// for vector of images
if (images.size()!=0)
{
// move to position in vector
long posI= static_cast<long>(pos*images.size()+0.5);
itImg= images.begin() + posI;
// is it a valid position?
if (posI < images.size())
return true;
else
return false;
}
else
{ // if input is a capture device
return capture.set(CV_CAP_PROP_POS_AVI_RATIO, pos);
}
}
// 停止運作
void stopIt()
{
stop= true;
}
// 是否已停止運作?
bool isStopped()
{
return stop;
}
// 判斷是否是視訊捕獲裝置或圖像集
bool isOpened()
{
return capture.isOpened() || !images.empty();
}
// 擷取并處理圖像
void run()
{
cv::Mat frame; // 目前幀
cv::Mat output; // 輸出幀
// if no capture device has been set
if (!isOpened())
return;
stop= false;
while (!isStopped())
{
// 讀取下一幀
if (!readNextFrame(frame))
break;
// 顯示輸出幀
if (windowNameInput.length()!=0)
cv::imshow(windowNameInput,frame);
// 調用幀處理回調函數或FrameProcessor執行個體
if (callIt)
{
// 處理目前幀
if (process) // 如果是回調函數
process(frame, output);
else if (frameProcessor) //如果是FrameProcessor執行個體
frameProcessor->process(frame,output);
// 增加幀數
fnumber++;
}
else
{
output= frame;
}
// 寫出輸出圖像序列
if (outputFile.length()!=0)
writeNextFrame(output);
// 顯示輸出幀
if (windowNameOutput.length()!=0)
cv::imshow(windowNameOutput,output);
// 引入幀間延遲
if (delay>=0 && cv::waitKey(delay)>=0)
stopIt();
// 檢查是否需要停止運作
if (frameToStop>=0 && getFrameNumber()==frameToStop)
stopIt();
}
}
};
#endif // VPROCESSOR_H_
3. 主函數
#include "head.h"
#include "videoprocessor.h"
int main()
{
//----Zero Test----
cv::VideoCapture capture("../bike.avi"); // 打開視訊/攝像頭0
if (!capture.isOpened())
return 1;
double rate= capture.get(CV_CAP_PROP_FPS);// 擷取幀率
bool stop(false);
cv::Mat frame; // 目前幀
cv::namedWindow("Extracted Frame");
int delay= 1000/rate; // 延遲的毫秒
//int delay = 1000;
// 處理視訊所有幀
while (!stop)
{
// read next frame if any
if (!capture.read(frame))
break;
cv::imshow("Extracted Frame",frame);
if (cv::waitKey(delay)>=0) // 延遲等待直到cv::waitKey(delay)<0
stop= true;
}
capture.release(); // 因為capture自動調用析構函數,是以capture.release不是必須的!
cv::waitKey();
//----First Test----
VideoProcessor processor; // 建立VideoProcessor類執行個體 processor
processor.setInput("../bike.avi"); // 打開視訊檔案bike.avi
processor.displayInput("Input Video"); // 聲明輸入視訊顯示視窗
processor.displayOutput("Output Video"); // 聲明輸出視訊顯示視窗
processor.setDelay(1000./processor.getFrameRate()); // 設定播放視訊為原始輸入視訊幀率
processor.setFrameProcessor(canny); // 設定幀處理器的回調函數--canny
processor.run(); // 開始處理視訊檔案
cv::waitKey(); // 等待按鍵響應
//----Second test----
processor.setInput("../bike.avi"); // 重新設定打開視訊
cv::Size size= processor.getFrameSize(); // 擷取視訊檔案的基本資訊
std::cout << size.width << " " << size.height << std::endl; // 視訊圖像的寬度(列)和高度(行)
std::cout << processor.getFrameRate() << std::endl; // 視訊的幀率
std::cout << processor.getTotalFrameCount() << std::endl; // 視訊總的幀數
std::cout << processor.getFrameNumber() << std::endl; // 視訊幀的編号
std::cout << processor.getPositionMS() << std::endl; // 視訊幀的位置(ms)
processor.dontCallProcess(); // 不處理打開視訊檔案
// 輸出.jpg視訊圖像到output檔案夾, 圖像名字為bikeOut000.jpg~bikeOut118.jpg
processor.setOutput("../output/bikeOut",".jpg");
processor.run();
cv::waitKey();
// 輸出bike.avi視訊到output檔案夾,編解碼器為:XVID, 基于MPEG-4視訊标準的開源解碼庫
char codec[4]; // 編解碼器辨別
processor.setOutput("../output/bike.avi",processor.getCodec(codec),processor.getFrameRate());
std::cout << "Codec: " << codec[0] << codec[1] << codec[2] << codec[3] << std::endl;
processor.run();
cv::waitKey();
//----Three test----
processor.setInput("../bike.avi");
processor.displayInput("Input Video"); // 聲明輸入視訊顯示視窗
processor.displayOutput("Output Video"); // 聲明輸出視訊顯示視窗
processor.setFrameNumber(80); // 設定幀的位置
processor.stopAtFrameNo(120); // 停止的幀位置
processor.setDelay(1000./processor.getFrameRate());
processor.run();
cv::waitKey();
return 0;
}
Canny邊緣檢測
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SO1YTN5kjY4MGOidTMjlTMzYzX1MDOxgTM4AzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
視訊寫出結果(包含檔案和視訊)
制定開始幀和結束幀位置