前言:
😊😊😊欢迎来到本博客😊😊😊
🌟🌟🌟 本专栏主要结合OpenCV和C++来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快速查询等,随时更新。
😊😊😊 具体食用方式:可以点击本专栏【OpenCV快速查找(更新中)】–>搜索你要查询的算子名称或相关知识点,或者通过这篇博客👉通俗易懂OpenCV(C++版)详细教程——OpenCV函数快速查找(不断更新中)]查阅你想知道的知识,即可食用。
🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙
学习目标
本章内容较多,如有需要,耐心阅读。
- 初识OpenCV中Mat类
- 构造单、多通道Mat对象
- 获取单、多通道Mat的信息
- 访问单、多通道Mat对象中的值
一、 什么是Mat类
Mat
类(Matrix)是OpenCV中最核心的类,代表矩阵或者数组的意思,该类的声明在头文件<opencv2\core\core.hpp>中,当然直接声明 <opencv2/opencv.hpp>也可以,所以使用Mat类时要引入相关头文件。
构造Mat
对象相当于构造了一个矩阵(数组),需要四个基本要素:行数(高)、列数(宽)、通道数及其数据类型,Mat类的构造函数如下:
Mat(int rows,int cols,int type)
其中,rows代表矩阵的行数,cols代表矩阵的列数,type代表类型,包括通道数及其数据类型,可以设置为:
类型 | 注释 |
---|---|
<CV_8UC(n) | 占1字节的uchar类型 |
CV_8SC(n) | 占1字节的int类型 |
CV_16SC(n) | 占2字节的int类型 |
CV_16UC(n) | 占2字节的uchar类型 |
CV_32SC(n) | 占4字节的int类型 |
CV_32FC(n) | 占4字节的float类型 |
CV_64FC(n) | 占8字节的doule类型 |
注:8U、8S、16S、16U、32S、32F、64F前面的数字代表Mat中每一个数值所占的bit数,1byte=8bit,C(n)代表通道数,当n=1时,即构造单通道矩阵或称二维矩阵,当n≠1时,即构造多通道矩阵即三维矩阵,直观上就是n个二维矩阵组成的三维矩阵。
对于Mat构造函数也可以采用以下形式:
Mat(Size(int cols,int rows),int type);
这里使用了OpenCV的Size
类,这个类一般用来存储矩阵的列数和行数。需要注意的是:Size
的第一个元素是矩阵的列数(宽),第二个元素是矩阵的行数(高),即先存宽,再存高。
二、 了解向量Vec类
后面在介绍多通道Mat对象相关知识中,会涉及到OpenCV的向量类,这里先了解关于OpenCV中的向量类相关内容。
这里的向量可以理解为我们数学学过的列向量,构造一个_cn×1的列向量,数据类型为_Tp,格式如下:
Vec<TypeName _Tp,int _cn>
构造一个长度为5,数据类型为int且初始化12,13,14,15,16的列向量:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
Vec<int, 5> vv(12,13,14,15,16);
cout << "vec列向量的行数为:" << vv.rows << endl;
cout << "vec列向量的列数为:" << vv.cols << endl;
return 0;
}
![](https://img-blog.csdnimg.cn/5a4754fbcd794f69a8a7c362588da25f.png)
如果我们想访问向量里的元素,则可以利用“[]”
或者“()”
操作符访问向量中的值:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
Vec<int, 5> vv(12,13,14,15,16);
cout << "vec列向量的行数为:" << vv.rows << endl;
cout << "vec列向量的列数为:" << vv.cols << endl;
cout << "访问第一个元素:" << vv[0] << endl;
cout << "访问第二个元素:" << vv(1) << endl;
return 0;
}
![](https://img-blog.csdnimg.cn/fa23d264cc7d47b0ad00a7411c5fe8a2.png)
OpenCV为向量类的声明取了一个别名,例如:
typedef Vec<unchar,3> Vec3b;
typedef Vec<int,2> Vec2i;
typedef Vec<float,4> Vec4f;
typedef Vec<double,3> Vec3d;
注:通道矩阵的每一个元素都是一个数值,多通道矩阵的每一个元素都可以看作一个向量。这是多通道Mat的基础,后面会在彩色图像的数字化详细介绍。
三、 构造单、多通道Mat对象
3.1 构造单通道Mat对象
构造单通道Mat对象的方式有很多,不同的方式有各自特点及注意点,下面介绍各种构造方法。
构造3行4列float类型的单通道矩阵,代码如下:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造3行4列的矩阵
Mat m = Mat(3, 4, CV_32FC(1));
return 0;
}
也可以采用Size
对象构造:
//借助Size对象
Mat m = Mat(Size(4, 3), CV_32FC(1));
注:这里的Size(4,3)
是指4列3行。
还可以使用Mat中的成员函数create
完成Mat对象的构造,代码如下:
Mat m;
m.create(3, 4, CV_32FC(1));
//m.create(Size(4, 3), CV_32FC(1));
注:CV_32FC(1)和CV_32FC1的写法都可以。
在平时操作过程中,常见的0矩阵、1矩阵能够更直接去构造,比如构造一个3行4列全1的float类型的单通道矩阵:
//构造全1的3行4列单通道矩阵
Mat one = Mat::ones(3, 4, CV_32FC(1));
Mat one = Mat::ones(Size(4, 3), CV_32FC(1));
构造一个3行4列全0的float类型的单通道矩阵:
//构造全0的3行4列单通道矩阵
Mat zero = Mat::zeros(3, 4, CV_32FC(1));
Mat zero = Mat::zeros(Size(4, 3), CV_32FC(1));
平时在测试一些小项目时,可以快速创建矩阵,那么就可以直接定义:
Mat m = (Mat_<int>(3, 4) << 1, 2, 3, 4, 5, 6);
3.2 构造多通道Mat对象
构造一个由n个rows*cols
二维浮点型矩阵组成的三维矩阵,具体如下:
Mat(int rows, int cols, CV_32FC(n))
前面讲了当n=1时,就是单通道矩阵了。如果构造一个2行2列的float类型的三通道矩阵,代码如下:
//构造一个2行2列的float类型的三通道矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 22, 21), Vec3f(2, 12, 31), Vec3f(3, 13, 23), Vec3f(4, 24, 34));
注:这里的Vec3f(1, 22, 21)
写法详见上述二、 了解向量Vec类
。
四、 获取单、多通道Mat的信息
4.1 获取单通道Mat的信息
下面详细介绍Mat
的成员变量和成员函数,以便获得矩阵m的基本信息,以3行2列的二维矩阵为例:
![](https://img-blog.csdnimg.cn/474ac0df56c74c5ba5d649c97390b0fb.png)
(1) 使用成员变量rows和cols获取矩阵的行数和列数
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
//矩阵行数
cout << "矩阵m的行数为:" << m.rows << endl;
//矩阵列数
cout << "矩阵m的列数为:" << m.cols << endl;
return 0;
}
![](https://img-blog.csdnimg.cn/17f9bd0f8fac4d579644139a37fa5c96.png)
(2) 使用成员函数size()
获取矩阵的尺寸
上述是通过成员函数获取矩阵行和列,我们还可以通过成员函数size()
直接得到矩阵尺寸的Size对象,即矩阵大小:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
//矩阵行数
cout << "矩阵m的行数为:" << m.rows << endl;
//矩阵列数
cout << "矩阵m的列数为:" << m.cols << endl;
//矩阵大小
cout << "矩阵m的大小为:" << m.size() << endl;
return 0;
}
![](https://img-blog.csdnimg.cn/5ec60a6eedd2479887edf8624114917c.png)
(3) 使用成员函数channels()
得到矩阵的通道数
可以通过成员函数channels()
得到Mat的通道数:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
//矩阵行数
cout << "矩阵m的行数为:" << m.rows << endl;
//矩阵列数
cout << "矩阵m的列数为:" << m.cols << endl;
//矩阵大小
cout << "矩阵m的大小为:" << m.size() << endl;
//矩阵通道数
cout << "矩阵m的通道数为:" << m.channels() << endl;
return 0;
}
![](https://img-blog.csdnimg.cn/067eff5f8c554e5ab83338de0e73faff.png)
(4) 成员函数total()
total()
的返回值是矩阵的行数乘以列数,即面积。注意和通道数无关,返回的不是矩阵中数据的个数。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
//矩阵行数
cout << "矩阵m的行数为:" << m.rows << endl;
//矩阵列数
cout << "矩阵m的列数为:" << m.cols << endl;
//矩阵大小
cout << "矩阵m的大小为:" << m.size() << endl;
//矩阵通道数
cout << "矩阵m的通道数为:" << m.channels() << endl;
//面积
cout << "矩阵m的面积为:" << m.total() << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/0295d1953ccf369b5af38e258d5959a7.png)
(5) 成员变量dims
dims
代表矩阵的维数,对于单通道矩阵来说就是一个二维矩阵,对于多通道矩阵来说就是一个三维矩阵。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
//矩阵行数
cout << "矩阵m的行数为:" << m.rows << endl;
//矩阵列数
cout << "矩阵m的列数为:" << m.cols << endl;
//矩阵大小
cout << "矩阵m的大小为:" << m.size() << endl;
//矩阵通道数
cout << "矩阵m的通道数为:" << m.channels() << endl;
//面积
cout << "矩阵m的面积为:" << m.total() << endl;
//矩阵维度
cout << "矩阵m的维度为:" << m.dims << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/d630f7fe6bbeb9086be608cb5a91e8ee.png)
4.2 获取多通道Mat中某区域的值
(1) 使用成员函数row(i)或col(j)得到矩阵的第i行或者第j列
对于单通道矩阵而言,获取某行或者某列的所有值很好理解,即:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
int r = 1;
int c = 0;
Mat mr = m.row(r);
Mat ml = m.col(c);
cout <<"矩阵m第["<<r<<"]行为:" <<mr << endl;
cout << "矩阵m第[" << c << "]列为:" << ml << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/f3a24b4e56d04f370f2716d900faf4bb.png)
注意:返回值仍然是一个单通道的Mat类型。
(2) 使用成员函数rowRange
或colRange
得到矩阵的连续行或者连续列
在学习该成员函数之前,先了解OpenCV中的Range类,该类用于构造连续的整数序列,构造函数如下:
Range(int _start, int _end);
注意:这是一个左闭右开的序列[_start,_end)
,比如Range(2,6)其实产生的是2、3、4、5的序列,不包括6,常用作rowRange
和colRange
的输入参数,从而访问矩阵中的连续行或者连续列。
下面我们构造一个4x4的矩阵:
![](https://i-blog.csdnimg.cn/blog_migrate/af5f4339d82a167317b20842d02034fc.png)
访问mm的第2、3行:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Mat mm_range = mm.rowRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.rowRange(2, 4);
for (int r = 0; r <mm_range.rows; r++)
{
for (int c = 0; c < mm_range.cols; c++)
{
cout << mm_range.at<int>(r,c)<< ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/571e342b6d502858c299cc074321ed5f.png)
成员函数rowRange
是一个重载函数,也可以直接将 Range 的_start 和_end
直接作为rowRange的输入参数
,上述获取矩阵连续行的操作可以直接写为:
Mat mm_range = mm.rowRange(2, 4);
同理,想获取矩阵第几列可以用colRange
,例如:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
//Mat mm_range = mm.rowRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.rowRange(2, 4);
Mat mm_range = mm.colRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.colRange(2, 4);
for (int r = 0; r <mm_range.rows; r++)
{
for (int c = 0; c < mm_range.cols; c++)
{
cout << mm_range.at<int>(r,c)<< ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/a190c194e001aa00c4059ee7c4bc5b63.png)
需要特别注意的是:成员函数rows、cols、rowRange、colRange
返回的矩阵是指向原矩阵的,比如改变r_range的第1行第1列的值:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
//Mat mm_range = mm.rowRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.rowRange(2, 4);
Mat mm_range = mm.colRange(Range(2, 4)); //另一种写法:Mat mm_range = mm.colRange(2, 4);
for (int r = 0; r <mm_range.rows; r++)
{
for (int c = 0; c < mm_range.cols; c++)
{
cout << mm_range.at<int>(r,c)<< ",";
}
cout << endl;
}
cout <<"===================================="<< endl;
//改变第1行第1列值
mm_range.at<int>(1, 1) = 50;
cout << "改变后的矩阵为:" << endl;
cout << mm_range << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/160ea018f086ef12e04c64bc3886c8bd.png)
但是,我们只访问原矩阵的某些行或列,不想改变原矩阵的值,该如何做呢?
(3) 成员函数clone()
和copyTo()
对于刚刚遗留的问题,从函数名就可以看出,clone()
和copyTo()
用于将矩阵克隆或者复制一份。
首先,关于clone()
函数:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
//克隆函数
Mat mm_range_clone = mm.rowRange(Range(2, 4)).clone();
//改变mm_range_clone第1行第1列值
mm_range_clone.at<int>(1, 1) = 50;
cout << "改变后的矩阵为:" << endl;
cout << mm_range_clone << endl;
cout << "====================================" << endl;
cout << "原矩阵为:" << endl;
cout << mm << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/bef0dafab77244f813c5c7fb81ae8055.png)
接下来,类似操作,也可以使用copyTo()
函数:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
//克隆函数
Mat mm_range_clone = mm.rowRange(Range(2, 4)).clone();
//改变mm_range_clone第1行第1列值
mm_range_clone.at<int>(1, 1) = 50;
cout << "改变后的矩阵为:" << endl;
cout << mm_range_clone << endl;
cout << "====================================" << endl;
cout << "原矩阵为:" << endl;
cout << mm << endl;
cout << "*******************************************" << endl;
//复制函数
Mat mm_range_copy;
mm.rowRange(Range(2, 4)).copyTo(mm_range_copy);
//改变mm_range_copy第1行第1列值
mm_range_copy.at<int>(1, 1) = 100;
cout << "改变后的矩阵为:" << endl;
cout << mm_range_copy << endl;
cout << "====================================" << endl;
cout << "原矩阵为:" << endl;
cout << mm << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/ff7bb519dd8cf969bddbe2d04bddb076.png)
(4) 使用Rect
类
如果我们要获取矩阵中某一特定的矩形区域,根据上述方法可以先使用rowRange
,再使用colRange
来定位。
OpenCV提供了一种更简单的方式,就是使用Rect
类(Rect是Rectangle的缩写,矩形的意思)。构造一个矩形有多种方式:
条件 | 构造函数 |
---|---|
知道左上角的坐标(x,y),还有矩形的宽度和高度 | Rect(int_x,int_y,int_width,int_hight) |
将_width 和_height 保存在一个Size中 | Rect(int_x,int_y,Size size) |
知道左上角和右下角的坐标也可以构造一个矩形 | Rect(Point1,Point2) 注意范围为前闭后开,不含点Point2 |
当然,还有其他的构造函数,这里不再一一列举,都差不多,掌握这三种也就够了。我们还以4x4的矩阵为例:
![](https://i-blog.csdnimg.cn/blog_migrate/af5f4339d82a167317b20842d02034fc.png)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char**argv) {
//构造矩阵
Mat mm = (Mat_<int>(4, 4) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
Mat rec1 = mm(Rect(Point(1, 1), Point(3, 3)));//左上角坐标、右下角坐标
Mat rec2 = mm(Rect(1,1,2,2));//左上角坐标、宽、高
Mat rec3 = mm(Rect(Point(1, 1),Size(2,2)));//左上角坐标、尺寸
cout << "rec1区域为:" << endl;
cout << rec1 << endl;
cout << "====================================" << endl;
cout << "rec2区域为:" << endl;
cout << rec2 << endl;
cout << "====================================" << endl;
cout << "rec3区域为:" << endl;
cout << rec3 << endl;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/590866c3cced5f1b864a49be0c6b322f.png)
这样得到的矩形区域是指向原矩阵的,要改变rec1中的值,mm也会发生变化,如果不想这样,则仍然可以使用clone()
或者copyTo()
。
五、 访问单、多通道Mat对象中的值
5.1 访问单通道Mat对象中的值
(1) 使用成员函数at
访问Mat对象中的值,最直接的方式是使用Mat的成员函数at
,如对于单通道且数据类型为CV_32F的3行2列的二维矩阵矩阵m:
![](https://img-blog.csdnimg.cn/474ac0df56c74c5ba5d649c97390b0fb.png)
访问它的第r行第c列的值,格式为:m.at<Typename>(r,c)
。我们还是以矩阵m为例,使用at
访问它的值。对于矩阵m中的值依次存入下表中,按照行和列的索引取值即可:
![](https://i-blog.csdnimg.cn/blog_migrate/185d9c806dd262c4c010b373dffde0f4.png)
利用成员函数at
依次访问m中的所有值并打印:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//构造矩阵
int main(int argc,char**argv){
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
for (int r = 0; r <m.rows; r++)
{
for (int c = 0; c < m.cols; c++)
{
cout << m.at<int>(r, c) << ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/9d746738124f1acb71eb2d8e833de74b.png)
除了使用成员函数at
访问,还可以使用Point
类和成员函数at
来实现:即将代码m.at<Typename>(r,c)
改为m.at<Typename>(Point(c,r))
,把行和列的索引变为坐标的形式。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//构造矩阵
int main(int argc,char**argv){
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
for (int r = 0; r <m.rows; r++)
{
for (int c = 0; c < m.cols; c++)
{
//cout << m.at<int>(r, c) << ",";
cout << m.at<int>(Point(c,r)) << ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/9d746738124f1acb71eb2d8e833de74b.png)
注:我们习惯表示矩阵的第r行第c列,也可以利用Point
指明矩阵中某一个固定位置,那么该位置就用Point(c,r)
来定义,注意第一个元素是列坐标(列号),第二个元素是行坐标(行号),符合我们将水平方向作为x轴,将垂直方向作为y轴的习惯,这里的y轴的方向是朝下的。
(2) 使用成员函数ptr
Mat中的数值在内存中的存储,每一行的值是存储在连续的内存区域中的,我们可以通过成员函数ptr
获得指向每一行首地址的指针。
以矩阵m为例,m中所有的值在内存中的存储方式如下图所示,其中如果行与行之间的存储是有内存间隔的,那么间隔也是相等的。
![](https://i-blog.csdnimg.cn/blog_migrate/34e232910c6a8ba76bbc488b277a302d.png)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//构造矩阵
int main(int argc,char**argv){
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
/*成员函数ptr*/
for (int r = 0; r <m.rows; r++)
{
//获取矩阵m第r行行首地址
const int *ptr = m.ptr<int>(r);
//打印第r行所有值
for (int c = 0; c < m.cols; c++)
{
cout << ptr[c] << ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/80a783677a47230e13d3354a31e11925.png)
(3) 使用成员函数isContinuous()
和ptr
从刚刚的图中可以一目了然,即每一行的所有值存储在连续的内存区域中,行与行之间可能会有间隔,
如果isContinuous
返回值为true
,则代表行与行之间也是连续存储的,即所有的值都是连续存储的,如图下图所示。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat m = (Mat_<int>(3, 2) << 12, 15, 13, 16, 14, 17);
/*成员函数isContinuous*/
if (m.isContinuous())
{
cout << "isContinuous is: " << m.isContinuous()<< endl;
//获取矩阵m第1个值的地址
const int *ptr = m.ptr<int>(0);
for (int n = 0; n < m.rows * m.cols; n++)
{
cout << ptr[n] << ",";
}
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/87c690707d6d77e8e80767d7d02de888.png)
(4) 使用成员变量step
和data
从前面的讨论我们已经知道,Mat
中的值在内存中存储分为两种情况,以矩阵m为例,如上述(2)和(3)所述。下面通过这两种情况,介绍两个重要的成员变量——step
和data
。
如下图所示是行与行之间有相等的内存间隔的情况,即不连续:
![](https://i-blog.csdnimg.cn/blog_migrate/6e0f77e21c8d8925ccca3d392550e2c5.png)
如下图所示是是连续的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/c249c8534fff9f6ae5c23e663e936542.png)
如上图所示:对于单通道矩阵而言,step[0]
代表每一行所占的字节数,而如果有间隔的话,这个间隔也作为字节数的一部分被计算在内; step[1]
代表每一个数值所占的字节数;data
是指向第一个数值的指针,类型为uchar
。
所以,无论哪一种情况,如访问一个int类型的单通到矩阵的第r行第c列的值,都可以通过以下代码来实现:
*((int*)(m.data+m.step[0]*r+c*m.step[1]))
总结:如果是CV_32F类型,则将int
替换成float
即可。从取值效率上说,直接使用指针的形式取值是最快的,使用at
是最慢的,但是可读性很高。
5.2 访问多通道Mat对象中的值
访问多通道Mat对象中的值使用的成员函数与单通道几乎一致,不同的是多通道所表示的矩阵维度有差异,具体如下。
(1) 使用成员函数at
成员函数at
访问多通道Mat的元素值,可以将多通道Mat看作一个特殊的二维数组,即在每一个位置上不是一个数值而是一个向量(元素)。
![](https://i-blog.csdnimg.cn/blog_migrate/61cf608b401db702a4136424fb9aa9ce.png)
通过上图按行号、列号取出每一个元素Vec3f即可:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
/* 成员函数at*/
for (int r = 0; r <mm.rows; r++)
{
for (int c = 0; c < mm.cols; c++)
{
cout << mm.at<Vec3f>(r, c) << ",";
//cout << m.at<Vec3f>(Point(c, r)) << ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/16f59156091efbb91cae1ab1eb5ec0c4.png)
也可以使用cout << m.at<Vec3f>(Point(c, r)) << ",";
替换cout << mm.at<Vec3f>(r, c) << ",";
,具体注意点与单通道成员函数at
一致。
(2) 使用成员函数ptr
多通道Mat的数值在内存中也是按行存储的,且每一行存储在连续的内存区域中,如果行与行之间有内存间隔,这个间隔也是相等的,成员函数ptr
可以返回指向指定行的第一个元素(注意不是第一个数值)的指针,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/fbeb4556a3a94f5b7b60c862fe376a1c.png)
使用ptr
访问多通道矩阵的每一个元素:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
/*成员函数ptr*/
for (int r = 0; r <mm.rows; r++)
{
//获取矩阵m第r行行首地址
const Vec3f *ptr = mm.ptr<Vec3f>(r);
//打印第r行所有值
for (int c = 0; c < mm.cols; c++)
{
cout << ptr[c] << ",";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/16f59156091efbb91cae1ab1eb5ec0c4.png)
(3) 使用成员函数isContinuous()
和ptr
与单通道Mat对象类似,通过isContinuous
判断整个Mat对象中的元素值是否存储在连续内存区域中,如果返回值是true,即表示是连续存储的,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/741f1296b36d6c19c884a5e7e58753bf.png)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
/*成员函数isContinuous*/
if (m.isContinuous())
{
cout << "isContinuous is: " << mm.isContinuous() << endl;
//指向多通道矩阵mm第1个元素的地址
const Vec3f *ptr = mm.ptr<Vec3f>(0);
for (int n = 0; n < mm.rows * m.cols; n++)
{
cout << ptr[n] << ",";
}
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/209540058f6356e80a955c74a772557b.png)
如果只获取第r行第c列的元素值,那么就可以通过ptr[r*rows+c*cols]
命令得到。
(4) 使用成员变量step
和data
单通道Mat类似,通过data
和step
获取多通道Mat的每一个元素。也是分两种情况:行与行之间有内存间隔和连续存储。
如下图所示是行与行之间有相等的内存间隔的情况,即不连续,step[0]
代表包含这个间隔的字节数:
![](https://i-blog.csdnimg.cn/blog_migrate/16f4af61bbc5861b3747feb0e2dd9f8e.png)
如下图所示是是连续的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/22f0e5e80f27eb38f482ea3f5ada98cb.png)
使用data
、step
、ptr
获得多通道矩阵的每一个元素:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
// 成员函数data step ptr
for (int r = 0; r <mm.rows; r++)
{
for (int c = 0; c < mm.cols; c++)
{
Vec3f *ptr = (Vec3f*)(mm.data + r*mm.step[0] + c*mm.step[1]);
cout << *ptr << " ";
}
cout << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/b3cc59fe3c8b0f0bd42f81a26a5b7f23.png)
(5) 分离通道
对于多通道矩阵,可以将其分离为多个单通道矩阵,然后按照单通道矩阵的规则访问其中的值。
如下图可以形象看出多通道矩阵分离成单通道矩阵的,即把所有向量的第一个值组成的单通道矩阵作为第一通道,将所有向量的第二元素组成的单通道矩阵作为第二通道,依次类推:
![](https://i-blog.csdnimg.cn/blog_migrate/2cff18ddd096cbf8fc29d575e1d04f03.png)
OpenCV中提供了的split()
函数来分离多通道,将多通道矩阵mm分离为多个单通道,这些单通道矩阵被存放在vector
容器中:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
//构造矩阵
Mat mm = (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 22), Vec3f(3, 13, 23), Vec3f(4, 14, 24));
/* 成员函数split*/
vector<Mat> SingleChannel;
split(mm, SingleChannel);
//获取每个通道的值
for (int i = 0; i < SingleChannel.size(); i++)
{
cout << "第["<< i+1 <<"]通道:" << endl;
cout << SingleChannel[i] << endl;
cout << "==========" << endl;
}
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/7b91ea28aa98a28a94bf877022e369f8.png)
(6) 合并通道
有分有合,merge()
函数可以将多个单通道矩阵合并为一个三维矩阵。merge()
函数声明如下:
void merge(const Mat * mMV, size_ t count, OutputArray dst)
例如将三个2行2列的int类型的单通道矩阵合并为一个多通道矩阵:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
Mat channel1 = (Mat_<int>(2, 2) << 11, 12, 13, 14);
Mat channel2 = (Mat_<int>(2, 2) << 21, 22, 23, 24);
Mat channel3 = (Mat_<int>(2, 2) << 31, 32, 33, 34);
Mat multiChannel[] = {channel1, channel2, channel3};
Mat MM;
merge(multiChannel, 3, MM);
cout << MM;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/a38d53daf79c164139c51c67611fda35.png)
上面代码将三个单通道矩阵存储在一个Mat
数组中,当然也可以将其存储在vector
容器中,使用merge的重载函数:
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc,char**argv){
Mat channel1 = (Mat_<int>(2, 2) << 11, 12, 13, 14);
Mat channel2 = (Mat_<int>(2, 2) << 21, 22, 23, 24);
Mat channel3 = (Mat_<int>(2, 2) << 31, 32, 33, 34);
//Mat multiChannel[] = {channel1, channel2, channel3};
vector<Mat> multiChannel;
multiChannel.push_back(channel1);
multiChannel.push_back(channel2);
multiChannel.push_back(channel3);
Mat MM;
//merge(multiChannel, 3, MM);
merge(multiChannel, MM);
cout << MM;
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/21b5eeb66b73638dbe5e1e2910e59d0c.png)
六、 总结
最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。
![](https://i-blog.csdnimg.cn/blog_migrate/2da0b374ad5b43a181d44ff039f60900.gif)