OpenCV入门笔记


1.  图像的表示

对计算机而言,一幅图像只是一堆亮度各异的点。一副尺寸为M ×N的图像可以用一个M ×N的矩阵来表示,矩阵元素的值表示这个位置上的像素的亮度,一般来说像素值越大表示该点越亮。

一般来说,灰度图用2维矩阵表示,彩色(多通道)图像用3维矩阵(M ×N×3)表示。对于图像显示来说,目前大部分设备都是用无符号8 位整数(类型为CV_8U)表示像素亮度。

2.  常用的Mat构造函数

 Mat::Mat()无参数构造方法;

 Mat::Mat(introws, int cols, int type)创建行数为rows,列数为col,类型为type的图像;

 Mat::Mat(Sizesize, int type)创建大小为size,类型为type的图像;

 Mat::Mat(int rows, int cols, int type, constScalar& 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, inttype, 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, constRange& rowRange, const Range& colRange)创建的新图像为m的一部分,具体的范围由rowRangecolRange指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据;

Mat::Mat(const Mat& m, constRect& roi)创建的新图像为m的一部分,具体的范围roi指定,此构造函数也不进行图像数据的复制操作,新图像与m共用图像数据。

type可以是CV_8UC1,CV_16SC1,…,CV_64FC4等。里面的8U表示8位无符号整数,16S表示16位有符号整数,64F表示64位浮点数(即double类型);C后面的数表示通道数,例如C1表示一个通道的图像,C4表示4个通道的图像,以此类推。

3. create()函数创建对象

例:

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

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

使用create()函数无法设置图像像素的初始值

4. zeros(),ones()和eyes()

例:

Mat Z = Mat::zeros(2,3, CV_8UC1);

cout << "Z = " << endl<< " " << Z << endl;

Mat O = Mat::ones(2, 3, CV_32F);

cout << "O = " << endl<< " " << O << endl;

Mat E = Mat::eye(2, 3, CV_64F);

cout << "E = " << endl<< " " << E << endl;

输出:

5. 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;

6.  像素值读写

1)at()函数:如果要遍历图像,不推荐使用at()函数。优点是代码可读性高,缺点是效率不是很高。(img.at<type>(x,y))

例程代码:

#include <stdio.h>

#include <iostream>

#include "opencv2/opencv.hpp"

 

using namespace std;

using namespace cv;

 

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)

        {

            Vec3bpixel;

            pixel[0] = i % 255;//B

            pixel[1] = j % 255;//G

            pixel[2] = 0;//R

            colorim.at<Vec3b>(i,j) = pixel;

        }

    }

    imshow("grayim", grayim);

    imshow("colorim", colorim);

    waitKey(0);

    return 0;

}

2) 迭代器iterator:比较方便的遍历所有元素。(MatIterator_<type>)

例程代码:

#include <stdio.h>

#include <iostream>

#include "opencv2/opencv.hpp"

 

using namespace std;

using namespace cv;

 

int main(int argc, char* argv[])

{

    Mat grayim(600, 800, CV_8UC1);//灰度图

    Mat colorim(600, 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;//B

        (*colorit)[1] = rand() % 255;//G

        (*colorit)[2] = rand() % 255;//R

    }

    imshow("grayim", grayim);

    imshow("colorim", colorim);

    waitKey(0);

    return 0;

}

3)通过数据指针:使用IpIImage结构时经常使用,通过指针操作来访问像素十分高效,但容易出错。C/C++中的指针操作是不进行类型以及越界检查的,如果指针访问出错,程序运行时有时候可能看上去一切正常,有时候却突然弹出“段错误(segment fault)。(type*p=img.ptr<type>(i))

例程代码:

#include <stdio.h>

#include <iostream>

#include "opencv2/opencv.hpp"

 

using namespace std;

using namespace cv;

 

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)

    {

        //获取第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)

    {

        //获取第i行首像素指针

        Vec3b *p= colorim.ptr<Vec3b>(i);

        for (int j =0; j < colorim.cols; ++j)

        {

 

            p[j][0] = i % 255;//B

            p[j][1] = j % 255;//G

            p[j][2] = 0;//R

        }

    }

    imshow("grayim", grayim);

    imshow("colorim", colorim);

    waitKey(0);

    return 0;

}

7.  选取图像局部区域以及Mat表达式

1)Mat Mat::row(int i) const

MatMat::col(int j) const

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

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

2Range():

例:C =A(Range(5, 9), Range(1,3)),前半部分提取行,后半部分提取列,包含了0行0列。

Range类还提供了一个静态方法all(),表示所有的行或者列。

3)Rect(int x, int y, int width, int height):也是从(0,0)开始的

例:

Mat roi2 = img(Rect(10,10,100,100));

取对角线元素:Mat Mat::diag(int d) const

参数d=0时,表示取主对角线;当参数d>0是,表示取主对角线下方的次对角线,如d=1时,表示取主对角线下方,且紧贴主多角线的元素;当参数d<0时,表示取主对角线上方的次对角线。

4)Mat表达式:

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

l 缩放取值范围:A*alpha

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

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

l 矩阵转置:A.t()

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

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

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

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

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

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

 

例程:

#include <stdio.h>

#include <iostream>

#include "opencv2/opencv.hpp"

 

using namespace std;

using namespace cv;

 

int main(int argc, char* argv[])

