[openCV]视频的读入与一些简单的图像处理

1.序言

作为一个计算机图像处理的工具,因为免费公开呀,版权等等的原因,openCV开始展现出一些无可取代的优势(已经展露好久了好吧)。嘛,之前我研究生阶段,一直再用MATLAB在干活,总感觉调试呀,验证啊,还是MATLAB要好用一些呢!不久前,自己也开始接触了openCV,总感觉虽然都是工具,但是水很深的样子。嘛,总是还是开个关于openCV的坑,记录下自己的学习的轨迹吧。
自带吐槽1:基础的图像处理部分的坑,还有一些没有整理完,等过几天开始整理完后,再开始填坑吧,不算烂尾不算烂尾(= =!!自我催眠)
自带吐槽2:不过话说也没谁真的在等着看吧(= = 好桑心!)

2.视频的读入与图像的显示

2.1从AVI文件中读取图像

对于图像处理来说,最开始的实验代码,基本都是从读取一幅图像,然后将其显示出来开始入手的。这是图像处理的“Hello World!”。openCV里将一枚图像读入,并显示的代码如下所示。

IplImage* ori= cvLoadImage("..\\Data\\Fig0320(4) (bottom_left).tif",Gray_image);    
cvNamedWindow("Original", CV_WINDOW_AUTOSIZE );   //new window
cvShowImage("Original",ori);        //show

其实也就相当于MATLAB里面的imread()imshow()figure()这三个函数;这里值得说的是,如果要让窗口内图片跟着窗口一起扩大的话,可以使用式样CV_WINDOW_NORMAL,也就是如下代码所示。

cvNamedWindow("Original", CV_WINDOW_NORMAL );   //new window
cvShowImage("Original",ori);        //show

对于视频的读入,其实和图像是差不多一样的,使用函数
cvCreateFileCapture(argv[1])去读入一段视频。然后通过函数cvQueryFrame(capture)将其一帧一帧的取出来,然后再用显示图像的方式(cvShowImage()),将其按照一定的时间间隔,逐帧显示出来。

基本思路就是上述那样,这里按照教材上的做法,加入了一个滑块控件作为进度显示的。其代码实现如下(代码基本和教材一致= =)

//------------------------------------------------------
// 内容:
//       视频「江ノ電」的读入
//       视屏的播放
//       滑块控件的添加
//     
//                                    2015.5.18   
//                                    by zhou fan
// ------------------------------------------------------


#include "stdafx.h"

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

using namespace cv;  
using namespace std;  

int g_slider_position = 0;
CvCapture* capture = NULL;

void onTrackbarSilde(int pos)
{
    cvSetCaptureProperty(capture,
                         CV_CAP_PROP_POS_FRAMES,
                         //CV_CAP_PROP_POS_AVI_RATIO,
                         pos
                         );
}


int main(int argc, char** argv[])
{
    cvNamedWindow("ENoDenn" , CV_WINDOW_AUTOSIZE );

    capture = cvCreateFileCapture("..\\Data\\IMG_2023.AVI") ;

    //读取视频的总帧数
    int frames = (int) cvGetCaptureProperty(capture,        
                                            CV_CAP_PROP_FRAME_COUNT);
    if(frames != 0)     // 视频有效的话,添加滑块控件
    {
        cvCreateTrackbar("Position",
                         "ENoDenn",
                         &g_slider_position,
                         frames,
                         onTrackbarSilde
                         );
    }   

    IplImage* frame;

    while(1)
    {
        frame = cvQueryFrame(capture); //按帧读取
        if(!frame) break;   

        cvSetTrackbarPos("Position",   //更新滑块的位置
                         "ENoDenn",
                          ++g_slider_position
                         );

        cvShowImage("ENoDenn",frame);
        char c = cvWaitKey(33);  //等待33ms,帧数30每秒
        if(c == 27)  break;
    }

    cvReleaseCapture(&capture);
    cvDestroyWindow("ENoDenn");

    return (int)0;
}

执行结果如下图所示。
这里写图片描述
这里值得说的是函数

cvCreateTrackbar("Position",
                  "ENoDenn",
                 &g_slider_position,
                 frames,
                 onTrackbarSilde
                 );

其最后一个参数为onTrackbarSilde,这个函数可以设置(或者来讲指定)某一帧。比如,将滑块拖动到115帧的位置,那么这个函数可以使得视频从115帧开始播放。如果将这里改为NULL的话,用户拖动滑块到115帧,仅仅只会使得滑块从115开始往下更新,并不会使得视频从115帧位置开始播放。

