图像的基本操作

图像的表示

  在正式介绍之前,先简单介绍一下数字图像的基本概念。如图 3.1 中所示 的图像,我们看到的是 Lena 的头像,但是计算机看来,这副图像只是一堆亮度 各异的点。一副尺寸为 M × N 的图像可以用一个 M × N 的矩阵来表示,矩 阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越 亮。如图 3.1 中白色圆圈内的区域,进行放大并仔细查看,将会如图 3.2 所 示。
 

一般来说,灰度图用 2 维矩阵表示,彩色(多通道)图像用 3 维矩阵(M × N × 3)表示。对于图像显示来说,目前大部分设备都是用无符号 8 位整 数(类型为 CV_8U)表示像素亮度。 图像数据在计算机内存中的存储顺序为以图像最左上点(也可能是最左下 点)开始,存储如表 3-1 所示。

Iij 表示第 i 行 j 列的像素值。如果是多通道图像,比如 RGB 图像,则每个 像素用三个字节表示。在 OpenCV 中,RGB 图像的通道顺序为 BGR ,存储如 表 3-2 所示。

Mat类

  早期的 OpenCV 中,使用 IplImage 和 CvMat 数据结构来表示图像。IplImage 和 CvMat 都是 C 语言的结构。使用这两个结构的问题是内存需要手动管理,开 发者必须清楚的知道何时需要申请内存,何时需要释放内存。这个开发者带来了 一定的负担,开发者应该将更多精力用于算法设计,因此在新版本的 OpenCV 中 引入了 Mat 类。

  新加入的 Mat 类能够自动管理内存。使用 Mat 类,你不再需要花费大量精 力在内存管理上。而且你的代码会变得很简洁,代码行数会变少。但 C++接口唯 一的不足是当前一些嵌入式开发系统可能只支持 C 语言,如果你的开发平台支持 C++,完全没有必要再用 IplImage 和 CvMat。在新版本的 OpenCV 中,开发者依 然可以使用 IplImage 和 CvMat,但是一些新增加的函数只提供了 Mat 接口。本书 中的例程也都将采用新的 Mat 类,不再介绍 IplImage 和 CvMat。

  Mat 类的定义如下所示,关键的属性如下方代码所示:

class CV_EXPORTS Mat
{
    public:
        //一系列函数
        ...
        /* flag 参数中包含许多关于矩阵的信息,如:
        -Mat 的标识
        -数据是否连续
        -深度
        -通道数目
        */
        int flags;
        //矩阵的维数,取值应该大于或等于 2
        int dims;
        //矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
        int rows, cols;
        //指向数据的指针
        uchar* data;


        //指向引用计数的指针
        //如果数据是由用户分配的,则为 NULL
        int* refcount;

        //其他成员变量和成员函数
        ...
};

创建 Mat 对象

  Mat 是一个非常优秀的图像类,它同时也是一个通用的矩阵类,可以用来创 建和操作多维矩阵。有多种方法创建一个 Mat 对象。

 构造函数方法

  Mat 类提供了一系列构造函数,可以方便的根据需要创建 Mat 对象。下面是 一个使用构造函数创建对象的例子。

Mat M(3,2, CV_8UC3, Scalar(0,0,255));  
cout << "M = " << endl << " " << M << endl; 

  第一行代码创建一个行数(高度)为 3,列数(宽度)为 2 的图像,图像元 素是 8 位无符号整数类型,且有三个通道。图像的所有像素值被初始化为(0, 0, 255)。由于 OpenCV 中默认的颜色顺序为 BGR,因此这是一个全红色的图像。

  第二行代码是输出Mat类的实例M的所有像素值。Mat重定义了<<操作符, 使用这个操作符,可以方便地输出所有像素值,而不需要使用 for 循环逐个像素 输出。

Mat的常见构造方法

