OPENCV 4学习笔记 ——01

发布时间 2023-09-19 16:33:37作者: 成信吴彦祖(略胜亿筹)

1. 图像存储容器

  不同于字符串、整数(分别以string、int类型保存),图像时以矩阵的形式存储的,OPENCV 4提供了 Mat 类来存储矩阵数据。

  早期的OPENCV是采用 C 语言接口,以结构体IplImage来存储图像的矩阵数据,但是C语言中需要手动释放内存,这极容易导致因程序结束后没有释放内存而产生内存泄漏。OPENCV 4采用C++接口,提供的Mat类用于存储数据,利用自动内存管理技术很好的解决了内存自动释放问题,当变量不需要时内存自动释放。

  创建Mat类示例:

cv::Mat a;                           //创建一个名为a的矩阵头      
a = cv::imread( "test.jpg" ); //向a中复制图像数据,矩阵指针指向像素数据。
cv::Mat b=a;                       //赋值矩阵头并命名为b

  声明一个指定类型的Mat类:

cv::Mat A = Mat_<double>(3, 3);    //创建一个3 * 3的矩阵用于存放double类型数据

  通过opencv数据类型创建Mat类:

cv::Mat a(640, 480, CV_8UC3);  //创建640 * 480的三通道矩阵用于存放彩色图像。
cv::Mat a(4, 4, CV_8UC1);      //创建4 * 4的八位无符号整型单通道矩阵
cv::Mat a(4, 4, CV_8U);        //创建单通道矩阵,C1可以省略,是默认值

2. Mat类构造与赋值

——构造

  根据OPENCV的源码定义,关于Mat类的构造方式共有20余种,然而,在平时一些简单的应用程序种,很多复杂的构造方法并没有太多的用武之地,此处介绍几种项目中常用的构造、赋值方式。

【1】利用默认构造函数

  默认构造函数构造一个Mat类,不需要输入任何参数,后续赋值的时候会自动判断矩阵类型和大小,实现灵活存储。例:

cv::Mat::Mat();

【2】根据输入矩阵的尺寸和类型构造

  有以下几种方式:

//1. 用矩阵尺寸和参数类型构造Mat类
cv::Mat::Mat( int rows,
            int cols,
            int type
            );

//2. 用Size()结构构造Mat类(注意:size中的行列与第一种相反
cv::Mat::Mat( Size size(),
            int type 
            );

//3. 利用已有矩阵构造Mat类(注:当希望复制两个一模一样的Mat类而彼此之间不受影响,那么可以使用m = a.clone());
cv::Mat::Mat( const Mat &  m);

//4. 构造Mat类的子类
cv::Mat::Mat(const Mat & m, 
            const Range & rowRange,
            const Range & colRange = Range::all()
            );

  示例:

//示例2(此处为480行,640列,注意行列不可颠倒):
cv::Mat::Mat(Size(480, 640), CV_8UC1);  
cv::Mat::Mat(Size(480, 640), CV_32FC3);

//示例4:
cv::Mat b(a, Range(2, 5), Range(2, 5)); //从a中截取部分数据构造b
cv::Mat c(a, Range(2, 5));              //默认是最后一个参数构造c

——赋值

【1】构造时赋值

  利用该方法时,只能对一个通道中的数据赋相同的值,形如:

cv::Mat::Mat(int rows, 
            int cols, 
            int type, 
            const Scalar & s
            );

  示例:

cv::Mat a(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));
cv::Mat b(2, 2, cv_8UC2, cv::Scalar(0, 255));
cv::Mat c(2, 2, CV_8UC1, cv::Scalar(255));

  注意,scalar中的参数个数必须与前面的通道对应,如果超过通道数,则后面的参数将不会读取。而当少于通道数时后面的通道会全部赋值为0。可以在return之前设置一个断点,用image watch查看每一个Mat类变量里的数据。

【2】枚举法赋值

  这种赋值方法是将矩阵中的元素一一列举,并用数据流的形式赋值给Mat类。具体的赋值形式如下:

cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<double>(2, 3) << 1.0, 3.2, 4.0, 5.1, 6.2);

  其中枚举的数据会按行填充,先填充第一行,再填充第二行....如果枚举的数据数量与矩阵的大小不符,则会报错。

【3】循环赋值法赋值(利用双for循环)

  将矩阵看作一个二维数组,利用双循环按行以此填充数据,例如:

cv::Mat c = cv::Mat_<int>(3, 3);
for (int i = 0; i < c.rows; i++) {
    for (int j = 0; j < c.cols; j++) {
        c.at<int>(i, j) = i+j;
    }
}

  需要注意,第一行定义的变量类型时int,则循环中声明的变量类型也应该是int,如果改为 c.at<double> 则会报错。

【4】利用数组进行赋值

  该方法类似于枚举法,但是该方法可以根据需求改变Mat类矩阵的通道数,可以看作枚举法的拓展,如:

float a[8] = {5, 6, 7, 8, 1, 2, 3, 4};
cv::Mat b = cv::Mat(2, 2, CV_32FC2, a);
cv::Mat c = cv::Mat(2, 4, CV_32FC1, a);

   第一个赋值后得到的结果是一个2x2的双通道矩阵,分别存放1/2, 3/4, 5/6,  7/8。第二个赋值后得到的结果是一个2x4的单通道矩阵。