- Mat类介绍
- Mat对象的创建
- create()函数
- Matlab风格的函数
- 矩阵元素的表达
- 像素的读写(at函数/迭代器/指针)
Mat类是什么
- 之前提到数字图像可以使用矩阵来表示。OpenCV的Mat类是一个优秀的表示图像的类,同时也是一个通用的表示矩阵的类
Mat类的组成
class CV_EXPORTS Mat
{
public:
//! 一系列构造函数
...
/*! flags参数包含一些关于矩阵的信息:
- 矩阵标识
- 数据是否连续
- 深度
- 通道数目
*/
int flags;
//! 矩阵的维数,取值大于或等于2
int dims;
//! 矩阵的行数和列数,如果矩阵超过2维,这两个变量的值都为-1
int rows, cols;
//! 指向数据的指针
uchar* data;
//! helper fields used in locateROI and adjustROI
const uchar* datastart;
const uchar* dataend;
const uchar* datalimit;
//! custom allocator
MatAllocator* allocator;
//! 其他成员变量和成员函数
...
//! 与UMat的交互
UMatData* u;
//行步长
MatSize size;
MatStep step;
}
Mat对象的创建
1.使用构造函数
无参数构造函数
Mat::Mat()
创建指定大小、类型为 type 的图像
Mat::Mat(int rows, int cols, int type)
Mat::Mat(Size size, int type)
Mat::Mat(int ndims, const int* sizes, int type)
Mat::Mat(const std::vector<int>& sizes, int type)
有四种方式指定图像大小:
1. 通过设定 int rows 和 int cols 指定行数和列数
2. 通过 Size(cols, rows)指定行数和列数
3. 通过 int ndims 和 int* sizes 设定维数和每个维度的形状指定图像尺寸
4. 通过容器 vector<int> & sizes 设定维数和每个维度的形状指定图像尺寸
参数type:规定了矩阵中元素的类型以及矩阵通道数,它是interface.h中定义的预定义常量
定义格式为:CV_(位数)+(数据类型)+(通道数)
具体有以下值:
CV_8UC1 CV_8UC2 CV_8UC3 CV_8UC4 CV_8SC1 CV_8SC2 CV_8SC3 CV_8SC4
CV_16UC1 CV_16UC2 CV_16UC3 CV_16UC4 CV_16SC1 CV_16SC2 CV_16SC3 CV_16SC4
CV_32SC1 CV_32SC2 CV_32SC3 CV_32SC4
CV_32FC1 CV_32FC2 CV_32FC3 CV_32FC4 CV_64FC1 CV_64FC2 CV_64FC3 CV_64FC4
--这里U表示无符号整数(unsigned integer),S是有符号整数(signed integer),F时浮点数(float)
例如:CV_8UC3,表示的时元素类型时一个8位无符号整数,通道为3
C1,C2,C3,C4 表示通道数1,2,3,4
创建指定大小、类型为 type、行步长为 step 的图像
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
Mat::Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0)
Mat::Mat(const std::vector<int>& sizes, int type, void* data, const size_t* stpes=0)
行步长是指矩阵每行所占的字节数,包含在每行末为对齐而添加的字节。如果 step 设 定为 AUTO_STEP 或 0 则表示不计算补齐的字节数。此构造函数不创建图像数据所需的
内存,而是直接使用 data 所指的内存。
创建大小、类型与 m 相同的图像
Mat::Mat(const Mat& m)
此构造函数不会复制图像数据,新的图像与 m 共享图像数据,引用计数增加。所以,
如果修改新创建的这个矩阵,m 也被修改了。
创建大小、类型与指定 m 部分相同的图像
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all())
Mat::Mat(const Mat& m, const Rect& roi)
Mat::Mat(const Mat& m, const Range* ranges)
Mat::Mat(const Mat& m, const std::vector<Range>& ranges) 此构造函数不会复制图像数据,新图像与指定的 m 部分共享图像数据,引用计数增加。
所以,如果修改新创建的这个矩阵,m 的内容也被修改了。
有四种方式指定 m 区域:
1. 通过 rowRange 和 colRange 指定
2. 通过 Rect 指定
3. 通过 Range 的数组指定
4. 通过 Range 的容器指定
- 例子
//创建行数位3,列数位2,类型位8位无符号整型,所有元素初始值位(0,0,255)的三通道图像
Mat M(3, 2, CV_8UC3,Scalar(0,0,255));
// 输出M
cout << M << endl;
打印结果:
[ 0, 0, 255, 0, 0, 255; 0, 0, 255, 0, 0, 255; 0, 0, 255, 0, 0, 255]
另外,如果需要创建更多通道数的图像,可以使用宏CV_8UC(n)定义
//创建行数3,列数2,通道数为5的图像
Mat M1(3, 2, CV_8UC(5));
2.使用create()函数
Mat 类的 create()函数也可以创建图像,它是 Mat 类的一个重要方法。
Mat::create(int rows, int cols, int type)
Mat::create(Size size, int type)
Mat::create(int ndims, const int* sizes, int type)
Mat::create(const std::vector<int>& sizes, int type)
OpenCV 的很多函数和方法均调用 create()创建输出矩阵。如果 create()函数指定的参数
与当前的图像参数相同,则不进行内存申请,如果参数不同,则会减少当前图像数据内存的
索引,并重新申请内存。
需要注意的是,create()函数不能设置图像像素的初始值。
- 例子
//2.create函数
Mat m2(3, 2, CV_8UC3);
m2.create(2, 2, CV_8UC2);
cout << m2 << endl;
打印结果:
[205, 205, 205, 205; 205, 205, 205, 205]
3.使用Matlab风格的函数
OpenCV 也提供了如下一些 Matlab 风格的静态成员函数,使用很方便,也可以使代码简
洁:
Mat::zeros(int rows, int cols, int type),
Mat::zeros(Size size, int type)
Mat::zeros(int ndims, const int* sz, int type)
Mat::ones(int rows, int cols, int type),
Mat::ones(Size size, int type),
Mat::ones(int ndims, const int* sz, int type)
Mat::eye(int rows, int cols, int type)
Mat::eye(Size size, int type)
- 代码实现:
//Matlab风格函数
Mat zero = Mat::zeros(2, 3, CV_8UC1);
cout << zero << endl;
Mat one = Mat::ones(2, 3, CV_32F);
cout << one << endl;
//对角线
Mat eye = Mat::eye(2, 3, CV_64F);
cout << eye << endl;
打印结果:
[ 0, 0, 0;
0, 0, 0]
[1, 1, 1;
1, 1, 1]
[1, 0, 0;
0, 1, 0]
矩阵元素的表达
- 对于单通道元素,其元素类型一般为8U(8位无符号整数),也可以为16S,32F等,这些类型可以用uchar,short,float等C/C++语言中的基本数据类型表达
- 对于多通道元素,如RGB彩色图像一般使用8UCV3表示,在内存中的存储表现为(Bij,Gij,Rij ...),也可以将图像看作一个二位矩阵,但此时矩阵的元素不再是基本数据类型。而是用模板类Vec来表示一个向量,
typedef Vec<uchar, 2> Vec2b; //2维uchar向量
typedef Vec<uchar, 3> Vec3b; //3维uchar向量
typedef Vec<uchar, 4> Vec4b; //4维uchar向量
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; //6维float向量
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
Vec类型的结构为:内部使用[]数组存储
Vec3b color; //描述RGB颜色的color变量
color[0] = 255; //B分量
color[1] = 0; //G
color[2] = 0; //R
像素的读写
- 读取某个像素的值并进行设置新值,也可以对图像所有像素进行遍历
1.at()函数:可以返回指定矩阵元素的引用
//单通道图像:对图像进行遍历并进行单个元素的赋值
Mat grayImg(480, 640, CV_8UC1);
//uchar value = grayImg.at<uchar>(1, 2);
//cout <<"value:" << value << endl;
for (int i = 0; i < grayImg.rows; i++)
{
for (int j = 0; j < grayImg.cols; j++)
{
grayImg.at<uchar>(i, j) = (i + j) % 255;
}
}
imshow("grayImg", grayImg);
灰色图像赋值效果
//彩色图像
Mat colorImg(480, 640, CV_8UC3);
for (int i = 0; i < colorImg.rows; i++)
{
for (int j = 0; j < colorImg.cols; j++)
{
Vec3b pixel; //彩色图像单个像素有三个变量
pixel[0] = i % 255; //Blue
pixel[1] = j % 255; //Green
pixel[2] = 0; //Red
colorImg.at<Vec3b>(i, j) = pixel;
}
}
imshow("colorImg", colorImg);
彩色图像赋值后效果
2.使用迭代器遍历图像元素
//迭代器方式遍历图像元素
Mat grayImg(480, 640, CV_8UC1);
//MatIterator_<uchar> ite= grayImg.begin<uchar>();
//MatIterator_<uchar> end = grayImg.end<uchar>();
MatIterator_<uchar> grayIte, grayEnd; //地址
for (grayIte = grayImg.begin<uchar>(), grayEnd = grayImg.end<uchar>();
grayIte != grayEnd; grayIte++){
*grayIte = rand() % 255;
}
imshow("grayImg", grayImg);
//彩色图像
Mat colorImg(480, 640, CV_8UC3);
MatIterator_<Vec3b> colorIte, colorEnd;
for (colorIte = colorImg.begin<Vec3b>(), colorEnd = colorImg.end<Vec3b>();
colorIte != colorEnd; colorIte++) {
(*colorIte)[0] = rand() % 255; //Blue
(*colorIte)[1] = rand() % 255; //Green
(*colorIte)[2] = rand() % 255; //Red
}
imshow("colorImg", colorImg);
3.还可以使用指针访问元素
- 注意:C/C++中的指针操作,没有类型和越界检查,如果指针访问出错,很难进行问题定位
- 如果程序的运行速度有要求,使用指针进行遍历效率很高
//指针方式遍历像素
Mat grayImg(480, 640, CV_8UC1);
for (int i = 0; i < grayImg.rows; i++)
{
uchar* p = grayImg.ptr<uchar>(i);
for (int j = 0; j < grayImg.cols; j++)
{
p[j] = (i + j) % 255;
}
}
imshow("grayImg", grayImg);
//彩色图像
Mat colorImg(480, 640, CV_8UC3);
for (int i = 0; i < colorImg.rows; i++)
{
Vec3b* p = colorImg.ptr<Vec3b>(i);
for (int j = 0; j < colorImg.cols; j++)
{
p[j][0] = i % 255; //Blue
p[j][1] = j % 255; //Green
p[j][2] = 0; //Red
}
}
imshow("colorImg", colorImg);