常用的构造函数有:
   Mat::Mat() 无参数构造方法;
   Mat::Mat(int rows, int cols, int type) 创建行数为 rows,列数为 col,类型为 type 的图像;
   Mat::Mat(Size size, int type) 创建大小为 size,类型为 type 的图像;
   Mat::Mat(int rows, int cols, int type, const Scalar& s) 创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始 化为值 s;
   Mat::Mat(Size size, int type, const Scalar& s) 创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s;
   Mat::Mat(const Mat& m) 将 m 赋值给新创建的对象,此处不会对图像数据进行复制,m 和新对象 共用图像数据;
   Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP) 创建行数为 rows,列数为 col,类型为 type 的图像,此构造函数不创建 图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step 指定。
   Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP) 创建大小为 size,类型为 type 的图像,此构造函数不创建图像数据所需 内存,而是直接使用 data 所指内存,图像的行步长由 step 指定。
   Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange) 创建的新图像为 m 的一部分,具体的范围由 rowRange 和 colRange 指 定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数 据;
   Mat::Mat(const Mat& m, const Rect& roi) 创建的新图像为 m 的一部分,具体的范围 roi 指定,此构造函数也不进 行图像数据的复制操作,新图像与 m 共用图像数据。

  这些构造函数中,很多都涉及到类型type。type可以是CV_8UC1,CV_16SC1, …, CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F 表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个 通道的图像,C4 表示 4 个通道的图像,以此类推。
  如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:
  Mat M(3,2, CV_8UC(5));//创建行数为 3,列数为 2,通道数为 5 的图像

create()函数创建对象

  除了在构造函数中可以创建图像,也可以使用 Mat 类的 create()函数创建图 像。如果 create()函数指定的参数与图像之前的参数相同,则不进行实质的内存 申请操作;如果参数不同,则减少原始数据内存的索引,并重新申请内存。使用 方法如下面例程所示:

    Mat M(2,2, CV_8UC3);//构造函数创建图像

    M.create(3,2, CV_8UC2);//释放内存重新创建图像

矩阵的基本元素表达

  对于单通道图像,其元素类型一般为8U(即8位无符号整数),当然也可以是16S、32F等;这些类型可以直接用uchar、short、float等C/C++语言中的基本数据类型表达。如果多通道图像,如RGB彩色图像,需要用三个通道来表示。在这种情况下,如果依然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型。27OpenCV中有模板类Vec,可以表示一个向量。OpenCV中使用Vec类预定义了一些小向量,可以将之用于矩阵元素的表达。

    typedef Vec<uchar, 2> Vec2b;
    typedef Vec<uchar, 3> Vec3b;
    typedef Vec<uchar, 4> Vec4b;
    typedef Vec<short, 2> Vec2s;
    typedef Vec<short, 3> Vec3s;
    typedef Vec<short, 4> Vec4s;
    typedef Vec<int, 2> Vec2i;
    typedef Vec<int, 3> Vec3i;
    typedef Vec<int, 4> Vec4i;
    typedef Vec<float, 2> Vec2f;
    typedef Vec<float, 3> Vec3f;
    typedef Vec<float, 4> Vec4f;
    typedef Vec<float, 6> Vec6f;
    typedef Vec<double, 2> Vec2d;
    typedef Vec<double, 3> Vec3d;
    typedef Vec<double, 4> Vec4d;
    typedef Vec<double, 6> Vec6d;

  例如8U类型的RGB彩色图像可以使用Vec3b,3通道float类型的矩阵可以使用Vec3f。

  对于Vec对象,可以使用[]符号如操作数组般读写其元素,如:

  

Vec3b color; //变量描述一种RGB颜色
color[0]=255; //B分量
color[1]=0; //G分量
color[2]=0; //R分量

像素值的读写

很多时候,我们需要读取某个像素值,或者设置某个像素值;在更多的时候,我们需要对整个图像里的所有像素进行遍历。OpenCV提供了多种方法来实现图像的遍历。

at()函数

    函数at()来实现读去矩阵中的某个像素,或者对某个像素进行赋值操作。下面两行代码演示了at()函数的使用方法。
uchar value = grayim.at<uchar>(i,j);//读出第i行第j列像素值
grayim.at<uchar>(i,j)=128; //将第i行第j列像素值设置为128
    如果要对图像进行遍历,可以参考下面的例程。这个例程创建了两个图像,分别是单通道的grayim以及3个通道的colorim,然后对两个图像的所有像素值进行赋值,最后现实结果。

 

