CV02-07:OpenCV视频采集

  OpenCV的机器视觉包含静态图像处理(2D与3D)与动态视频处理,本主题主要梳理下C++的视频采集处理。


OpenCV视频处理结构

结构图

OpenCV视频在处理结构

核心类说明

VideoCapture类

  • 负责抓取视频,抓取源支持:照相机,视频文件,图形序列等。

VideoWriter

  • 负责写视频数据到文件,包括压缩等。

VideoCapture视频抓取

构造器

  • 一共三个构造器
    1. 默认构造器
    2. 视频设备构造器
    3. 视频文件构造器器
  1. 默认构造器
    • 默认构造器没有初始化设备,需要调用open函数初始化设备。

    cv::VideoCapture::VideoCapture( )   

  1. 视频设备构造器
    
    cv::VideoCapture::VideoCapture( 
        int     index,                                                  // 视频设备编号,从0开始
        int     apiPreference = CAP_ANY                     // 视频处理的底层API。FFMPEG,IMAGES,DSHOW
    )
        
  • FFMPEG:开源的视频处理框架;
  • IMAGES:图片序列(比如:GIF);
  • DSHOW:微软的DirectX中视频模块DSHOW;
  1. 视频文件构造器

    cv::VideoCapture::VideoCapture( 
        const String &       filename,                              // 视频文件路径。
        int                       apiPreference = CAP_ANY      // 视频处理的底层API。
    )

设备初始化

  • 使用open函数实现设备初始化。
    • 与构造器相同。
  1. 使用视频文件初始化
    virtual bool open (const String &filename, int apiPreference=CAP_ANY)
  1. 使用视频设备初始化
    virtual bool open (int index, int apiPreference=CAP_ANY)

判别设备是否初始化

  • isOpened函数
    virtual bool    isOpened () const

读取数据

  • 读取数据两种方式
    1. 直接读取
    2. 多设备情况下,需要先抓取缓冲,然后读取;

直接读取数据

  • 使用read函数与操作符读取;
  1. read函数读取
    virtual bool read (OutputArray image)
  • OutputArray image返回抓取的视频图像。
  1. >>操作符读取
    virtual VideoCapture& cv::VideoCapture::operator >>(Mat &   image   )   

缓冲读取

  • 缓冲读取分成两步:
    1. 使用grap函数抓取到缓冲;
    2. 使用retrieve函数返回视频图像;
  1. grap函数
    • 抓取视频图像成功,返回true;否则返回false
    virtual bool    grab ()
  1. retrieve 函数
    • 返回抓取的图像,比read多一个读取标记;
    virtual bool retrieve (
        OutputArray image, 
        int flag=0)                 // 用来指定图像的帧索引,或者底层IP的读取标记;

属性处理

  • 提供了set/get来设置与获取设备与底层属性
  1. set函数
     virtual bool   set (int propId, double value)
  1. get函数
    virtual double  get (int propId) const

释放设备

  • 使用release函数释放
    virtual void    release()

视频抓取应用例子

  • 例子的思路:
    • 创建一个线程,不同抓取视频图像,然后用信号的方式发送给窗体显示;

抓取图像的线程

  1. 头文件
    #ifndef VIDEO_THRWAD_H
    #define VIDEO_THRWAD_H
    #include <opencv2/opencv.hpp>
    #include <QThread>

    class VideoThread : public QThread{
        Q_OBJECT
    public:
        VideoThread();
        VideoThread(int deviceid);
        ~VideoThread();

    private:
        cv::VideoCapture  *device;
    protected:
        virtual void run();

    // 信号
    signals:
        void video(cv::Mat img);
    };
    #endif // !VIDEO_THRWAD_H

  1. 实现文件
    #include "videoth.h"
    #include<iostream>

    // 构造器
    VideoThread::VideoThread():
        device(new cv::VideoCapture(0)){
    }
    VideoThread::VideoThread(int deviceid):
        device(new cv::VideoCapture(deviceid)){

    }
    // 析构器
    VideoThread::~VideoThread(){
        device->release();
        delete device;
    }

    // 线程主方法
    void VideoThread::run(){
        while(true){
            // 抓取图像,发送信息
            cv::Mat img;
            device->read(img);
            emit video(img);
            QThread::usleep(100);

        }
    }

