【图像处理OpenCV(C++版)】——2.1 深入理解OpenCV之Mat类及相关成员函数

news2024/9/22 21:18:07

前言

😊😊😊欢迎来到本博客😊😊😊

🌟🌟🌟 本专栏主要结合OpenCV和C++来实现一些基本的图像处理算法并详细解释各参数含义,适用于平时学习、工作快速查询等,随时更新。

😊😊😊 具体食用方式:可以点击本专栏【OpenCV快速查找(更新中)】–>搜索你要查询的算子名称或相关知识点,或者通过这篇博客👉通俗易懂OpenCV(C++版)详细教程——OpenCV函数快速查找(不断更新中)]查阅你想知道的知识,即可食用。

🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙


文章目录

    • 学习目标
    • 一、 什么是Mat类
    • 二、 了解向量Vec类
    • 三、 构造单、多通道Mat对象
      • 3.1 构造单通道Mat对象
      • 3.2 构造多通道Mat对象
    • 四、 获取单、多通道Mat的信息
      • 4.1 获取单通道Mat的信息
        • (1) 使用成员变量rows和cols获取矩阵的行数和列数
        • (2) 使用成员函数`size()`获取矩阵的尺寸
        • (3) 使用成员函数`channels()`得到矩阵的通道数
        • (4) 成员函数`total()`
        • (5) 成员变量`dims`
      • 4.2 获取多通道Mat中某区域的值
        • (1) 使用成员函数row(i)或col(j)得到矩阵的第i行或者第j列
        • (2) 使用成员函数`rowRange`或`colRange`得到矩阵的连续行或者连续列
        • (3) 成员函数`clone()`和`copyTo()`
        • (4) 使用`Rect`类
    • 五、 访问单、多通道Mat对象中的值
      • 5.1 访问单通道Mat对象中的值
        • (1) 使用成员函数`at`
        • (2) 使用成员函数`ptr`
        • (3) 使用成员函数`isContinuous()`和`ptr`
        • (4) 使用成员变量`step`和`data`
      • 5.2 访问多通道Mat对象中的值
        • (1) 使用成员函数`at`
        • (2) 使用成员函数`ptr`
        • (3) 使用成员函数`isContinuous()`和`ptr`
        • (4) 使用成员变量`step`和`data`
        • (5) 分离通道
        • (6) 合并通道
    • 六、 总结

学习目标

本章内容较多,如有需要,耐心阅读。

  • 初识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;
}

  如果我们想访问向量里的元素,则可以利用“[]”或者“()”操作符访问向量中的值:

#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;
}

  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列的二维矩阵为例:

(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;
}

  

(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;
}

  

(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;
}

  

(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;
}

  

(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;
}

  

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;
}

注意:返回值仍然是一个单通道的Mat类型。

  

(2) 使用成员函数rowRangecolRange得到矩阵的连续行或者连续列

  在学习该成员函数之前,先了解OpenCV中的Range类,该类用于构造连续的整数序列,构造函数如下:

Range(int _start, int _end);

注意:这是一个左闭右开的序列[_start,_end),比如Range(2,6)其实产生的是2、3、4、5的序列,不包括6,常用作rowRangecolRange输入参数,从而访问矩阵中的连续行或者连续列。

  下面我们构造一个4x4的矩阵:

  访问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;
}

  成员函数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;
}

  需要特别注意的是:成员函数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;
}

   但是,我们只访问原矩阵的某些行或列,不想改变原矩阵的值,该如何做呢?

  

(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;
}
  将矩阵mm的第2、3行克隆一份,这时候改变mm_range_clone的值,mm中的值是不变的。

  
  接下来,类似操作,也可以使用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;

}
  将mm中的第2、3行复制到mm_range_copy中。

  

(4) 使用Rect

  如果我们要获取矩阵中某一特定的矩形区域,根据上述方法可以先使用rowRange,再使用colRange来定位
   OpenCV提供了一种更简单的方式,就是使用Rect类(Rect是Rectangle的缩写,矩形的意思)。构造一个矩形有多种方式:

条件构造函数
知道左上角的坐标(x,y),还有矩形的宽度和高度Rect(int_x,int_y,int_width,int_hight)
_width_height保存在一个SizeRect(int_x,int_y,Size size)
知道左上角和右下角的坐标也可以构造一个矩形Rect(Point1,Point2) 注意范围为前闭后开,不含点Point2

   当然,还有其他的构造函数,这里不再一一列举,都差不多,掌握这三种也就够了。我们还以4x4的矩阵为例:

#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;



}

  这样得到的矩形区域是指向原矩阵的,要改变rec1中的值,mm也会发生变化,如果不想这样,则仍然可以使用clone()或者copyTo()

五、 访问单、多通道Mat对象中的值

5.1 访问单通道Mat对象中的值

(1) 使用成员函数at

  访问Mat对象中的值,最直接的方式是使用Mat的成员函数at,如对于单通道且数据类型为CV_32F的3行2列的二维矩阵矩阵m:

  访问它的第r行第c列的值,格式为:m.at<Typename>(r,c)。我们还是以矩阵m为例,使用at访问它的值。对于矩阵m中的值依次存入下表中,按照行和列的索引取值即可:

  利用成员函数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;
}

  
  除了使用成员函数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;
}

  注:我们习惯表示矩阵的第r行第c列,也可以利用Point指明矩阵中某一个固定位置,那么该位置就用Point(c,r)来定义,注意第一个元素是列坐标(列号),第二个元素是行坐标(行号),符合我们将水平方向作为x轴,将垂直方向作为y轴的习惯,这里的y轴的方向是朝下的。

  