#include<iostream>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍历图像,并不推荐使用at()函数。使用这个函数的优点是代码的可读性比较高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    Mat grayim(600,800,CV_8UC1);
    Mat colorim(600,800,CV_8UC3);

    //遍历所有元素,并设置像素值
    for(int i=0;i<grayim.rows;i++)
        for(int j=0;j<grayim.cols;j++)
            grayim.at<uchar>(i,j)=(i+j)%255;

    //遍历所有像素,并且设置像素值
    for(int i=0;i<colorim.rows;i++)
        for(int j=0;j<colorim.cols;j++)
        {
            Vec3b pixel;
            pixel[0]=i%255;
            pixel[1]=j%255;
            pixel[2]=0;
            colorim.at<Vec3b>(i,j)=pixel;
        }
    //显示结果
    imshow("grayim",grayim);
    imshow("colorim",colorim);
    waitKey(0);
    return 0;
}

 

  如果你熟悉C++的STL库那么你一定了解迭代器的使用。迭代器可以方便的遍历所有的元素。Mat也增加了迭代器的支持,一边矩阵元素的便利。下面的实例功能跟上一节的比较类似,但是由于使用了迭代器,而不是使用行数和列数来便利所有这儿没有了i和j变量,像素的像素值为一个随机数。

#include<iostream>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍历图像,并不推荐使用at()函数。使用这个函数的优点是代码的可读性比较高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    Mat grayim(680,800,CV_8UC1);
    Mat colorim(680,800,CV_8UC3);

    //遍历所有元素,并设置像素值
    MatIterator_<uchar> grayit,grayend;
    for(grayit=grayim.begin<uchar>(),grayend=grayim.end<uchar>();grayit!=grayend;grayit++)
        *grayit=rand()%255;

    MatIterator_<Vec3b> colorit,colorend;
    for(colorit=colorim.begin<Vec3b>(),colorend=colorim.end<Vec3b>();colorit!=colorend;colorit++)
    {
        (*colorit)[0]=rand()%255;//Blue
        (*colorit)[1]=rand()%255;//Green
        (*colorit)[2]=rand()%255;//Red
    }
    imshow("yuan",grayim);
    imshow("chong",colorim);
   // printf("asdddddddddddddd");
    waitKey(0);
    return 0;
}

 

通过数据指针

  使用iplimage结构的时候,我们会经常使用数据指针来直接操作像素。通过指针操作来访问像素值非常高效的,但是C/C++中的指针操作是不进行类型以及是否越界检查的,如果指针的访问出错程序运行时有时候可能看上去一切正常但是,有时候会弹出来一个“段错误”(segment fault)。

  当程序的规模比较大,并且逻辑十分复杂的时候,查找指针错误就十分困难。对于不熟悉指针的编程者来说指针就如同噩梦。如果你对指针使用没有自信,则不建议使用。如果非常注重程序的运行速度,那么遍历象素的时候,建议使用指针。


0.783

#include<iostream>
#include<time.h>
#include<stdlib.h>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
/*需要注意的是:
    如果要遍历图像,并不推荐使用at()函数。使用这个函数的优点是代码的可读性比较高但是at的效率不是很高*/
int main(int argc,char* argv[])
{
    clock_t start,finish;          //计时之用
    double totalTime;               //计时之用
    start=clock();              //计时之用


    Mat grayim(680,800,CV_8UC1);
    Mat colorim(680,800,CV_8UC3);

    //遍历所有元素,并设置像素值
    for(int i=0;i<grayim.rows;i++)
    {
        uchar *p=grayim.ptr<uchar>(i);
        for(int j=0;j<grayim.cols;j++)
        {
            p[j]=(i+j)%255;
        }
    }
    for(int i=0;i<colorim.rows;i++)
    {
        Vec3b *p=colorim.ptr<Vec3b>(i);
        for(int j=0;j<colorim.cols;j++)
        {
            p[j][0]=j%255;
            p[j][1]=j%255;
            p[j][2]=j;
        }
    }
    imshow("yuan",grayim);
    imshow("chong",colorim);
    finish=clock();//计时之用
    totalTime=(double)(finish-start)/CLOCKS_PER_SEC;//计时之用
    printf("------------%lf------------\n",totalTime);
    waitKey(0);
    return 0;
}

 

 

 


