mat 的综合使用
http://wenku.baidu.com/link?url=rmTE6meVj-6O0o6IGFJxcdS6CVJMCeGUh8QEKZmnfTeezuyggC-M9wu6g5AQA61b3kyG9AnQosVnRYKw8yqAoTJcEsAE7MVMVetELJhs5e7
Mat::data的使用
在opencv中,Mat很方便;但当用到不是以opencv为主体的代码中,就不能直接使用Mat,而要转换为其它形式的数据结构。最简单的解决方案是指针,即将Mat拷贝到自定义的指针中。
Mat::data是数据段的首地址;使用memcpy()将Mat的数据拷贝至某个指针中,当然要先new一段内存。
memcpy的说明:http://blog.youkuaiyun.com/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法对其进行赋值,vector的使用说明:http://blog.youkuaiyun.com/hancunai0017/article/details/7032383
4字节对齐的情况
但如果图像大小不是4的整数倍,某些场合下不能直接使用Mat::data。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,有些地方也称作“位对齐”。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data
[cpp] view plain copy
1. int nr=image.rows;
2. int nc=image.cols;
3. if(image.isContinuous())
4. {
5. nr=1;
6. nc=nc*image.rows*image.channels();
7. }
8. for(int i=0;i<nr;i++)
9. {
10. const uchar* inData=image.ptr<uchar>(i);
11. uchar* outData=outImage.ptr<uchar>(i);
12. for(int j=0;j<nc;j++)
13. {
14. *outData++=*inData++;
15. }
16. }
例如保存BMP格式的图像时,BMP要求图像数据按四字节对齐,此时就需要对Mat中的数据进行补零
对齐方法就是在每一行尾部补零,零的个数可能是1~3个
Mat::data的默认类型
Mat::data的默认类型为uchar*,但很多时候需要处理其它类型,如float、int,此时需要将data强制类型转换,如:
[cpp] view plain copy
1. Mat src(1000,1000,CV_32F);
2. float* myptr = (float*)src.data;
无论Mat的type是何种类型,Mat::data均为uchar*
--------------------------------------------------------------------
指针数据拷贝至Mat
这个千万注意,使用Mat接收指针指定的一段内存数据,通过指针初始化一个Mat:
[cpp] view plain copy
1. Mat(row,col,CV_8U,ptr)
此时,Mat::data就等于ptr,例子
[cpp] view plain copy
1. uchar ptr[25]={0,1};
2. Mat mat(5,5,CV_8U,ptr);
3. mat = mat*255;
修改mat就修改了ptr指向内存的值
注意:Mat的类型要与指针的类型一致,如,uchar指针对应CV_8U,double指针对应CV_64F,如果把double指针赋给一个CV_32F的Mat,那Mat的每个元素只占32位,即把double数一分为二,是错误的。
这里又涉及一个问题,类型转换。opencv提供了Mat::convertTo接口进行类型转换,当然我们可以逐个元素进行强制类型转换,但有时候两者是有区别的。例如,把double转unsigned int,这是有符号数转无符号数,如果使用convertTo方法,会把负数置零;如果使用强制类型转换(unsigned int),结果是错误的,因为负数应该变成其补数,无符号的第一个比特位是有意义的
--------------------------------------------------------------------
Mat数据拷贝至指针
有些时候,我们不希望函数的调用者看到opencv的数据结构Mat,可以通过把Mat数据拷贝至一段动态申请的内存,此时千万要注意数据类型,指针和Mat要统一。
[cpp] view plain copy
1. typedef ushort mtype;
2. Mat src;
3. ...
4. mtype* psrc = (mtype*)src.data;
5. mtype *pdst = new mtype [src.total()];
6. for(int i=0;i<h;i++) //遍历行
7. {
8. const mtype* p0 = psrc + i*w;
9. mtype* p1 = pdst + i*w;
10. for(int j=0;j<w;j++) //遍历列
11. {
12. *p1++= p0[j];
13. }
14. } //耗时:0.7ms
以上是最浅显的做法,如果使用memcpy可以更高效:
[cpp] view plain copy
1. memcpy(pdst,psrc,src.total()*sizeof(mtype)); //耗时:0.5ms
src是一个1000*1000的ushort矩阵
需要注意的是,src.data需要先进行强制类型转换
图片分析1:考虑二维情况(stored row by row)按行存储
上面是一个 3 X 4 的矩阵,假设其数据类型为 CV_8U,也就是单通道的 uchar 类型
· 这是一个二维矩阵,那么维度为 2 (M.dims == 2);
· M.rows == 3; M.cols == 4;
· sizeof(uchar) = 1,那么每一个数据元素大小为 1 (M.elemSize() == 1, M.elemSize1() == 1);
· CV_8U 得到 M.depth() == 0, M.channels() == 1;
· 因为是二维矩阵,那么 step 数组只有两个值, step[0] 和 step[1] 分别代表一行的数据大小和一个元素的数据大小,则 M.step[0] == 4, M.step[1] == 1;
· M.step1(0) == M.cols = 4; M.step1(1) == 1;
假设上面的矩阵数据类型是 CV_8UC3,也就是三通道
· M.dims == 2; M.channels() == 3;M.depth() == 0;
· M.elemSize() == 3 (每一个元素包含3个uchar值) M.elemSize1() == 1 (elemSize / channels)
· M.step[0] == M.cols * M.elemSize() == 12, M.step[1] == M.channels() * M.elemSize1() == M.elemSize() == 3;
· M.step(0) == M.cols * M.channels() == 12 ; M.step(1) == M.channels() == 3;
图片分析2:考虑三维情况(stored plane by plane)按面存储
上面是一个 3 X 4 X 6 的矩阵,假设其数据类型为 CV_16SC4,也就是 short 类型
· M.dims == 3 ; M.channels() == 4 ; M.elemSize1() == sizeof(short) == 2 ;
· M.rows == M.cols == –1;
· M.elemSize() == M.elemSize1() * M.channels() == M.step[M.dims-1] == M.step[2] == 2 * 4 == 8;
· M.step[0] == 4 * 6 * M.elemSize() == 192;
· M.step[1] == 6 * M.elemSize() == 48;
· M.step[2] == M.elemSize() == 8;
· M.step1(0) == M.step[0] / M.elemSize() == 48 / 2 == 96 (第一维度(即面的元素个数) * 通道数);
· M.step1(1) == M.step[1] / M.elemSize() == 12 / 2 == 24(第二维度(即行的元素个数/列宽) * 通道数);
· M.step1(2) == M.step[2] / M.elemSize() == M.channels() == 4(第三维度(即元素) * 通道数);
End
以上为Mat的存放形式
Fn 1 :利用step。
Code 1:
int main()
{
//新建一个uchar类型的单通道矩阵(grayscale image 灰度图)
Mat m(400, 400, CV_8U, Scalar(0));
for (int col = 0; col < 400; col++)
{
for (int row = 195; row < 205; row++)
{
//获取第[row,col]个像素点的地址并用 * 符号解析
*(m.data + m.step[0] * row + m.step[1] * col) = 255;
}
}
imshow("canvas", m);
cvWaitKey();
return 0;
}
Output 1 :
Code1只是演示了单通道的情况,对于多通道的例子,请看 Code2 然后再看 Code3。
Fn 2 :
使用 Mat::at 函数
· 原型 template<typename _Tp> inline _Tp& Mat::at(…) //其中参数有多个,也就是说 at 函数有多个重载
· 返回值为 Mat 类型, Mat 有个索引的重载,也就是 [] 符号的重载,用这个重载可以定位多通道数据,具体示例可以看下面代码。
下面的代码把红色通道值大于128的颜色的置为白色,左边为原图,右边为处理过后的图。
Code 2 :
int main()
{
Mat img = imread("lena.jpg");
imshow("Lena Original", img);
for (int row = 0; row < img.rows; row++)
{
for (int col = 0; col < img.cols; col++)
{
/* 注意 Mat::at 函数是个模板函数, 需要指明参数类型, 因为这张图是具有红蓝绿三通道的图,
所以它的参数类型可以传递一个 Vec3b, 这是一个存放 3 个 uchar 数据的 Vec(向量). 这里
提供了索引重载, [2]表示的是返回第三个通道, 在这里是 Red 通道, 第一个通道(Blue)用[0]返回 */
if(img.at<Vec3b>(row, col)[2] > 128)
img.at<Vec3b>(row, col) = Vec3b(255, 255, 255);
}
}
imshow("Lena Modified", img);
cvWaitKey();
return 0;
}
Output 2 :
Code 3 :
这段代码用的是 Fn1 的方式,效果和 Code 2 等价,不过是处理三通道数据而已:
int main()
{
Mat img = imread("lena.jpg");
imshow("Lena Original", img);
for (int row = 0; row < img.rows; row++)
{
for (int col = 0; col < img.cols; col++)
{
//主要是这里的代码
if(*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) > 128)
{
//[row, col]像素的第 1 通道地址被 * 解析(blue通道)
*(img.data + img.step[0] * row + img.step[1] * col) = 255;
//[row, col]像素的第 2 通道地址被 * 解析(green通道), 关于elemSize1函数的更多描述请见 Fn1 里所列的博文链接
*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) = 255;
//[row, col]像素的第 3 通道地址被 * 解析(red通道)
*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) = 255;
}
}
}
imshow("Lena Modified", img);
cvWaitKey();
return 0;
}
Output 3 = Output 2
Fn 3 :
使用 Mat 的一个模板子类 Mat_<typename _Tp> 的 ( ) 符号重载定位一个像素
Code 4 :
int main()
{
Mat m(400, 400, CV_8UC3, Scalar(255, 255, 255));
// m2 是 Mat_<Vec3b> 类型的, 因为 m 中元素的类型是 CV_8UC3, 可以用 Vec3b 存储 3 个通道的值
// 注意 Mat_<CV_8UC3> 这种写法是错误的, 因为 CV_8UC3 只是一个宏定义
// #define CV_8UC3 CV_MAKETYPE(CV_8U, 3)
Mat_<Vec3b> m2 = m;
// for 循环画一个红色的实心圆
for (int y = 0; y < m.rows; y++)
{
for (int x = 0; x < m.rows; x++)
{
if (pow(double(x-200), 2) + pow(double(y-200), 2) - 10000.0 < 0.00000000001)
{
// Mat_ 模板类实现了对()的重载, 可以定位到一个像素
m2(x, y) = Vec3b(0, 0, 255);
}
}
}
imshow("Image", m);
cvWaitKey();
return 0;
}
Output 4 :[ 看上去怎么有点不爽]
Fn 4 :
使用 Mat::ptr 模板函数
Code 5 :
int main()
{
Mat m(400, 400, CV_8UC3, Scalar(226, 46, 166));
imshow("Before", m);
for (int row = 0; row < m.rows; row++)
{
if (row % 5 == 0)
{
// data 是 uchar* 类型的, m.ptr<uchar>(row) 返回第 row 行数据的首地址
// 需要注意的是该行数据是按顺序存放的,也就是对于一个 3 通道的 Mat, 一个像素有
// 有 3 个通道值, [B,G,R][B,G,R][B,G,R]... 所以一行长度为:
// sizeof(uchar) * m.cols * m.channels() 个字节
uchar* data = m.ptr<uchar>(row);
for (int col = 0; col < m.cols; col++)
{
data[col * 3] = 102; //第row行的第col个像素点的第一个通道值 Blue
data[col * 3 + 1] = 217; // Green
data[col * 3 + 2] = 239; // Red
}
}
}
imshow("After", m);
cout << (int)m.at<Vec3b>(0, 0)[0] << ','; //利用 Fn 1 介绍的方法输出一下像素值到控制台
cout << (int)m.at<Vec3b>(0, 0)[1] << ',';
cout << (int)m.at<Vec3b>(0, 0)[2] << endl;
cvWaitKey();
return 0;
}
Output 5 :
End :
Author : Ggicci
谢谢阅读,有误希望指正!
--OpenCV初学者
Mat 内存指针的使用
http://blog.youkuaiyun.com/kelvin_yan/article/details/48315175
Mat::data的使用
在opencv中,Mat很方便;但当用到不是以opencv为主体的代码中,就不能直接使用Mat,而要转换为其它形式的数据结构。最简单的解决方案是指针,即将Mat拷贝到自定义的指针中。
Mat::data是数据段的首地址;使用memcpy()将Mat的数据拷贝至某个指针中,当然要先new一段内存。
memcpy的说明:http://blog.youkuaiyun.com/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法对其进行赋值,vector的使用说明:http://blog.youkuaiyun.com/hancunai0017/article/details/7032383
4字节对齐的情况
但如果图像大小不是4的整数倍,某些场合下不能直接使用Mat::data。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,有些地方也称作“位对齐”。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data
例如保存BMP格式的图像时,BMP要求图像数据按四字节对齐,此时就需要对Mat中的数据进行补零
对齐方法就是在每一行尾部补零,零的个数可能是1~3个
Mat::data的默认类型
Mat::data的默认类型为uchar*,但很多时候需要处理其它类型,如float、int,此时需要将data强制类型转换,如:
无论Mat的type是何种类型,Mat::data均为uchar*
--------------------------------------------------------------------
指针数据拷贝至Mat
这个千万注意,使用Mat接收指针指定的一段内存数据,通过指针初始化一个Mat:
此时,Mat::data就等于ptr,例子
修改mat就修改了ptr指向内存的值
注意:Mat的类型要与指针的类型一致,如,uchar指针对应CV_8U,double指针对应CV_64F,如果把double指针赋给一个CV_32F的Mat,那Mat的每个元素只占32位,即把double数一分为二,是错误的。
这里又涉及一个问题,类型转换。opencv提供了Mat::convertTo接口进行类型转换,当然我们可以逐个元素进行强制类型转换,但有时候两者是有区别的。例如,把double转unsigned int,这是有符号数转无符号数,如果使用convertTo方法,会把负数置零;如果使用强制类型转换(unsigned int),结果是错误的,因为负数应该变成其补数,无符号的第一个比特位是有意义的
--------------------------------------------------------------------
Mat数据拷贝至指针
有些时候,我们不希望函数的调用者看到opencv的数据结构Mat,可以通过把Mat数据拷贝至一段动态申请的内存,此时千万要注意数据类型,指针和Mat要统一。
以上是最浅显的做法,如果使用memcpy可以更高效:
src是一个1000*1000的ushort矩阵
需要注意的是,src.data需要先进行强制类型转换