IplImage 结构解读:
typedef struct _IplImage
{
int nSize; /* IplImage大小,等于width*height */
int ID; /* 版本 (=0)*/
int nChannels; /* 大多数OPENCV函数支持1,2,3 或 4 个通道 */
int alphaChannel; /* 被OpenCV忽略 */
int depth; /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */
char colorModel[4]; /* 被OpenCV忽略 */
char channelSeq[4]; /* 同上 (ditto)*/
int dataOrder; /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道. cvCreateImage只能创建交叉存取图像 */
int origin; /* 0 - 顶—左结构,1 - 底—左结构 (Windows bitmaps 风格) */
int align; /* 图像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */
int width; /* 图像宽像素数 */
int height; /* 图像高像素数*/
struct _IplROI *roi; /* 图像感兴趣区域. 当该值非空只对该区域进行处理 */
struct _IplImage *maskROI; /* 在 OpenCV中必须置NULL */
void *imageId; /* 同上*/
struct _IplTileInfo *tileInfo; /*同上*/
int imageSize; /* 图像数据大小(在交叉存取格式下imageSize=image->height*image->widthStep),单位字节*/
char *imageData; /* 指向排列的图像数据 */
int widthStep; /* 排列的图像行大小,以字节为单位 */
int BorderMode[4]; /* 边际结束模式, 被OpenCV忽略 */
int BorderConst[4]; /* 同上 */
char *imageDataOrigin; /* 指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的 */
}
IplImage;
重要结构元素说明:
depth和nChannels
depth代表颜色深度,使用的是以下定义的宏,nChannels是通道数,为1,2,3或4。
depth的宏定义:
IPL_DEPTH_8U,无符号8bit整数(8u)
IPL_DEPTH_8S,有符号8bit整数(8s)
IPL_DEPTH_16S,有符号16bit整数(16s)
IPL_DEPTH_32S,有符号32bit整数(32s)
IPL_DEPTH_32F,32bit浮点数,单精度(32f)
IPL_DEPTH_64F,64bit浮点数,双精度(64f)
origin和dataOrder
origin变量可以有两个取值:IPL_ORIGIN_TL或者IPL_ORIGIN_BL,分别代表图像坐标系原点在左上角或是左下角。相应的,在计算机视觉领域,一个重要的错误来源就是原点位置的定义不统一。例如,图像的来源不同,操作系统不同,视频解码codec不同,存储方式不同等等,都可以造成原点位置的变化。例如,你可能认为你正在从图像上面的脸部附近取样,但实际上你却在图像下方的裙子附近取样。最初时,就应该检查一下你的系统中图像的原点位置,这可以通过在图像上方画个形状等方式实现。
dataOrder的取值可以是IPL_DATA_ORDER_PIXEL或者IPL_DATA_ORDER_PLANE,这个成员变量定义了多通道图像数据存储时颜色数据的排列方式,如果是IPL_DATA_ORDER_PIXEL,通道颜色数据排列将会是BGRBGR...的交错排列,如果是IPL_DATA_ORDER_PLANE,则每个通道的颜色值在一起,有几个通道,就有几个“颜色平面”。大多数情况下,通道颜色数据的排列是交错的。
widthStep与CvMat中的step类似,是以字节数计算的图像的宽度。成员变量imageData则保存了指向图像数据区首地址的指针。
最后还有一个重要参数roi(region of interest 感兴趣的区域),这个参数是IplROI结构体类型的变量。IplROI结构体包含了xOffset,yOffset,height,width,coi成员变量,其中xOffset,yOffset是x,y坐标,coi代表channel of interest(感兴趣的通道)。有时候,OpenCV图像函数不是作用于整个图像,而是作用于图像的某一个部分。这是,我们就可以使用roi成员变量了。如果IplImage变量中设置了roi,则OpenCV函数就会使用该roi变量。如果coi被设置成非零值,则对该图像的操作就只作用于被coi指定的通道上了。不幸的是,许多OpenCV函数忽略了coi的值。
访问图像中的数据
就象访问矩阵中元素一样,我们希望用最直接的办法访问图像中的数据,例如,如果我们有一个三通道HSV图像(HSV色彩属性模式是根据色彩的三个基本属性:色相H、饱和度S和明度V来确定颜色的一种方法),我们要将每个点的饱和度和明度设置成255,则我们可以使用指针来遍历图像,请对比一下,与矩阵的遍历有何不同:
- void sat_sv( IplImage* img )
- for( int y=0; y<height; y++ )
- {
- uchar* ptr = (uchar*) ( img->imageData + y * img->widthStep );
- for( int x=0; x<width; x++ )
- {
- ptr[3*x+1] = 255;
- ptr[3*x+2] = 255;
- }
- }
上面只是简单的直接计算相关行y最左边的像素的指针ptr。从那里为参考,引用第x列的饱和度数据。因为图像是三通道的,第c通道的地址为3*x+c。
注意一下,3*x+1,3*x+2的方法,因为每一个点都有三个通道,所以这样设置。另外imageData成员的类型是uchar*(即byte型),即字节指针类型,所以与CvMat的data指针类型(union)不同——CvMat的数据元素是一个联合体,所以必须说明你想要的指针类型。不需要象CvMat那样麻烦(还记得step/4,step/8的那种情形吗)。CvMat的数据元素是一个联合体,所以必须说明你想要的指针类型,imageData是一个byte型(uchar*)。我们知道,被指向的数据不一定是uchar类型,这意味着当对指针作算术运算时,你可以简单的加上widthStep(同样是以字节数为度量的)而不用担心实际的数据类型,只需在做完加法后,把你计算所得的指针转换成你想要的数据类型。总结:党对矩阵操作时,你必须对偏移量进行缩减,因为数据指针可能不是byte型,而当对图像操作时,你可以使用“看上去”那么多的偏移量,因为数据指针永远都是byte型,因此在你准备使用它时,只需把整部分做类型转换。
roi和widthStep
roi和widthStep在实际工作中有很重要的作用,在很多情况下,使用它们会提高计算机视觉代码的执行速度。这是因为它们允许对图像的某一小部分进行操作,而不是对整个图像进行运算。在OpenCV中,所有的对图像操作的函数都支持roi,如果你想打开roi,可以使用函数cvSetImageROI(),并给函数传递一个矩形子窗口。而cvResetImageROI()是用于关闭roi的。
void cvSetImageROI(IplImage* image,CvRect rect);
void cvResetImageROI(IplImage* image);
注意,在程序中,一旦使用了roi做完相应的运算,就一定要用cvResetImageROI()来关闭roi,否则,其他操作执行时还会使用roi的定义。
(以下参考百度文库《IplImage的像素的访问》)
opencv中访问图像数据——假设要访问第k通道、第i行、第j列的像素
一、间接访问(通用,但效率低,可以访问任意格式的图像)
对于单通道字节型图像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
- CvScalar s;
- s = cvGet2D(img, i, j);// get the (i,j) pixel value
- printf("intensity = %f\n", s.val[0]);
- s.val[0] = 111;
- cvSet2D(img, i, j, s);// set the (i,j) pixel value
对于多通道字节/浮点型图像
- // mul channels byte/float image
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- CvScalar s;
- s = cvGet2D(img, i, j);// get the (i,j) pixel value
- printf("B=%f, G=%f, R=%f\n", s.val[0], s.val[1], s.val[2]);
- s.val[0] = 111;
- s.val[1] = 111;
- s.val[2] = 111;
- cvSet(img, i, j, s);// set the (i,j) pixel value
二、直接访问(效率高,但容易出错)
对于单通道字节型图像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
- ((uchar *) (img->imageData + i*img->widthStep))[j] = 111;// 也相当于在后面加j,即(uchar *) (img->imageData + i*img->widthStep + j) = 111;
对于多通道字节型图像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 3);
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 0] = 111;// B
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 1] = 112;// G
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 2] = 113;// R
对于多通道浮点型图像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 0] = 111;// B
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 1] = 112;// G
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 2] = 113;// R
三、基于指针的直接访问(简单高效)
对于单通道字节型图像:
- 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;
对于多通道字节型图像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 3);
- int height = img->height;
- int width = img->width;
- int step = img->widthStep/sizeof(uchar);
- int channels = img->nChannels;
- uchar* data = (uchar *)img->imageData;
- data[i*step + j*channels + k] = 111;
对于多通道浮点型图像(假设图像数据采用4字节(32位)行对齐方式):
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- int height = img->height;
- int width = img->width;
- int step = img->widthStep/sizeof(float);
- int channels = img->nChannels;
- float * data = (float *)img->imageData;
- data[i*step + j*channels + k] = 111;
ROI和widthStep有非常重要的实用价值,因为它允许代码只处理图像的一部分子区域,从而在很多场合加速计算机视觉的处理。OpenCV对ROI和widthStep的支持是普遍的:每一个函数都允许把操作只限定在一个子区域。使用cvSetImageROI()和cvResetImageROI()可以开启或关闭ROI。
- void cvSetImageROI( IplImage* image, CvRect rect );
- void cvResetImageROI( IplImage* image );
下面我们看ROI是怎么使用的,我们载入一张图,然后变更图像的一部分。在显示之前用cvResetImageROI()释放ROI是很重要的,否则只会忠实的显示ROI区域。
- #include <cv.h>
- #include <highgui.h>
- int main()
- {
- IplImage* src = cvLoadImage("poppy.jpg", 1);// 载入图像
- cvSetImageROI(src, cvRect(200, 100, 100, 100));// 为图像设置ROI区域
- cvAddS(src, cvScalar(100, 100, 100), src);// 对图像做与运算
- cvResetImageROI(src);// 释放ROI区域
- cvSaveImage("poppy1.jpg", src);// 保存处理后的图像
- cvNamedWindow("Roi_add");// 创建一个窗口
- cvShowImage("Roi_add", src);// 在窗口中显示图像
- cvWaitKey();// 延时
- return 0;
- }
如果我们灵活的使用widthStep,也可以达到同样的效果
- #include <cv.h>
- #include <highgui.h>
- int main()
- {
- IplImage* src = cvLoadImage("poppy.jpg", 1);// 载入图像
- CvRect interest_rect = cvRect(200, 100, 100, 100);
- // 创建一个和源图像属性相同的子图像
- IplImage* sub_img = cvCreateImageHeader(cvSize(interest_rect.width, interest_rect.height), src->depth, src->nChannels);
- sub_img->origin = src->origin;// 设置相同的P的原点标准
- sub_img->widthStep = src->widthStep;// 这是子图像的widthStep,这是此技术中最精妙的一步
- sub_img->imageData = src->imageData
- + interest_rect.y*src->widthStep
- + interest_rect.x*src->nChannels;// 设置图像的数据区域
- cvAddS(sub_img, cvScalar(100, 100, 100), sub_img);// 对图像做与运算
- cvReleaseImageHeader(&sub_img);// 释放子图像头
- cvSaveImage("poppy1.jpg", src);// 保存处理后的图像
- cvNamedWindow("Roi_add");// 创建一个窗口
- cvShowImage("Roi_add", src);// 在窗口中显示图像
- cvWaitKey();// 延时
- return 0;
- }
设定和重置ROI区域好像更方便,那么为什么还需要用widthStep呢?这样做的原因是有时候在图像处理过程中想要保持多个子区域都是活动的,但是ROI只能顺序的进行,必须不断地设定和重置ROI区域。
附:
首先,个人总结大写的Cv开头的是数据类型,小写的cv开头是函数(如CvMat与cvMat,CvScalar与cvScalar)
CvScalar&cvScalar:前者是一个数组,里面有四个double型的元素。后者是其构造函数,分别将四个值赋给数组里面的四个元素。
CvMat 定义如下:
- typedef struct CvMat
- {
- int type; /* CvMat 标识 (CV_MAT_MAGIC_VAL), 元素类型和标记 */
- int step; /* 以字节为单位的行数据长度*/
- int* refcount; /* 数据引用计数 */
- union
- {
- uchar* ptr;
- short* s;
- int* i;
- float* fl;
- double* db;
- } data; /* data 指针 */
- #ifdef __cplusplus
- union
- {
- int rows;
- int height;
- };
- union
- {
- int cols;
- int width;
- };
- #else
- int rows; /* 行数 */
- int cols; /* 列数*/
- #endif
- } CvMat;
cvMat用法参考:1/百度文库cvmat(提供者xxhhzhw)http://wenku.baidu.com/view/dcad6efb941ea76e58fa0452.html
2/http://blog.sina.com.cn/s/blog_7275089501011xgd.html
3/http://hi.baidu.com/eilianhell/blog/item/8c3d8e551e4ad73a43a75bb4.html
cvAddS:将数组中每个元素都与一个数相加
cvAdd:一个数组对应元素与另一个数组对应元素相加