IplImage應用解析

參考:http://www.cnblogs.com/zyx2007/archive/2011/09/07/2169698.html

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:一個數組對應元素與另一個數組對應元素相加

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章