写了一个小程序,功能是将一幅图像的四周扩展若干像素,开始时的代码如下:
#include <cv.h>
#include <highgui.h>
void SpreadAround(IplImage* src,IplImage* dst,int N)
{
int w=src->width;
int h=src->height;
int wp=w+2*N;
int hp=h+2*N;
int i,j;
uchar* srcdata=(uchar*)src->imageData;
uchar* dstdata=(uchar*)dst->imageData;
for(i=0;i<hp;i++)
for(j=0;j<wp;j++)
dstdata[ i*wp + j]=0;
for(i=0;i<h;i++)
memcpy(dstdata+ N + wp*(i+N), srcdata + w*i, w);
}
int main(int argc, char** argv )
{
uchar image[300]={
0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,
0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,
0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,
0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,
0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,
0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,
0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,
0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,
0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,
0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,
0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,
0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,
0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,
255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
IplImage* pI=cvCreateImage(cvSize(15,20),IPL_DEPTH_8U,1);
int i,j;
memcpy(pI->imageData, image, 15*20);
cvNamedWindow("原图",1);
cvShowImage("原图",pI);
cvWaitKey();
IplImage* pIP=cvCreateImage(cvSize(35,40),IPL_DEPTH_8U,1);
SpreadAround(pI,pIP,10);
cvNamedWindow("扩展后的图像",1);
cvShowImage("扩展后的图像",pIP);
cvWaitKey();
cvReleaseImage(&pI);
cvReleaseImage(&pIP);
cvDestroyWindow("原图");
cvDestroyWindow("扩展后的图像");
return 0;
}
这个程序的运行结果为,两幅图像显示的并不是想要的结果,而是很奇怪的图像。
这个问题困扰了我两天,始终想不通是为什么,最后终于看到这篇帖子(http://apps.hi.baidu.com/share/detail/17915994),找到了原因。
对OpenCV稍有了解的同学都知道里边用于存储图像数据的IplImage,其中有两个属性非常值得关注,稍不留神就会导致错误:
一是width属性;二是widthStep属性。
前者是表示图像的每行像素数,后者指表示存储一行像素需要的字节数。
在OpenCV里边,widthStep必须是4的倍数,从而实现字节对齐,有利于提高运算速度。
如果8U单通道图像宽度为3,那么widthStep是4,加一个字节补齐。这个图像的一行需要4个字节,只使用前3个,最后一个空着。
也就是一个宽3高3的图像的imageData数据大小为4*3=12字节。
需要注意的是,空着的那个像素并不是无效的,它仍然可以被操作,这就是导致错误的根源。
其实原因就在于,在cvCreateImage的时候,OpenCV为实现字节对齐,使得每行数据实际有16个字节(多出一个),在使用memcpy的过程中,这些多出的字节就把对应的数据给“吃”了,因为这些数据在cvShowImage的时候并不会显示出来,这样,第二行就少一个字节,第三行少两个字节,……,所以整个图像就显示错误了!
知道这一点后可以将程序更改如下(红色部分),这样,程序才能按我们的设想运行。
#include <cv.h>
#include <highgui.h>
void SpreadAround(IplImage* src,IplImage* dst,int N)
{
int w=src->width;
int h=src->height;
int wp=w+2*N;
int hp=h+2*N;
int i,j;
uchar* srcdata=(uchar*)src->imageData;
uchar* dstdata=(uchar*)dst->imageData;
for(i=0;i<hp;i++)
for(j=0;j<dst->widthStep;j++)
dstdata[i*dst->widthStep+j]=0;
for(i=0;i<h;i++)
memcpy(dstdata+ N + dst->widthStep*(i+N), srcdata + src->widthStep*i, w);
}
int main(int argc, char** argv )
{
uchar image[300]={
0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,
0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,
0,0,0,0,0,0,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,
0,0,0,0,0,0,0,255,255,255,255,255,255,0,0,
0,0,0,0,0,0,0,0,255,255,255,255,255,255,0,
0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,
0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,
0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,
0,0,0,0,0,0,255,255,255,255,255,255,255,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,255,0,0,0,
0,0,0,0,0,255,255,255,255,255,255,0,0,0,0,
0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,
0,0,0,255,255,255,255,255,255,0,0,0,0,0,0,
0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,
0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,
255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};
IplImage* pI=cvCreateImage(cvSize(15,20),IPL_DEPTH_8U,1);
int i,j;
for(i = 0; i<20; i++)
memcpy(pI->imageData + pI->widthStep*i, image + 15*i, 15);
cvNamedWindow("d",1);
cvShowImage("d",pI);
cvWaitKey();
IplImage* pIP=cvCreateImage(cvSize(35,40),IPL_DEPTH_8U,1);
SpreadAround(pI,pIP,10);
cvNamedWindow("dd",1);
cvShowImage("dd",pIP);
cvWaitKey();
cvReleaseImage(&pI);
cvReleaseImage(&pIP);
cvDestroyWindow("d");
cvDestroyWindow("dd");
return 0;
}