Mat 是 OpenCV 中的数据类型,储存矩阵形式的数据,构造 Mat 类型的方法有很多,都是通过 Mat 这个构造函数进行实现(Mat 也是构造 Mat 数据类型的函数)。
本文主要介绍 Mat 的构造方法和 Mat 类的访问以及相关的数据类型。
Mat matrix(10, 10, CV_8UC(1)); // 参数 10 分别表示矩阵的行数和列数,
// CV_8UC(1) 规定矩阵元素为单通道
// 8 位无符号整型.
如果想要初始化数据:
Mat matrix(10, 10, CV_8UC(1), Scalar(0));
使用 Scalar(0) 将所有元素初始化为零,如果为多通道,使用 Scalar::all()。
该参数将数据类型、位计数和通道数统一到一个宏中,通常使用的宏模式为:
CV_< bits >< type >C(< channels >)
其中:
< bits > 可以替换为:
< type > 可以替换为:
< channels > 表示的是每一个元素的通道数,比如一个 3 x 3 的 Mat 类型的矩阵,通道数为 3,假如元素初始化为 1,那么这个矩阵可以写成 [(1, 1, 1), (1, 1,1), (1, 1, 1); (1, 1, 1), (1, 1,1), (1, 1, 1); (1, 1, 1), (1, 1,1), (1, 1, 1)],其中每一个元素都是一个向量,示例程序如下:
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat matrix(3, 3, CV_8UC3, Scalar::all(1));
cout << matrix;
}
可以看到,opencv 的输出风格并未给每一个元素加上小括号,这一点与 C++ 的矩阵风格输出不同,但是也应该要知道,这表示的是多通道的输出数据。
创建一个立方体(三维数组),边长为 10, 类型为双精度(64位)的双通道元素,并用 1.0 初始化所有值:
int sizes[] = {10, 10, 10};
Mat cube(3, sizes, CV_64FC(2), Scalar::all(1.0));
实际上,计算机只能输出 2 维的矩阵数据,因此此处的 3 维数据虽然可以创建,但不能如上一个程序一样使用 cout 输出。
Mat matrix(3, 3, CV_32S(2));
matrix.create(10, 10, CV_8UC(2)); // 更改大小和类型
创建一个 Mat 类,它是另一个 Mat 类的一部分,称之为感兴趣区域(Region of Interest, ROI)。当只需要访问图片的一部分区域时,可以使用这样的方式。比如需要对图像的一部分进行滤波,创建一个包含 50 x 50 像素的正方形 ROI Mat 类:
Mat roi(image, Rect(25, 25, 50, 50);
第一个参数是想要处理的图像,第二个参数是使用 Rect() 进行区域约束,前两个数据分别表示起点的横坐标和纵坐标(以原图象 image 的左上角为起点,向下和向右为正)。
需要注意的是使用上述方法创建的 Mat 类 roi,本质上使用的是原始 Mat 类的头和地址,因此如果对 roi 进行操作,那么原始图片也将被改变。如果想要复制 Mat 类,应该使用 clone() 函数。
Mat imageCopy = image. clone();
选择一行或一列:
Mat r = image.row(0); // 第一行
Mat c = image.col(0); // 第一列
选择多行或多列:
下列示例程序将在图像中创建一个加号:
#include
#include
using namespace cv;
int main()
{
Mat logo = imread("D:\\w.jpg"); // 读取图片
// 使用 rowRange() 和 colRange() 方法选取 ROI,此处选择了中心处宽度为 4 的像素区域
Mat centralRows = logo.rowRange(logo.rows / 2 - 2, logo.rows / 2 + 2);
Mat centralCols = logo.colRange(logo.cols / 2 - 2, logo.cols / 2 + 2);
// 将 ROI 的值设置为 0,这样其输出图像则为黑色
centralRows = Scalar(0);
centralCols = Scalar(0);
// 创建窗口
namedWindow("+", WINDOW_AUTOSIZE);
// 显示图片
imshow("+", logo);
// 图片显示时间,以 ms 为单位,参数为 0 时,窗口在用户有键盘动作时销毁
waitKey(0);
}
at 是一个函数模板,可以用来访问 Mat 类中的一个元素。
假设有一个标准的三通道彩色图像,加载到名为 image 的 Mat 类中(image 是 CV_8UC(3) 类型),可以编写以下代码段来访问 (X, Y) 位置处的像素,并将其颜色值设置为 C:
image.at< Vec3b >(X, Y) = C;
注意到此处使用了 vec 类,这是 OpenCV 提供以便于更容易地进行数据访问和处理的方法。实际上可以使用 typedef 创建并命名自己的 Vec 类型:
typedef Vec< Type, C > NewType;
OpenCV 中创建的 Vec 类型如下(参考博文):
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<ushort, 2> Vec2w;
typedef Vec<ushort, 3> Vec3w;
typedef Vec<ushort, 4> Vec4w;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<int, 6> Vec6i;
typedef Vec<int, 8> Vec8i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
针对这三种方法,使用以下代码实现相同目标,它们都通过将每个像素的值除以 5,使图像变得更暗。
at 函数:
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
image.at<Vec3b>(i, j) /= 5;
}
}
通过 begin 和 end 函数使用类似于 STL 的迭代器:
MatIterator_<Vec3b> it_begin = image.begin<Vec3b>(); // 得到迭代器的开始
MatIterator_<Vec3b> it_end = image.end<Vec3b>(); // 得到迭代器的结束
for (; it_begin != it_end; it_begin++)
{
*it_begin /= 5; // 对迭代器进行循环
}
使用 forEach 函数(与 lambda)一起使用
image.forEach<Vec3b>([](Vec3b& p, const int*)
{
p /= 5;
});