OpenCV核心模块中的矩阵操作

发布于:2025-04-16 ⋅ 阅读:(36) ⋅ 点赞:(0)

OpenCV计算机视觉开发实践:基于Qt C++ - 商品搜索 - 京东

3.1.1  矩阵类Mat

Mat类是Core模块中常用的一个矩阵类,该类的声明在头文件opencv2\core\core.hpp中,所以使用Mat类时要引入该头文件。类Mat是OpenCV新定义的数据类型,类似于传统的数据类型int、float或String。Mat类用于在内存中存储图像,图像都是二维数组。所以OpenCV定义了处理图像的矩阵类别Matrix,取英文的前3个字母Mat,就如同int取integer的前3个字母一样。我们在处理一块数据的时候,如果使用Mat类,得到的好处是:不需要手动申请一块内存,在不需要时不用再手动释放内存,可以通过类的封装方便地获取数据的相关信息。

Mat利用了类的特性,将内存管理和数据信息封装在类的内部,用户只需要对Mat类对象进行面向对象操作即可。

该类用来保存图像及其他矩阵数据结构(向量场、直方图、张量、点云等),是从OpenCV 2.0以后才使用的,之前一直用C风格的IplImage。使用IplImage最大的问题就是容易造成内存泄露,管理内存相当麻烦,而Mat类的出现不需要我们手动为其开辟空间,也不需要在不用它时立即释放。补充说明一下,很多OpenCV函数仍然手动地管理内存空间,这样不浪费内存,比如传递一个已存在的Mat对象时,开辟好了的那个空间会被再次使用。但手动管理内存不再是必需的,对于初学者来说,完全不用考虑这些。

Mat类由两部分组成:矩阵头和指向存储所有像素值的矩阵的指针。如何理解矩阵头呢?矩阵头相当于矩阵的说明书,它描述了矩阵的尺寸、存储方法、存储地址以及引用次数。何为引用次数?是这样的,矩阵头的尺寸是一个常数,不会随图像的变化而变化,但是存储图像的矩阵可以随图像大小而变化,一般来说,比矩阵头大好几个数量级。而在处理图像时,经常会遇到复制图像、传递图像的操作,此时如果复制整个矩阵,不仅耗费内存,还影响运行效率。所以,OpenCV中的“引用次数”即“计数机制”,让每一个Mat对象都有一个矩阵头,但是它们共享一个矩阵。这是通过让矩阵指针指向同一个地址实现的,比如:

Mat A;                      //仅仅创建了矩阵头

A = imread("1.jpg",1);     //为矩阵开辟一段内存,及创建矩阵

Mat B(A);                   //拷贝构造函数

Mat C = A;                  //赋值

这段代码中,A、B和C都是Mat类,它们指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变都会影响其他对象。

通过clone()或者copyTo()复制一个图像,就包括了矩阵本身,因此,改变复制对象的内容并不会改变源矩阵,例如frame1显然是复制frame,因此对frame1的操作并不会改变frame。

当利用 Mat 类定义的时候,只是创建了矩阵头。在使用拷贝构造或者赋值的时候,其实是新创建了不同的信息头和矩阵指针,它们共享一个矩阵。

有人说了,我就想复制整个矩阵,可以吗?当然可以,此时可以使用clone ()函数或者copyTo函数来实现。

Mat D = A.clone()       //D等于A的复制品

Mat E;

A.copyTo(E);           //把A复制到E

要记住这个结论:Mat是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等信息)和一个指向存储所有像素值的矩阵的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会因图像的不同而不同,通常比矩阵头的尺寸大数个数量级。复制矩阵数据往往会花费较多时间,因此除非有必要,不要复制大的矩阵。

Mat类的两个数据部分可以用图3-1来表示。

图3-1

要使用Mat类,必须先对Mat对象进行初始化。初始化就是构造Mat对象,创建Mat对象的数据区,并根据需要赋予初值或不赋值(不赋值的话,则保留乱码值,比如205)。