单行或者单列选择

  提取矩阵的一行或者一列可以使用row()或者col()。函数的声明如下:

Mat Mat::row(int i) const
Mat Mat::col(int j) const

 

  参数i和j分别是行标和列标。例如取出A矩阵的第i行可以使用如下代码:

    Mat line = A.row(i);

  例如取出A矩阵的第i行,将这一行的所有元素都乘以2,然后赋值给第j行可以这样写:

    A.row(j) = A.row(i)*2;

  

用range选择多行或多列

  Range是OpenCV中新增的类,该类有两个关键变量star和end。Range对象可以用来表示矩阵的多个连续的行或者多个连续的列。其表示的范围为从start到end,包含start但不包含end。Range类的定义如下。

class range
{
public:
    ...
    int start,end;
};

 

  Range类还提供了一个静态方法all(),这个方法的作用如同Matlab中的“:”,表示所有的行或者列。

//创建一个单位阵
Mat A = Mat::eye(10,10,CV_32S);
//提取第一行到第三行(不包括三)
Mat B = A(Range::all(),Range(1,3));
//提取B的第5至第9行(不包括9)
//其实等价于C = A(Range(5,9),Range(1,3))
Mat C = B(Range(5,9),Range::all());

 

感兴趣的区域

  从头像中提取感兴趣的区域有两种方法,一种是使用构造函数,如下例所示:

//创建宽度为320,高度为241,的三通道图像
Mat img(Size(320,240),CV_8UC3);
//roi是表示img中rect(10,10,100,100)区域的对象
Mat roi(img,rect(10,10,100,100));
//除了使用构造函数,还可以使用括号运算符,如下:
Mat roi2=img(Rect(10,10,100,100));
//当然也可以使用Range运算符来定义感兴趣对象,如下:
Mat roi3=img(Range(10,100),Range(10,100));

 

Mat表达式

  利用C++中的运算符重载,OpenCV2中引入了Mat运算表达式。这一特点使得用C++进行编程的时候,就如同写Matlab脚本一样,代码变得简洁易懂,也便于维护。

  如果矩阵A和B大小相同,则可以使用如下表达式:

    C=A+B+1;

      其执行结果是矩阵A和B大小相同,则可以使用如下表达式:

        C=A+B+1;

  其执行结果是A和B的元素相加,然后再加上1,并将生成的矩阵赋值给C变量。

  下面是Mat表达式所支持的运算。下面的列表中使用A和B表示Mat类型的对象,使用s表示Scalar对象,alpha表示double值。

    *加法,减法,取负:A+B,A-B,A+s,A-s,s+A,s-A,-A

    *缩放取值范围:A*alpha

    *矩阵对应元素的乘法和除法:A.mul(B),A/B ,alpha/A

    *矩阵乘法:A*B(此处是矩阵乘法,而不是矩阵对应元素相乘)

    *矩阵转置:A.t()

    *矩阵求逆和求伪逆:A.inv()

    *矩阵比较运算:A cmpop B,A cmpop alpha , alphga cmpop A。此处的cmpop可以是>,>=,==,!=,<=,<。如果条件成立则结果矩阵?(8U类型矩阵)的对应元素被设置为255,否则设置为0。

    *矩阵位逻辑运算:A logicop B,A logicop s , s logicop A,~A,此处logicop可以是&,|和^。

    *矩阵对应元素的最大值和最小值:min(A,B), min(A,alpha), max(A,B), max(A,alpha)。

    *矩阵中元素的绝对值:abs(A)

    *叉积和点积:A.cross(B),A.dot(B)

  下面的例程展示了Mat表达式的使用方法,例程的输出结果如下所示。

    

