三、OpenCV 基本数据结构
(一)Mat 类
- Mat 类的构造函数和成员函数介绍
构造函数:
Mat 类有多种构造函数形式,以满足不同的初始化需求。
-
无参数构造函数:
Mat::Mat()
,它创建一个空的矩阵对象,可后续通过其他方法来分配内存和赋值数据。例如,Mat emptyMat;
创建了一个初始为空的 Mat 对象,可用于存储图像或其他数据矩阵。 -
以尺寸、数据类型和初始值构造:
Mat(int rows, int cols, int type, const Scalar& s = Scalar())
。其中,rows
表示矩阵的行数,cols
表示列数,type
指定数据类型(如CV_8UC3
表示 8 位无符号整数、3 通道,常用于彩色图像),Scalar
参数用于指定初始值,默认为 0。例如,Mat matrix(3, 4, CV_8UC1, Scalar(255));
创建了一个 3 行 4 列、每个元素为 8 位无符号整数且初始值为 255 的单通道矩阵。 -
从已有数据构造:可以使用指向外部数据的指针来创建 Mat。例如,
Mat(int rows, int cols, int type, void* data, size_t step = AUTO_STEP)
,其中data
是指向数据的指针,step
是每行数据的字节数(如果是AUTO_STEP
,OpenCV 会自动计算)。这种方式在处理外部数据源(如从文件读取的数据)时很有用。
成员函数:
-
rows
和cols
函数:用于获取矩阵的行数和列数。例如,Mat image = imread("image.jpg"); int rowCount = image.rows; int colCount = image.cols;
可以获取加载图像的行数和列数,这对于遍历图像或确定图像尺寸非常有用。 -
channels()
函数:返回矩阵的通道数。对于彩色图像(如CV_8UC3
类型),它返回 3,表示有红、绿、蓝三个通道;对于灰度图像(如CV_8UC1
),返回 1。例如,int numChannels = image.channels();
可以获取图像的通道信息,在处理图像像素时可根据通道数进行不同的操作。 -
depth()
函数:获取矩阵元素的数据深度,即每个元素的数据类型所占用的位数。例如,CV_8U
数据类型的深度为 8 位,CV_32F
为 32 位浮点数。这对于了解数据精度和进行数据类型转换等操作有帮助。 -
at<type>(i, j)
函数:用于访问矩阵中的元素。这里的<type>
需要根据矩阵的数据类型指定,如at<uchar>(i, j)
用于访问CV_8UC1
类型矩阵的元素,at<Vec3b>(i, j)
用于访问CV_8UC3
类型矩阵(彩色图像)的元素。其中i
和j
分别是行和列索引。例如,在处理灰度图像时,可以通过image.at<uchar>(row, col) = newValue;
来修改特定位置的像素值。 -
clone()
和copyTo()
函数:clone()
函数创建一个与原矩阵完全相同的新矩阵,包括数据内容和内存分配。例如,Mat newImage = image.clone();
。copyTo()
函数也用于复制矩阵,但可以指定目标矩阵,并且在复制过程中可以进行一些掩码操作。例如,Mat destination; image.copyTo(destination);
将image
复制到destination
矩阵中。
- 如何创建、复制、赋值和释放 Mat 对象
创建 Mat 对象:
-
如前文所述,可以使用构造函数创建。例如,通过指定尺寸和类型创建一个新的矩阵:
Mat matrix(100, 200, CV_8UC3); // 创建一个 100×200 的彩色(3 通道)矩阵
-
也可以从其他矩阵或图像创建。比如从已有的图像读取创建 Mat:
Mat image = imread("example.jpg"); // 从文件读取图像创建 Mat 对象
复制 Mat 对象:
-
使用
clone()
方法:Mat originalMat(5, 5, CV_8UC1, Scalar(10)); // 创建一个 5×5 的单通道矩阵,初始值为 10
Mat clonedMat = originalMat.clone(); // 复制 originalMat 到 clonedMat
-
使用
copyTo()
方法:Mat sourceMat(3, 3, CV_8UC3, Scalar(255, 0, 0)); // 创建一个 3×3 的彩色矩阵,初始值为蓝色
Mat targetMat;
sourceMat.copyTo(targetMat); // 将 sourceMat 复制到 targetMat
赋值 Mat 对象:
-
直接赋值:
Mat mat1(4, 4, CV_8UC1, Scalar(5));
Mat mat2;
mat2 = mat1; // 将 mat1 的内容赋值给 mat2
-
注意,这种简单赋值在某些情况下可能只是浅拷贝,即两个 Mat 对象可能共享相同的数据内存。如果修改其中一个,可能会影响另一个。例如:
mat1.at<uchar>(0, 0) = 10; // 修改 mat1 的一个元素
cout << mat2.at<uchar>(0, 0); // 此时 mat2 的对应元素也被修改
释放 Mat 对象:
- 在 C++ 中,Mat 对象的内存管理是自动的。当 Mat 对象超出其作用域时,内存会自动释放。但在某些特殊情况下,比如手动分配了内存或使用了外部数据指针创建 Mat,需要注意内存的释放。如果是使用 OpenCV 标准的构造函数创建的 Mat,一般无需手动释放内存。
- Mat 中存储图像数据的原理(内存布局、数据类型等)
内存布局:
Mat 类以一种高效且灵活的方式存储图像数据。图像数据在内存中是按行连续存储的。对于多通道图像,通道数据在内存中也是连续存储的。例如,对于一个CV_8UC3
(8 位无符号整数、3 通道)的彩色图像,其内存布局是每行像素的通道数据依次排列。假设图像宽度为width
,高度为height
,那么在内存中,首先是第一行的第一个像素的红、绿、蓝通道数据,然后是第一行第二个像素的红、绿、蓝通道数据,依此类推,直到第一行所有像素,然后是第二行、第三行…… 直到最后一行。
这种连续存储的方式有利于快速访问和处理图像数据。例如,在进行图像滤波等操作时,可以方便地通过指针遍历图像的每一行和每个像素。
数据类型:
OpenCV 的 Mat 支持多种数据类型,常见的数据类型包括:
-
CV_8U
:8 位无符号整数,常用于表示灰度图像(单通道)或彩色图像的每个通道(如CV_8UC3
中的每个通道),取值范围是 0 - 255。 -
CV_8S
:8 位有符号整数,可用于一些特殊的图像数据表示或计算,取值范围是 - 128 - 127。 -
CV_16U
:16 位无符号整数,用于需要更高精度的灰度或彩色图像表示,取值范围更大。 -
CV_16S
:16 位有符号整数。 -
CV_32S
:32 位有符号整数。 -
CV_32F
:32 位浮点数,常用于图像的一些数学计算或处理,如在一些图像滤波算法中可能需要使用浮点数类型来存储中间结果。 -
CV_64F
:64 位浮点数,用于更高精度的计算场景。
不同的数据类型在存储图像数据时具有不同的精度和范围,开发者可以根据具体的应用需求选择合适的数据类型。例如,在进行简单的图像显示和处理时,CV_8UC3
类型的彩色图像就足够了;但如果是在进行图像的数值分析或深度学习相关的图像预处理等需要高精度计算的场景中,可能需要使用CV_32F
或CV_64F
类型的数据。
(二)其他辅助数据结构
- Point 类、Size 类和 Rect 类的定义和用法
Point 类:
-
定义:
Point
类用于表示二维平面上的一个点。它有两个成员变量,通常是x
和y
坐标,用于指定点在平面中的位置。在 OpenCV 中,Point
类的定义如下:class Point { public: Point(); Point(int _x, int _y); int x, y; };
-
用法:可以用于指定图像中的某个点的位置。例如,在绘制图形或标记图像中的特定位置时使用。假设要在图像中标记一个点,可以这样做:
Mat image = Mat::zeros(500, 500, CV_8UC3); // 创建一个黑色的 500×500 彩色图像
Point point(250, 250); // 创建一个坐标为 (250, 250) 的点
circle(image, point, 10, Scalar(0, 0, 255), -1); // 在图像上以该点为圆心画一个半径为 10 的红色实心圆
Size 类:
-
定义:
Size
类用于表示二维尺寸,即宽度和高度。它的定义如下:class Size { public: Size(); Size(int _width, int _height); int width, height; };
-
用法:常用于指定图像的大小、窗口大小或其他二维对象的尺寸。例如,在创建一个特定大小的图像或调整图像大小时使用。如:
Size imageSize(300, 200); // 创建一个宽度为 300,高度为 200 的尺寸对象
Mat newImage(imageSize, CV_8UC3); // 根据这个尺寸创建一个彩色图像
Rect 类:
-
定义:
Rect
类用于表示矩形区域。它有四个成员变量,通常包括左上角顶点的坐标(x
和y
)以及矩形的宽度和高度。其定义如下:class Rect { public: Rect(); Rect(int _x, int _y, int _width, int _height); int x, y, width, height; };
-
用法:在图像处理中有广泛的应用,比如指定感兴趣区域(ROI)、图像裁剪等。例如:
Mat image = imread("example.jpg"); // 读取一张图像
Rect roi(100, 100, 200, 150); // 定义一个左上角坐标为 (100, 100),宽度为 200,高度为 150 的矩形区域
Mat croppedImage = image(roi); // 从图像中裁剪出这个矩形区域对应的图像
- 向量类(如 Vec2b、Vec3b、Vec4b 等)在处理图像像素中的应用
向量类的定义和类型:
- OpenCV 中的向量类用于表示具有固定元素个数的向量。例如,
Vec2b
表示包含两个 8 位无符号整数的向量,Vec3b
表示包含三个 8 位无符号整数的向量(常用于彩色图像像素的表示,对应红、绿、蓝通道),Vec4b
表示包含四个 8 位无符号整数的向量。这些向量类的定义是模板化的,以适应不同的数据类型和元素个数。
在处理图像像素中的应用:
-
在处理彩色图像时,由于每个像素由多个通道组成,向量类非常方便。以
CV_8UC3
彩色图像为例,当访问图像中的像素时,可以使用Vec3b
类型。例如:Mat image = imread("color_image.jpg"); // 读取彩色图像
for (int row = 0; row < image.rows; row++) { for (int col = 0; col < image.cols; col++) { Vec3b pixel = image.at<Vec3b>(row, col); // 获取当前像素的 Vec3b 向量
pixel [0] = 255 - pixel [0]; // 对红色通道的值进行修改(这里是取反操作)
pixel [1] *= 2; // 对绿色通道的值进行操作(这里是加倍)
pixel [2] = 0; // 将蓝色通道的值设为 0
image.at<Vec3b>(row, col) = pixel; // 更新像素值
}
}` -
这种方式使得对彩色图像像素的多通道操作更加简洁和直观。同时,对于其他具有多个元素的图像数据表示(如一些特殊的图像编码格式或特征向量),也可以使用相应的向量类进行处理,提高代码的可读性和可维护性。
- 数据结构之间的相互转换和操作
Mat 与 Point、Size、Rect 之间的转换和操作:
-
从 Mat 到 Point、Size、Rect 的获取:
可以通过 Mat 的属性和方法来获取相关的数据结构信息。例如,对于一个表示图像的 Mat 对象,可以通过Mat::cols
和Mat::rows
获取图像的宽度和高度,从而创建一个Size
对象:Size imageSize(image.cols, image.rows);
。如果要获取图像中的某个点,可以通过指定坐标创建Point
对象,如Point pointInImage(100, 200);
(这里假设是在图像范围内)。对于矩形区域,可以通过指定左上角坐标和宽度、高度创建Rect
对象,或者通过一些算法(如在目标检测中确定目标的位置和大小)得到Rect
对象来表示目标在图像中的位置。 -
在 Mat 中使用 Point、Size、Rect 进行操作:
Rect
对象常用于在 Mat 中指定感兴趣区域(ROI),通过Mat::operator()(const Rect& rect)
重载操作符,可以获取指定矩形区域内的子矩阵。例如,Mat image = imread("example.jpg"); Rect roi(100, 100, 200, 150); Mat croppedImage = image(roi);
从图像中裁剪出指定的 ROI。Point
对象可用于在图像上绘制图形、标记点等操作,结合其他绘图函数(如circle
、line
等)在 Mat 表示的图像上进行绘制。Size
对象可用于调整图像大小,如通过resize
函数:Mat originalImage = imread("original.jpg"); Size newSize(300, 200); Mat resizedImage; resize(originalImage, resizedImage, newSize);
将原始图像调整为指定大小。
向量类与 Mat 的转换和操作:
-
从 Mat 到向量类的转换(以彩色图像为例):
当处理彩色图像(如CV_8UC3
类型)时,访问图像像素会涉及到向量类。如前文所述,通过Mat::at<Vec3b>(i, j)
(这里以Vec3b
为例)可以获取图像中指定位置的像素向量,从而对像素的各个通道进行操作。 -
向量类到 Mat 的转换(构建图像或子图像):
可以使用向量类来构建或修改图像。例如,创建一个新的彩色图像时,可以通过循环遍历每个像素位置,为每个像素赋值一个Vec3b
类型的向量来构建图像。假设要创建一个纯色的彩色图像:Mat newImage(200, 300, CV_8UC3); // 创建一个 200×300 的彩色图像
for (int row = 0; row < newImage.rows; row++) { for (int col = 0; col < newImage.cols; col++) { Vec3b pixel(255, 0, 0); // 蓝色像素向量 newImage.at<Vec3b>(row, col) = pixel; } }
同时,在处理图像的局部区域(子图像)时,也可以使用向量类来修改像素值,实现图像的局部处理。例如,在图像的某个矩形区域内改变像素颜色等操作。这种数据结构之间的相互转换和操作使得在图像处理中能够更加灵活地实现各种功能。