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;
- 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;
- void cvSetImageROI( IplImage* image, CvRect rect );
- void cvResetImageROI( IplImage* image );
- #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;
- }
- #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;
- }
附:
首先,个人总结大写的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;
2/http://blog.sina.com.cn/s/blog_7275089501011xgd.html
3/http://hi.baidu.com/eilianhell/blog/item/8c3d8e551e4ad73a43a75bb4.html
cvAddS:将数组中每个元素都与一个数相加
cvAdd:一个数组对应元素与另一个数组对应元素相加