OpenCV基礎: 攝像頭/視頻讀取與保存(適用4.1版本)

其實VideoCapture, VideoWriter 兩個類的使用並不難, 但總是遇到很奇怪的問題. 跟着《學習opencv3》練習例程的時候就碰到接口改變, fourcc碼不一致的問題, 最近項目開發中又碰到了視頻保存"6kb"問題, 浪費了一天的時間. 感覺應該把這一塊的接口搞明白了, 因此記錄下來, 以供參考.



摘要: 介紹在使用VideoCapture, VideoWriter中遇到的一些問題, 所用版本 4.1.1


VideoCapture 接口說明

用作從視頻文件/圖像序列/攝像頭捕獲視頻幀. 以下是除去構造函數/析構函數後, 所有的成員函數. 點擊訪問源文檔

常用的成員函數

成員函數 描述
open 啓動攝像頭/打開視頻文件, 並設置參數, 是默認構造函數的補充
isOpened 判斷攝像頭/視頻文件是否成功打開
get 查詢當前攝像頭/視頻文件的參數
set 設置當前攝像頭/視頻文件的參數
operator>> 輸出圖像幀到Mat變量
release 關閉攝像頭/視頻文件, 釋放內存

常用代碼彙總

  • 攝像頭相關
// 實例化
VideoCapture camera;
camera.open(cameraIndex);    // 打開攝像頭, 默認攝像頭cameraIndex=0
if(!camera.isOpened())
{
    cerr << "Couldn't open camera." << endl;
}

// 設置參數
camera.set(cv::CAP_PROP_FRAME_WIDTH, frameWidth);      // 寬度
camera.set(cv::CAP_PROP_FRAME_HEIGHT, frameHeight);    // 高度
camera.set(cv::CAP_PROP_FPS, fps);                     // 幀率

// 查詢參數
double frameWidth = camera.get(cv::CAP_PROP_FRAME_WIDTH);
double frameHeight = camera.get(cv::CAP_PROP_FRAME_HEIGHT);
double fps = camera.get(cv::CAP_PROP_FPS);

// 循環讀取視頻幀
while(true)
{
    cv::Mat frame;
    camera >> frame;
    cv::imshow("camera", frame);
    if(cv::waitKey(33) == 27) break;   // ESC 鍵退出
}

// 釋放
camera.release();
cv::destroyWindow("camera");
  • 離線視頻相關, 大部分與前者一樣
video.open("video.mp4");    // 打開攝像頭, 默認攝像頭cameraIndex=0
double frameCount = video.get(cv::CAP_PROP_FRAME_COUNT);  

嚴禁使用查詢的總幀數 frameCount 測量視頻處理算法的幀率!!!
因爲opencv是用ffmpeg讀取視頻,讀到的是關鍵幀,而壓縮過後的視頻中間有很多的過渡幀,這個數值和逐幀讀取的計數值是不一致的!!!參看 How to know total number of Frame in a file

未涉及的成員函數

  • getBackendName
  • getExceptionMode
  • grab
  • setExceptionMode

VideoWriter 接口說明

用作保存視頻. 對比VideoCapture, 可以看出, 二者使用了同一套接口 點擊訪問源文檔
在這裏插入圖片描述
主要不同在於構造函數/open函數的參數, 以及重載的操作符. 可以直接看常用代碼彙總.

// 實例化
VideoWriter recorder;

// 設置拍攝參數
cv::Size size(640, 480);                            // 視頻尺寸
double fps = 30;                                    // 幀率
int fourcc = recorder.fourcc('M', 'J', 'P', 'G');   // avi編碼格式
string fileName = "record video.avi"
int fourcc = recorder.fourcc('m', 'p', '4', 'v');   // mp4編碼格式
string fileName = "record video.mp4";

// 開始錄製
recorder.open(fileName, fourcc, fps, size);
if(recorder.isOpened())
{
    cout << "正在錄製..." << endl;
}
else 
{
    cerr << "參數錯誤." << endl;
    return -1;
}

// 循環保存視頻幀
while(true)
{
    recorder << frame;
    if(cv::waitKey(33) == 27) break;   // ESC 鍵退出
}

// 釋放
recorder.release();
cout << "錄製完成." << endl;

代碼示例 1: 攝像頭實時顯示並錄製視頻

#include <iostream>
#include "opencv2/highgui/highgui.hpp"  // 窗口顯示
#include "opencv2/core/core.hpp"        // 數學運算

using namespace std;
using namespace cv;

