3、Opencv播放視訊、儲存、暫停視訊,開啟攝像頭

phinoo發表於2021-01-05

一、載入視訊或攝像頭

1、原理理解
所謂的視訊播放,無非就是將一堆有序的圖片序列,按照順序,以一定的間隔顯示出來。這個間隔的多少與我們所聽到的幀率相關。有了這個意識後,我們其實按照自己的思想都可以實現視訊的播放:視訊檔案是由一幀幀具有先後順序的圖片組成,我們只需要將視訊的每一幀按照原有的順序顯示出來即可。為此Opencv提供了專用的物件和API以供載入視訊並按順序提取視訊的每一幀。

2、視訊管理類 cv::VideoCapture載入視訊或攝像頭
Opencv提供了用於獲取視訊檔案的類 cv::VideoCapture,這個類提供了c++ API,用於從攝像機捕捉視訊,或者讀取視訊檔案和影像序列。例項化該類的物件後,便可以通過該類提供的方法獲取視訊幀和獲得視訊檔案的相關資訊。

通常使用如下兩種方法構造 cv::VideoCapture物件。
方法一:先宣告 cv::VideoCapture的物件,再呼叫該物件的open方法開啟指定的視訊檔案

	VideoCapture vc1;
	vc1.open("O:\\image\\1.mp4");

注意cv::VideoCapture物件的open方法的函式原型為:

    virtual bool cv::VideoCapture::open	(const String & filename,
                                         int apiPreference = CAP_ANY)	

open方法的第一個引數指定視訊檔案,第二個引數可忽略。open方法的另一個常用的過載為:

    virtual bool cv::VideoCapture::open	(int index,
                                         int apiPreference = CAP_ANY)	

此時,第一個引數用於繫結使用攝像頭的編號,從0開始計數。

方法二:宣告 cv::VideoCapture物件的時候,呼叫有參建構函式直接繫結視訊檔案

    VideoCapture vc1("O:\\image\\1.mp4");

該建構函式的原型為:

    cv::VideoCapture::VideoCapture	(const String & filename,
                                     int apiPreference = CAP_ANY)	

其中,第一個引數指定視訊檔案,第二個引數可忽略。其對應的開啟攝像頭的過載為:

    cv::VideoCapture::VideoCapture	(int index,
                                     int apiPreference = CAP_ANY)	

此時,第一個引數還是指定攝像頭編號,從0開始計數。

二、獲取視訊幀或攝像頭的幀

1、在獲取視訊幀之前,通常我們需要判斷一下視訊或者攝像頭有無開啟(初始化):

	if (!vc1.isOpened())
	{
		fprintf(stderr, "failed to open %s\n", "O:\\image\\1.mp4");
		return EXIT_FAILURE;
	}

如果視訊或者攝像頭初始化成功,該方法會返回 true

2、獲取視訊幀或者攝像頭的幀內容有兩種方式
方法一:

	Mat frame;
	vc1.read(frame);

該函式將VideoCapture::grab()VideoCapture::retrieve() 組合在一個呼叫中。這是最方便的讀取視訊檔案或捕獲資料解碼和返回方法。如果沒有捕獲幀(相機已斷開連線,或沒有更多幀),方法返回false,函式返回空影像。

方法二:

	Mat frame;
	vc1 >> frame;

這主要是基於cv::VideoCapture類過載了 >> 操作符。

三、迴圈邏輯控制視訊顯示

結合原理理解,現在已經有方法獲取視訊的每一幀內容了,接下來要做的就是寫好迴圈邏輯來逐幀顯示影像即可。

	Mat frame;
	vc1 >> frame;
	while (!frame.empty())
	{
		imshow("demo", frame);
		vc1 >> frame;
		waitKey(30);
	}
	destroyAllWindows();

四、暫停視訊播放和中斷視訊播放