初始化Mat对象通常有这几种方法:构造法、直接赋值法、数组法、create函数法和定义特殊矩阵。

3.1.2  构造法

构造法就是利用Mat的构造函数,但要注意的是,不是所有的构造函数都会创建数据区,有些构造函数只会创建一个Mat信息头,比如:

Mat mymat;  //只创建一个Mat信息头,并不会创建数据区

我们可以通过Mat::data指针是否为NULL来判断数据区是否创建,如果为NULL,就说明没有创建数据区。Mat类的常用构造函数如下:

1)Mat::Mat()

无参构造方法,这是默认的构造函数。

2)Mat::Mat(int rows, int cols, int type)

创建行数为rows、列数为col、类型为type的图像(图像元素类型,如CV_8UC3等)。

3)Mat::Mat(Size size, int type)   

创建大小为size、类型为type的图像。

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

创建行数为rows、列数为col、类型为type的图像,并将所有元素初始化为值s。

5)Mat::Mat(Size size, int type, const Scalar& s)   

创建大小为size、类型为type的图像,并将所有元素初始化为值s。

6)Mat::Mat(const Mat& m)   

将m赋值给新创建的对象,此处不会对图像数据进行复制,m和新对象共用图像数据。

7)Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)   

创建行数为rows、列数为cols(像素的列数,对于多通道,一列像素可能对应多列矩阵元素)、类型为type的图像,此构造函数不创建图像数据所需的内存,而是直接使用data指定内存,图像的步长由step指定。

8)Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)   

创建大小为size、类型为type的图像,此构造函数不创建图像数据所需的内存,而是直接使用data指定内存,图像的行步长由step指定。

9)Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)

创建的新图像为m的一部分,具体的范围由rowRange和colRange指定,此构造函数不进行图像数据的复制操作,新图像与m共用图像数据。

10)Mat::Mat(const Mat& m, const Rect& roi)

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

11)Mat::Mat (int ndims, const int *sizes, int type, const Scalar &s)

创建维数为ndims、类型为type的矩阵,并将所有元素初始化为值s,每一维数的数量由数组sizes确定,比如3行3列,sizes= { 3, 3 }。

12)Mat (int rows, int cols, int type, void *data, size_t step=AUTO_STEP)

创建行数为rows、列数为cols(像素的列数,对于多通道,一列像素可能对应多列矩阵元素)、类型为type的图像,并且用数组data初始化元素值。

13)Mat (Size size, int type, void *data, size_t step=AUTO_STEP)

创建大小为size、类型为type的图像,并且用数组data初始化元素值。

其中,type表示图像元素类型,其定义形式为:

CV_<bit_depth>(S|U|F)C<number_of_channels>

中文含义为CV_(位数)+(数据类型)+(通道数),位数也叫深度,比如现在创建一个存储灰度图片的Mat对象,这个图像的大小为宽100、高100,现在这张灰度图片中有10000个像素点,它每一个像素点在内存空间所占的空间大小是8bit,所以它对应的就是CV_8。不同的图像有不同的像素类型,对于不同的像素类型,需要在模板参数传入不同的值。像素的数据类型包括CV_32U、CV_32S、CV_32F、CV_8U、CV_8UC3等,这些类型都是什么含义呢?CV_后面的第一个数字表示比特数,第二个数字表示C++中的数据类型,如果还有后面两个字符,这两个字符就表示通道数。例如,对于CV_32U,表示具有32比特的unsigned int类型;对于CV_8UC3,表示具有8比特,并且有三个通道的unsigned char类型,C1、C2、C3、C4则表示通道数是1、2、3、4。例如CV_16UC2,表示元素类型是一个16位的无符号整数,通道为2。OpenCV中具体可选的数据类型如表3-1所示。

表3-1

数据类型

含    义

CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4

Unsigned 8bits uchar 0~255

CV_8SC1,CV_8SC2,CV_8SC3,CV_8SC4

