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在后的格式。
(坐标是从0开始的,并且是相对图像原点的位置。
图像原点或者是左上角(img->origin=IPL_ORIGIN_TL) 或者是左下角 (img->origin=IPL_ORIGIN_BL) )∙假设有 8-bit 1-通道的图像 I (IplImage* img):I(x,y) ~ ((uchar*)(img->imageData + img->widthStep*y))[x]∙假设有 8-bit 3-通道的图像 I (IplImage* img):I(x,y)blue ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3]I(x,y)green ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3+1]I(x,y)red ~ ((uchar*)(img->imageData + img->widthStep*y))[x*3+2]例如,给点 (100,100) 的亮度增加 30 ,那么可以这样做:CvPoint pt = {100,100};((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3] += 30;((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3+1] += 30;((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3+2] += 30;或者更高效地:CvPoint pt = {100,100};uchar* temp_ptr = &((uchar*)(img->imageData + img->widthStep*pt.y))[pt.x*3]; temp_ptr[0] += 30;temp_ptr[1] += 30;temp_ptr[2] += 30;∙假设有 32-bit 浮点数, 1-通道图像 I (IplImage* img):I(x,y) ~ ((float*)(img->imageData + img->widthStep*y))[x]∙现在,一般的情况下,假设有 N-通道,类型为 T 的图像:I(x,y)c ~ ((T*)(img->imageData + img->widthStep*y))[x*N + c]你可以使用宏 CV_IMAGE_ELEM( image_header, elemtype, y, x_Nc )I(x,y)c ~ CV_IMAGE_ELEM( img, T, y, x*N + c )也有针对各种图像(包括 4 通道图像)和矩阵的函数(cvGet2D, cvSet2D),但是它们非常慢。
如何访问矩阵元素?方法是类似的(下面的例子都是针对 0 起点的列和行)∙设有 32-bit 浮点数的实数矩阵 M (CvMat* mat):M(i,j) ~ ((float*)(mat->data.ptr + mat->step*i))[j]∙设有 64-bit 浮点数的复数矩阵 M (CvMat* mat):Re M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2]Im M(i,j) ~ ((double*)(mat->data.ptr + mat->step*i))[j*2+1]∙对单通道矩阵,有宏 CV_MAT_ELEM( matrix, elemtype, row, col ), 例如对 32-bit 浮点数的实数矩阵:M(i,j) ~ CV_MAT_ELEM( mat, float, i, j ),例如,这儿是一个 3x3 单位矩阵的初始化:CV_MAT_ELEM( mat, float, 0, 0 ) = 1.f;CV_MAT_ELEM( mat, float, 0, 1 ) = 0.f;CV_MAT_ELEM( mat, float, 0, 2 ) = 0.f;CV_MAT_ELEM( mat, float, 1, 0 ) = 0.f;CV_MAT_ELEM( mat, float, 1, 1 ) = 1.f;CV_MAT_ELEM( mat, float, 1, 2 ) = 0.f;CV_MAT_ELEM( mat, float, 2, 0 ) = 0.f;CV_MAT_ELEM( mat, float, 2, 1 ) = 0.f;CV_MAT_ELEM( mat, float, 2, 2 ) = 1.f;OpenCV中获取图像某一像素值收藏This is a basic example for the OpenCV.First we must know the structure of IplImage:IPL image:IplImage|-- int nChannels; // Number of color channels (1,2,3,4)|-- int depth; // Pixel depth in bits:| // IPL_DEPTH_8U, IPL_DEPTH_8S,| // IPL_DEPTH_16U,IPL_DEPTH_16S,| // IPL_DEPTH_32S,IPL_DEPTH_32F,| // IPL_DEPTH_64F|-- int width; // image width in pixels|-- int height; // image height in pixels|-- char* imageData; // pointer to aligned image data| // Note that color images are stored in BGR order|-- int dataOrder; // 0 - interleaved color channels,| // 1 - separate color channels| // cvCreateImage can only create interleaved images|-- int origin; // 0 - top-left origin,| // 1 - bottom-left origin (Windows bitmaps style)|-- int widthStep; // size of aligned image row in bytes|-- int imageSize; // image data size in bytes = height*widthStep|-- struct _IplROI *roi;// image ROI. when not NULL specifies image| // region to be processed.|-- char *imageDataOrigin; // pointer to the unaligned origin of image data | // (needed for correct image deallocation)||-- int align; // Alignment of image rows: 4 or 8 byte alignment| // OpenCV ignores this and uses widthStep instead|-- char colorModel[4]; // Color model - ignored by OpenCV//------------------------------------------------------------------------------int main(int argc, char* argv[])...{IplImage *img=cvLoadImage("c://fruitfs.bmp",1);CvScalar s;for(int i=0;i<img->height;i++)...{for(int j=0;j<img->width;j++)...{s=cvGet2D(img,i,j); // get the (i,j) pixel valueprintf("B=%f, G=%f, R=%f ",s.val[0],s.val[1],s.val[2]);s.val[0]=111;s.val[1]=111;s.val[2]=111;cvSet2D(img,i,j,s);//set the (i,j) pixel value}}cvNamedWindow("Image",1);cvShowImage("Image",img);cvWaitKey(0); //等待按键cvDestroyWindow( "Image" );//销毁窗口cvReleaseImage( &img ); //释放图像return 0;}其实还有更好的方法,例如将其封装成类,调用起来更加方便,效率也很高。