前面已經實現了視訊的連續播放,接下來要做的就是實現按下空格鍵(space,其ASCII值為32)時視訊暫停播放,再次按下的時候繼續播放。按下退出鍵(ESC,ASCII值為27)時中斷視訊播放。這兒會使用到一點簡單的程式設計技巧,直接看程式碼:

	Mat frame;
	vc1 >> frame;
	while (!frame.empty())
	{
		imshow("demo", frame);
		vc1 >> frame;
		int k = waitKey(30);
		
		if (k == 27)
			break;
		else if (k == 32)
		{
			while (waitKey(0) != 32)
				waitKey(0);
		}
	}
	destroyAllWindows();

在每隔30ms顯示一幀影像後會等待使用者按鍵,如果在30ms內使用者沒按鍵,則waitKey(30) 的返回值為-1,程式繼續顯示下一幀,迴圈繼續。如果使用者按下了ESC鍵,則直接break退出迴圈,視訊播放結束,如果按下空格後,則程式會一直等待使用者再次按下空格後才繼續。

五、視訊儲存

Opencv實現視訊的儲存需要定義編解碼器並建立cv::VideoWriter物件。
1、例項化視訊寫物件cv::VideoWriter

    VideoWriter writer;

cv::VideoWriter類有很多個過載的構造方法,可以在宣告物件的同時定義編解碼器,也可以在後面呼叫該物件的fourcc 方法指定編解碼器。

    int code = writer.fourcc('M', 'J', 'P', 'G');
    writer.open("O:\\image\\result.avi", code, 40, Size(1280, 720), true);

宣告物件的同時繫結檔案和編解碼器,其函式原型為:

    CV_WRAP VideoWriter(const String& filename, int fourcc, double fps,
                        Size frameSize, bool isColor = true);
  • 引數 filename:指定儲存視訊的檔名(路徑+名稱)
  • 引數 fourcc:指定編解碼器
  • 引數 fps:指定儲存視訊的幀率
  • 引數 frameSize:指定儲存視訊的尺寸,為Size物件
  • 引數 isColor:指定儲存視訊是否為彩色,預設為true

呼叫例項為:

    string fileName = "O:\\image\\result.avi";
	VideoWriter writer = VideoWriter(fileName, VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(1280, 720), true);

其它建構函式基本一致,感興趣的小夥伴自行查閱資料。

2、在視訊顯示的迴圈幀中,將每一幀儲存即可
呼叫 cv::VideoWriter物件的write函式:

    writer.write(frame);

在寫完視訊後,記得釋放資源:

    writer.release();

六、完整程式碼示例

#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    // 初始化視訊管理物件,直接指定視訊檔案的路徑
	VideoCapture vc1("O:\\image\\1.mp4");
    
    // 判斷視訊是否初始化成功
	if (!vc1.isOpened())
	{
		fprintf(stderr, "failed to open %s\n", "O:\\image\\1.mp4");
		return EXIT_FAILURE;
	}

    // 宣告視訊寫入物件
	string fileName = "O:\\image\\result.avi";
	VideoWriter writer = VideoWriter(fileName, VideoWriter::fourcc('M', 'J', 'P', 'G'), 30, Size(1280, 720), true);
    
    // 迴圈處理視訊的每一幀,先顯示,再寫入
	Mat frame;
	vc1 >> frame;
	while (!frame.empty())
	{
		imshow("demo", frame);
		// 寫入視訊幀
		writer.write(frame);
		vc1 >> frame;
		int k = waitKey(30);
		// 實現按鍵暫停和退出視訊播放功能		
		if (k == 27)
			break;
		else if (k == 32)
		{
			while (waitKey(0) != 32)
				waitKey(0);
		}
	}
	// 釋放資源,清空快取
	destroyAllWindows();
	writer.release();

	system("pause");
	return EXIT_SUCCESS;
}

七、致謝

1、常規的謝天謝地謝父母,謝朋友
2、感興趣的小夥伴歡迎入群一起探討學習 :725027506

相關文章