opencv学习中——图片的数据结构

来源:互联网 发布:2016中国网络广告公司 编辑:程序博客网 时间:2024/06/08 00:37

IplImage是用来编码“图象”的基本的数据类型,IplImage可以看做是从CvMat中衍生出来的。

  1. CvMat矩阵结构

    在OpenCV中没有向量(Vector)的构造方法。当我们需要向量时,我们使用1行的矩阵(或1列的矩阵,如果需要转置或共轭向量)

    创建二维矩阵的例程原型为:
    cvMat* cvCreateMat (int rows, int cols, int type);
    //这里type可以是预定义的长列表中的任何一种形式:CV_(S|U|F)C。因此,矩阵可以包含32位的浮点型数(CV_32FC1),或者三通道8位整形数(CV_8UC3).
    CvMat的内部结构:

typedef struct CvMat{        int type;        int step;     int* refcount;    int hdr_refcount;    union    {            uchar* ptr;            short* s;            int* i;            float* fl;            double* db;    } data;     #ifdef __cplusplus    union    {            int rows;            int height;    };    union    {            int cols;            int width;    };    #else            int rows;            int cols;    #endif}CvMat;

主要包含一个宽度、一个高度、一个元素类型、一个步长以及一个指向数据数组的指针。我们可以使用CvMat指针直接访问这些成员,或者使用已提供的访问函数。例如为了获取矩阵的尺寸信息,你可以调用cvGetSize(CvMat*)返回一个CvSize结构,也可以用matrix->width和matrix->height独立的访问width和height成员。
矩阵数组的步长(step)是矩阵一行长度的字节数,而不是元素个数。矩阵或图像在分配内存是4字节对齐的。因此当一个矩阵的宽度为3个字节时,会被分配4个字节,最后一个字节会被忽略。因此,当我们获得一个byte型的数据元素指针时,我们把指针加上步长(step)就可以达到把指针移动到下边一行元素下面的元素。如果我们有一个整形或浮点型的矩阵,和数据相应元素的整形或浮点型指针,要把指针移动到下一行,那么要加step/4;对于double则需要加step/8(这只是考虑到C会自动把偏移量乘以数据类型的字节数)。
2. 创建:

cvCreateMat( )组合了两个原子函数
(1) cvCreateMatHeader( )(创建一个CvMat结构,但没有给数据分配内存)和cvCreateData( )。
(2) cvCreateData( )处理给数据分配内存。

有三种方法访问矩阵中的数据:
一、最简单的获取矩阵中数据的方法是使用CV_MAT_ELEM( )宏(只适于访问一维和二维数组)。这个宏输入矩阵、数据的类型、行、列,然后返回矩阵元素。
例如:
CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );
float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 );
二、使用函数族cvPtr*D和cvGet*D
(1) cvPtr*D族包含cvPtr1D( )、cvPtr2D( )、cvPtr3D( )和cvPtrND( )…. 前三个中的每一个都输入一个矩阵指针参数CvArr*,接着是一个适当的整型数值为指示,返回一个感兴趣元素的指针。对于cvPtrND( ),第二个参数是用以指示数值的整型数组指针。在函数原型中,我们也注意到有一些可选的参数,如果需要时可以给它们赋地址。
可以使用cvPtr*D函数返回的指针达到访问矩阵指定的点,然后可以使用算数运算把指针从那里来回移动。
在多通道矩阵中通道是相连的。
例如,在表示红、绿、蓝字节的三通道二维矩阵中,矩阵中的数据是这样存储的:rgbrgb….。因此,要把适当类型的指针移动到下一个通道,我们只把指针加1即可。如果想要把指针移动到下一个“像素”或下一组元素,我们只需加上和通道数相同大小的偏移量即可(这个例子中为3)。
(2) 如果仅仅是读取数据,那么可以使用外有一个函数族cvGet*D,返回的是矩阵元素的实际值。
三、最恰当的方法是用自己的指针算数运算,并简单的对矩阵进行引用。如果要处理数组中的每一个元素,维护自己的指针是非常重要的,
例:对一个三通道矩阵所有元素求和

float sum( const CvMat * mat){    float s = 0.0f;    for (int row = 0; row < mat->rows; row++)    {     const float* ptr = (cosnt float*) (mat->data.ptr + row * mat->step);       for (int col = 0; col < mat->cols; col++)        {            s += *ptr++;        }    }    return s;}

当计算矩阵内部的指针时,要记住矩阵元素“data”是一个联合结构。所以,引用这个指针时必须指明联合中正确的成员以获得正确的指针类型。接着,对指针偏移时必须使用矩阵中“step”这个成员。之前也曾注意到,元素“step”是字节数。安全起见,指针最好以字节为单位进行算术运算,然后再把它转换成合适的类型,在上个例子中是float类型。虽然CvMat结构为了和旧的IplImage结构相兼容,提供了“height”和”width”成员,但是我们应该更新的“row”和”col”成员。最后,注意到每一行重新计算”ptr”变量,而不是简单的从头开始为每一次读数据而增加指针。这看上去好像做了额外的工作,因为CvMat数据指针只指向大数组内的ROI区域,因此不能保证数据在行间是连续的。
3. IplImage头结构:

 typedefstruct _IplImage{   int  nSize;                    int  ID;                        int  nChannels;                 int  alphaChannel;            int  depth;                     char colorModel[4];             char channelSeq[4];             int  dataOrder;                 int  origin;                  int  align;                     int  width;                     int  height;                  struct _IplROI *roi;            struct _IplImage *maskROI;    void  *imageId;                struct _IplTileInfo *tileInfo;    int  imageSize;                 char *imageData;                int  widthStep;                 int  BorderMode[4];             int  BorderConst[4];            char *imageDataOrigin;       }IplImage;