(2) 使用成员函数ptr

  Mat中的数值在内存中的存储,每一行的值是存储在连续的内存区域中的,我们可以通过成员函数ptr获得指向每一行首地址的指针
   以矩阵m为例,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);
	
	/*成员函数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;

}

  

(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;

}

  

(4) 使用成员变量stepdata

  从前面的讨论我们已经知道,Mat中的值在内存中存储分为两种情况,以矩阵m为例,如上述(2)和(3)所述。下面通过这两种情况,介绍两个重要的成员变量——stepdata

   如下图所示是行与行之间有相等的内存间隔的情况,即不连续

   如下图所示是是连续的情况

  如上图所示:对于单通道矩阵而言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看作一个特殊的二维数组即在每一个位置上不是一个数值而是一个向量(元素)

  通过上图按行号、列号取出每一个元素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;
}

  也可以使用cout << m.at<Vec3f>(Point(c, r)) << ",";替换cout << mm.at<Vec3f>(r, c) << ",";具体注意点与单通道成员函数at一致。

  

(2) 使用成员函数ptr

  多通道Mat的数值在内存中也是按行存储的,且每一行存储在连续的内存区域中,如果行与行之间有内存间隔,这个间隔也是相等的,成员函数ptr可以返回指向指定行的第一个元素(注意不是第一个数值)的指针,如下图所示:

  使用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;

}

  

(3) 使用成员函数isContinuous()ptr

  与单通道Mat对象类似,通过isContinuous判断整个Mat对象中的元素值是否存储在连续内存区域中,如果返回值是true,即表示是连续存储的,如下图所示:

#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;

}

  如果只获取第r行第c列的元素值,那么就可以通过ptr[r*rows+c*cols]命令得到。

  

(4) 使用成员变量stepdata

  单通道Mat类似,通过datastep获取多通道Mat的每一个元素。也是分两种情况:行与行之间有内存间隔连续存储。

   如下图所示是行与行之间有相等的内存间隔的情况,即不连续,step[0]代表包含这个间隔的字节数

  
   如下图所示是是连续的情况

  使用datastepptr获得多通道矩阵的每一个元素:

#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;

}

  

(5) 分离通道

  对于多通道矩阵,可以将其分离为多个单通道矩阵,然后按照单通道矩阵的规则访问其中的值
  如下图可以形象看出多通道矩阵分离成单通道矩阵的,即把所有向量的第一个值组成的单通道矩阵作为第一通道,将所有向量的第二元素组成的单通道矩阵作为第二通道,依次类推:

  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;

}

  

(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;


}

  上面代码将三个单通道矩阵存储在一个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;
}

六、 总结

  最后,长话短说,大家看完就好好动手实践一下,切记不能三分钟热度、三天打鱼,两天晒网。OpenCV是学习图像处理理论知识比较好的一个途径,大家也可以自己尝试写写博客,来记录大家平时学习的进度,可以和网上众多学者一起交流、探讨,有什么问题希望大家可以积极评论交流,我也会及时更新,来督促自己学习进度。希望大家觉得不错的可以点赞、关注、收藏。


🚶🚶🚶今天的文章就到这里啦~
喜欢的话,点赞👍、收藏⭐️、关注💟哦 ~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/69238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读31】《OptCuts: Joint Optimization of Surface Cuts and Parameterization》

目录 一些疑惑 0 引言 1 introduction 2 related work 2.1 parameterization with fixed connectivity 2.2 Separate Cut Computation 3 problem statement ​编辑​ 3.1 数学形式1 3.2 数学形式2 3.3 能量函数 3.3.1 接缝线长度&#xff08;归一化&#xff09;&am…

程序员们,你会考虑使用中文编程吗?

众所周知&#xff0c;编程语言有一条无形的“鄙视链”。 Java和C#相互不服&#xff0c;并且看不起写Python的&#xff0c;Python看不起PHP&#xff0c;PHP看不起前端。而中文编程就在这个语言“鄙视链”的底端艰难生存。 有人对中文编程嗤之以鼻&#xff0c;相比于“人生苦短…

免费网课题库系统接口

免费网课题库系统接口 本平台优点&#xff1a;免费查题接口搭建 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a;题库后台http://daili.jueguangzhe.cn/ 题…

[附源码]计算机毕业设计JAVA职业中介信息管理系统

[附源码]计算机毕业设计JAVA职业中介信息管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

Java进阶——IO流(I)

文章目录IO流一、文件1.1、文件流1.2、常用的文件操作1.2.1、创建文件对象相关构造和方法1.2.2、获取文件的相关信息1.2.3、目录的操作和文件删除二、IO流原理及流的分类2.1、Java IO流的原理2.2、流的分类2.3、IO流常用的类2.3.1、InputStream&#xff1a;字节输入流FileInput…

SSH 远程连接协议详解

一、SSH 协议 1、SSH 协议简介 SSH全称是Secure Shell,SSH协议是基于应用层的协议,为远程登录会话和其他网络服务提供安全性的协议。 SSH使用最多的是远程登录和传输文件,实现此功能的传统协议都不安全(ftp、telnet等),因为它们使用明文传输数据,而SSH在传输过程中的…

【云原生.docker】docker部署Redis集群

1、镜像拉取 docker pull redis:5.0.5 2、容器启动 docker run -d --nameredis-6400 --net host --privilegedtrue -v /home/dev/toms/docker_redis/data/redis-6400:/data redis:5.0.5 --cluster-enabled yes --appendonly yes --port 6400 & docker run -d --nameredis-…

在抖音及一些直播上,如何进行违禁词在线检测呢?

本文首发于&#xff1a;行者AI谛听 随着直播行业的兴起&#xff0c;越来越多的网红主播纷纷加入&#xff0c;平台的审核能力也面临着巨大的挑战&#xff0c;相对于平台的严格监管&#xff0c;很多主播也需要进行深层学习&#xff0c;避免在直播中出行违规。 其实很多主播在直播…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java小型餐饮综合管理系统j1c7m

首先选择计算机题目的时候先看定什么主题&#xff0c;一般的话都选择当年最热门的话题进行组题&#xff0c;就比如说&#xff0c;今年的热门话题有奥运会&#xff0c;全运会&#xff0c;残运会&#xff0c;或者疫情相关的&#xff0c;这些都是热门话题&#xff0c;所以你就可以…

文档对象模型 (DOM) :初学者介绍和指南

果你已经使用JavaScript一段时间了&#xff0c;你可能相当熟悉DOM&#xff08;文档对象模型&#xff09;和CSSOM&#xff08;CSS对象模型&#xff09;脚本。除了 DOM 和 CSSOM 规范定义的接口之外&#xff0c;CSSOM 视图模块中还指定了方法和属性的子集&#xff0c;从而提供了用…

Vue3知识点之数据侦测

Vue 的核心之一就是响应式系统&#xff0c;通过侦测数据的变化&#xff0c;来驱动更新视图。 实现可响应对象的方式 通过可响应对象&#xff0c;实现对数据的侦测&#xff0c;从而告知外界数据变化。实现可响应对象的方式&#xff1a; getter 和 setterdefinePropertyProxy …

IB课程工具书单,助力你考高分

亚马逊汇总了近期最畅销的IB课程工具书单&#xff0c;该书单以购买量和评分进行综合排名。其中的许多书籍都是IBDP学生可选用的绝佳工具书&#xff01;1、Physics for the IB Diploma Coursebook 这本书涵盖了IB 2016年首考的物理课程要求。这本课程手册的第六版针对IB物理教学…

写出go程序

1.安装go包 下载地址&#xff1a;https://golang.google.cn/dl/ 2.安装vscode 下载地址&#xff1a;https://code.visualstudio.com/ 安装好后 以打开文件夹形式打开.go文件。 该文件自己创建一个即可。

怎么视频提取音频文件?分享这3种简单实用的提取方法

不知道大家平时用手机刷视频的时候&#xff0c;会不会被一些好听的背景音乐给吸引了呢&#xff1f;这些背景音乐大多都是网友们自己合成导入视频上传的&#xff0c;可能在许多音乐平台都不能找到音源播放。遇到这样的情况&#xff0c;大家一定都很苦恼吧&#xff1f;但其实&…

Fluorescein(5-isomer)-DBCO,2054339-00-1,二苯基环辛炔-FITC(5-异构体)

一、理论分析&#xff1a; 中文名&#xff1a;二苯基环辛炔-FITC(5-异构体)&#xff0c;二苯并环辛炔-荧光素(5-异构体) 英文名&#xff1a;DBCO-FITC(5-isomer)&#xff0c;5-FITC-DBCO&#xff0c;Fluorescein(5-isomer)-DBCO CAS号&#xff1a;2054339-00-1 化学式&#xff…

遥感影像目标检测:从CNN(Faster-RCNN)到Transformer(DETR)实践技术应用

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

代码随想录第一天

专题&#xff1a;数组 题目&#xff1a;二分查找 题目要求&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。…

信号相角位移量的计算与信号位移计算-附Matlab代码

一、初始相角的位移量 在信号处理中正弦信号经常表示为 x(n)Acos⁡(2πf0n/fsθ)x\left( n \right)A\cos (2\pi {{f}_{0}}n/{{f}_{s}}\theta )x(n)Acos(2πf0​n/fs​θ)&#xff0c;其中 fs{{f}_{s}}fs​是采样频率&#xff0c; f0{{f}_{0}}f0​是正弦信号的频率&#xff0c;…

volatile关键字的原理和要避免的误区

1>防止指令重排 2>禁用工作内存缓冲区&#xff0c;直接使用主内存。 经典使用场景 场景1 public static Singleton getInstance() { //第一次null检查 if (instance null) { synchronized (Singleton.class) { //1 //第二次null检查 if (instance null) { //2…

flink sql gateway初探

文章目录前言1.启动SQL gateway2.打开session3.执行flink SQL4.查看执行结果5.获取operationHandle的status6.注意事项7.官方链接前言 flink 1.16版本中发布了一个新功能–SQL gateway&#xff0c;本篇文章就来实践测试下该功能。 1.启动SQL gateway ./bin/sql-gateway.sh st…