1.概述
1、The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. It can be used to store real or complex-valued vectors and matrices, grayscale or color images, voxel volumes, vector fields, point clouds, tensors, histograms (though, very high-dimensional histograms may be better stored in a SparseMat )
2、Mat类定义于core.hpp中,主要包含有两部分数据:一部分是矩阵头(matrix header),这部分的大小是固定的,包含矩阵的大小,存储的方式,矩阵存储的地址等等;另一个部分是一个指向矩阵包含像素值的指针(data)。
2.Mat的存储
存储的图像不管是彩色的还是灰度图像,都是二维的矩阵,具体的存储格式如下:
(1)灰度图像的格式:

(2)彩色图像的格式:

虽然彩色图像由BGR三个通道,但是是存储在同一个平面内的,只不过OpenCV在这里把三列才当作一列,因此有img.cols等于图像的列数。
一般我们用Opencv读取的灰度图像的数据类型为uchar类型的,而彩色图像的一个像素的数据类型为
类型的,灰度图一个像素占用1个字节,而彩色图像一个像素3个字节。
3.6Mat中相关成员的意义
1、data
Mat对象中的一个指针,指向存放矩阵数据的内存(uchar* data)
2、dims
矩阵的维度,3
4的矩阵维度为2维,34
5的矩阵维度为3维
3、channels
矩阵通道,矩阵中的每一个矩阵元素拥有的值的个数,比如说 3 * 4 矩阵中一共 12 个元素,如果每个元素有三个值,那么就说这个矩阵是 3 通道的,即 channels = 3。常见的是一张彩色图片有红、绿、蓝三个通道。
4、depth
深度,即每一个像素的位数,也就是每个通道的位数。在opencv的Mat.depth()中得到的是一个0 – 6的数字,分别代表不同的位数:enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 },可见 0和1都代表8位, 2和3都代表16位,4和5代表32位,6代表64位。
5、elemSize
矩阵中每个元素的大小,每个元素包含channels个通道。如果Mat中的数据的数据类型是CV_8U那么elemSize = 1;是CV_8UC3那么elemSize = 3,是CV_16UC2那么elemSize = 4。
6、elemSize1
矩阵中数据类型的大小,即elemSize/channels,也就是depth对应的位数。
7、step
是一个数组,定义了矩阵的布局,参考下图
若矩阵有n维,则step数组大小为n
step[n-1] = elemSize(每个矩阵元素的数据大小)
step[n-2] = size(1维)elemSize
step[n-3] = size(2维)
size(1维)elemSize
...
step[0] = size(n-1维)
size(n-2维)...size(1维)*elemSize
8、step1
step1也是一个数组,为step/elemSize1,若矩阵有n维,则step1[n-1] = channels。
举例程序如下:
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("1.jpg");
cout<<"img.rows: "<<img.rows<<" img.cols: "<<img.cols<<" img.channels(): "<<img.channels()<<endl;
cout<<"###############################################"<<endl;
cout<<"img.step1(0): "<<img.step1(0)<<endl;
cout<<"img.step1(1): "<<img.step1(1)<<endl;
cout<<"img.step[0]: "<<img.step[0]<<endl;
cout<<"img.step[1]: "<<img.step[1]<<endl;
cout<<"img.size[0]: "<<img.size[0]<<endl;
cout<<"img.size[1]: "<<img.size[1]<<endl;
cout<<"img.elemSize(): "<<img.elemSize()<<endl;
cout<<"img.elemSize1(): "<<img.elemSize1()<<endl;
return 0;
}
测试结果如下:

