2.1 图像存储容器Mat类介绍

  • 1.Mat类介绍
  • 2.Mat类构造与赋值
  • 2.1Mat类的构造
  • 2.1.1 采用默认构造函数
  • 2.1.2 根据输入矩阵尺寸和类型构造
  • 2.1.3 利用已有矩阵构造Mat类
  • 2.2Mat类的赋值
  • 2.2.1 构造时赋值
  • 2.2.2 枚举法赋值
  • 2.2.3 循环法赋值
  • 2.2.4 类方法赋值
  • 2.2.5 利用数组进行赋值
  • 2.3Mat类支持的运算
  • 2.3.1Mat类加减乘除运算
  • 2.3.2Mat类矩阵之间的乘法、点积和内积
  • 2.4 Mat类元素的访问
  • 2.4.1 通过at方法读取Mat类矩阵中的元素
  • 2.4.2 通过指针ptr读取Mat类矩阵中的元素
  • 2.4.3 通过迭代器访问Mat类矩阵中的元素


1.Mat类介绍

  Mat类分为【矩阵头】和指向存储数据的【矩阵指针】两部分。【矩阵头】中包含矩阵的尺寸、存储方法、地址和引用次数等。【矩阵头】的大小是一个常数,不会随着矩阵尺寸的大小而改变。图像复制和传递过程中主要的开销是存放矩阵数据。为了解决这个问题,在OpenCV中复制和传递图像时,只是复制了【矩阵头】和指向存储数据的【矩阵指针】。因此在创建Mat类时可以先创建矩阵头后赋值数据,其方法如下:

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

  上述代码首先创建了一个名字为a的矩阵头,之后读入一张图像,并将a中矩阵指针指向该图像的像素数据,最后将a矩阵头中的内容复制到b矩阵头中。虽然a、b有各自的矩阵头,但是其矩阵指针指向的是同一个矩阵数据,通过任意一个矩阵头修改矩阵中的数据,另一个矩阵头指向的数据会跟着发生变化。但是当删除a变量时,b变量不会指向一个空数据,只有当两个变量都删除后,才会释放矩阵数据。因为矩阵头中引用次数标记了某个矩阵数据的次数,只有当矩阵数据引用次数为0的时候,才会释放矩阵数据。
  接下来讲解Mat类里可以存储的数据类型。根据官方给出的Mat类集成关系图,我们可以发现Mat类可以存储的数据类型包含double、float、uchar、unsigned char,以及自定义的模板等。

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

如下表是OpenCV中的数据类型及取值范围。

  仅有数据类型是不够的,还需要定义图像数据的通道(Channel)数,例如灰度图图像数据是单通道数据,彩色图像数据是3通道或者3通道数据。用C1、C2、C3、C4分别表示单通道、双通道、3通道和4通道。如下代码的方式创建一个生命通道数和数据类型的Mat类:

