参考自 http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/user_guide/ug_mat.html
以前opencv提供IplImage 的C语言结构体存储一张图片,现在opencv有提供了一个C++接口Mat用来存储图像,和之前C语言的IplImage比,Mat提供了自动的内存管理,使用这个方法,你不需要纠结在管理内存上,而且你的代码会变得简洁(少写多得)。
关于 Mat ,首先要知道的是,你不必再手动地为其开辟空间,在不需要时立即将空间释放。
Mat是一个类由两个数据部分组成:
- 矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)
- 一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针
Mat中像素数据的存储形式:一般是二维向量,如果是灰度图,一般存放
类型;如果是RGB彩色图,存放
类型
单通道灰度图数据存放格式:
多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:
矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。
uchar类型的指针,指向Mat数据矩阵的首地址。Mat提供了isContinuous()函数用来查看Mat在内存中是不是连续存储,如果是则图片被存储在一行中,访问各像素值时利用首地址可以用数组下标方式[],地址++方式或迭代器方式访问。
Mat矩阵的维度,若Mat是一个二维矩阵,则dims=2,三维则dims=3,大多数情况下处理的都是二维矩阵。
Mat矩阵的行数。
Mat矩阵的列数。
先size是一个结构体,定义了Mat矩阵内数据的分布形式,数值上有关系式:
image.size().width==image.cols;
image.size().height==image.rows
Mat矩阵元素拥有的通道数。例如常见的RGB彩色图像,channels==3;而灰度图像只有一个灰度分量信息,channels==1。
用来度量每一个像素中每一个通道的精度,但它本身与图像的通道数无关!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 }
其中U是unsigned的意思,S表示signed,也就是有符号和无符号数。
elem是element(元素)的缩写,表示矩阵中每一个元素的数据大小,如果Mat中的数据类型是CV_8UC1
,那么elemSize==1
;如果是CV_8UC3
或CV_8SC3
,那么elemSize==3
;如果是CV_16UC3
或者CV_16SC3
,那么elemSize==6
;即elemSize是以8位(一个字节)为一个单位,乘以通道数和8位的整数倍;
elemSize1
elemSize加上一个“1”构成了elemSize1这个属性,1可以认为是元素内1个通道的意思,这样从命名上拆分后就很容易解释这个属性了:表示Mat矩阵中每一个元素单个通道的数据大小,以字节为一个单位,所以有:
eleSize1==elemSize/channels;
可以理解为Mat矩阵中每一行的“步长”,以字节为基本单位,每一行中所有元素的字节总量,是累计了一行中所有元素、所有通道、所有通道的elemSize1之后的值;
step1()
以字节为基本单位,Mat矩阵中每一个像素的大小,累计了所有通道、所有通道的elemSize1之后的值,所以有:
step1==step/elemSize1;
Mat矩阵的类型,包含有矩阵中元素的类型以及通道数信息,type的命名格式为CV_(位数)+(数据类型)+(通道 数),所有取值如下:
enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }
Mat直接读入图片
Mat input1 = imread("F:/test/0.jpg");
Mat构造函数
Mat img(512, 1024, CV_8UC3, Scalar(0, 0, 255)); // Scalar()设置初始化值,依次是b,g,r
imshow("img",img); // 显示图片
imwrite("path/img.jpg",img); // 保存图片
对于二维多通道图像,首先要定义其尺寸,即行数和列数,如上所示512行,1024列。
然后,需要指定存储元素的数据类型以及每个矩阵点的通道数。为此,依据下面的规则有多种定义
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
比如CV_8UC3
表示使用8位的 unsigned char
型,每个像素由三个元素组成三通道。
输出所有像素点
cout << "M = "<< endl << " " << M << endl << endl;
格式化打印
调用函数 randu() 来对一个矩阵使用随机数填充,需要指定随机数的上界和下界:
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
Mat A, C; // 只创建信息头部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存
Mat B(A); // 使用拷贝构造函数
C = A; // 赋值运算符
以上代码中的所有Mat对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其它对象。
可以使用函数 clone() 或者 copyTo()
Mat F = A.clone();
Mat G;
A.copyTo(G);
注意以下几点
要获取像素的亮度值,你必须知道图像的类型和通道的数目。如下例子展示了获取单通道灰度图(类型 8UC1)的(x, y)位置处的像素值:
Scalar intensity = img.at(x, y);
intensity.val[0] 中保存从0到255的值。注意:(x,y)x在这里指的是行,y指的是列,从图像左上角开始。这里Scalar
是一个可存放1—4个数值的数值的结构体
typedef struct Scalar
{
double val[4];
}Scalar;
如果使用的图像是1通道的,则s.val[0]中存储数据,如果使用的图像是3通道的,则s.val[0],s.val[1],s.val[2]中存储数据,颜色顺序为 BGR,如下
Vec3b intensity = img.at(x, y);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
你可以使用同样的方法处理浮点图像(例如通对一个3通道图像进行Sobel运算得到的浮点图像):
Vec3f intensity = img.at(x, y);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
同样的方法也可用于像素值的修改:
img.at(x, y) = 128;
一些OpenCV函数,例如calib3d模块中的 projectPoints 函数,需要以 Mat 的格式输入二维或者三维的点。这样的矩阵必须有且仅有一列,这样每行对应一个点,矩阵类型需要是32FC2或者32FC3。这样的矩阵可以很容易的从 std::vector 转换而来:
vector points;
//... fill the array
Mat pointsMat = Mat(points);
您也可以通过 Mat::at 方法来读写矩阵中的一个元素:
Point2f point = pointsMat.at(i, 0);
Mat cc = Mat(100, 100, CV_8UC3, Scalar(0, 0, 0));
Mat bb = Mat(100, 100, CV_8UC3, Scalar(0, 0, 0));
for (int i = 0; i < cc.rows; i++)
{
for (int j = 0; j < cc.cols; j++)
{
intensity[i][j] = cc.at(i, j);
Vec3b intensity1 = cc.at(i, j);
b[i][j] = intensity1.val[0]; // 修改blue通道
g[i][j] = intensity1.val[1]; // 修改green通道
r[i][j] = intensity1.val[2]; // 修改red通道
bb.at(i, j)[0] = 255;
}
}
(参考:http://blog.csdn.net/xiaowei_cqu/article/details/19839019)
使用ptr和[]操作符
Mat最直接的访问方法是通过.ptr<>函数得到一行的指针,并用[]操作符访问某一列的像素值。
// using .ptr and []
void colorReduce0(cv::Mat &image, int div=64) {
int nr= image.rows; // number of rows
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j(j);
for (int i=0; i
//Scalar()设置初始化值,依次是b,g,r
Mat cc = Mat(10, 10, CV_8UC3, Scalar(0, 0, 0));
for (int i = 0; i < cc.rows; i++)
{
uchar* pt = cc.ptr<uchar>(i);
for (int j = 0, k = 0; j < cc.cols; j++)
{
pt[k] = 255; // blue通道
pt[k + 1] = 0; // green通道
pt[k + 2] = 0; // red通道
k = k + 3;
}
}
使用指针++
// using .ptr and * ++
void colorReduce1(cv::Mat &image, int div=64) {
int nr= image.rows; // number of rows
int nc= image.cols * image.channels(); // total number of elements per line
for (int j=0; j(j);
for (int i=0; i
指针运算(step值为每行的长度)
int nr= image.rows; // number of rows
int nc= image.cols * image.channels();
int step= image.step; // effective width
uchar *data= image.data;
for (int j=0; j
迭代器方法,官方推荐方法
// using Mat_ iterator
void colorReduce8(cv::Mat &image, int div=64) {
// get iterators
cv::Mat_::iterator it= image.begin();
cv::Mat_::iterator itend= image.end();
for ( ; it!= itend; ++it) {
(*it)[0]= 255;
(*it)[1]= 255;
(*it)[2]= 255;
}
}
将图像转为灰度图像:
Mat img = imread("image.jpg"); // loading a 8UC3 image
Mat grey;
cvtColor(img, grey, CV_BGR2GRAY);
灰度图的灰度信息就是YUV里的Y亮度信息,其实就是RGB和YUV色度空间的转换,每个像素点值的计算方式,
uchar gray = (uchar)(0.299*red + 0.587*green + 0.114*blue);
将图像的类型从8UC1转为32FC1:
src.convertTo(dst, CV_32F);
调整图片尺寸
resize(input1, input1, Size(780, 1020), 0, 0, CV_INTER_LINEAR); // 行为1020 列为780