其实VideoCapture
, VideoWriter
两个类的使用并不难, 但总是遇到很奇怪的问题. 跟着《学习opencv3》练习例程的时候就碰到接口改变, fourcc码不一致的问题, 最近项目开发中又碰到了视频保存"6kb"问题, 浪费了一天的时间. 感觉应该把这一块的接口搞明白了, 因此记录下来, 以供参考.
补充:webcam 帧率设定后必须手动评估实际帧率
文章目录
摘要: 介绍在使用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: 摄像头实时显示并录制视频
纠错:webcam 帧率设定后,必须手动评估实际帧率!!!不能使用get读取!!!
#include <iostream>
#include "opencv2/highgui/highgui.hpp" // 窗口显示
#include "opencv2/core/core.hpp" // 数***算
using namespace std;
using namespace cv;
VideoCapture camera;
void get_webcam_fps(double& fps)
{
fps = camera.get(CV_CAP_PROP_FPS);
printf("Frames per second using camera.get(CV_CAP_PROP_FPS): %.2f\n", fps);
int num_frames = fps*10;
double tick_count = cv::getTickCount();
for (int i = 0; i < num_frames; i++) {
camera>> frame;
}
tick_count = cv::getTickCount() - tick_count;
double t = tick_count / cv::getTickFrequency(); // s
fps = num_frames / t;
printf("Estimated FPS: %.2f\n\n", fps);
}
int main(int argc, char* argv[])
{
// 设置参数
int cameraIndex = 0;
double frameWidth = 800;
double frameHeight = 600;
double fps = 30;
// 初始化摄像头
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); // 可能读取到错误帧率,必须手动测得
get_webcam_fps(fps); // 评估 webcam 实际帧率
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码错误: 参考代码示例中
avi
与mp4
格式的设置方法 - 录制的视频文件只有
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);
// 格式化字符串,win-vs下使用sprintf_s
char* name = new char[50];
sprintf_s(name, 50, "%s/%4d-%02d-%02d %02d.%02d.mp4", folder,
1900 + now.tm_year, 1 + now.tm_mon, now.tm_mday,
now.tm_hour, now.tm_min);
return name;
}
int main()
{
cout << getVideoName("avi") << endl; // videos/2019-12-03 20.20.avi
return 0;
}