使用函数cvSetTrackbarPos()可以设置滑块的位置。使用函数cvGetTrackbarPos()可以Get到滑块的现在的位置。

自带吐槽3:视频拍摄于上个月,地点为镰仓高校门前的信号灯,也就是灌篮高手OP的那个信号灯。

2.1从Camera读取图像

从摄像头读入视频文件其实也很简单。需要使用函数CvCapture* cvCreateCameraCapture(int index)去指定Camera。一般的话,这里取CV_CAP_ANY也就是任何位置读取图像。如果在有多个摄像头状态下,就可以改变index去指定想要的摄像头了。如果没有摘到摄像头,或者发生Error的话,会得到一个空指针NULL。
图像的读入与显示,和上面的代码是一样的,使用通过函数cvQueryFrame(capture)将其一帧一帧的取出来,然后再用显示图像的方式cvShowImage(),将其按照一定的时间间隔,逐帧显示出来。

int main(int argc, char** argv[])
{
    cvNamedWindow("Camera" , CV_WINDOW_AUTOSIZE );

    CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY);

    assert(capture != NULL);

    IplImage *frame = 0;
    frame = cvQueryFrame(capture);

    while(1)
    {
        frame = cvQueryFrame(capture);
        if(!frame) break;
        cvShowImage("Camera",frame);
        char c = cvWaitKey(33);
        if(c == 27)  break;
    }   
    return (int)0;
}

运行结果就不贴了,打开了摄像头而已。

3.一些图像的基本操作

图像与视频确认可以读取之后,就可以开始进行一些比较基础的图像处理了。比如,图像平滑(低通滤波),图像的缩小与扩大(降采样与升采样),图像边缘的检测等等。

3.1创建一幅图像任意的图像

在使用MATKLAB处理的图像时候,一般都会创建一副与对象图像等大的图像。然后将其初始化为同样的格式,全部赋值为0。在openCV中,使用
IplImage* cvCreateImage( CvSize size, int depth, int channels )
去创建一副图像,这个函数有3个参数,分别是尺寸,格式和通道数。比如,创建一副与对象图像(灰度图像)等大的图像,并将其赋值为0的话,可以如下操作。

IplImage* image_edge = cvCreateImage(cvGetSize(image_input),
                                     IPL_DEPTH_8U,
                                     1);
cvZero(image_edge);                 

这里使用了cvGetSize()去获取图片的尺寸。想要设置特定的尺寸的话,可以按照如下这样去处理。

IplImage* image_edge = cvCreateImage(cvSize(200,200),
                                     IPL_DEPTH_8U,
                                     1);
cvZero(image_edge);                 

如上处理的话,可以得到一张200x200的全0图像。

第二个参数IPL_DEPTH_8U为图像的格式,有如下格式。

IPL_DEPTH_8U - 无符号8bit整型
IPL_DEPTH_8S - 有符号8bit整型
IPL_DEPTH_16U - 无符号16bit整型
IPL_DEPTH_16S - 有符号16bit整型
IPL_DEPTH_32S - 有符号32bit整型
IPL_DEPTH_32F - 单精度浮点型
IPL_DEPTH_64F - 双精度浮点型

第三个参数为通道数,比如RGB图像的话,就可以使用3个通道去存储这幅图像。

3.2 图像平滑

图像平滑相当于低通滤波,这一点在我之前的图像处理基础博文中讲得挺清楚了(自我感觉)。
[数字图像处理]空间滤波
[数字图像处理]频域滤波(1)–基础与低通滤波器
在openCV中,可以使用cvSmooth()去平滑一幅图像。比如如下代码。

IplImage* Image_smooth(IplImage* image_input)
{
    IplImage* image_output = cvCreateImage(cvGetSize(image_input),
                                           IPL_DEPTH_8U,
                                           1);

    cvSmooth(image_input,image_output,CV_GAUSSIAN,13,13);

    return(image_output);
}

这里我默认使用的是13x13的高斯低通滤波器,其运行结果如下所示。
这里写图片描述
可以看到,右边的图像比左边模糊了,其实其本质是由于高频成分被滤掉了。嘛,这里其实很有必要贴出两张图像的傅里叶频谱的,不过这是后面的博文的事了,后面会说到傅里叶频谱的计算与现实。(神速填坑啦!!!)
[openCV]图像的傅里叶频谱

