上一个教程 : 使用 OpenCV 和相似度测量输入视频
下一个教程 : 使用 Kinect 和其他兼容 OpenNI 的深度传感器
原作者 | Bernát Gábor |
---|---|
兼容性 | OpenCV >= 3.0 |
目标
每当您处理视频馈送时,您可能最终希望以新视频文件的形式保存图像处理结果。对于简单的视频输出,您可以使用 OpenCV 内置的 cv::VideoWriter 类。
- 如何使用 OpenCV 创建视频文件
- 使用 OpenCV 可以创建哪些类型的视频文件
- 如何从视频中提取指定颜色通道
作为一个简单的演示,我将从输入的视频文件中提取一个 BGR 颜色通道到一个新视频中。您可以通过控制台行参数来控制应用程序的流程:
- 第一个参数指向要处理的视频文件
- 第二个参数可以是以下字符之一 这将指定提取哪个通道。
- 最后一个参数是字符 Y(是)或 N(否)。如果是 “否”,则输入视频文件使用的编解码器将与输出视频文件相同。否则,将弹出一个窗口,允许您自行选择要使用的解码器。
例如,有效的命令行如下
video-write.exe video/Megamind.avi R Y
源代码
您也可以在 OpenCV 源代码库的 samples/cpp/tutorial_code/videoio/video-write/ 文件夹中找到源代码和这些视频文件,或从此处下载。
#include <iostream> // 用于标准 I/O
#include <string> // 用于字符串
#include <opencv2/core.hpp> // OpenCV 基本结构 (cv::Mat)
#include <opencv2/videoio.hpp> // 视频写入
using namespace std;
using namespace cv;
static void help()
{
cout
<< "------------------------------------------------------------------------------" << endl
<< "This program shows how to write video files." << endl
<< "You can extract the R or G or B color channel of the input video." << endl
<< "Usage:" << endl
<< "./video-write <input_video_name> [ R | G | B] [Y | N]" << endl
<< "------------------------------------------------------------------------------" << endl
<< endl;
}
int main(int argc, char *argv[])
{
help();
if (argc != 4)
{
cout << "Not enough parameters" << endl;
return -1;
}
const string source = argv[1]; // 源文件名
const bool askOutputType = argv[3][0] =='Y'; // 如果为假,将使用输入的编解码器类型
VideoCapture inputVideo(source); // 打开输入
if (!inputVideo.isOpened())
{
cout << "无法打开输入视频: " << source << endl;
return -1;
}
string::size_type pAt = source.find_last_of('.'); // 查找扩展点
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi"; // 使用容器形成新名称
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC)); // Get Codec Type- Int form
// 通过比特运算符将 int 转换为 char
char EXT[] = {(char)(ex & 0XFF) ,(char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24), 0};
Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH), // 获取输入大小
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
VideoWriter outputVideo; // 打开输出
if (askOutputType)
outputVideo.open(NAME,ex=-1,inputVideo.get(CAP_PROP_FPS),S,true);
else
outputVideo.open(NAME, ex, inputVideo.get(CAP_PROP_FPS), S, true);
if (!outputVideo.isOpened())
{
cout << "无法打开输出视频进行写入: " << source << endl;
return -1;
}
cout << "输入帧分辨率: 宽度=" << S.width << " 高度=" << S.height
<< " of nr#: " << inputVideo.get(CAP_PROP_FRAME_COUNT) << endl;
cout << "输入编解码器类型: " << EXT << endl;
int channel = 2; // 选择要保存的通道
switch(argv[2][0])
{
case 'R' : channel = 2; break;
case 'G' : channel = 1; break;
case 'B' : channel = 0; break;
}
Mat src, res;
vector<Mat> spl;
for(;;) //显示窗口中捕获的图像并重复
{
inputVideo >> src; // 读取
if (src.empty()) break; // 检查是否已结束
split(src, spl); // 处理 - 仅提取正确的通道
for (int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
merge(spl, res);
//outputVideo.write(res); //保存或
outputVideo << res;
}
cout << "Finished writing" << endl;
return 0;
}
视频的结构
首先,您应该了解视频文件的外观。每个视频文件本身就是一个容器。容器的类型由文件扩展名表示(如 avi、mov 或 mkv)。它包含多种元素,如:视频源、音频源或其他轨道(如字幕)。这些素材的存储方式由每个素材所使用的编解码器决定。音频轨道常用的编解码器是 mp3 或 aac。视频文件的编解码器则更长,包括 XVID、DIVX、H264 或 LAGS(Lagarith Lossless Codec)。您可以在系统中使用的全部编解码器列表取决于您安装的编解码器。
正如您所看到的,视频方面的事情可能会变得非常复杂。不过,OpenCV 主要是一个计算机视觉库,而不是一个视频流、编解码器和写入库。因此,开发人员试图让这部分尽可能简单。因此,用于视频容器的 OpenCV 仅支持其第一个版本的 avi 扩展名。这样做的一个直接限制是,你无法保存超过 2 GB 的视频文件。此外,您只能在容器内创建和扩展单个视频轨。这里不支持音频或其他音轨编辑。不过,您系统中的任何视频编解码器都可以使用。如果遇到上述限制,你需要了解更专业的视频编写库,如 FFmpeg 或 HuffYUV、CorePNG 和 LCL 等编解码器。作为替代方案,您可以使用 OpenCV 创建视频音轨,然后用音轨进行扩展,或者使用 VirtualDub 或 AviSynth 等视频处理程序将其转换为其他格式。
视频写入器类
这里所写的内容基于以下假设:您已经阅读过 OpenCV 视频输入和相似性测量 教程,并且知道如何读取视频文件。要创建视频文件,您只需创建一个 cv::VideoWriter 类的实例。您可以在构造函数中通过参数指定其属性,也可以稍后通过 cv::VideoWriter::open 函数指定其属性。无论哪种方式,参数都是一样的: 1. 输出名称,其扩展名中包含容器类型。目前只支持 avi。我们从输入文件中构建该名称,在其中添加要使用的通道名称,最后加上容器扩展名。
const string source = argv[1]; // 源文件名
string::size_type pAt = source.find_last_of('.'); // 查找扩展点
const string NAME = source.substr(0, pAt) + argv[2][0] + ".avi"; // 使用容器形成新名称
- 视频轨要使用的编解码器。现在,所有视频编解码器都有一个最多四个字符的唯一简短名称。因此,就有了 XVID、DIVX 或 H264 名称。这就是所谓的四字符代码。您也可以通过使用输入视频的 get 函数来查询。因为 get 函数是一个通用函数,它总是返回双数值。双数值存储在 64 位上。四个字符是四个字节,即 32 位。这四个字符被编码在 double 值的低 32 位。扔掉上面 32 位的简单方法是将该值转换为 int:
VideoCapture inputVideo(source); // 打开输入端
int ex = static_cast<int>(inputVideo.get(CAP_PROP_FOURCC)); // Get Codec Type- Int form
OpenCV 内部使用这种整数类型,并将其作为第二个参数。现在,要将整数形式转换为字符串,我们可以使用两种方法:位运算符和联合方法。第一种方法是从 int 中提取字符("和 "运算、移位并在字符串末尾添加 0 以关闭字符串):
char EXT[] = {ex & 0XFF , (ex & 0XFF00) >> 8,(ex & 0XFF0000) >> 16,(ex & 0XFF000000) >> 24, 0};
你也可以用 union 做同样的事情:
union { int v; char c[5];} uEx ;
uEx.v = ex; // 通过联合从 Int 到 char
uEx.c[4]='\0';
这样做的好处是在赋值后自动完成转换,而对于位运算符,则需要在改变编解码器类型时进行操作。如果事先知道编解码器的四位字符代码,可以使用 CV_FOURCC 宏来生成整数:
CV_FOURCC('P','I','M,'1') // 这是一个从字符到整数的 MPEG1 编解码器。
如果将该参数减一,运行时将弹出一个窗口,其中包含系统中安装的所有编解码器,并要求您选择要使用的编解码器:
- 输出视频的每秒帧数。同样,在这里我使用 get 函数保持输入视频的每秒帧数。
- 输出视频的帧大小。这里我也是通过 get 函数保留输入视频的每秒帧数。
- 最后一个参数是可选参数。默认为 true,表示输出将是彩色的(因此写入时将发送三通道图像)。要创建灰度视频,请在此处输入假参数。
下面是我在示例中的使用方法:
VideoWriter outputVideo.Size S = Size((int)
Size S = Size((int) inputVideo.get(CAP_PROP_FRAME_WIDTH), //获取输入尺寸
(int) inputVideo.get(CAP_PROP_FRAME_HEIGHT));
outputVideo.open(NAME , ex, inputVideo.get(CAP_PROP_FPS),S, true);
之后,您可以使用 cv::VideoWriter::isOpened() 函数查看打开操作是否成功。当 VideoWriter 对象被销毁时,视频文件会自动关闭。成功打开对象后,您可以使用该类的 cv::VideoWriter::write 函数按顺序发送视频帧。或者,也可以使用其重载操作符 << :
outputVideo.write(res); //or
outputVideo << res;
从 BGR 图像中提取一个颜色通道意味着将其他通道的 BGR 值设为零。您可以通过图像扫描操作或使用分割和合并操作来实现这一点。首先将通道分割成不同的图像,然后将其他通道置零,最后将大小和类型相同的图像合并:
split(src, spl); // 处理--只提取正确的通道
for( int i =0; i < 3; ++i)
if (i != channel)
spl[i] = Mat::zeros(S, spl[0].type());
merge(spl, res);
将所有这些合并在一起,您将得到上部源代码,其运行结果将显示与该想法大致相同的内容:
您可以在 YouTube 上看到运行时的实例。