“depth”变量可能的值:

IPL_DEPTH_8U            8位无符号整型IPL_DEPTH_8S            8位有符号整型IPL_DEPTH_16S          16位有符号整型IPL_DEPTH_32S          32位有符号整型IPL_DEPTH_32F          32位单精度浮点型IPL_DEPTH_64F          64位双精度浮点型 nChannel的可能的值为1、2、3或4。

“origin”变量取值IPL_ORIGIN_TL或IPL_ORIGIN_BL,分别对应于原点坐标分别在图象的左上角或左下角。
“dataOrder”:指定了数据的包装方式, OpenCV只支持IPL_DATA_ORDER_PIXEL方式,表示交叉存取颜色通道,像素存储顺序为BGR BGR BGR … BGR(IPL_DATA_ORDER_PLANE表示通道聚集在一起构成一个图象的面(Plane),这些面是逐个相连的,像素存储顺序为RRR…R GGG…G BBB…B)。
“WidthStep”:包含了行偏移的字节数(CvMat中step参数相似)。只有变量width不足以计算距离,为了达到更快的处理图象的目的,每一行可能会以一定数目的字节数对齐(内存对齐)。因此两行之间可能会存在一些空隙。
“imageData”:包含了第一行数形数据的指针。
“RIO”(region of interest):是一个特殊的重要的感兴趣的区域,它实际上是一个另外的IPL/IPP结构IplROI。它的定义如下(在cxtype.h中有定义):

 typedefstruct _IplROI{   int  coi;   int  xOffset;   int  yOffset;   int  width;   int  height;}IplROI;```: ROI背后的理念是:一旦它被设定,通常会对整个图象进行处理的函数就只处理ROI指示的区域。如果ROI被设定了,几乎所有的OpenCV函数都会使用它。如果COI被设定为非零值,表示一些操作就只在指定的通道上起作用。很不幸的是,大部分OpenCV函数都会忽略这个参数。 考虑一个例子,我们想要把三通道HSV图象的饱合度调整为2558位图象的最大值)而保持色调不变。要完成这个任务最好自己处理图象的每个象素。这和前边对矩阵的操作相似,但在IplImage和CvMat之间也有一些主要的不同。高效的方式: 例,把HSV图象的S、V分量最大化:<div class="se-preview-section-delimiter"></div>

void saturate_sv( IplImage* img )
{
for( int y=0; yheight; y++ )
{
uchar* ptr = (uchar*) (img->imageData + y * img->widthStep);
{
ptr[3*x+1] = 255;
ptr[3*x+2] = 255;
}
}
}

只是简单的直接计算相关行y最左边的象素的指针ptr,引用第x列的饱合度数值。因为图象是三通道的,第c通道的地址为3*x+c。<div class="se-preview-section-delimiter"></div>

在OpenCV中,一副HSV图象和RGB图象,除了对通道的翻译有所不同,其它是没有区别的。因此由一副HSV图象构建一副RGB图象实际所有的操作完全只在“数据”区域。在图象头中,没有任何成员用来表明数据通道的意义。
IplImage和CvMat相比一个重要的不同是imageData的行为。CvMat的数据元素是一个联合体,所以必须说明你想要的指针类型;imageData是一个byte型(uchar*)。但被指向的数据不一定是uchar类型,这意味着当对指针作算术运算时,可以简单的加上widthSetp(同样是以字节数为度量的)而不用担心实际的数据类型,直需在做完加法后,把计算所得的指针转换成想要的数据类型。
总结:当对矩阵操作时,必须对偏移量进行缩减,因为数据指针可能不是byte型;而当对图象操作时,可以使用“看上去”那么多的偏移量,因为数据指针永远是byte型,因此在准备使用它时,只需把整部分做类型转换。

例:假设你要访问第k通道、第i行、第j列的像素。基于指针的直接访问: (简单高效)(1)对于单通道字节型图像:<div class="se-preview-section-delimiter"></div>

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;

2)对于多通道字节型图像:<div class="se-preview-section-delimiter"></div>

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;

3)对于多通道浮点型图像(假设图像数据采用4字节(32位)行对齐方式):<div class="se-preview-section-delimiter"></div>

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;

4. OpenCV对ROI和widthSetp的支持是普遍的:每一个函数都充许把操作只限定在一个子区域。使用cvSetImageROI( )和cvResetImageROI( )可以开启或关闭ROI。<div class="se-preview-section-delimiter"></div>

void cvSetImageROI(IplImage*image, CvRect rect);
void cvResetImageROI( IplImage* image );

例:载入一张图片,然后变更图象的一部分。在显示之前用cvResetImageROI( )释放ROI是很重要的,否则只会忠实的显示ROI区域。<div class="se-preview-section-delimiter"></div>

include

#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); //用CvRect结构设定一个感兴趣的区域   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只能顺序的进行,必须不断的设定和重置ROI区域。


参考:《O’Reilly Learning OpenCV》& OpenCV中文网站


阅读全文
0 0
原创粉丝点击