#include<iostream>
#include<stdio.h>
#include"opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc,char* argv[])
{
    Mat M(600,800,CV_8UC1);

    for(int i=0;i<M.rows;i++)
    {
        uchar *p=M.ptr<uchar>(i); //声明一个uchar指针,并且将矩阵M的第i行头指针赋给该指针,单位是uchar。
        for(int j=0;j<M.cols;j++)
        {
            double d1=(double)((i+j%255));
            //用at读写像素的时候需要指定类型。
            M.at<uchar>(i,j)=d1;
            //下面的代码错误,应该使用at<uchar>()
            //但是编译时不会提醒错误
            //运行结果不正确,d2不等于d1.
            double d2 = M.at<double>(i,j);
        }
    }
    //在变量声明时指定矩阵元素类型
    Mat_<uchar> M1 = (Mat_<uchar>&)M;
    for(int i=0;i<M1.rows;i++)
    {
        uchar *p=M1.ptr(i);
        for(int j=0;j<M1.cols;j++)
        {
            double d1 = (double)((i+j)%255);
            M1(i,j)=d1;
            double d2=M1(i,j);
        }
    }
    return 0;
}

 

Mat类的内存管理

  使用Mat类,内存管理变得简单,不再像使用iplimage那样需要自己申请和释放内存了。虽然不了解Mat的内存管理机制,也无妨Mat类的使用,但是如果清楚了解Mat的内存管理机制,会更清楚一些函数到底执行了那些数据。

  Mat是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,储存方法,储存地址等信息)和一个指向储存所有像素值的矩阵的指针,如图所示矩阵头的尺寸是常数值,但是矩阵的尺寸会依图像的不同而不同,通常比矩阵头大数个数量级。复制矩阵数据往往需要花费较多的时间,所以一般情况下不要赋值较大矩阵,能够复用就尽量复用。

  为了解决矩阵数据的传递,OpenCV使用了引用计数机制。其思路是让每个Mat对象有自己的矩阵头信息,但是多个Mat对象可以共享一个矩阵数据。让矩阵指针指向同一地址而实现这一目的。很多函数以及很多操作(比如函数参数传递),支付至矩阵头信息,而不复制矩阵数据。

  前面提到过,有很多方法创建Mat类。如果Mat类自己申请数据空间,那么会多申请4个字节,多出的四个字节储存数据被引用的次数。引用次数储存于数据空间的后面,refcount指向这个位置,如图所示当计数为0的时候就释放该空间。

 关于多个矩阵对象共享同一矩阵数据,我们可以看这个例子:

  

Mat A(100,100,CV_8UC1);

Mat B=A;

Mat C = A(Rect(50,50,30,30));

 

  

上面的代码有三个Mat对象,分别是A,B和C。这三者共有同一矩阵数据其示意图如:

  

 

输出

  从前面的例程,可以看到Mat类重载了<<运算符,可以方便的使用溜操作来输出矩阵的内容。默认情况下输出的格式是类似于Matlab中矩阵的输出格式。除了默认格式,Mat也支持其他的输出格式。代码如下:

    首先创建一个矩阵,并用随机数填充。填充的范围由randu()函数的第二个和第三个参数去诶的那个,下面代码是介于0到255之间。

  

 1 #include<iostream>
 2 #include<fstream>
 3 #include<iostream>
 4 #include<stdio.h>
 5 #include"opencv2/opencv.hpp"
 6 #include<stdlib.h>
 7 using namespace std;
 8 using namespace cv;
 9 int main(int argc,char* argv[])
10 {
11     FILE *f;
12     ofstream fileio("C:\\Users\\xpower\\Desktop\\jack.txt",ios::out|ios::in);
13 
14     Mat R = Mat(300,200,CV_8UC3);
15 
16     randu(R,Scalar::all(0),Scalar::all(255));//填充的范围由randu()参数和第三个参数确定,下面代码是介于0到255之间。
17 
18 //默认格式输出的代码如下:
19     imshow("jack",R);
20 
21     //cout<<"R (default) = "<<endl<<R<<endl<<endl;
22     fileio<<R<<endl;
23     fileio.close();
24     printf("end!\n");
25     waitKey(0);
26 }

 

Mat与iplimage和CvMat的转换

  在OpenCV2中虽然引入了方便的Mat类,处于兼容性的考虑,OpenCV依然支持C语言接口的iplimage和CvMat结果,如果想要和以前的代码兼容将会涉及到Mat与iplimage和CvMat的转换。

