本文档创建于2023年3月2日
opencv早期使用lplImage格式(intel另一个开源库的格式)来保存图像。到2.0以后的版本,opencv库引入面向对象的思想,采用C++重写了大量代码。并引入了Mat类作为图像容器。Mat(matrix缩写)类本身是一个矩阵格式,也可以用来保存图像。所以opencv中的矩阵运算也可以使用Mat型。
Mat的创建
opencv提供了很多方法创建Mat,这里介绍两种。
- 使用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型数据,还有如下类型:
- 使用create()方法
cv::Mat m3;
m3.create(3,4,CV_8UC3);
先声明了一个Mat类的对象m3,然后将其尺寸定为3*4,类型为CV_8UC3
Mat复制
Mat的复制分为浅复制和深复制两种
- 浅复制
#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++中的浅拷贝。
运行结果如下:
可见,修改了dstM的值之后srcM的值也发生了改变,所以dstM与srcM使用的是内存中的同一块空间,类似C++中的引用。
同时,我们从输出中也可以看出,一个元素的不同通道的数是横着放的。也就是Mat中的每一个元素,在多通道的情况下,是一个行向量。
- 深复制
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
- 利用.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;
}
运行的效果如下:
直观上看,将每个通道的值减半后,图片的亮度降低了。
使用.ptr方法遍历Mat时,使用指针接收矩阵每一行的头地址,然后这个指针就是一个一维数组,这个一维数组的长度是每一行的通道数,其中的每个元素就是一个通道。用.ptr处理时是直接处理每一行的每个通道。
- 利用.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方法更容易对每一个元素整体进行操作。