可以看出这是一张1000
750的彩色图像。以上面所说,step1(i)表示的每一维的通道数,我们知道彩色图像存储方式是一种二维矩阵,因此只有两维,其中最后一维表示的是一个点,前面一维表示的是线,如果有第三维,那么再前面一维表示的是面。这里有一个规律,就是最后一维一定是点,其他的依次往前推就可以了。
接着分析结果,img.step1(1)表示的是一个点的通道数,我们知道彩色图像的一个像素,通道数为3(BGR),因此img.step1(1) = 3,而img.step1(0)是一行的像素的通道数,因此为7503 = 2250
而step[i]表示的是每一维中的大小,以字节计数,在彩色图像中一个像素分为三块,每一块为一个字节,因此step[0] = 2250 ,step[1] = 3
size表示的是每一维元素的大小,注意这里是元素,即像素,size[0]表示rows,size[1]表示cols
elemSize表示的是每个元素的大小,以字节计,一个元素分为三块,一块是1字节,因此为3(彩色图像)
elemSize1表示的是一个元素的每个通道的大小,因此为1
9、type
矩阵元素的类型,即创建Mat时传递的类型,例如CV_8UC3、CV_16UC2等。
3.2Mat类的数据类型
Mat的存储是逐行存储的,矩阵中的数据类型包括:Mat_
对应的是CV_8U,Mat_对应的是CV_8U,Mat_对应的是CV_8S,Mat_对应的是CV_32S,Mat_对应的是CV_32F,Mat_对应的是CV_64F,对应的数据深度如下:
• CV_8U - 8-bit unsigned integers ( 0..255 )
• CV_8S - 8-bit signed integers ( -128..127 )
• CV_16U - 16-bit unsigned integers ( 0..65535 )
• CV_16S - 16-bit signed integers ( -32768..32767 )
• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
预定义类型的结构:
CV_
(S|U|F)C
bit_depth:图像的比特数,如24bit
S|U|F:分别代表数据类型signed int,unsigned int,float
number_of_channels:一张图片的通道数
1--灰度图片--grayImg---是--单通道图像
2--RGB彩色图像---------是--3通道图像
3--带Alph通道的RGB图像--是--4通道图像
3.3创建Mat矩阵
//1.使用构造函数,常见的几个如下:
Mat::Mat(); //default
Mat::Mat(int rows, int cols, int type);
Mat::Mat(Size size, int type);
Mat::Mat(int rows, int cols, int type, const Scalar& s);
Mat::Mat(Size size, int type, const Scalar& s);
Mat::Mat(const Mat& m);
//参数说明:
//int rows:高
//int cols:宽
//int type:参见"Mat类型定义"
//Size size:矩阵尺寸,注意宽和高的顺序:Size(cols, rows)
//const Scalar& s:用于初始化矩阵元素的数值
//const Mat& m:拷贝m的矩阵头给新的Mat对象,但是不复制数据!相当于创建了m的一个引用对象
//例子1:创建100*90的矩阵,矩阵元素为3通道32位浮点型
cv::Mat M(100, 90, CV_32FC3);
//例子2:使用一维或多维数组来初始化矩阵,
double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
cv::Mat M = cv::Mat(3, 3, CV_64F, m);
//2.使用create函数:
Mat a = create(10, 9, CV_16U); //创建10*9的矩阵,矩阵元素为16位无符号整型
//create的一个特殊用法:如果初始化的时候没有传入size的参数,或者后面需要改变size的参数,可以使用create来调整
// make 7x7 complex matrix filled with 1+3j.
cv::Mat M(7,7,CV_32FC2,Scalar(1,3));
// and now turn M to 100x60 15-channel 8-bit matrix.
// The old content will be deallocated:隐式使用release()释放
M.create(100,60,CV_8UC(15));
3.4合并矩阵
//假设现有需要将n个CvMat类型的矩阵(每个都是1*m的行向量)合并到一个n*m(n行m列)的矩阵中,即:矩阵合并问题。
CvMat* vector; //行向量:1*m
Mat dst; //目标矩阵:n*m
Mat vectorMat = Mat(vector, true); //将CvMat转为Mat
Mat tmpMat = M.row(i); //注意:浅拷贝(参见"复制矩阵")
vectorMat.copyTo(tmpMat); //注意:深拷贝(此时tmpMat已改变为vectorMat,即:目标矩阵的第i+1行被赋值为vectorMat)
//以上为改变目标矩阵的i+1行的值,以此类推即可将n个行向量合并到目标矩阵中。
3.5访问矩阵元素
//1.at函数
M.at<double>(i,j) += 1.f; //set (i,j)-th element
int a = M.at<int>(i,j); //get (i,j)-th element
//2.矩阵元素指针(ptr)
for(int i = 0; i < M.rows; i++)
{
const double* pData = M.ptr<double>(i); //第i+1行的所有元素
for(int j = 0; j < M.cols; j++)
cout<<pData[j]<<endl;
}
3.5Mat成员函数
Mat::eye
返回一个恒等指定大小和类型矩阵。
C++: static MatExpr Mat::eye(int rows, int cols, inttype)
C++: static MatExpr Mat::eye(Size size, int type)
参数
rows –的行数。
cols– 的列数。
size –替代矩阵大小规格Size(cols, rows)的方法。
type – 创建的矩阵的类型
4.Mat按像素读取图像内容
这里主要介绍两种方法,一种非常简单,易于编程,但是效率会比较低;另外一种效率高,但是不太好记。下面依次看代码:
(1)易于编程的
对于灰度图像进行操作:
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("1.jpg");
resize(img, img, Size(375, 500));//resize为500*375的图像
cvtColor(img, img, CV_RGB2GRAY);//转为灰度图
imshow("gray_ori", img);
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
//at<类型>(i,j)进行操作,对于灰度图
img.at(i, j) = i+j;
}
}
imshow("gray_result", img);
waitKey(0);
return 0;
}
运行结果如下:

可以看出,使用at的操作很容易定位,就跟操作一个普通的二维数组一样,那么对于彩色图像呢,方法很简单,只需要把at<类型>中的类型改变为Vec3b即可,代码如下:
#include
#include
#include
#include
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("1.jpg");
resize(img, img, Size(375, 500));//resize为500*375的图像
imshow("ori", img);
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
//at<类型>(i,j)进行操作,对于灰度图
img.at(i, j)[0] = 255;//对于蓝色通道进行操作
//img.at(i, j)[1] = 255;//对于绿色通道进行操作
//img.at(i, j)[2] = 255;//对于红色通道进行操作
}
}
imshow("result", img);
waitKey(0);
return 0;
}
效果图如下:
(2)采用指针对图像进行访问
这里直接写对于彩色图像的操作:
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("1.jpg");
int rows = img.rows;
int cols = img.cols * img.channels();
if(img.isContinuous())//判断是否在内存中连续
{
cols = cols * rows;
rows = 1;
}
imshow("ori",img);
for(int i = 0;i<rows;i++)
{
//调取存储图像内存的第i行的指针
uchar *pointer = img.ptr<uchar>(i);
for(int j = 0;j<cols;j += 3)
{
//pointer[j] = 255;//对蓝色通道进行操作
//pointer[j+1] = 255;//对绿色通道进行操作
pointer[j+2] = 255;//对红色通道进行操作
}
}
imshow("result",img);
waitKey();
return 0;
}
从上面个的代码中可以很明显的看出我们是如何操作图像的数据以及图像在Mat中的存放格式的,就是我们上面那个彩色图像的存放示意图中的格式,这里把彩色图像中的一个像素点分成三份,每一份都是uchar类型,因此我们这里不需要使用Vec3b数据类型。把彩色图像看成一个rows * (cols * channels)的二维数组进行操作,其中的每个元素的类型都是uchar类型。
这里需要注意的是j += 3是因为我们按照一个像素点进行操作,而一个像素点在这里面又被分成三份,因此需要j += 3,如果是灰度图像则直接j++即可
这种操作方式虽然复杂一些,但是执行效率会比上面的算法高很多。
下面是执行的结果:
(3)两种方式进行同一操作的时间对比
测试代码如下
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
#include <time.h>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("1.jpg");
Mat img2;
img.copyTo(img2);
cout<<"图像的行数: "<<img.rows<<endl;
cout<<"图像的列数: "<<img.cols<<endl;
cout<<"图像通道数: "<<img.channels()<<endl;
double time1;
time1 = (double)getTickCount();
int rows = img.rows;
int cols = img.cols * img.channels();
if(img.isContinuous())//判断是否在内存中连续
{
cols = cols * rows;
rows = 1;
}
for(int i = 0;i<rows;i++)
{
//调取存储图像内存的第i行的指针
uchar *pointer = img.ptr<uchar>(i);
for(int j = 0;j<cols;j += 3)
{
//pointer[j] = 255;//对蓝色通道进行操作
//pointer[j+1] = 255;//对绿色通道进行操作
pointer[j+2] = 255;//对红色通道进行操作
}
}
time1 = 1000 * ((double)getTickCount() - time1) / getTickFrequency();
//imshow("result",img);
cout<<"第一种方法用时: "<<time1<<endl;
double time2 = (double)getTickCount();
for (int i = 0; i < img2.rows; i++)
{
for (int j = 0; j < img2.cols; j++)
{
//at<类型>(i,j)进行操作,对于灰度图
img2.at<Vec3b>(i, j)[0] = 255;//对于蓝色通道进行操作
//img.at<Vec3b>(i, j)[1] = 255;//对于绿色通道进行操作
//img.at<Vec3b>(i, j)[2] = 255;//对于红色通道进行操作
}
}
time2 = 1000 * ((double)getTickCount() - time2)/getTickFrequency();
cout<<"第二种方法用时: "<<time2<<endl;
imshow("img",img);
imshow("img2",img2);
waitKey(0);
return 0;
}
测试结果如下:
point
次数据结构表示了由其图像坐标 x,y 指定的2D点。可定义为:
Point pt;
pt.x = 10;
pt.y = 8;
或者
Point pt = Point(10, 8);
Scalar
inline CvScalar cvScalar( double val0, double val1=0,double val2=0, double val3=0 );
表示了具有4个元素的数组。此类型在OpenCV中被大量用于传递像素值。
本节中,我们将进一步用它来表示RGB颜色值(三个参数)。如果用不到第四个参数,则无需定义。
我们来看个例子,如果给出以下颜色参数表达式:
1)Scalar( a, b, c ): Red = c, Green = b and Blue = a
2) 存放单通道图像中像素:cvScalar(255);
3) 存放三通道图像中像素:cvScalar(255,255,255);