Signed 8bits char -128~127

CV_16UC1,CV_16UC2,CV_16UC3,CV_16UC4

Unsigned 16bits ushort 0~65535

CV_16SC1,CV_16SC2,CV_16SC3,CV_16SC4

Signed 16bits short -32768~32767

CV_32SC1,CV_32SC2,CV_32SC3,CV_32SC4

Signed 32bits int -2147483648~2147483647

CV_32FC1,CV_32FC2,CV_32FC3,CV_32FC4

Float 32bits float -1.18*10-38~3.40*10-38

CV_64FC1,CV_64FC2,CV_64FC3,CV_64FC4

Double 64bits double

有时会遇到不带通道数的类型,如CV_32S、CV_8U等,通常不带通道数的类型默认通道数为1,例如CV_8U就等同于CV_8UC1,CV_32S就等同于CV_32SC1。

Scalar是一个可以用来存放4个double数值的数组,没有提供的值默认是0,一般用来存放像素值(不一定是灰度值),最多可以存放4个通道,其定义如下:

typedef struct Scalar

{

    double val[4];

}Scalar;

比如,Mat M(7,7,CV_32FC2,Scalar(1,3));表示一个二通道,且每个通道的值都为(1,3),深度为32,7行7列的图像矩阵。

另外,要注意多通道矩阵的表示,比如RGB的图,假设分辨率为40×40像素,则每个像素由R、G、B三个通道构成,一般行的排列方式为BGR依次交错排列(特殊情况是每个通道排列一行),则在OpenCV中每行的长度为40×3个数(120列),行数依旧是40。简单地说,就是每个像素点都是由1×3的小矩阵构成的。比如,我们定义一个5×5的3通道矩阵,并赋初值为4,5,6。

Mat r5(Size(5,5), CV_8UC3, Scalar(4, 5,6));

每一行的依次3列的3个元素表示一个像素(这三列的每一列就是一个通道,3列就是3通道,3列元素的值分别为4,5,6),全部矩阵元素表示如下:

[  4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6;

   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6;

   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6;

   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6;

   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6,   4,   5,   6]

我们看第一行,每3列就是4,5,6,表示一个像素的3个通道,合起来就表示一个像素;第二行也是如此。

还要注意的是,在OpenCV中,Size_是一个模板类,有成员函数Size_(_Tp _width, _Tp _height);,注意宽在前、高在后(列在前、行在后),而Size只不过是Size_的重命名:

typedef Size_<int> Size2i;     //此时_Tp相当于int

typedef Size_<int64> Size2l;

typedef Size_<float> Size2f;

typedef Size_<double> Size2d;

typedef Size2i Size;           //定义Size

而宽度就是矩阵像素的列数,高度就是矩阵像素的行数,所以Size(m,n)表示矩阵像素有n行,m列、不要弄反了。

比如,指定矩阵的行和列,并表示为4通道的矩阵,每个点的颜色值为(0, 0, 0, 255),代码可以这样写:

cv::Mat M1(3, 3, CV_8UC4, cv::Scalar(0, 0, 0, 255));

std::cout << "M1 = " << std::endl << M1 << std::endl;

输出结果如下:

M1 =

[ 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255;

 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255;

 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]

下面我们利用多种形式构造函数来创建Mat类对象。

【例3.1】多方法构造Mat类对象

(1)打开Qt Creator,新建一个控制台工程。

(2)在IDE中打开main.cpp,并输入代码如下:

#include <iostream>

#include "opencv2/imgcodecs.hpp"

using namespace cv;  //所有OpenCV类都在命名空间cv下

using namespace std;



int main(void)