cv::Mat a(640, 480, CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像
    cv::Mat b(3, 3, CV_8UC1);     // 创建一个3*3的8位无符号证书的单通道矩阵
    cv::Mat c(3, 3, CV_8U);       // 同b,C1可以省略

2.Mat类构造与赋值

  前一节介绍了3中构造Mat类变量的方法,如下:

//方法1
cv::Mat a;//创建一个名为a的矩阵头
a=cv::imread("../test.jpg");//向a中赋值图像数据,矩阵指针指向像素数据
//方法2
cv::Mat A=cv::Mat_<double>(3,3);//创建一个3*3的矩阵用于存放double类型数据
//方法3
cv::Mat a(640, 480, CV_8UC3); // 创建一个640*480的3通道矩阵用于存放彩色图像

后两种没有给变量初始化赋值,本小姐介绍如何构造并赋值Mat类变量。

2.1Mat类的构造

2.1.1 采用默认构造函数

cv::Mat::Mat();

这种构造方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型大小,实现灵活的存储。

2.1.2 根据输入矩阵尺寸和类型构造

cv::Mat::Mat(int rows,int cols, int type);

rows是行数,cols是列数,type是数据类型。数据类型的通道数是可以设置为多个的,如CV_8UC(n),n最大可以为512.
也可以用Size()结构构造Mat类

cv::Mat::Mat(Size size(), int type);

  size()是二维数组变量尺寸,通过Size(cols,rows)进行赋值。在Size()结构里,矩阵的行和列与之前是相反的,不要弄错。实例如下:

cv::Mat a(cv::Size(480, 640), CV_8UC1);  // 构造一个行为640、列为480的单通道矩阵
cv::Mat b(cv::Size(480, 640), CV_32FC3); // 构造一个行为640、列为480的3通道矩阵

2.1.3 利用已有矩阵构造Mat类

cv::Mat::Mat(const Mat& m);

  m是已经构建完成的Mat类型矩阵。这种方式只是复制了Mat类的矩阵头,矩阵指针指向的是同一个地址。因此,如果通过某一个Mat类变量修改了矩阵中的数据,那么另一个变量中的数据也会发生变化。如果希望复制两个一模一样的Mat类而彼此之间不会收到影响,那么可以使用m=a.clone()实现。

构造已有Mat类的子类

cv::Mat::Mat(
	const Mat& m,
	const Range& rowRange,
	const Range& colRange=Range::all()
	);

m是已经构建完成的Mat类矩阵数据。rowRange是需要截取的行数范围,colRange是需要截取的列数范围。注意:这种方式构造的子类与原图像享有共同的数据,原Mat类数据发生变化后,子类也会发生变化。实例如下:

cv::Mat a(cv::Size(480, 640), CV_8UC1);          // 构造一个行为640、列为480的单通道矩阵
cv::Mat b(a, cv::Range(2, 5), cv::Range(2, 6));  // 从a中截取2-5行,2-6列到b
cv::Mat c(a, cv::Range(2, 5), cv::Range::all()); // 从a中截取2-5行,所有列到c

2.2Mat类的赋值

  构造Mat类后需要为其赋值,OpenCV4.1提供了多种赋值方式。

2.2.1 构造时赋值

  利用Scalar在构造Mat类的时候为其赋值

cv::Mat a(2, 2, CV_8UC3, cv::Scalar(0, 0, 255)); // 创建一个3通道矩阵,每个像素都是0,0,255
    cv::Mat b(2, 2, CV_8UC2, cv::Scalar(0, 255));    // 创建一个2通道矩阵,每个像素都是0,255
    cv::Mat c(2, 2, CV_8UC1, cv::Scalar(255));       // 创建一个1通道矩阵,每个像素都是255
    std::cout << "a:\n"
              << a << std::endl;
    std::cout << "b:\n"
              << b << std::endl;
    std::cout << "c:\n"
              << c << std::endl;
a:
[  0,   0, 255,   0,   0, 255;
   0,   0, 255,   0,   0, 255]
b:
[  0, 255,   0, 255;
   0, 255,   0, 255]
c:
[255, 255;
 255, 255]

注意Scalar中数据的数量要和Mat类的通道数对应,否则大于通道数之后的Scaler中的数据不会被读取,例如:

cv::Mat d(2, 2, CV_8UC2, cv::Scalar(0, 0, 255)); 
    std::cout << "d:\n"
              << d << std::endl;

最终的单个元素输出时0,0,而不是0,0,255。

d:
[  0,   0,   0,   0;
   0,   0,   0,   0]

2.2.2 枚举法赋值

如下逐个元素赋值的方式,先填充第一行,再依次填充第二行,第三行。

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, 2.1, 3.2, 4.3, 5.8, 6.1);
    std::cout << "a:\n"
              << a << std::endl;
    std::cout << "b:\n"
              << b << std::endl;
***test06***
a:
[1, 2, 3;
 4, 5, 6;
 7, 8, 9]
b:
[1, 2.1, 3.2;
 4.3, 5.8, 6.1]

2.2.3 循环法赋值

int count=0;
    cv::Mat a = cv::Mat_<int>(3, 3); //定义一个3*3的矩阵
    for (int i = 0; i < a.rows; i++)
    {
        for (int j = 0; j < a.cols; j++)
        {
            a.at<int>(i, j) = count++;
        }
    }
    std::cout<<"a:\n"<<a<<std::endl;
***test07***
a:
[0, 1, 2;
 3, 4, 5;
 6, 7, 8]

2.2.4 类方法赋值

  快速生成单位矩阵(eye),对角矩阵(diag),全1和全0矩阵(ones,zeros)。

cv::Mat a = cv::Mat::eye(3, 3, CV_8UC1);
    cv::Mat b = (cv::Mat_<int>(1, 3) << 1, 2, 3);
    cv::Mat c = cv::Mat::diag(b);
    cv::Mat d = cv::Mat::ones(3, 3, CV_8UC1);
    cv::Mat e = cv::Mat::zeros(4, 2, CV_8UC3);
    std::cout << "a:\n"
              << a << std::endl;
    std::cout << "b:\n"
              << b << std::endl;
    std::cout << "c:\n"
              << c << std::endl;
    std::cout << "d:\n"
              << d << std::endl;
    std::cout << "e:\n"
              << e << std::endl;
***test08***
a:
[  1,   0,   0;
   0,   1,   0;
   0,   0,   1]
