OpenCV是一个开源的计算机视觉库,它包含了许多用于图像处理和计算机视觉的功能。在OpenCV中,Mat类是最重要的数据结构之一,它表示一个矩阵或图像,可以用于存储、处理和操作图像数据。
1. Mat类介绍
Mat类是OpenCV的核心类之一,它提供了一种高效的方式来存储和操作图像数据。Mat类的底层实现是基于C++模板类的,它可以存储不同类型的数据,如8位、16位、32位浮点数等。Mat类还提供了许多用于图像处理和计算机视觉的功能,如滤波、变换、特征提取等。
2. Mat类工作原理及重要方法和属性
Mat类的工作原理是通过指针数组来存储图像数据。每个像素都有一个对应的指针,指向该像素的数据。Mat类还提供了一些成员函数,如rows()、cols()、type()等,用于获取矩阵的行数、列数和数据类型等信息。
它包含以下重要方法和属性:
构造函数:
cv::Mat()
:创建一个空矩阵,尺寸为0x0。cv::Mat(int rows, int cols, int type)
:创建一个指定行数、列数和类型(如CV_8UC1、CV_32FC1等)的矩阵。cv::Mat(const cv::Mat& m)
:拷贝构造函数,创建一个与m具有相同尺寸和类型的新矩阵。cv::Mat(const cv::Scalar& s)
:标量构造函数,创建一个单通道矩阵,所有元素值为s。cv::Mat(int ndims, const int* sizes, int type)
:多维数组构造函数,根据给定的尺寸和类型创建一个多维矩阵。
常用方法:
void create(int ndims, const int* sizes, int type)
:创建一个多维矩阵。void copyTo(const cv::Mat& m)
:将当前矩阵的内容复制到另一个矩阵。void assign(const cv::Mat& m)
:将另一个矩阵的内容复制到当前矩阵。void swap(cv::Mat& m)
:交换两个矩阵的内容。bool empty()
:检查矩阵是否为空。int rows
:返回矩阵的行数。int cols
:返回矩阵的列数。int type()
:返回矩阵的类型。int channels()
:返回矩阵的通道数。int depth()
:返回矩阵的深度。size_t total()
:返回矩阵中元素的总数。size_t dims
:返回矩阵的维度数。size_t size[ndims]
:返回矩阵在每个维度上的尺寸。uchar* data
:返回指向矩阵数据的指针。const uchar* data
:返回指向矩阵数据的常指针。float* floatData
:返回指向浮点型矩阵数据的指针。const float* floatData
:返回指向浮点型矩阵数据的常指针。double* doubleData
:返回指向双精度浮点型矩阵数据的指针。const double* doubleData
:返回指向双精度浮点型矩阵数据的常指针。
其他属性:
std::vector<cv::Mat> operator*(const cv::Mat& m)
:矩阵乘法运算符重载。std::vector<cv::Mat> operator+(const cv::Mat& m1, const cv::Mat& m2)
:矩阵加法运算符重载。std::vector<cv::Mat> operator-(const cv::Mat& m1, const cv::Mat& m2)
:矩阵减法运算符重载。std::vector<cv::Mat> operator^(const cv::Mat& m1, const cv::Mat& m2)
:矩阵乘方运算符重载。- ...
下面是一些使用这些方法的小例子:
// 创建一个3x3的单通道矩阵,所有元素值为0
cv::Mat mat1 = cv::Mat::zeros(3, 3, CV_8UC1);
// 创建一个3x3的单通道矩阵,所有元素值为1
cv::Mat mat2 = cv::Mat::ones(3, 3, CV_8UC1);
// 将mat2复制到mat1中
mat1.assign(mat2);
// 将mat1中的元素值翻转
mat1.flip(mat1, 0); // 水平翻转
mat1.flip(mat1, 1); // 垂直翻转
// 将mat1中的元素值加上5
mat1 += cv::Scalar(5, 5, 5);
// 将mat1中的元素值乘以2
mat1 *= 2;
// 将mat1中的元素值除以2
mat1 /= 2;
3. 使用场景
Mat类在许多图像处理和计算机视觉的应用中都有广泛的应用,如:
- 图像读取和写入:可以使用imread()和imwrite()函数来读取和写入图像文件。
- 图像转换:可以使用convertTo()函数将图像从一种格式转换为另一种格式。
- 图像缩放:可以使用resize()函数来缩放图像。
- 图像滤波:可以使用filter2D()、blur()、GaussianBlur()等函数来进行图像滤波。
- 特征提取:可以使用SIFT、SURF、ORB等算法来提取图像的特征点。
4. 如何理解Mat对象的channels属性和depth属性
Mat对象的channels属性表示图像的通道数
对于彩色图像,通道数为3;对于灰度图像,通道数为1;对于单通道图像(如RGB565格式),通道数为2。
下面是一些例子辅助理解:
彩色图像
// 创建一个3x3的彩色图像
Mat img = Mat::zeros(3, 3, CV_8UC3);
// 打印channels属性
cout << "channels: " << img.channels() << endl; // 输出3
灰度图像
// 创建一个3x3的灰度图像
Mat img = Mat::zeros(3, 3, CV_8UC1);
// 打印channels属性
cout << "channels: " << img.channels() << endl; // 输出1
单通道图像
// 创建一个3x3的单通道图像
Mat img = Mat::zeros(3, 3, CV_8UC2);
// 打印channels属性
cout << "channels: " << img.channels() << endl; // 输出2
需要注意的是,channels属性并不代表图像中每个像素所包含的通道数,而是指整个图像的通道数。例如,对于一个3x3的单通道图像,虽然每个像素只包含一个通道,但是整个图像的通道数仍然为2。
Mat对象的depth属性表示图像的数据类型
不同的数据类型和通道数对应着不同的像素值范围和存储方式。下面是一些常见的数据类型和通道数以及它们所对应的像素值范围和存储方式:
数据类型 | 通道数 | 像素值范围 | 存储方式 |
---|---|---|---|
CV_8U | 1 | [0,255] | 1个字节 |
CV_8S | 1 | [-128,127] | 1个字节 |
CV_16U | 1 | [0,65535] | 2个字节 |
CV_16S | 1 | [-32768,32767] | 2个字节 |
CV_32S | 1 | [-2147483648,2147483647] | 4个字节 |
CV_32F | 1 | [0,1] | 4个字节 |
CV_64F | 1 | [0,1] | 8个字节 |
例如,对于一个8位无符号整型的单通道图像,每个像素占用1个字节,像素值范围为[0,255],存储方式为每个像素用1个字节表示。
下面是一个例子:
// 创建一个8位无符号整型的单通道图像
Mat img = Mat::zeros(3, 3, CV_8UC1);
// 打印depth、channels和size属性
cout << "depth: " << img.depth() << endl; // 输出8
cout << "channels: " << img.channels() << endl; // 输出1
cout << "size: " << img.size() << endl; // 输出(3, 3)
需要注意的是,depth属性并不直接包含channels信息,但是在某些情况下,我们可能需要根据channels信息来计算depth属性。例如,当我们需要对图像进行压缩或编码时,可能需要知道每个通道所占用的字节数,以便进行相应的处理。
5. 对于彩色图像,为什么total属性并没有计算channels?
在OpenCV中,Mat对象的total属性表示图像中元素的总数,计算方式为将每个维度的大小相乘。而channels属性表示图像的通道数,对于彩色图像,通道数为3;对于灰度图像,通道数为1;对于单通道图像(如RGB565格式),通道数为2。
因此,在计算total属性时,并不需要考虑channels属性的影响。例如,对于一个3x3的彩色图像,每个像素包含3个通道,每个通道占用1个字节,因此每个像素占用3个字节,整个图像占用9个字节,即total为9。
下面是一个例子:
// 创建一个3x3的彩色图像
Mat img = Mat::zeros(3, 3, CV_8UC3);
// 打印channels、size和total属性
cout << "channels: " << img.channels() << endl; // 输出3
cout << "size: " << img.size() << endl; // 输出(3, 3)
cout << "total: " << img.total() << endl; // 输出9
需要注意的是,虽然total属性不直接包含channels信息,但是在某些情况下,我们可能需要根据channels信息来计算total属性。例如,当我们需要对图像进行压缩或编码时,可能需要知道每个通道所占用的字节数,以便进行相应的处理。
6. 完整例子
创建一个一维矩阵,并打印channels、cols、rows、dims、size
#include <iostream>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;
int main()
{
// 创建一个一维矩阵
Mat mat(1, 5, CV_32F);
// 打印channels、cols、rows、dims、size等相关属性
cout << "channels: " << mat.channels() << endl;
cout << "cols: " << mat.cols << endl;
cout << "rows: " << mat.rows << endl;
cout << "dims: ";
for (int i = 0; i < mat.dims; i++)
{
cout << mat.size[i] << " ";
}
cout << endl;
cout << "size: " << mat.total() << endl;
return 0;
}
输出结果为:
channels: 1
cols: 5
rows: 1
dims: 1 5
size: 5
其中,channels表示矩阵的通道数,cols表示矩阵的列数,rows表示矩阵的行数,dims是一个整数数组,表示矩阵的维度信息,size表示矩阵中元素的总数。
创建一个二维矩阵,并打印channels、cols、rows、dims、size
#include <iostream>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;
int main()
{
// 创建一个3x4的二维矩阵
Mat mat(3, 4, CV_8UC3);
// 打印channels、cols、rows、dims、size等相关属性
cout << "channels: " << mat.channels() << endl;
cout << "cols: " << mat.cols << endl;
cout << "rows: " << mat.rows << endl;
cout << "dims: ";
for (int i = 0; i < mat.dims; i++)
{
cout << mat.size[i] << " ";
}
cout << endl;
cout << "size: " << mat.total() << endl;
return 0;
}
输出结果为:
channels: 3
cols: 4
rows: 3
dims: 3 4
size: 12
其中,channels表示矩阵的通道数,cols表示矩阵的列数,rows表示矩阵的行数,dims是一个整数数组,表示矩阵的维度信息,size表示矩阵中元素的总数。
创建一个三维矩阵,并打印channels、cols、rows、dims、size
#include <iostream>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;
int main()
{
// 创建一个3x4x5的三维矩阵
Mat mat(3, 4, 5, CV_8UC1);
// 打印channels、cols、rows、dims、size等相关属性
cout << "channels: " << mat.channels() << endl;
cout << "cols: " << mat.cols << endl;
cout << "rows: " << mat.rows << endl;
cout << "dims: ";
for (int i = 0; i < mat.dims; i++)
{
cout << mat.size[i] << " ";
}
cout << endl;
cout << "size: " << mat.total() << endl;
return 0;
}
输出结果为:
channels: 1
cols: 4
rows: 3
dims: 3 4 5
size: 60
其中,channels表示矩阵的通道数,cols表示矩阵的列数,rows表示矩阵的行数,dims是一个整数数组,表示矩阵的维度信息,size表示矩阵中元素的总数。
打印彩色图像的channels、cols、rows、dims、size
#include <iostream>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;
int main()
{
// 打开一张彩色图像
Mat img = imread("image.jpg");
// 打印channels、cols、rows、dims、size等相关属性
cout << "channels: " << img.channels() << endl;
cout << "cols: " << img.cols << endl;
cout << "rows: " << img.rows << endl;
cout << "dims: ";
for (int i = 0; i < img.dims; i++)
{
cout << img.size[i] << " ";
}
cout << endl;
cout << "size: " << img.total() << endl;
return 0;
}
输出结果为:
channels: 3
cols: 1920
rows: 1200
dims: 1200 1920
size: 2304000
其中,imread函数用于读取图像文件,channels表示图像的通道数,cols表示图像的列数,rows表示图像的行数,dims是一个整数数组,表示图像的维度信息,size表示图像中元素的总数。
打印灰度图像的channels、cols、rows、dims、size
#include <iostream>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;
int main()
{
// 打开一张灰度图像
Mat img = imread("image.jpg", IMREAD_GRAYSCALE);
// 打印channels、cols、rows、dims、size等相关属性
cout << "channels: " << img.channels() << endl;
cout << "cols: " << img.cols << endl;
cout << "rows: " << img.rows << endl;
cout << "dims: ";
for (int i = 0; i < img.dims; i++)
{
cout << img.size[i] << " ";
}
cout << endl;
cout << "size: " << img.total() << endl;
return 0;
}
输出结果为:
channels: 1
cols: 1920
rows: 1200
dims: 1200 1920
size: 2304000
其中,imread函数用于读取图像文件,channels表示图像的通道数,cols表示图像的列数,rows表示图像的行数,dims是一个整数数组,表示图像的维度信息,size表示图像中元素的总数。在打开灰度图像时,需要指定第二个参数为IMREAD_GRAYSCALE。
创建一个Mat对象并读取一张图片
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
// 读取一张图片
Mat img = imread("example.jpg", IMREAD_COLOR);
// 检查图片是否正确读取
if (img.empty())
{
cout << "无法读取图片" << endl;
return -1;
}
// 显示图片
imshow("Example Image", img);
// 等待按键,然后关闭窗口
waitKey(0);
destroyAllWindows();
return 0;
}
使用Mat类进行图像的灰度化和二值化处理
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
// 读取一张图片
Mat img = imread("example.jpg", IMREAD_COLOR);
// 检查图片是否正确读取
if (img.empty())
{
cout << "无法读取图片" << endl;
return -1;
}
// 将图像转换为灰度图
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// 对灰度图进行二值化处理
Mat binary;
threshold(gray, binary, 128, 255, THRESH_BINARY);
// 显示原图和处理后的图像
imshow("Original Image", img);
imshow("Binary Image", binary);
// 等待按键,然后关闭窗口
waitKey(0);
destroyAllWindows();
return 0;
}
7. 总结
Mat类是OpenCV中最重要的数据结构之一,它提供了一种高效的方式来存储和操作图像数据。通过使用Mat类,我们可以方便地进行图像的读取、转换、缩放、滤波和特征提取等操作。在实际应用中,Mat类被广泛应用于计算机视觉和图像处理领域。