OpenCV中文站中有一篇关于OpenCV基础操作的文章《OpenCV 编程简介(矩阵/图像/视频的基本读写操作)入门必读》,上面对OpenCV的一些入门操作进行了详细的介绍。我也是看了这篇文章后才开始了OpenCV的编程。但是最近发现了一个理解上的小失误,导致一个问题困扰了很长时间。现在问题解决了,就把思考的过程写在这跟大家分享一下。
该文章其中有一部分是关于如何操作图像像素值的介绍:
基于指针的直接访问:(简单高效)
IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
int height = img->height;
int width = img->width;
int step = img->widthStep/sizeof(uchar);
uchar* data = (uchar *)img->imageData;
data[i*step+j] = 111;
对像素值的访问,作者用了data[i*step+j]这样的格式,当时没怎么仔细想,就认为这是图像中坐标为(i, j)像素的值,其实并不是这样的。
首先,来看一下IplImage的结构,其中像素值存放在char *imageData指针做指向的区域,OpenCV官方文档中将其解释为"A pointer to the aligned image data",就是指向排列好的图像数据的指针。但是指针取出来,是一个一维数组data[i*step+j],是怎么体现出“排列好的”这个性质的呢?OpenCV用了一个辅助的变量来实现。就是IplImage的widthStep,它表示图像一行有多少个字节。用这个值除以一个像素所占的字节数,就可以得到一行有多少个像素。data[i*step+j]就是第i行第j个像素的像素值(i和j从0开始,这个应该不用我说吧)。
但是,如果用(i, j)来表示坐标的话,正好反了。因为行号是在列的方向递增的。比如说第5行,那么它的纵坐标应该是5-1。第五行的第几个,这个几才代表横坐标。
明白了这点就好办了,如果想用(i, j)同时来表示坐标和数组中的点,那么应该用data[i+j*step]。i在图像宽度范围内递增,j在图像高度范围内递增。如果图像宽和高非别为w和h,那么对像素的遍历可以用下面的循环搞定:
for(int j = 0; j < h; j++) {
for(int i = 0; i < w; i++) {
printf("(%d, %d)=%d\t", i, j, data[i+j*step]);
}
}
以后就定义成data[i+j*step],方便后面的操作。一般情况下还是习惯i在前、j在后的格式。