图像处理类

  1. 头文件
    #ifndef IMAGE_PROCESSING_H
    #define IMAGE_PROCESSING_H

    #include <opencv2/opencv.hpp>

    class ImageProcessing{
    public:
        ImageProcessing();
        ~ImageProcessing();
    public:
        void handle_filter2d(cv::InputArray img_src, cv::OutputArray img_out);
    };
    #endif // !IMAGE_PROCESSING_H
  1. 实现文件

    #include "imageprocessing.h"

    ImageProcessing::ImageProcessing(){

    }
    ImageProcessing::~ImageProcessing(){

    }
    void ImageProcessing::handle_filter2d(cv::InputArray img_src, cv::OutputArray img_dst){
        cv::Mat kernel = (cv::Mat_<int>(3,3) << 
            -1,-1,-1,
             0, 0, 0,
             1, 1, 1);

        cv::filter2D(
            img_src, 
            img_dst,
            -1,                 // 输出图像的深度
            kernel,             // 滤波核
            cv::Point2i(-1, -1),    // 锚点
            64.0,                  // 正向(加法运算)偏移值
            cv::BORDER_REFLECT_101
        );
    }

UI文件的设计

UI文件设计

信号传递的用户类型处理

  1. 引入头文件

    • #include<QMetaType>
  2. 申明需要注册的类型

    • Q_DECLARE_METATYPE(cv::Mat);
  3. 注册类型

    • qRegisterMetaType<cv::Mat>("Mat");
    • qRegisterMetaType<cv::Mat>("Mat&");
  4. 在主程序中注册的完整代码

    #include <QApplication>
    #include "dlgvideo.h"

    #include <iostream>
    #include<QMetaType>
    Q_DECLARE_METATYPE(cv::Mat);   

    int main(int argc, char* argv[]) {
        // 初始化QT应用
        QApplication app(argc, argv);
        qRegisterMetaType<cv::Mat>("Mat");
        qRegisterMetaType<cv::Mat>("Mat&");
        DlgVideo dlg;
        dlg.show();
        return app.exec();
    }

对话框类

  1. 头文件
    #ifndef DLG_VIDEO_H
    #define DLG_VIDEO_H
    #include <opencv2/opencv.hpp>
    #include <QDialog>
    #include "ui_output.h"
    #include "videoth.h"
    #include "imageprocessing.h"

    class DlgVideo: public QDialog{
    Q_OBJECT
    public:
        DlgVideo(QWidget *parent = 0); 
        ~DlgVideo();
    private:
        Ui::dlg_video  *dlg;
        VideoThread *th;
        ImageProcessing  *proc;
    private slots:
        void record();
        void showImage(cv::Mat img);
    };
    #endif

  1. 实现文件
    #include "dlgvideo.h"
    #include <iostream>
    // 构造器与析构器
    DlgVideo::DlgVideo(QWidget *parent):
            QDialog(parent),
            dlg(new Ui::dlg_video()),
            th(new VideoThread()),
            proc(new ImageProcessing()){
            dlg->setupUi(this);  
            QObject::connect(th, SIGNAL(video(cv::Mat)), this, SLOT(showImage(cv::Mat)));
            th->start();
    }
    DlgVideo::~DlgVideo(){
            th->terminate();
            delete dlg;     // 释放对话框
            delete th;      // 线程对象释放
            delete proc;
    }

    // 线程信号发过来的图像,并显示
    void DlgVideo::showImage(cv::Mat img){
        if(img.empty()) return;   // 图像非空才显示
        cv::Mat img_src;
        proc->handle_filter2d(img, img_src);
        cv::Mat  m_out;
        // OpenCV到Qt的颜色格式转换
        cv::cvtColor(img_src, m_out, cv::COLOR_BGR2RGBA); 
        // 转换为Qt图像
        QImage i_out(m_out.data, m_out.cols, m_out.rows, QImage::Format_RGBA8888);
        // 转换为组件能处理的像素格式
        QPixmap p_out = QPixmap::fromImage(i_out);
        // 显示图像
        dlg->lbl_video->setPixmap(p_out);
        // 缩放图像适应组件大小
        dlg->lbl_video->setScaledContents(true);
    }

    // 事件处理
    void DlgVideo::record(){

    }