Mat转为IpLimage和CvMat的转换

  。。。。。

 读写图像文件

  将图像文件读入内存,可以使用imread()函数;将Mat对象以图像文件格式写入内存,可以使用imwrite()函数。

读取图像文件

  imread函数返回的是Mat对象,如果读取文件失败,则会返回一个空矩阵,即Mat::data的值食NULL.执行imread()之后, 需要检查文件是否读入成功, 你可以使用Mat::empty()函数进行检查。imread()函数的声明如下。

Mat imread(const string &filename,int flag=1)

 很明显参数filename就是被读取或者保存的文件名。在imread()函数中flag的取值有三种情况:

    flag>0, 该函数返回3通道图像,如果磁盘上的图像文件时单通道的则会被强制转换为三通道;

    flag=0,该函数返回单通道图像,如果磁盘的文件是强制转化为单通道图像。

    flag<0, 函数不对图像做通道转换。

imread()函数支持多种文件格式,且该函数是根据图像文件的内容来确定文件格式,而不是根据文件的扩展名来确定,文件格式名如下。

  Windows位图文件-BMP,DIB;

  JPEG文件-JPEG,JPG,JPE;

  便携式网络图片-PNG

  便携式图像格式-PBM,PGM,PPM;

  Sun rasters - SR,RAS;

  TIFF文件-TIFF,TIF

  OpenEXR HDR图片,EXR

  JPEG图片 - jp2

你所安装的OpenCV并不一定能支持上述所有格式,文件格式的支持需要特定的库,只有在编译OpenCV添加了相应的的文件格式库,才可支持其格式,。

写图像文件

  将图像写入文件,可以使用imwrite()函数,该函数的声明如下:

bool imwrite(const string &filename,InputArray image,const vector<int> &params=vector<int>())

 

  文件的格式由filename参数指定的文件扩展名确定。推荐使用PNG文件格式。BMP格式是无损格式,但是一般不进行压缩,文件尺寸非常大;JPEG格式的文件娇小,但是JPEG是有损压缩,会丢失一些信息。PNG是无损压缩格式,推荐使用。

imwrite()函数的第三个参数params可以指定文件格式的一些细节信息。这个参数里面的数值是跟文件格式相关的:

    *JPEG:表示图像的质量,取值范围从0到100。数值越大表示图像质量越高,当然文件也越大。默认值是95。

    *PNG:表示压缩级别,取值范围是从0到9。数值越大表示文件越小,但是压缩花费的时间也越长。默认值是3。

    *PPM,PGM或PBM:表示文件是以二进制还是纯文本方式存储,取值为0或1。如果取值为1,则表示以二进制方式存储。默认值是1。

  并不是所有的Mat对象都可以存为图像文件,目前支持的格式只有8U类型的单通道和3通道(颜色顺序为BGR)矩阵;如果需要要保存16U格式图像,只能使用PNG、JPEG 2000和TIFF格式。如果希望将其他格式的矩阵保存为图像文件,可以先用Mat::convertTo()函数或者cvtColor()函数将矩阵转为可以保存的格式。
  另外需要注意的是,在保存文件时,如果文件已经存在,imwrite()进行提醒,将直接覆盖掉以前的文件。

下面例程展示了如何读入一副图像,然后对图像进行Canny边缘操作,最后将结果保存到图像文件中。

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

int main(int argc,char *argv[])
{
    //读入图像并将之转化为单通道图像
    Mat im = imread("C:\\Users\\xpower\\Desktop\\niao.jpg",0);
    //检查是否读取成功
    if(im.empty())
    {
        cout<<"读取文件失败"<<endl;
        return -1;
    }
    //进行Canny操作,并将结果储存于result
    Mat result;
    Canny(im,result,50,150);
    //保存结果
    imwrite("C:\\Users\\xpower\\Desktop\\niao.jpg",result);
    imshow("niaoniao",result);
    waitKey(0);
}

 