{

    Mat A = Mat::eye(4, 4, CV_32SC1);

    Mat B = A * 3 + 1;

    Mat C = B.diag(0) + B.col(1);

    Mat D = B.diag(0);//取对角线,0主对角线,>0取主对角线下方的次对角线,<0取主对角线上方的次对角线

    Mat E = B.col(1);//有0、1、2、3列,这里第1列

    Mat F = B.row(1);

    Mat G = B(Range(1, 3), Range::all());

    Mat H = G(Range::all(), Range(1, 3));

    Mat I = B(Rect(0, 0, 4, 4));//(x,y,width,height)

    Mat J(B, Rect(1, 1, 3, 3));//最前3行3列,与Range不同

 

    cout << "A="<< A << endl << endl;

    cout << "B="<< B << endl << endl;

    cout << "C="<< C << endl << endl;

    cout << "C.*diag(B)=" << C.dot(B.diag(0)) << endl << endl;

    cout << "D="<< D << endl << endl;

    cout << "E="<< E << endl << endl;

    cout << "F="<< F << endl << endl;

    cout << "G="<< G << endl << endl;

    cout << "H="<< H << endl << endl;

    cout << "I="<< I << endl << endl;

    cout << "J="<< J << endl << endl;

 

    system("pause");

    return 0;

}

结果:

8.      Mat_类

它是对Mat类的一个包装。由于Mat类需要不停地指定数据类型,十分繁琐且容易出错,因此出现了Mat_类,可以在变量声明时确定元素的类型,访问元素时不再需要指定元素类型。

例:MatM(600, 800, CV_8UC1);

Mat_<uchar>M1 =(Mat_<uchar>&)M;

   例程:

#include <stdio.h>

#include <iostream>

#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)

    //{

    //  //获取第i行首像素指针

    //  uchar *p = M.ptr<uchar>(i);

    //  for (int j = 0; j < M.cols; ++j)

    //  {

    //      double d1 = (double)((i + j) % 255);

 //           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);

            M.at<uchar>(i,j) = d1;

            //直接用Matlab风格的矩阵元素读写,简洁

            M1(i, j) = d1;

            double d2= M1(i, j);

        }

    }

    system("pause");

    return 0;

}

9.  Mat与IpIImage和CvMat的转换

1)Mat转为IpIImage和CvMat格式:

以前写的函数定义:void mycvOldFunc(IplImage * p, ...);

a.转为IpIImage:

Mat img(Size(320, 240), CV_8UC3);

...

IplImage iplimg = img;//转为IplImage结构

mycvOldFunc(&iplimg, ...);//对iplimg取地址

b.如果要转为CvMat类型,操作类似:

CvMat cvimg=img;//转为CvMat

需要特别注意的是,类型转换后,IplImage和CvMat与Mat共用同一矩阵数据,而IplImage和CvMat没有引用计数功能,如果上例中的img中数据被释放,iplimg和cvimg也就失去了数据。

IpIImage和CvMat格式转为Mat格式:

Mat类有两个构造函数,可以实现IplImage和CvMat到Mat的转换。这两个函数都有一个参数copyData。如果copyData的值是false,那么Mat将与IplImage或CvMat共用同一矩阵数据;如果值是true,Mat会新申请内存,然后将IplImage或CvMat的数据复制到Mat的数据区。

Mat::Mat(const CvMat* m, bool copyData=false)

Mat::Mat(const IplImage* img, boolcopyData=false)

例子代码如下:

IplImage * iplimg= cvLoadImage("lena.jpg");

Mat im(iplimg, true);

10.读写图像文件

1)读取图像:Mat imread(const string&filename, int flags=1 )

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

flag=0,该函数返回单通道图像,如果磁盘的图像文件是多通道图像,则会被强制转为单通道;

flag<0,则函数不对图像进行通道转换。

2)写图像文件

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

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

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

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

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

 

3)Canny边缘检测算子

void cvCanny( const CvArr* image, CvArr*edges, double threshold1, double threshold2, int aperture_size=3 )

image

单通道输入图像.

edges

单通道存储边缘的输出图像

threshold1

第一个阈值

threshold2

第二个阈值

aperture_size

Sobel 算子内核大小 (见 cvSobel).

函数 cvCanny 采用 CANNY 算法发现输入图像的边缘而且在输出图像中标识这些边缘。threshold1和threshold2 当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割。

§ 注意事项:cvCanny只接受单通道图像作为输入

11. 读写视频

1)读视频

VideoCapture既可以从视频文件读取图像,也可以从摄像头读取图像。

#include <stdio.h>

#include <iostream>

#include "opencv2/opencv.hpp"

 

using namespace std;

using namespace cv;

 

int main(int argc, char* argv[])

{

    //打开视频文件

    VideoCapture cap("c00.avi");

    if (!cap.isOpened())

    {

        cerr << "Can not open a camera or file." << endl;

        return -1;

    }

    Mat edges;

    namedWindow("edges", 1);//创建窗口

    for (;;)

    {

        Matframe;

        cap >> frame;//从cap中读取一帧,存到frame中

 

        if(frame.empty())

            break;

        cvtColor(frame, edges, CV_BGR2GRAY);//转为灰度图

        Canny(edges, edges, 0, 30, 3);//进行边缘提取

        imshow("edges", edges);

        if(waitKey(30) >= 0)//等待30秒,如果按键则退出循环

            break;

    }

    return 0;

}

2)写视频

写视频需要在创建视频时设置一系列参数,包括:文件名,编解码器,帧率,宽度和高度等。编解码器使用四个字符表示,可以是CV_FOURCC('M','J','P','G')、CV_FOURCC('X','V','I','D')及CV_FOURCC('D','I','V','X')等。如果使用某种编解码器无法创建视频文件,请尝试其他的编解码器。

另外需要注意:待写入的图像尺寸必须与创建视频时指定的尺寸一致。

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值