int main(int argc, char* argv[])
{
    // 設置參數
    int cameraIndex = 0;
    double frameWidth = 800;
    double frameHeight = 600;
    double fps = 10;

    // 初始化攝像頭
    VideoCapture camera;
    camera.open(cameraIndex, cv::CAP_DSHOW);            // 去除黑邊
    camera.set(CAP_PROP_FRAME_WIDTH, frameWidth);       // 寬度
    camera.set(CAP_PROP_FRAME_HEIGHT, frameHeight);     // 高度
    camera.set(CAP_PROP_FPS, fps);                      // 幀率

    // 初始化錄像機
    VideoWriter recorder;
    frameWidth = camera.get(CAP_PROP_FRAME_WIDTH);      // 尺寸設置會失敗, 使用get查詢實際參數
    frameHeight = camera.get(CAP_PROP_FRAME_HEIGHT);
    cv::Size size((int)frameWidth, (int)frameHeight);
    fps = camera.get(CAP_PROP_FPS);
    int fourcc = recorder.fourcc('M', 'J', 'P', 'G');   // 設置avi文件對應的編碼格式
    recorder.open("x64/2019-12-03 20.20.avi", fourcc, fps, size, CAP_DSHOW);
    if(recorder.isOpened())
    {
        cout << "正在錄製...\n" << endl;
    }
    else
    {
        cerr << "參數錯誤, 錄製失敗.\n" << endl;
        return -1;
    }

    // 實時顯示並錄製
    for(int counter = 0;;)
    {
        cv::Mat frame;
        camera >> frame;
        cv::imshow("camera", frame);
        recorder << frame;
        printf("正在錄製第%4d幀.\n", counter++);
        if(cv::waitKey(33) == 27) break;   // ESC 鍵退出
    }

    // 釋放
    camera.release();     // 不必要
    recorder.release();   // 必要
    cv::destroyWindow("camera");
    cout << "\n錄製完成." << endl;
    return 0;
}

代碼示例2: 序列幀圖像合併爲視頻

#include <iostream>
#include "opencv2/core/core.hpp"       
#include "opencv2/highgui.hpp"

using namespace std;

string frame_file = "../Seq/Seq.%04d.jpg";  //序列幀圖片的命名格式爲 Seq.0001.jpg
string outVideo = "../videos/show_video";
string outFormat = "avi";
double fps = 25.;  //設置視頻的幀率

int getFourCC(string format);

int main() 
{
    cv::VideoCapture cap(frame_file);
    cv::VideoWriter write;
    string winName = "video";
    cv::namedWindow(winName, cv::WINDOW_NORMAL);

    // 獲取視頻的分辨率,也就是圖片的寬高
    int height = int(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
    int width = int(cap.get(cv::CAP_PROP_FRAME_WIDTH));
    int count = (int)cap.get(cv::CAP_PROP_FRAME_COUNT);
    printf("video info: (%d, %d), %d frames\n", width, height, count);
    int fourcc = getFourCC(outFormat);
    string fileName = outVideo + '.' + outFormat;
    write.open(fileName, fourcc, fps, cv::Size(width, height), true);

    // 保存
    cv::Mat frame;
    for (int i = 1;; i++)
    {
        cap >> frame;
        if (frame.empty() or 27 == cv::waitKey(10))
        {
            break;
        }
        write.write(frame);
        cv::imshow(winName, frame);
        printf("\r正在處理第%d幀...", i);
    }
    cout << "\n視頻幀合併完成!\n" << endl;
    
    //釋放內存
    cv::destroyAllWindows();
    cap.release();
    write.release();
    system("pause");
    return 0;
}


int getFourCC(string format)
{
    if (format == "mp4")
    {
        return cv::VideoWriter::fourcc('m', 'p', '4', 'v');
    }
    else if (format == "avi")
    {
        return cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
    }
    else if (format == "mov")
    {
        return cv::VideoWriter::fourcc('a', 'v', 'c', '1');
    }
    else
    {
        return -1;
    }
}

遇到的問題與解決

  • 攝像頭圖像存在黑邊: 附加參數 cv::CAP_DSHOW
  • FourCC碼錯誤: 參考代碼示例中 avimp4格式的設置方法
  • 錄製的視頻文件只有6kb: VideoWriter 參數錯誤, 用get方法獲取攝像頭/視頻文件的實際參數值, 尤其是尺寸大小. 或者使用cv::resize(), 保存爲自定義尺寸
  • 錄製的視頻打不開: 需要手動釋放 VideoWriter::release()
  • 圖像顯示界面卡死或不刷新: 設置等待 cv::waitKey(1);
  • Failed to load OpenH264 library: openh264-1.6.0-win64.dll 根據提示的github地址, 下載對應dll, 放到項目exe所在x64/release文件夾
  • Moov atom not found 視頻下載不完整, 修復mov 或使用迅雷影音轉碼

What does ‘moov atom not found’ error means?
moov atom is the special part of the file, which defines the timescale, duration, display characteristics of the video, as well as subatoms containing information for each track in the video. This atom may be located at the end of the file, which is why you may get the error when the file was not completely uploaded.

附: C++操作路徑, 獲取本地日期和時間, 格式化字符串

#include <iostream>
#include <io.h>         // _access
#include <direct.h>     // _mkdir
#include <ctime>        // unix time
#include <atlstr.h>     // CString

using namespace std;

/************* 以日期和時間構造文件名 ***************/
string getVideoName(string format)
{
    // 查詢目標路徑是否存在, 不存在則新建
    const char* folder = "videos";
    if(0 != _access(folder, 0))
    {
        int ret = _mkdir(folder);
    }
    // 獲取本地日期和時間
    time_t unixtime = time(nullptr);
    struct tm now;
    localtime_s(&now, &unixtime);
    // 格式化字符串
    CString name;
    name.Format("%s/%4d-%02d-%02d %02d.%02d.%s", 
         folder, 1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday, 
         now.tm_hour, now.tm_min, format);
    return name.GetBuffer();
}

int main()
{
    cout << getVideoName("avi") << endl;   // videos/2019-12-03 20.20.avi
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章