b:
[1, 2, 3]
c:
[1, 0, 0;
 0, 2, 0;
 0, 0, 3]
d:
[  1,   1,   1;
   1,   1,   1;
   1,   1,   1]
e:
[  0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0]

2.2.5 利用数组进行赋值

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);
    std::cout << "b:\n"
              << b << std::endl;
    std::cout << "c:\n"
              << c << std::endl;

依次赋值,优先为通道数赋值,例如5和6放到了b中第一个元素的两个通道中。

***test09***
b:
[5, 6, 7, 8;
 1, 2, 3, 4]
c:
[5, 6, 7, 8;
 1, 2, 3, 4]

2.3Mat类支持的运算

2.3.1Mat类加减乘除运算

cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    cv::Mat b = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    cv::Mat c = (cv::Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.9, 9.1);
    cv::Mat d = (cv::Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.3, 5.4, 6.5, 7.6, 8.9, 9.1);
    cv::Mat e,f,g,h,i;
    e=a+b;
    f=c-d;
    g=2*a;
    h=d/2.0;
    i=a-1;
    std::cout << "e:\n"
              << e << std::endl;
    std::cout << "f:\n"
              << f << std::endl;
    std::cout << "g:\n"
              << g << std::endl;
    std::cout << "h:\n"
              << h << std::endl;
    std::cout << "i:\n"
              << i << std::endl;
***test10***
e:
[2, 4, 6;
 8, 10, 12;
 14, 16, 18]
f:
[0, 0, 0;
 0, 0, 0;
 0, 0, 0]
g:
[2, 4, 6;
 8, 10, 12;
 14, 16, 18]
h:
[0.5, 1.05, 1.6;
 2.15, 2.7, 3.25;
 3.8, 4.45, 4.55]
i:
[0, 1, 2;
 3, 4, 5;
 6, 7, 8]

2.3.2Mat类矩阵之间的乘法、点积和内积

cv::Mat a = (cv::Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    cv::Mat b = (cv::Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    cv::Mat c, e;
    double d;
    c = a * b;
    d = a.dot(b);
    e = a.mul(b);
    std::cout << "c:\n"
              << c << std::endl;
    std::cout << "d:\n"
              << d << std::endl;
    std::cout << "e:\n"
              << e << std::endl;
***test11***
c:
[30, 36, 42;
 66, 81, 96;
 102, 126, 150]
d:
285
e:
[1, 4, 9;
 16, 25, 36;
 49, 64, 81]

2.4 Mat类元素的访问

2.4.1 通过at方法读取Mat类矩阵中的元素

单通道

cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    int b=(int)a.at<uchar>(0,0);//行,列
    int c=(int)a.at<uchar>(2,0);
    std::cout << "a:\n"
              << a << std::endl;
    std::cout << "b:\n"
              << b << std::endl;
    std::cout << "c:\n"
              << c << std::endl;
***test12***
a:
[  1,   2,   3;
   4,   5,   6;
   7,   8,   9]
b:
1
c:
7

多通道

cv::Mat b(3, 4, CV_8UC3, cv::Scalar(0, 0, 1));
    cv::Vec3b vc3 = b.at<cv::Vec3b>(0, 0);
    int first = (int)vc3.val[0];
    int second = (int)vc3.val[1];
    int third = (int)vc3.val[2];
    std::cout<<first<<" "<<second<<" "<<third<<std::endl;
***test13***
0 0 1

2.4.2 通过指针ptr读取Mat类矩阵中的元素

cv::Mat b(3,4,CV_8UC3,cv::Scalar(1,2,3));
    for (int i = 0; i < b.rows; i++)
    {
        uchar* ptr=b.ptr<uchar>(i);//读取i行
        for (int j = 0; j < b.cols*b.channels(); j++)
        {
            std::cout<<(int)ptr[j]<<" ";
        }
        // 可以直接输出b.ptr<uchar>(i)[j]来输出第i行第j列的数据
        std::cout<<std::endl;
    }
***test14***
1 2 3 1 2 3 1 2 3 1 2 3 
1 2 3 1 2 3 1 2 3 1 2 3 
1 2 3 1 2 3 1 2 3 1 2 3

2.4.3 通过迭代器访问Mat类矩阵中的元素

cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
    cv::MatIterator_<uchar> it = a.begin<uchar>();
    cv::MatIterator_<uchar> it_end = a.end<uchar>();

    for (int i = 0; it != it_end; it++)
    {
        std::cout<<(int)(*it)<<" ";
        if ((++i % a.cols) == 0)
        {
            std::cout<<std::endl;
        }
    }
***test15***
1 2 3 
4 5 6 
7 8 9