读写视频

  介绍OpenCV读写视频之前,先介绍一下编解码器(codec)。如果是图像文件,我们可以根据文件扩展名得知图像的格式。但是此经验并不能推广到视频文件中。有些OpenCV用户会碰到奇怪的问题,都是avi视频文件,有的能用OpenCV打开,有的不能。
  视频的格式主要由压缩算法决定。压缩算法称之为编码器(coder),解压算法称之为解码器(decoder),编解码算法可以统称为编解码器(codec)。视频文件能读或者写,关键看是否有相应的编解码器。编解码器的种类非常多,常用的有MJPG、XVID、DIVX等,完整的列表请参考FOURCC网站3。因此视频文件的扩展名(如avi等)往往只能表示这是一个视频文件。

  OpenCV 2中提供了两个类来实现视频的读写。读视频的类是VideoCapture,写视频的类是VideoWriter。

读视频

  VideoCapture既可以从视频文件读取图像,也可以从摄像头读取图像。可以使用该类的构造函数打开视频文件或者摄像头。如果VideoCapture对象已经创建,也可以使用VideoCapture::open()打开,VideoCapture::open()函数会自动调用VideoCapture::release()函数,先释放已经打开的视频,然后再打开新视频。

  如果要读一帧,可以使用VideoCapture::read()VideoCapture类重载了操作符,实现了读视频帧的功能。下面的例程演示了使用VideoCapture类读视频。

 1 #include<iostream>
 2 #include"opencv2/opencv.hpp"
 3 using namespace std;
 4 using namespace cv;
 5 int main(int argc,char** argv)
 6 {
 7 
 8     //打开第一个摄像头
 9     //VideoCapture cap(0);
10 
11     //打开视频文件
12     VideoCapture cap("video.short.raw.avi");// 文件名就是这个。
13     if(!cap.isOpened())
14     {
15         cerr<<"Can not open a camera or file."<<endl;
16         return -1;
17     }
18     Mat edges;
19     namedWindow("jackchen",1);
20     int i=0;
21     for(;;)
22     {
23         cerr<<i<<endl;
24         i++;
25         Mat frame;
26         //从cap中读一帧,存到frame。
27         cap>>frame;
28         //如果没有读到图像
29         if(frame.empty())
30         {
31            // cerr<<"没有读取到图像!"<<endl;
32             break;
33         }
34         //将读取到的图像转化为灰度图
35         cvtColor(frame,edges,CV_BGR2GRAY);
36         //进行边缘提取操作
37         Canny(edges,edges,0,30,3);
38         //显示结果
39         imshow("edges",edges);
40         if(waitKey(30)>=0)
41             break;
42 
43     }
44     return 0;
45 }

 

写视频

  使用OpenCV创建视频也非常简单,与毒食品不同的是,你需要在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示,可以是CV_FOURCC('M','J','P','G'), CV_FOURCC('X','V','I','D')以及CV_FOURCC('D','I','V','X');等。如果使用编码器无法创建视频文件,请尝试用其他的编码器。

  将图像写入视频可以用VideoWrite::Write函数,VideoWrite类中也重载了<<运算符,使用起来非常方便。另外需要注意的是待写入图像的尺寸必须和写入图像的尺寸一致。

  下面的例程演示了如何写视频文件。本例程将生成一个视频文件,视频的第0帧上是一个红色的“0”,第一帧上是一个红色的“1”,以此类推共100帧。

#include<stdio.h>
#include<iostream>
#include"opencv2/opencv.hpp"

using namespace std;
using namespace cv;


int main(int argc,char **argv)
{
    //定义视频的高度和宽度
    Size s(320,240);
    //创建Write,并指定Fourcc和FPS等参数
    VideoWriter writer = VideoWriter("MyVideo.avi",CV_FOURCC('M','J','P','G'),25,s);
    //检查是否创建成功
    if(!writer.isOpened())
    {
        cerr<<"创建视频文件失败\n";
        return -1;
    }
    //视频帧
    Mat frame(s,CV_8UC3);
    for(int i=0;i<100;i++)
    {
        //将图像设置成黑色
        frame=Scalar::all(0);
        //将整数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;
}

 

posted @ 2016-11-21 15:10  X-POWER  阅读(2040)  评论(0编辑  收藏  举报