【从零学习OpenCV 4】Mat类构造与赋值
重磅干货,第一时间送达
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《从零学习OpenCV 4》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。
前一小节已经介绍了三种构造Mat类变量的方法,但是后两种没有给变量初始化赋值,本小节将重点介绍如何灵活的构造并赋值Mat类变量。根据OpenCV的源码定义,关于Mat类的构造方式共有二十余种,然而在平时一些简单的应用程序中很多复杂的构造方式并没有太多的用武之地,因此本书重点讲解笔者在学习和做项目中常用的构造与赋值方式。
1
01
Mat类的构造
(1)利用默认构造函数
代码清单2-4 默认构造函数使用方式
cv::Mat::Mat();
通过代码清单2-4,利用默认构造函数构造了一个Mat类,这种构造方式不需要输入任何的参数,在后续给变量赋值的时候会自动判断矩阵的类型与大小,实现灵活的存储,常用于存储读取的图像数据和某个函数运算输出结果。
(2)根据输入矩阵尺寸和类型构造
代码清单2-5 利用矩阵尺寸和类型参数构造Mat类
cv::Mat::Mat( int rows,
int cols,
int type
)
rows:构造矩阵的行数
cols:矩阵的列数
type:矩阵中存储的数据类型。此处除了CV_8UC1、CV_64FC4等从1到4通道以外,还提供了更多通道的参数,通过CV_8UC(n)中的n来构建多通道矩阵,其中n最大可以取到512.
这种构造方法我们前文也见过,通过输入矩阵的行、列以及存储数据类型实现构造。这种定义方式清晰、直观、易于阅读,常用在明确需要存储数据尺寸和数据类型的情况下,例如相机的内参矩阵、物体的旋转矩阵等。利用输入矩阵尺寸和数据类型构造Mat类的方法存在一种变形,通过将行和列组成一个Size()结构进行赋值,代码清单2-6中给出了这种构造方法的原型。
代码清单2-6 用Size()结构构造Mat类
cv::Mat::Mat(Size size(),
int type
)
size:2D数组变量尺寸,通过Size(cols, rows)进行赋值。
type:与代码清单2-5中的参数一致
利用这种方式构造Mat类时要格外注意,在Size()结构里矩阵的行和列的顺序与代码清单2-5中的方法相反,使用Size()时,列在前、行在后。如果不注意同样会构造成功Mat类,但是当我们需要查看某个元素时,我们并不知道行与列颠倒,就会出现数组越界的错误。使用该种方法构造函数如下:
代码清单2-7 用Size()结构构造Mat示例
cv::Mat a(Size(480, 640), CV_8UC1); //构造一个行为640,列为480的单通道矩阵
cv::Mat b(Size(480, 640), CV_32FC3); //构造一个行为640,列为480的3通道矩
(3)利用已有矩阵构造
代码清单2-8 利用已有矩阵构造Mat类
cv::Mat::Mat( const Mat & m);
m:已经构建完成的Mat类矩阵数据。
这种构造方式非常简单,可以构造出与已有的Mat类变量存储内容一样的变量。注意这种构造方式只是复制了Mat类的矩阵头,矩阵指针指向的是同一个地址,因此如果通过某一个Mat类变量修改了矩阵中的数据,另一个变量中的数据也会发生改变。
提示
如果想复制两个一模一样的Mat类而彼此之间不会受影响,可以使用m=a.clone()实现。
如果需要构造的矩阵尺寸比已有矩阵小,并且存储的是已有矩阵的子内容,那么可以用代码清单2-9中的方法进行构建:
代码清单2-9 构造已有Mat类的子类
cv::Mat::Mat(const Mat & m,
const Range & rowRange,
const Range & colRange = Range::all()
)
m:已经构建完成的Mat类矩阵数据。
rowRange:在已有矩阵中需要截取的行数范围,是一个Range变量,例如从第2行到第5行可以表示为Range(2,5)。
colRange:在已有矩阵中需要截取的列数范围,是一个Range变量,例如从第2列到第5列可以表示为Range(2,5),当不输入任何值时表示所有列都会被截取。
这种方式主要用于在原图中截图使用,不过需要注意的是,通过这种方式构造的Mat类与已有Mat类享有共同的数据,即如果两个Mat类中有一个数据发生更改,另一个也会随之更改。使用该种方法构造Mat类如代码清单2-10中所示。
代码清单2-9 构造已有Mat类的子类
cv::Mat::Mat(const Mat & m,
const Range & rowRange,
const Range & colRange = Range::all()
)
1
02
Mat类的赋值
构建完成Mat类后,变量里并没有数据,需要将数据赋值给它。针对不同情况,OpenCV 4.1提供了多种赋值方式,接下来将介绍如何给Mat类变量进行赋值。
(1)构造时赋值
代码清单2-11 在构造时赋值的方法
cv::Mat::Mat(int rows,
int cols,
int type,
const Scalar & s
)
rows:矩阵的行数
cols:矩阵的列数
type:存储数据的类型
s:给矩阵中每个像素赋值的参数变量,例如Scalar(0, 0, 255)。
该种方式是在构造的同时进行赋值,将每个元素想要赋予的值放入Scalar结构中即可,这里需要注意的是,用此方法会将图像中的每个元素赋值相同的数值,例如Scalar(0, 0, 255)会将每个像素的三个通道值分别赋值0,0,255。我们可以使用如下的形式构造一个已赋值的Mat类
代码清单2-12 在构造时赋值示例
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)); //创建一个单通道矩阵,每个像素都是255
我们在程序return语句之前加上断点进行调试,用Image Watch查看每一个Mat类变量里的数据,结果如图2-3所示,证明我们已成功构造矩阵并赋值。
图2-3 使用Scalar结构给Mat类赋值结果
提示
Scalar结构中变量的个数一定要与定义中的通道数相对应,如果Scalar结构中变量个数大于通道数,则位置大于通道数之后的数值将不会被读取,例如执行a(2, 2, CV_8UC2, Scalar(0,0,255))后,每个像素值都将是(0,0),而255不会被读取。如果Scalar结构中变量数小于通道数,则会以0补充。
(2)枚举赋值法
这种赋值方式是将矩阵中所有的元素都一一枚举出,并用数据流的形式赋值给Mat类。具体赋值形式如代码清单2-13所示。
代码清单2-13 利用枚举法赋值示例
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.0, 5.1, 6.2);
上面第一行代码创建了一个3×3的矩阵,矩阵中存放的是从1-9的九个整数,先将矩阵中的第一行存满,之后再存入第二行、第三行,即1、2、3存放在矩阵a的第一行,4、5、6存放在矩阵a的第二行,7,8,9存放在矩阵a的第三行。第二行代码创建了一个2×3的矩阵,其存放方式与矩阵a相同。
提示
采用枚举法时,输入的数据个数一定要与矩阵元素个数相同,例如代码清单2-13中第一行代码只输入从1到8八个数,赋值过程会出现报错,因此本方法常用在矩阵数据比较少的情况。
(3)循环赋值
与通过枚举法赋值方法相类似,循环法赋值也是对矩阵中的每一位元素进行赋值,但是可以不在声明变量的时候进行赋值,而且可以对矩阵中的任意部分进行赋值。具体赋值形式如代码清单2-14所示。
代码清单2-14 利用枚举法赋值示例
cv::Mat c = cv::Mat_<int>(3, 3); //定义一个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;
}
}
上面代码同样创建了一个3×3的矩阵,通过for循环的方式,对矩阵中的每一位元素进行赋值。需要注意的是,在给矩阵每个元素进行赋值的时候,赋值函数中声明的变量类型要与矩阵定义时的变量类型相同,即上面代码中第1行和第6行中变量类型要相同,如果第6行代码改成c.at<double>(i, j) ,程序就会报错,无法赋值。
(4)类方法赋值
在Mat类里提供了可以快速赋值的方法,可以初始化指定的矩阵。例如生成单位矩阵、对角矩阵、所有元素都为0或者1的矩阵等。具体使用方法如代码清单2-15所示。
代码清单2-15 利用类方法赋值示例
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);
上面代码中,每个函数作用及参数含义分别如下:
eye():构建一个单位矩阵,前两个参数为矩阵的行数和列数,第三个参数为矩阵存放的数据类型与通道数。如果行和列不相等,则在矩阵的 (1,1),(2,2),(3,3)等主对角位置处为1。
diag():构建对角矩阵,其参数必须是Mat类型的1维变量,用来存放对角元素的数值。
ones():构建一个全为1的矩阵,参数含义与eye()相同。
zeros():构建一个全为0的矩阵,参数含义与eye()相同。
(5)利用数组进行赋值
这种方法与枚举法相类似,但是该方法可以根据需求改变Mat类矩阵的通道数,可以看作枚举法的拓展,在代码清单2-16中给出了这种方法的赋值形式。
代码清单2-16 利用数组赋值示例
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);
这种赋值方式首先将需要存入到Mat类中的变量存入到一个数组中,之后通过设置Mat类矩阵的尺寸和通道数将数组变量拆分成矩阵,这种拆分方式可以自由定义矩阵的通道数,当矩阵中的元素数目大于数组中的数据时,将用-1.0737418e+08填充赋值给矩阵,如果矩阵中元素的数目小于数组中的数据时,将矩阵赋值完成后,数组中剩余数据将不再赋值。由数组赋值给矩阵的过程是首先将矩阵中第一个元素的所有通道依次赋值,之后再赋值下一个元素,为了更好的体会这个过程,我们将定义的b和c矩阵在图2-4中给出。
图2-4 矩阵b和c中存储的数据