Makefile

  • 这个文件使用了文件处理函数,来生成moc文件;因为有多个moc文件需要生成,所以还采用了循环。
  • opencv的视频处理需要的共享库:opencv_videoio

INCLUDES    =-I/Users/yangqiang/Qt512/5.12.1/Src/qtbase/include \
             -I/Users/yangqiang/Qt512/5.12.1/Src/qtbase/include/QtCore \
             -I/Users/yangqiang/Qt512/5.12.1/Src/qtbase/include/GtGui \
             -I/Users/yangqiang/Qt512/5.12.1/Src/qtbase/include/QtWidgets \
             -I/Users/yangqiang/Qt512/5.12.1/Src/qtbase/include/QThread \
             -I/usr/local/include/opencv4/
LIBS        =-lopencv_core \
             -lopencv_highgui \
             -lopencv_imgcodecs \
             -lopencv_imgproc \
             -lopencv_videoio
FRAMEWORKS  =-F/Users/yangqiang/Qt512/5.12.1/clang_64/lib \
             -framework QtCore  \
             -framework QtGui  \
             -framework QtWidgets
LD_ARGS     =-Wl,-rpath,/Users/yangqiang/Qt512/5.12.1/clang_64/lib
UICTOOL     =/usr/local/Qt-5.12.1/bin/uic
MOCTOOL     =/usr/local/Qt-5.12.1/bin/moc
HEADER      =dlgvideo.h videoth.h
MOCS        =$(HEADER:%.h=moc_%.cpp)
SOURCES     =main.cpp dlgvideo.cpp videoth.cpp imageprocessing.cpp
UICS        =ui_output.ui
UICH        =$(UICS:%.ui=%.h)

main: $(SOURCES) $(UICH) $(MOCS)
    g++ -omain $(SOURCES) $(MOCS) -std=c++11 $(INCLUDES) $(LIBS)  $(FRAMEWORKS) $(LD_ARGS)

$(MOCS): $(HEADER)
    $(foreach VAR, $(HEADER), $(MOCTOOL) -o $(VAR:%.h=moc_%.cpp) $(VAR);)

$(UICH): $(UICS)
    $(UICTOOL) -o $(UICS:%.ui=%.h)  $(UICS)

clean:
    rm -f main $(MOCS)  $(UICS:%.ui=%.h)  moc_*.cpp


视频采集效果:添加了Prewitt查分计算

VideoWriter视频存储

  • 视频存储的API与VideoCapture类似,主要包含:
    1. 构造器
    2. 初始化
      • virtual bool open (const String &filename, int fourcc, double fps, Size frameSize, bool isColor=true)
    3. 是否初始化
      • virtual bool isOpened () const
    4. 写入
      • virtual void write (InputArray image)
      • virtual VideoWriter & operator<< (const Mat &image)
    5. 释放;
      • virtual void release ()
    6. 属性设置
      • virtual double get (int propId) const
      • virtual bool set (int propId, double value)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,183评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,850评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,766评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,854评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,871评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,457评论 1 311
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,999评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,914评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,465评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,543评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,675评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,354评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,029评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,514评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,616评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,091评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,685评论 2 360

推荐阅读更多精彩内容