3.2 图像的缩小

对于图像的缩小,其原理可以参看我以前的博文,那里也说明了缩小与放大操作的问题点与解决对策。
[数字图像处理]数字图像的整数倍扩大(数字图像插值)
[数字图像处理]数字图像的整数倍缩小
[数字图像处理]数字图像的有理数倍缩放(缩小与放大)
在openCV中,可以使用cvPyrDown()cvPyrUp()去对一副图像进行缩小与放大。

IplImage* doPyrDown(IplImage* image_input)
{
    assert(image_input->width%2 == 0 && image_input->height%2 == 0);  //error判断

    IplImage* image_output = cvCreateImage(cvSize(image_input->width/2,image_input->height/2),
                                           image_input->depth,
                                           image_input->nChannels);

    cvPyrDown(image_input,image_output);

    return(image_output);
}

运行结果如下所示。
这里写图片描述
图像的长和宽分别缩小了一半,面积变为了原来的0.25倍。嘛,cvPyrUp()与之类似,就是在相邻的信号间插入0,然后全图用低通滤波器过一遍。结果呢,cvPyrDown()cvPyrUp()只能是进行2的倍数的扩大与缩小。其实我想说,如果能实现其他倍数的扩大与缩小的话,可以通过扩大M倍,再缩小N倍的方式,去实现M/N倍的缩放了。参考我之前的博文,其实可以试试去实现的(恩,愉快的挖个坑吧)。

3.3 图像边缘检测

这个也就是熟悉了一下canny边缘检测算子,贴个代码过去吧。原理部分,找个时间补博文再讨论一下吧(再挖一个坑,+_+,乖乖,貌似填不完了)。

IplImage* doCanny(IplImage* image_input,
                  double lowThresh,
                  double highThresh,
                  double aperture)
{
    if(image_input->nChannels != 1)
        return (0);

    IplImage* image_output = cvCreateImage(cvGetSize(image_input),
                                           image_input->depth,
                                           image_input->nChannels);

    cvCanny(image_input,image_output,lowThresh,highThresh,aperture);

    return(image_output);
}

运行结果如下所示。
这里写图片描述

3.4 图像的ROI(Region Of Interest)操作

也就是将图像某一个矩形区域,设置为ROI区域,然后对这个已经设置好的区域进行操作。设置的函数为
void cvSetImageROI(IplImage* image, CvRect rect)
CvRect是一个图像内的矩形区域的构造体,其内有4个成员int x , y , width , height。代表了矩形区域的起点坐标(x,y)与其尺寸(长与宽)width与height。当然,其实ROI区域也可以设定为其他形状的。

将已经设置的ROI区域的解除,可以用如下函数去实现。
void cvResetImageROI(IplImage* image)
这两个函数在实践上挺有意义的,可以用于图像的局部操作,可以加快运算的速度。

作为一个ROI区域的联系,贴个代码吧。

IplImage* RIODomainAdd(IplImage* image_input,
                      CvRect RIO_Domain)
{
    if(image_input->nChannels != 1)
        return (0);

    IplImage* image_edge = cvCreateImage(cvGetSize(image_input),
                                         IPL_DEPTH_8U,
                                         1);
    image_edge = doCanny(image_input,10,100,3);

    cvSetImageROI(image_input,RIO_Domain);
    cvSetImageROI(image_edge,RIO_Domain);   
    //cvAddS(image_input,cvScalar(100),image_input);

    cvAddWeighted(image_input,
                  0.8,
                  image_edge,
                  0.8,
                  0,
                  image_input);     //image_input = image_input * 0.2 + image_edge * 0.8; (ROI領域のみ) 

    cvResetImageROI(image_input);

    return(image_input);
}

运行结果如下所示。
这里写图片描述

4. 结言

我的openCV的第一篇,就这样结束吧。虽然木有什么关系,但是还想说一句,Markdown太好用了!!!接下来真想尝试一下用Latex去写数学公式了。(恩?是不是以前的博文也全更新一遍呢)。
接下来,会更新一个直方图的处理,与傅里叶频谱的程序。预计本周之内完事吧。

原文发于博客:http://blog.csdn.net/thnh169/

=============更新日志===================

2015 - 5 - 19 初版完事
2015 - 5 - 21 添加了从摄像头读取视频的代码

猜你喜欢

转载自blog.csdn.net/thnh169/article/details/45843577
今日推荐