本文档创建于2023年3月2日

opencv早期使用lplImage格式(intel另一个开源库的格式)来保存图像。到2.0以后的版本,opencv库引入面向对象的思想,采用C++重写了大量代码。并引入了Mat类作为图像容器。Mat(matrix缩写)类本身是一个矩阵格式,也可以用来保存图像。所以opencv中的矩阵运算也可以使用Mat型。

Mat的创建

opencv提供了很多方法创建Mat,这里介绍两种。

  1. 使用Mat()构造函数
cv::Mat m1(2,2,CV_8UC3,Scalar(0,0,255));

该语句创建了一个名为m1的Mat,m1的尺寸为2*2,类型为CV_8UC3,8位unsigned char(实际上就是无符号整数),3通道,这个m1的每一个元素包含了3个通道或者说3个数值。然后用0,0,255为一个元素中的3个数值赋值。这里8位unsigned char类型的取值范围为0~255。实际上如果一个Mat类的对象是用来表示RGB图像的话应该声明为CV_8UC3类型。

Mat中的元素可以定义为不同类型,定义的方式如下:

CV_+ 位数 + 数据类型 + 通道数

如CV_32FC1表示1通道32位float型数据,还有如下类型:

opencv 保存yuv422图像 opencv 保存mat_人工智能

  1. 使用create()方法
cv::Mat m3;
m3.create(3,4,CV_8UC3);

先声明了一个Mat类的对象m3,然后将其尺寸定为3*4,类型为CV_8UC3

Mat复制

Mat的复制分为浅复制和深复制两种

  1. 浅复制
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
	Mat srcM(2, 2, CV_8UC3, Scalar(0, 0, 255));
	Mat dstM;
	dstM = srcM; //浅复制
	cout << "srcM = " << endl;
	cout << srcM << endl;
	cout << "dstM = " << endl;
	cout << dstM << endl;

	dstM = { Scalar(1,1,128) }; //修改dstM的值
	cout << "srcM = " << endl;
	cout << srcM << endl;
	cout << "dstM = " << endl;
	cout << dstM << endl;

	system("pause");
	return 0;
}

直接用等号 “=” 赋值,这样复制过去的矩阵,只是新生成了一个名字dstM,但是dstM还是指向srcM矩阵的data,类似C++中的浅拷贝。

运行结果如下:

opencv 保存yuv422图像 opencv 保存mat_计算机视觉_02

可见,修改了dstM的值之后srcM的值也发生了改变,所以dstM与srcM使用的是内存中的同一块空间,类似C++中的引用。

同时,我们从输出中也可以看出,一个元素的不同通道的数是横着放的。也就是Mat中的每一个元素,在多通道的情况下,是一个行向量。

  1. 深复制
cv::Mat srcM(2,2,CV_8UC3,Scalar(0,0,255));
cv::Mat dstM;
srcM.copyTo(dstM);

深复制通过copyTo()方法实现。使用了深复制的条件下,dstM使用了与srcM不同的另一块内存空间。

Mat遍历

Mat的遍历有2种方法,利用.ptr和.at

  1. 利用.ptr
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("../Jakob-Ingebrigtsen-of-Norway-1500m-Athletissima-Lausanne-Diamond-League-meeting.jpg");
	imshow("Jakob Ingebrigtsen(before)", img); //显示处理前的图片
	int height = img.rows; //行数
	int width = img.cols * img.channels(); //每一行的数值(通道)的个数
	for (int i = 0; i < height; i++)
	{
		//定义指针data,其值为img的第i行的头地址
		uchar* data = img.ptr<uchar>(i);
		for (int j = 0; j < width; j++)
		{
			//处理每个数值(通道),每个通道的值减少到1/2
			data[j] = data[j] / 2;
		}
	}
	imshow("Jakob Ingebrigtsen(after)", img); //显示处理后的图片
	waitKey(0);

	return 0;
}

运行的效果如下:

opencv 保存yuv422图像 opencv 保存mat_#include_03

直观上看,将每个通道的值减半后,图片的亮度降低了。

使用.ptr方法遍历Mat时,使用指针接收矩阵每一行的头地址,然后这个指针就是一个一维数组,这个一维数组的长度是每一行的通道数,其中的每个元素就是一个通道。用.ptr处理时是直接处理每一行的每个通道。

  1. 利用.at
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
	Mat img = imread("../Jakob-Ingebrigtsen-of-Norway-1500m-Athletissima-Lausanne-Diamond-League-meeting.jpg");
	imshow("Jakob Ingebrigtsen(before)", img); //显示处理前的图片
	int height = img.rows; //行数
	int width = img.cols; //每一行元素的个数
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			img.at<Vec3b>(i, j)[0] = img.at<Vec3b>(i, j)[0] / 2;
			img.at<Vec3b>(i, j)[1] = img.at<Vec3b>(i, j)[1] / 2;
			img.at<Vec3b>(i, j)[2] = img.at<Vec3b>(i, j)[2] / 2;
		}
	}
	imshow("Jakob Ingebrigtsen(after)", img); //显示处理后的图片
	waitKey(0);

	return 0;
}

效果与上方使用.ptr方法一样。

使用.at方法的话是获取每个元素,然后对每个元素的每个通道进行操作。使用.at方法更容易对每一个元素整体进行操作。