{

    Mat r1; //构造无参数矩阵

    Mat r2(2, 2, CV_8UC1);          //构造两行两列,深度是8位比特的单通道矩阵

    Mat r3(Size(3, 2), CV_8UC3);   //构造行数是2,列数是3*通道数,深度是8位比特的3通道矩阵

 //构造4行4列像素,深度是8位比特的2通道矩阵,且每个像素的通道初始值是1和3

    Mat r4(4, 4, CV_8UC2, Scalar(1, 3));

 //构造5行3列像素矩阵,深度是8位比特的3通道矩阵,且每个像素的通道初始值是4,5,6

 Mat r5(Size(3,5), CV_8UC3, Scalar(4, 5,6));

 //将r5赋值给r6,公用数据对象

    Mat r6(r5);

    //通过数组初始化矩阵维数

    int sz[2] = { 3, 3 };

    cv::Mat r7(2, sz, CV_8UC1, cv::Scalar::all(1));

 //通过数组初始化矩阵数据

    int a[2][3] = { 1, 2, 3, 4, 5, 6};

    Mat r8(2,3,CV_32S,a);   //float 对应的是CV_32F,double对应的是CV_64F,默认为单通道

    cout << r1 << endl<<r2<<endl<<r3<<endl << r4 << endl << r5 << endl << r6 << endl << r7 <<endl << r8;

}

 

图3-2

上述代码中,我们利用Mat的不同构造函数创建了Mat对象。Size表示大小时,第一个参数是列数,第二个参数是行数。另外要注意的是,对于多通道矩阵,一列像素对应多列通道值。比如3通道,某一行3列矩阵元素表示一个像素值。构造了6个矩阵后,我们最后用cout输出每个矩阵的所有元素。

(3)保存工程并按Ctrl+R键运行,运行结果如图3-2所示。

3.1.3  直接赋值法

Mat矩阵比较小时,可以使用直接赋值法。直接赋值法就是利用Mat_。Mat_也是一个类,该类是对Mat类的一个包装,其定义如下:

template<typename _Tp> class Mat_ : public Mat

{

public:

//只定义了几个方法

//没有定义新的属性

};

如果要让每个像素取不同的值,可以直接用Mat_赋值,代码如下:

Mat r8 = (Mat_<double>(3, 3) <<1, 2, 3, 4, 5, 6, 7, 8,9);

cout << "r8 total matrix:\n" << r1 << endl;

输出结果如下:

[1, 2, 3;
 4, 5, 6;
 7, 8, 9]

3.1.4  数组法

这个方法就是使用数组或指针传入Mat构造函数。这个构造函数是:

Mat (int rows, int cols, int type, void *data, size_t step=AUTO_STEP)

创建行数为rows、列数为cols(像素的列数,对于多通道,一列像素可能对应多列矩阵元素)、类型为 type 的图像,并且用数组data初始化元素值。

Mat (Size size, int type, void *data, size_t step=AUTO_STEP)

创建大小为size、类型为type的图像,并且用数组data初始化元素值。data就是要传入数据的指针,比如:

int a[2][3] = { 1, 2, 3, 4, 5, 6};  //定义2行3列二维数组

Mat m1(2,3,CV_32S,a);   //float对应的是CV_32F,double对应的是CV_64F,若不带通道数,则默认通道数是1

cout << m1 << endl;

输出结果如下:

[1, 2, 3;

 4, 5, 6]

数组适合操作数据量大的情况,比如可以通过for循环来构造二维数组,然后给Mat赋值。

3.1.5  create函数法

成员函数create可以分配新的矩阵数据,即重新创建矩阵元素数据,函数声明如下:

void create (int  rows, int cols,  int  type);

其中rows表示要创建的矩阵行数;cols表示要创建的矩阵列数;type表示图像矩阵元素的类型,比如CV_8UC3。

以下代码实现4×4的二维单通道矩阵,矩阵中的数据为乱值:

Mat M3;

M3.create(4, 4, CV_8UC1);

std::cout << "M3 = " << std::endl << M3 << std::endl;

输出结果如下:

[205, 205, 205, 205;

205, 205, 205, 205;

205, 205, 205, 205;

205, 205, 205, 205]


网站公告

今日签到

点亮在社区的每一天
去签到