OpenCV入门教程(8)-视频文件的读取和存储

1 编解码器
视频的压缩算法称为编码器;那么,与之对应的解压缩算法就是解码器。想要了解详细的codec编解码器知识,可以参考FOURCC网站。
在了解编解码器之前,我们先来学习一个概念:FourCC。它的全称是Four Charactors Code,称为四字符码,是一种独立标示视频数据流格式。在FOURCC网站你可以得到完整的基于FourCC的各种各样的编解码器。
我们通过这个标识符,就可以寻找与 FourCC 代码相关联的视频解码器来播放特定的视频流。
FOURCC的定义如下:

typedef unsigned int FOURCC;

一般用宏生成FOURCC,FOURCC是由4个字符拼接而成的,生成FOURCC的传统方法是:

#define MAKE_FOURCC(a,b,c,d) \
        (((uint32_t)d) | (((uint32_t)c) << 8) | (((uint32_t)b) << 16) | (((uint32_t)a) << 24))

这种方法的意思显而易见,我们可以使用下面一个模型进行操作:

switch(val)
{
    case MAKE_FOURCC('f','m','t',' '):
        .....
        break;
    case MAKE_FOURCC('Y','4','4','2'):
        ....
        break;
        ...
}

因为宏能生成常量,符合case 的条件。
难道要退回古老的宏?当然不是,C++的模板机制给程序员带来的无限的空间,它不光能让类型作为参数,还能将常量作为参数(这一点常常被人遗忘),而且这一切都是编译期决定的!这是我们用它来生成FOURCC的第1个基础。
于是,我们迫不及待地利用模板来改写上面的函数:

template <char ch0, char ch1, char ch2, char ch3>inline FOURCC MakeFOURCC()
{ 
    return (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24);
}

可是错误照旧。虽然这次可以保证返回值能在编译期计算出来,但可惜的是那个return语句却要等到运行才能运行(也有可能在优化阶段就能消除这个语句,但肯定不能再编译期就全部完成)。
别急,还有第2个基础才可以。那是什么?是一个从C语言继承来的东西――enum。很多朋友认为,它不是很重要,因为很多情况下可以用别的方法来取代它,比如const。但是它有一个经常被人忽略的特性,而且这个特性非常重要,那就是――它的值必须在编译期就得出,即它是个编译期常量!这不是正符合我们的需要吗?请看下面的模板:

template <char ch0, char ch1, char ch2, char ch3> struct MakeFOURCC
{
    enum
    {
        value = (ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24)
    };
};

核心还是和上面一样,通过表达式(ch0 << 0) + (ch1 << 8) + (ch2 << 16) + (ch3 << 24)计算FOURCC(那当然是一样的)。但是计算的时机从运行期或者优化期移到了编译期。编译器在编译时,通过模板带入的char常量计算出表达式的值,并把它保存在枚举值value里。看看现在的代码:

const FOURCC fccFMT = MakeFOURCC<'f', 'm', 't', ' '>::value;
const FOURCC fccDATA = MakeFOURCC<'d', 'a', 't', 'a'>::value;
switch (val)
{
    case fccFMT:
        //... 
        break;
    case fccDATA:
        //... 
        break;
}

成功了,MakeFOURCC模板顺利地完成了任务。FOURCC的模板生成法既让我们抛弃了那个不安全的宏,又让我们看到了inline的局限性,还让我们重新认识了enum的一些特性。其它许多类似的问题也能通过template + enum来解决。
OpenCV 2 中提供了两个类来实现视频的读写:读视频的类是 VideoCapture;写视频的类是 VideoWriter。
2 读视频
VideoCapture 既可以从视频文件读取图像,也可以从摄像头读取图像。可以使用该类的构造函数打开视频文件或者摄像头。如果 VideoCapture 对象已经创建,也可以使VideoCapture::open()打开,VideoCapture::open()函数会自动调用VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频。
如果要读一帧,可以使用 VideoCapture::read()函数。VideoCapture 类重载了>>操作符,实现了读视频帧的功能。下面的例程演示了使用 VideoCapture 类读视频。

#include <QCoreApplication>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(void)
{
    //打开第一个摄像头
    VideoCapture cap(0);
    //打开视频文件
    //VideoCapture cap("myvideo.avi");

    //检查是否成功打开
    if(!cap.isOpened())
    {
        cerr << "Can not open a camera or file." << endl;
        return -1;
    }

    Mat edges;
    //创建窗口
    namedWindow("edges",1);
    for(;;)
    {
        Mat frame;
        //从 cap 中读一帧,存到 frame
        cap >> frame;
        //如果未读到图像
        if(frame.empty())
            break;
        //将读到的图像转为灰度图
        cvtColor(frame, edges, CV_BGR2GRAY);
        //进行边缘提取操作
        Canny(edges, edges, 0, 30, 3);
        //显示结果
        imshow("edges", edges);
        //等待 30 秒,如果按键则推出循环
        if(waitKey(30) >= 0)
            break;
    }
    //退出时会自动释放 cap 中占用资源
    return 0;
}

下图是运行结果,打开视频和摄像头是一样的(下图是我本人的图像):
这里写图片描述
3 写视频
使用 OpenCV 创建视频也非常简单,与读视频不同的是,你需要在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示,可以是CV_FOURCC(‘M’,’J’,’P’,’G’)、
CV_FOURCC(‘X’,’V’,’I’,’D’)及CV_FOURCC(‘D’,’I’,’V’,’X’)等。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。
将图像写入视频可以使用 VideoWriter::write()函数,VideoWriter 类中也重载了<<操作符,使用起来非常方便。另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致。
下面例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第 0帧上是一个红色的“0”,第 1 帧上是个红色的“1”,以此类推,共 100 帧。生成视频的播放效果如下图所示。

#include <stdio.h>
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;

// 25帧/s
#define FRM_PER_SEC         (25)

int main(void)
{
    //定义视频的宽度和高度
    Size s(320, 240);
    //创建 writer,并指定 FOURCC 及 FPS 等参数
    VideoWriter  writer = VideoWriter("myvideo.avi",CV_FOURCC('M','J','P','G'), FRM_PER_SEC, s);
    //检查是否成功创建
    if(!writer.isOpened())
    {
        cerr << "Can not create video file.\n" << endl;
        return -1;
    }

    //视频帧
    Mat frame(s, CV_8UC3);
    for(int i = 0; i < 100; i++)
    {
        //将图像置为黑色
        frame = Scalar::all(0);
        //将整数 i 转为 i 字符串类型
        char text[128];
        snprintf(text, sizeof(text), "%d", i);

        //将数字绘到画面上
        putText(frame, text, Point(s.width/3, s.height/3), FONT_HERSHEY_SCRIPT_SIMPLEX, 3,Scalar(0,0,255), 3, 8);

        //将图像写入视频
        writer << frame;
    }
    //退出程序时会自动关闭视频文件
    return 0;
}

下面是视频截图:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值