指针遍历Mat
这是一个很简单的问题,但是如果粗心大意写错了i和j,将会造成数据出错。
为什么要用指针访问Mat?在Release模式下的at方法其实效率跟指针是一样的,编码时没要为了效率牺牲可读性而使用指针。但有一种场合必须使用指针,就是编写opencv无关的API,例如写dll函数时,调用方不想涉及任何关于opencv的东西,包括其数据结构,此时就不能采用Mat传递参数了,只能采用指针。因为Mat是C++的数据结构,如果在子函数内部定义了Mat,在该函数返回时会自动释放掉Mat的数据,所以不要想着通过取Mat的数据指针来传参。只能通过内部new一段内存,把Mat的数据逐个元素地扔进内存里。
RGB图(3维矩阵)
BYTE* iPtr = new BYTE [height*width*3];
for(int i=0;i<height;i++)
{
for(int j=0;j<width;j++)
{
for(int k=0;k<3;k++)
{
iPtr[i*width*3+j*3+k] = img.at<Vec3b>(i,j)[k];
}
}
}
其中,img是一个3维uchar的Mat,Vec3b代表3个uchar
对于灰度图、4维矩阵等,只要把通道数和at的数据类型改一下就可以套用以上格式
还有一点千万注意,Mat的(i,j)是按(行,列)的规则,而图像中则是(高,宽),跟Size(x,y),Rect(x,y)的(x,y)是不同的
--------------------------------------------------------------------
Mat::data的使用
在opencv中,Mat很方便;但当用到不是以opencv为主体的代码中,就不能直接使用Mat,而要转换为其它形式的数据结构。最简单的解决方案是指针,即将Mat拷贝到自定义的指针中。
Mat::data是数据段的首地址;使用memcpy()将Mat的数据拷贝至某个指针中,当然要先new一段内存。
memcpy的说明:http://blog.csdn.net/sszgg2006/article/details/7989404
常用到vector<Mat>,要用push_back方法对其进行赋值,vector的使用说明:http://blog.csdn.net/hancunai0017/article/details/7032383
4字节对齐的情况
但如果图像大小不是4的整数倍,某些场合下不能直接使用Mat::data。因为图像在OpenCV里的存储机制问题,行与行之间可能有空白单元(一般是补够4的倍数或8的倍数,称为padding。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。Mat提供了一个检测图像是否连续的函数isContinuous()。当图像连通时,我们就可以把图像完全展开,看成是一行。此时调用Mat::ptr<>()方法就等价于Mat::data
int nr=image.rows;
int nc=image.cols;
if(image.isContinuous())
{
nr=1;
nc=nc*image.rows*image.channels();
}
for(int i=0;i<nr;i++)
{
const uchar* inData=image.ptr<uchar>(i);
uchar* outData=outImage.ptr<uchar>(i);
for(int j=0;j<nc;j++)
{
*outData++=*inData++;
}
}
例如保存BMP格式的图像时,BMP要求图像数据按四字节对齐,此时就需要对Mat中的数据进行补零
对齐方法就是在每一行尾部补零,零的个数可能是1~3个
但其实大部分时候,Mat的内存都是连续的,只有极个别时候需要担心这个问题,这里有说明,和这里
Mat::data的默认类型
Mat::data的默认类型为uchar*,但很多时候需要处理其它类型,如float、int,此时需要将data强制类型转换,如:
Mat src(1000,1000,CV_32F);
float* myptr = (float*)src.data;
无论Mat的type是何种类型,Mat::data均为uchar*
--------------------------------------------------------------------
指针数据拷贝至Mat
这个千万注意,使用Mat接收指针指定的一段内存数据,通过指针初始化一个Mat:
Mat(row,col,CV_8U,ptr)
此时,Mat::data就等于ptr,例子
uchar ptr[25]={0,1};
Mat mat(5,5,CV_8U,ptr);
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要统一。
typedef ushort mtype;
Mat src;
...
mtype* psrc = (mtype*)src.data;
mtype *pdst = new mtype [src.total()];
for(int i=0;i<h;i++) //遍历行
{
const mtype* p0 = psrc + i*w;
mtype* p1 = pdst + i*w;
for(int j=0;j<w;j++) //遍历列
{
*p1++= p0[j];
}
} //耗时:0.7ms
以上是最浅显的做法,如果使用memcpy可以更高效:
memcpy(pdst,psrc,src.total()*sizeof(mtype)); //耗时:0.5ms
src是一个1000*1000的ushort矩阵
需要注意的是,src.data需要先进行强制类型转换
通过指针构造Mat
方法:
Mat img(image_size,TYPE,img_ptr);
这样,可以避免重复申请内存,对Mat的修改就是对指针内容的修改
--------------------------------END-------------------------------------