2008年12月29日 星期一

OpenCV統計應用-直方圖反向投影

影像處理的統計直方圖,可以知道一張圖片在該色彩空間的數據分布狀況,而這邊,就要介紹到直方圖反向投影的函式,直方圖反向投影,也就是將數據分布的狀況依照Look-up table的方式對應回去,實際上,這個函式是跟前面介紹到的cvLUT()是一樣的,只不過,差別是差異在cvLUT()的第三個引數改變成CvHistogram資料結構的輸入,直方圖反向投影,cvCalcBackProject()的第一個引數是輸入單通道IplImage資料結構,第二個引數是輸出單通道IplImage反向投影圖形資料結構,第三個引數是選定要被反向投影的CvHistogram直方圖資料結構,而cvCalcBackProject()把前面提到的Look-up table的計算方式包在cvCalcBackProject()函式的底層,因此,它可以整合CvHistogram這個資料結構做更多的應用,下面這個就是修改前面的範例"OpenCV統計應用-CvHistogram直方圖資料結構",來做直方圖反向投影的程式

灰階直方圖反向投影
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int HistogramBins = 50;
int HistogramBinWidth;
float HistogramRange1[2]={0,255};
float *HistogramRange[1]={&HistogramRange1[0]};

CvPoint Point1;
CvPoint Point2;

int main()
{
    IplImage *GrayImage1;
    IplImage *Image1;
    IplImage *Image2;
    IplImage *BackProjectImage;
    CvHistogram *Histogram1;
    IplImage *HistogramImage1;

    Image1=cvLoadImage("Riverbank.jpg",1);
    Image2=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,3);
    GrayImage1=cvLoadImage("Riverbank.jpg",0);
    BackProjectImage=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,1);
    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(256,300),8,3);

    cvSetZero(HistogramImage1);
    HistogramImage1->origin=1;
    HistogramBinWidth=256/HistogramBins;

    cvCalcHist(&GrayImage1,Histogram1);
    cvNormalizeHist(Histogram1,5000);

    cvThreshHist(Histogram1,50);
    cvCalcBackProject(&GrayImage1,BackProjectImage,Histogram1);
    cvCopy(Image1,Image2,BackProjectImage);

    for(int i=0;i<HistogramBins;i++)
    {
        printf("%f\n",cvQueryHistValue_1D(Histogram1,i));
        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)        cvQueryHistValue_1D(Histogram1,i));

        cvRectangle(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }

    cvNamedWindow("Histogram1",1);
    cvNamedWindow("Riverbank",1);
    cvNamedWindow("Back Project RiverBank",1);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Back Project RiverBank",Image2);
    cvShowImage("Histogram1",HistogramImage1);
    cvWaitKey(0);
}

執行結果:


這邊就是拿前面灰階去除較小直方圖區塊的程式碼做修改,然後將前面去除最小區塊的部份,對應到彩色的圖片去了,顯示的結果會是,只要是影像裡面灰階值分佈數量比較少的數據,全部都被對應成黑色的像素,也就是全部都變成0,而cvCalcBackProject(),就跟cvLUT()一樣直接拿直方圖的數據去對應,假設一個直方圖從頭開始的數據為254,129,80,70....那麼只要是圖片內像素值為1的數據就會對應到254,1的圖片裡面像素值的數據就會直接變成254,像素值為2的就會對應到129,2的數據就會直接變成129,以此類推,因此,用cvThreshHist()去除小於50的直方圖區段,讓小於50的全部歸0,再來,對他做一個圖片的反向投影,所對應出來的結果雖然不是0或255,可是它卻可以直接拿來當做是遮罩,也就是說,直接拿來給cvCopy()來做對應,因此,反向投影的結果就出來啦,在遮罩的部份就要參考"資料結構操作與運算-圖形的Mask遮罩實作",而這段程式碼則是用到"OpenCV統計應用-CvHistogram資料結構操作"裡面的部份.

在OpenCV Documentation的部份有提到cvCalcBackProject()可以對HSV色彩空間的Hue做反向投影,到底是怎麼實作出來呢?在OpenCV的Sample Code裡面有一個camshift.c的程式,就是用到這個反向投影的函式,而它的反向投影的簡單範例,原理就如下所示

HSV色彩空間反向投影
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


IplImage *Image1,*Image2;
IplImage *HSVImage;
IplImage *HueImage;
IplImage *BackProjectHueImage,*BackProjectImage;
CvHistogram *Histogram1;
IplImage *HistogramImage1;
CvPoint Point1,Point2;

int HueValue=0;

int HistogramBins = 180;
int HistogramBinWidth;
float HistogramRange1[2]={0,180};
float *HistogramRange[1]={&HistogramRange1[0]};
void onTrackbar(int position);

int main()
{

    Image1 =cvLoadImage("Riverbank.jpg",1);
    HSVImage = cvCreateImage( cvGetSize(Image1),8,3);
    HueImage = cvCreateImage( cvGetSize(Image1),8,1);

    BackProjectHueImage = cvCreateImage( cvGetSize(Image1),8,1);
    BackProjectImage = cvCreateImage( cvGetSize(Image1),8,3);

    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(180,300),8,3);
    HistogramImage1->origin=1;

    cvCvtColor( Image1, HSVImage, CV_BGR2HSV );
    cvSplit(HSVImage,HueImage,0,0,0);
    cvCalcHist( &HueImage, Histogram1);
    cvNormalizeHist(Histogram1,5000);
    cvZero( HistogramImage1 );
    cvNot(HistogramImage1,HistogramImage1);
    HistogramBinWidth = HistogramImage1->width/HistogramBins;
    for(int i=0;i<HistogramBins;i++)
    {

        Point1=cvPoint(i,0);
        Point2=cvPoint(i,(int)cvQueryHistValue_1D(Histogram1,i));
        printf("%d\n",(int)cvQueryHistValue_1D(Histogram1,i));
        cvLine(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }
    cvNamedWindow("Riverbank",1 );
    cvNamedWindow("Hue Histogram",1);
    cvCreateTrackbar("Hue Thresh","Riverbank",&HueValue,250,onTrackbar);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Hue Histogram",HistogramImage1);
    cvWaitKey(0);

}

void onTrackbar(int position)
{
    IplImage *Image2=cvCreateImage( cvGetSize(Image1),8,3);
    CvHistogram *Histogram2= cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    cvCopyHist(Histogram1,&Histogram2);

    cvThreshHist(Histogram2,position);
    cvCalcBackProject(&HueImage, BackProjectHueImage, Histogram2);
    cvCopy(Image1,Image2,BackProjectHueImage);

    cvZero( HistogramImage1 );
    cvNot(HistogramImage1,HistogramImage1);
    HistogramBinWidth = HistogramImage1->width/HistogramBins;
    for(int i=0;i<HistogramBins;i++)
    {

        Point1=cvPoint(i,0);
        Point2=cvPoint(i,(int)cvQueryHistValue_1D(Histogram2,i));
        printf("%d\n",(int)cvQueryHistValue_1D(Histogram2,i));
        cvLine(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }
    cvShowImage("Hue Histogram",HistogramImage1);
    cvShowImage("Riverbank",Image2);
}

執行結果:


在OpenCV裡面,HSV色彩空間,色調(Hue)值的範圍在0~180,飽和度(Saturation)的範圍在0~255,亮度(Value)的範圍在0~255,而這邊就只取色調(Hue)值在做反向投影,開啟了一個Track bar的功能,並且利用cvCvtColor()將BGR的色彩空間轉換成HSV,並且用cvSplit()通道分割取Hue通道的圖片,計算Hue值的直方圖,在onTrackbar()的部份,則是用Trackbar來調整去除cvThreshHist()的最小區塊的臨界值,去除之後在反向投影到原始的Hue的圖片,在由反向投影的結果當做遮罩,直接跟彩色圖片做對應.

而cvCalcBackProject()不單單只有這樣的功能,它可以對多維度空間的色彩直方圖做對應,cvCalcBackProject()提供了一個當CvHistogram資料結構維度為3的時候的一個反向投影,下面的這個例子就以HSV的色彩空間為例,建構一個三維的CvHistogram資料結構

HSV三維直方圖反向投影
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


IplImage *Image1,*Image2;
IplImage *HSV;
IplImage *HueImage,*SaturationImage,*ValueImage;
IplImage *ImageArray[3];
IplImage *BackProjectImage;
CvHistogram *Histogram1;
IplImage *HistogramImage1;
CvPoint Point1,Point2;

int HueValue=0;

int HistogramBins[3] ={180,256,256};
int HistogramBinWidth;
float HistogramRange1[6]={0,180,0,255,0,255};
float *HistogramRange[3]={&HistogramRange1[0],&HistogramRange1[2],&HistogramRange1[4]};
void onTrackbar(int position);

int main()
{

    Image1 =cvLoadImage("Riverbank.jpg",1);
    HSV = cvCreateImage( cvGetSize(Image1),8,3 );
    HueImage = cvCreateImage( cvGetSize(Image1),8,1 );
    SaturationImage = cvCreateImage( cvGetSize(Image1),8,1 );
    ValueImage = cvCreateImage( cvGetSize(Image1),8,1);
    ImageArray[0]=HueImage;
    ImageArray[1]=SaturationImage;
    ImageArray[2]=ValueImage;

    BackProjectImage = cvCreateImage( cvGetSize(Image1),8,3 );

    Histogram1 = cvCreateHist(3,HistogramBins,CV_HIST_SPARSE,HistogramRange);


    cvCvtColor( Image1, HSV, CV_BGR2HSV );
    cvSplit(HSV,HueImage,SaturationImage,ValueImage,0);

    cvCalcHist( ImageArray, Histogram1);


    cvNamedWindow("Riverbank",1 );
    cvCreateTrackbar("Hue Thresh","Riverbank",&HueValue,200,onTrackbar);
    cvShowImage("Riverbank",Image1);
    cvWaitKey(0);

}

void onTrackbar(int position)
{
    CvHistogram *Histogram2= cvCreateHist(3,HistogramBins,CV_HIST_SPARSE,HistogramRange);
    IplImage *Image2=cvCreateImage( cvGetSize(Image1),8,3 );
    IplImage *BackProjectImage = cvCreateImage( cvGetSize(Image1),8,1 );

    cvCopyHist(Histogram1,&Histogram2);

    cvThreshHist(Histogram2,position);
    cvCalcBackProject( ImageArray, BackProjectImage, Histogram2);
    cvCopy(Image1,Image2,BackProjectImage);

    cvShowImage("Riverbank",Image2);
}

執行結果:


在三維空間的作法上面,就要參考到前面"OpenCV統計應用-CvHistogram直方圖資料結構"關於三維空間製作的部份,除了用cvCvtColor()將色彩空間轉換,用cvSplit()將通道做分割,還要做個圖形陣列(ImageArray)來讓cvCalcHist()這個函式做運算,計算出來的結果為一個CvHistogram的三維空間稀疏矩陣直方圖,而在onTrackbar()的部份,cvCalcBackProject()直方圖反向投影亦是同樣要用ImageArray做輸入,而輸出則是一個單通道的圖形,在稀疏矩陣裡面,由於維度為三維,所以他所形成的統計直方圖數值都是極小,所以門檻值只要一點點就快要全部都分佈了,而這個三維空間的反向投影可以如此建構,是基於Look-up table的功能來實現,只不過他的缺點是,每一個維度的Look-up table範圍是0~255,因此如果是像Hue值的範圍0~180,它的數值就會被模糊化,也就是數據會被些許位移,但是仍不會影響它出來結果的精確度

在這個直方圖反向投影的部份,也可以結合連通成分來做去除某一門檻值的連通分量

cvCalcBackProject()
將統計直方圖的分布數據根據Look-up table對應回去,也就是說,當今天CvHistogram資料結構內的數據分佈是243,110,0,60...則使用cvCalcBackProject()函式單通道的圖片像素值會是,當遇到像素值為1的時候變243,像素值為2的時候變110,依此類推,cvCalcBackProject()直方圖反向投影可以根據多維度設計,而cvCalcBackProject()第一個引數為輸入單通道IplImage或CvMat資料結構,第二個引數為輸入單通道反向投影IplImage或CvMat資料結構,第三個引數為輸入CvHistogram資料結構
cvCalcBackProject(輸入單通道IplImage或CvMat資料結構,輸入單通道反向投影IplImage或CvMat資料結構,輸入CvHistogram資料結構)



2008年11月12日 星期三

OpenCV統計應用-影像增強,亮度/對比實作

在一般顯示螢幕以及圖形處理的應用軟體上,都會有一個亮度/對比的色彩(Brightness/Contrast)調整,它是屬於影像增強的部份,在OpenCV裡面的Sample Code裡面就有這樣的灰階程式的實作,在這邊就修改了OpenCV的Sample Code,來做色彩增強的亮度/對比的程式,而在一般的亮度/對比來講亮度(Brightness)的範圍為0~200而對比(Contrast)亦是0~200,它們由一條線性函數的公式所定義,對比所代表的是斜率,亮度則是偏移量,這條線性公式代表的是Look-up table的對應,它的數學式定義如下

原始的亮度對比數值範圍為-100~100之間,C代表對比,B代表亮度


對於對比率(Contrast ratio)來講,delta範圍應該落在0~255,這邊將對比率的公式做重新的調整



對比率代表著斜率的α值,而亮度則是決定線性公式位移的情況,也就是β值,而Y=αX+β這個線性公式它所表達的情況如下


α值的範圍落在0~255之間,而它的情況如下


再來下面是用虛擬碼的方式表達亮度/對比的演算法


下面就是亮度/對比的程式了

亮度/對比實作
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int BrightnessPosition = 100;
int ContrastPosition = 100;

int HistogramBins = 64;
int HistogramBinWidth;
float HistogramRange1[2]={0,256};
float *HistogramRange[1]={&HistogramRange1[0]};

IplImage *Image1,*Image2;
CvHistogram *Histogram1;
IplImage *HistogramImage;

uchar LookupTableData[256];
CvMat *LookupTableMatrix;
IplImage *LookupTableImage;
CvPoint Point1,Point2;


void OnTrackbar(int Position)
{
    int Brightness=BrightnessPosition-100;
    int Contrast=ContrastPosition -100;
    double Delta;
    double a,b;
    int y;

    //Brightness/Contrast Formula
    if(Contrast>0)
    {
        Delta=127*Contrast/100;
        a=255/(255-Delta*2);
        b=a*(Brightness-Delta);

        for(int x=0;x<256;x++)
        {
            y=(int)(a*x+b);

            if(y<0)
                y=0;
            if(y>255)
                y=255;

            LookupTableData[x]=(uchar)y;
        }
    }
    else
    {
        Delta=-128*Contrast/100;
        a=(256-Delta*2)/255;
        b=a*Brightness+Delta;

        for(int x=0;x<256;x++)
        {
            y=(int)(a*x+b);

            if(y<0)
                y=0;
            if(y>255)
                y=255;

            LookupTableData[x]=(uchar)y;
        }
    }
    //End

    //Look up table sketch
    cvSetZero(LookupTableImage);
    cvNot(LookupTableImage,LookupTableImage);
    Point2=cvPoint(0,LookupTableData[0]);
    for(int i=0;i<256;i++)
    {
        Point1=cvPoint(i,LookupTableData[i]);
        cvLine(LookupTableImage,Point1,Point2,CV_RGB(0,0,0),3);
        Point2=Point1;
    }
    cvLUT(Image1,Image2,LookupTableMatrix);
    //End

    //Gray Level Histogram
    cvCalcHist(&Image2,Histogram1);
    cvNormalizeHist(Histogram1,3000);

    cvSetZero(HistogramImage);
    cvNot(HistogramImage,HistogramImage);
    HistogramBinWidth = HistogramImage->width/HistogramBins;
    for(int i=0;i<HistogramBins;i++)
    {
        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram1,i));
        cvRectangle(HistogramImage,Point1,Point2,CV_RGB(0,0,0),CV_FILLED);
    }
    //End

    cvShowImage("Gray Level Histogram",HistogramImage);
    cvShowImage("Brightness/Contrast",Image2);
    cvShowImage("Image Enhance",LookupTableImage);
    cvZero(Image2);
}

int main()
{
    Image1=cvLoadImage("DarkClouds.jpg",0);
    Image2=cvCloneImage(Image1);

    Histogram1=cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage = cvCreateImage(cvSize(320,200),8,1);

    LookupTableImage=cvCreateImage(cvSize(256,256),8,3);
    LookupTableMatrix=cvCreateMatHeader(1,256,CV_8UC1);
    cvSetData(LookupTableMatrix,LookupTableData,0);

    LookupTableImage->origin=1;
    HistogramImage->origin=1;

    cvNamedWindow("Brightness/Contrast",1);
    cvNamedWindow("Gray Level Histogram",1);
    cvNamedWindow("Image Enhance",1);

    cvCreateTrackbar("brightness","Brightness/Contrast",&BrightnessPosition,200,OnTrackbar);
    cvCreateTrackbar("contrast","Brightness/Contrast",&ContrastPosition,200,OnTrackbar);

    OnTrackbar(0);

    cvWaitKey(0);
}

執行結果:


這隻程式同樣也是用到CvHistogram資料結構,使用到兩個拉軸(Trackbar),以及Look-up table的應用,在//Brightness/Contrast Formula的註解內所包的就是亮度/對比演算法虛擬碼的實作,再來就是把它的線性系統化出來,也就是Y=αX+β的函數方程式,這個方程式,當然同等於Look up table,而之後,在把他們灰階直方圖的分布畫出來,在main()裡面,當然是先讀取目標圖片轉成灰階,初始化繪製直方圖與線性系統圖片的空間,創立三個視窗介面,設立兩個拉軸,並且將拉軸的事件函式設定成同一個的副程式的名稱.而對於影像增強(Image Enhance)這個視窗介面,它所代表的含意如下



X軸代表為是原始灰階的輸入值,而Y軸代表的是灰階值所對應的結果,而X軸跟Y軸的範圍都是0~255,而這條直線公式也會受到斜率(α)以及平移(β)的結果改面灰階值輸入以及輸出的對應,它是將一張原始灰階圖片的每一個像素值做線性函式的對應,使得每個灰階值對應出來的結果產生了變化,由下面可以知道它(LUT)對應的關係

(a)亮度條為0因此小於100的灰階值都為0而灰階值方圖也像左偏移


(b)亮度條為100,因此大於156的灰階值都為255,而灰階值方圖也都向右偏移


(c)對比為0,這個時候斜率α為0,因此輸入的0~255的灰階值輸出都固定為128,因此整張圖片都是灰階值128的影像,而灰階直方圖則是所有數據都集中在128


(d)對比為100,這個時候斜率為255,而這樣的圖片又可以叫做二值化圖片,因為輸出結果非黑即白,而移動亮度則是在平移二值化的門檻值,由灰階值方圖可以得知,所有數據都被分開到0跟255兩邊


上面所表達的其實就是一種Look-up table的表達方式,藉由一個輸入灰階值的矩陣,對應岀另一個不同的灰階值數據,因此改變了原始灰階值的數據,而整張圖片也因此產生了變化



2008年11月7日 星期五

OpenCV統計應用-直方圖等化

直方圖等化(Histogram Equalization)為一種使用統計方法的影像處理程式設計,它的功能為將統計直方圖的色彩分布平均的打散在直方圖裡,也就是說,讓一張圖的直方圖分布均勻化,同樣的也是使用到LUT(Look-up Table)的方法

而在設計直方圖等化不可或缺的就是需要先知道統計學的機率密度函數(Probability Density Function,PDF)以及累積分配函數(Cumlative Distribution Function,CDF)相關的基本概念,在這邊會簡單的說明機率密度函數及累積分配函數的概念還有直方圖等化的程式設計實作,而OpenCV也提供了直接使用直方圖等化的函式,cvEqualizeHist(),下面就簡單的製作直方圖等化的程式設計

RGB結構直方圖等化
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int main()
{
    IplImage *Image1;
    IplImage *Image2;
    IplImage *RedImage;
    IplImage *GreenImage;
    IplImage *BlueImage;

    Image1=cvLoadImage("DarkClouds.jpg",1);
    Image2=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,3);
    RedImage=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,1);
    GreenImage=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,1);
    BlueImage=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,1);

    cvSplit(Image1,BlueImage,GreenImage,RedImage,0);

    cvEqualizeHist(BlueImage,BlueImage);
    cvEqualizeHist(GreenImage,GreenImage);
    cvEqualizeHist(RedImage,RedImage);

    cvMerge(BlueImage,GreenImage,RedImage,0,Image2);

    cvNamedWindow("DarkClouds",1);
    cvShowImage("DarkClouds",Image1);
    cvNamedWindow("Equalize DarkClouds",1);
    cvShowImage("Equalize DarkClouds",Image2);

    cvWaitKey(0);

}

原始圖片:


執行結果:


當然,使用了函式之後就完全不用知道直方圖等化在跑什麼啦,cvEqualizeHist()只支援單通道uchar型別的圖片資料結構,因此,就需要用cvSplit()分割,而這裡所做的是將RGB三原色直接做直方圖等化,這個直方圖等化的結果是彩色的,將個別的RGB直分割後再用cvMerge()合併,通道分割與合併的部份就要參考"資料結構操作與運算-通道的分割,合併與混合"這個地方了.

再來就是比較複雜的直方圖等化演算法,要知道直方圖等化如何計算就要知道以下的步驟

1.資料結構初始化
2.機率密度函數(PDF)的計算
3.累積分配函數(CDF)的計算
4.LUT對應

在機率密度函數的部分,在統計直方圖來講,它的X軸範圍會落在0~255,而它的Y軸範圍代表著機率,由0~255對應的數據代表著它發生的機率,也就是說,這代表著0到255數值出現的機率分佈,0~255代表著所有的機率的範圍,那從0加到255的總和會為1!它所代表的公式如下


PDF機率分佈





而累積分配函數就更簡單了,它只是將機率密度函數做累積的計算,也就是說,當數值為255的時候它的機率會為1,而且曲線會是由下往上的成長,表達的方式就是將機率密度函數做累加,它的公式如下


CDF機率分佈





再來是LUT的部份了,它只是開一個0~255的陣列做對應,將累積分配函數的數值乘以255,將它存放在0~255的陣列裡,這樣會出現許多重複的部份,因此,將Look-up table對應原來的圖形的灰階值,就可以將原本的統計直方圖分布打散掉了,而且直方圖等化的結果也會發現很多地方數值是空心的,也是因為如此,直方圖的分布就會向外推擠了


累積分配直方圖乘255存在LUT陣列的情況

再來,就是直方圖等化的程式了

灰階直方圖等化
#include <stdio.h>
#include <cv.h>
#include <highgui.h>

int HistogramBins = 256;
float HistogramRange1[2]={0,255};
float *HistogramRange[1]={&HistogramRange1[0]};

float CumulativeNumber;
float CDFArray[256];
uchar LookupTableData[256];

void DrawHistogramImage(CvHistogram *Histogram,IplImage *HistogramImage,int HistogramBins);

int main()
{
    IplImage *Image1;
    IplImage *Image2;
    CvHistogram *Histogram1;
    CvHistogram *Histogram2;
    CvMat *LookupTableMatrix;
    IplImage *GrayHistogramImage;
    IplImage *EqualizeHistogramImage;


    Image1=cvLoadImage("DarkClouds.jpg",0);
    Image2=cvCreateImage(cvGetSize(Image1),IPL_DEPTH_8U,1);
    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    Histogram2 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);

    LookupTableMatrix = cvCreateMat(1,256,CV_8UC1);
    GrayHistogramImage=cvCreateImage(cvSize(256,250),IPL_DEPTH_8U,3);
    EqualizeHistogramImage=cvCreateImage(cvSize(256,250),IPL_DEPTH_8U,3);

    GrayHistogramImage->origin=1;
    EqualizeHistogramImage->origin=1;

    cvCalcHist(&Image1,Histogram1);
    DrawHistogramImage(Histogram1,GrayHistogramImage,HistogramBins);

    //Probability Density Function (PDF)
    cvNormalizeHist(Histogram1,1);
    //End

    //Cumulative Distribution Function (CDF)
    CumulativeNumber=0;
    for(int i=0;i<HistogramBins;i++)
    {
        CumulativeNumber=CumulativeNumber+cvQueryHistValue_1D(Histogram1,i);
        CDFArray[i]=CumulativeNumber;
    }
    //End

    //Make Look-up Table
    printf("Look-up Table Number:\n");
    for(int i=0;i<HistogramBins;i++)
    {
        LookupTableData[i]=(uchar)(255*CDFArray[i]);
        printf("%f\n",(255*CDFArray[i]));
    }
    //End

    cvSetData(LookupTableMatrix,LookupTableData,CV_AUTOSTEP);
    cvLUT(Image1,Image2,LookupTableMatrix);

    cvCalcHist(&Image2,Histogram2);
    DrawHistogramImage(Histogram2,EqualizeHistogramImage,HistogramBins);

    cvNamedWindow("DarkClouds",1);
    cvNamedWindow("Equalize DarkClouds",1);
    cvNamedWindow("Gray Histogram",1);
    cvNamedWindow("Equalize Histogram",1);
    cvShowImage("DarkClouds",Image1);
    cvShowImage("Equalize DarkClouds",Image2);
    cvShowImage("Gray Histogram",GrayHistogramImage);
    cvShowImage("Equalize Histogram",EqualizeHistogramImage);

    cvWaitKey(0);

}

void DrawHistogramImage(CvHistogram *Histogram,IplImage *HistogramImage,int HistogramBins)
{
    CvPoint Point1;
    CvPoint Point2;

    for(int i=0;i<HistogramBins;i++)
    {
        Point1=cvPoint(i,(int)(cvQueryHistValue_1D(Histogram,i)/20));
        Point2=cvPoint(i,0);
        cvLine(HistogramImage,Point1,Point2,CV_RGB(127,127,127));

    }
}

執行結果:


前面資料結構初始化的部份,分別製造了兩個IplImage圖形資料結構,兩個CvHistogram直方圖資料結構,一個Look-up table對應矩陣結構,兩個直方圖圖形輸出的資料結構,並且將它做基本的設定,在用cvCalcHist()放入圖形的資料,繪製出灰階直方圖的圖形,再來,利用cvNormalizeHist()計算機率密度函數(PDF),機率密度函數有一條規則,所有機率的總和為1,因此,利用cvNormalizeHist()很快的就可以把機率密度函數算出,而在做累積分配直方圖,則是用for迴圈慢慢的累加,製造Look-up Table的時候,則是讓它乘以255並且給它用uchar型別轉換,將轉換後的結果存到CvMatrix資料結構裡,在用cvLUT()函式做對應,因此均化的灰階圖片就這樣被製造出來啦.

cvEqualizeHist()
將單通道8bits uchar型別的圖片做直方圖等化的演算法,輸入為單通道uchar型別的IplImage資料結構,輸出為直方圖等化後單通道uchar型別IplImage資料結構
cvEqualizeHist(輸入單通道uchar型別IplImage資料結構,輸出單通道uchar型別資料結構)



2008年11月1日 星期六

OpenCV統計應用-CvHistogram資料結構操作

CvHistogram資料結構有提供一些基本的功能,有許多可以不必去直接處理CvHistogram資料結構內部資料的函數,接著就修改前面灰階直方圖的部分做簡單操作的實作

不同範圍灰階直方圖
#include <cv.h>
#include <highgui.h>
#include <stdio.h>

int HistogramBins = 50;
int HistogramBinWidth;
float HistogramRange1[2]={0,255};
float *HistogramRange[1]={&HistogramRange1[0]};
float MinValue,MaxValue;
int MinIndex,MaxIndex;
int main()
{
    IplImage *Image1;
    CvHistogram *Histogram1;
    IplImage *HistogramImage1;
    IplImage *HistogramImage2;

    CvPoint Point1;
    CvPoint Point2;

    Image1=cvLoadImage("Riverbank.jpg",0);

    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(256,300),8,3);
    HistogramImage2 = cvCreateImage(cvSize(256,300),8,3);

    cvSetZero(HistogramImage1);
    HistogramImage1->origin=1;
    HistogramImage2->origin=1;
    HistogramBinWidth=256/HistogramBins;

    cvCalcHist(&Image1,Histogram1);
    cvNormalizeHist(Histogram1,5000);

    cvGetMinMaxHistValue(Histogram1,&MinValue,&MaxValue,&MinIndex,&MaxIndex);
    printf("Histogram1\n");
    printf("The Minimum value : %f\tIndex :%d\n",MinValue,MinIndex);
    printf("The Maximum value : %f\tIndex :%d\n",MaxValue,MaxIndex);

    for(int i=0;i<HistogramBins;i++)
    {

        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram1,i));

        cvRectangle(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }

    cvClearHist(Histogram1);

    HistogramRange1[0]=30;
    HistogramRange1[1]=255;
    HistogramRange[0]=&HistogramRange1[0];

    cvSetHistBinRanges(Histogram1,HistogramRange);
    cvCalcHist(&Image1,Histogram1);

    cvNormalizeHist(Histogram1,5000);

    cvGetMinMaxHistValue(Histogram1,&MinValue,&MaxValue,&MinIndex,&MaxIndex);
    printf("\nHistogram2\n");
    printf("The Minimum value : %f\tIndex :%d\n",MinValue,MinIndex);
    printf("The Maximum value : %f\tIndex :%d\n",MaxValue,MaxIndex);

    for(int i=0;i<HistogramBins;i++)
    {
        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram1,i));

        cvRectangle(HistogramImage2,Point1,Point2,CV_RGB(127,127,127));
    }

    cvReleaseHist(&Histogram1);
    cvNamedWindow("Histogram1",1);
    cvNamedWindow("Histogram2",1);
    cvNamedWindow("Riverbank",1);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Histogram1",HistogramImage1);
    cvShowImage("Histogram2",HistogramImage2);
    cvWaitKey(0);
}

執行結果:


這個執行出來得結果可以看得出來,直方圖的範圍改變之後,長方形的數據有向右平移的樣子,而在之前的前面的程式碼cvQueryHistValue_1D()所直接輸出的數據實在是過大了,在繪製直方圖的時候一定會超出座標軸的範圍,所以就在前面的程式碼以全部除以50來代替,這邊,改用另外一個解決的方法,cvNormalizeHist(),這個函數是將直方圖所有區塊加起來會等於5000,也就是直方圖數據的總和會等於5000,因此,用這種量化的方式可以避免直方圖因為數據過大而圖形繪不出來的問題,近而縮小直方圖所有的數據,當然,這樣的結果直方圖數據會變成相對的,而cvNormalizeHist()內部則是使用到了cvScale()這個函式來計算,cvGetMinMaxHistValue(),則是找出直方圖的最大值跟最小值的方法,跟前面的最大最小值不太一樣的地方,這邊找出了直方圖的最大值將會代表的是眾數,在統計上是很具意義的東西,cvGetMinMaxHistValue()同樣內部使用的是cvMinMaxLoc()的函式.再來的部分就是用cvClearHist()清除直方圖資料,從新設定直方圖的範圍為30~255,使用cvSetHistBinRanges()的函數,最後再用cvReleaseHist()釋放CvHistogram的記憶體


接下來的這個的結果也是有趣,它可以將直方圖較小的區塊去除掉,這個函式在可以用來製作去除最小面積的圖形區塊,當然在這前面就必須要建立一些相關的演算法來建構直方圖,這邊就簡單介紹cvThreshHist()的操作

去除較小直方圖區塊
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int HistogramBins = 50;
int HistogramBinWidth;
float HistogramRange1[2]={0,255};
float *HistogramRange[1]={&HistogramRange1[0]};
float MinValue,MaxValue;
int MinIndex,MaxIndex;
int main()
{
    IplImage *Image1;
    CvHistogram *Histogram1;
    CvHistogram *Histogram2;
    IplImage *HistogramImage1;
    IplImage *HistogramImage2;

    CvPoint Point1;
    CvPoint Point2;

    Image1=cvLoadImage("Riverbank.jpg",0);

    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(256,300),8,3);
    HistogramImage2 = cvCreateImage(cvSize(256,300),8,3);

    cvSetZero(HistogramImage1);
    HistogramImage1->origin=1;
    HistogramImage2->origin=1;
    HistogramBinWidth=256/HistogramBins;

    cvCalcHist(&Image1,Histogram1);
    cvNormalizeHist(Histogram1,5000);

    for(int i=0;i<HistogramBins;i++)
    {

        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram1,i));

        cvRectangle(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }

    Histogram2 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    cvCopyHist(Histogram1,&Histogram2);
    cvThreshHist(Histogram2,50);

    for(int i=0;i<HistogramBins;i++)
    {
        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram2,i));

        cvRectangle(HistogramImage2,Point1,Point2,CV_RGB(127,127,127));
    }

    cvReleaseHist(&Histogram1);
    cvReleaseHist(&Histogram2);
    cvNamedWindow("Histogram1",1);
    cvNamedWindow("Histogram2",1);
    cvNamedWindow("Riverbank",1);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Histogram1",HistogramImage1);
    cvShowImage("Histogram2",HistogramImage2);
    cvWaitKey(0);
}

執行結果:


在這邊同樣建立了一個50個區塊,範圍為0~255的直方圖,這裡開啟了兩個CvHistogram的資料結構,並且初始化用cvCopyHist()複製直方圖裡面的資料,而第二個直方圖資料結構Histogram2則是給定一個區塊門檻值,也就是當小於50的區塊數據就會被剃掉,因此圖形顯示的結果,小於50數據的直方圖都被刪除了.

cvNormalizeHist()
將所有直方圖的數據標準化,也就是將所有數據總和為指定的一個數,第一個引數為輸入CvHistogram資料結構,第二個引數為輸入double型別指定總和數據,cvNormalizeHist()內部使用cvScale()函式
cvNormalizeHist(輸入CvHistogram資料結構,輸入double型別總和數據)

cvGetMinMaxHistValue()
取得直方圖的最大最小值的數據,以及它得位置(Index),對於灰階統計直方圖,可以知道是哪個色彩數據擁有最大值,第一個引數為輸入CvHistogram資料結構,第二個引數為輸出float型別最小值數據,第三個引數為輸出float型別最大值數據,第四個引數為輸出int型別最小值的陣列索引(Index),第五個引數為輸出int型別最大值的陣列索引(Index)
cvGetMinMaxHistValue(輸入CvHistogram資料結構,輸出float最小值數據,輸出float最大值數據,輸出int最小值索引,輸出int最大值索引)

cvClearHist()
清除目標CvHistogram直方圖資料結構內的資料.
cvClearHist(目標CvHistogram資料結構)

cvSetHistBinRanges()
從新設立直方圖的數據範圍,也就是要挑選的直方圖最小值下限以及最大值上限,第一個引數為輸入CvHistogram資料結構,第二個引數為輸入float型別的範圍二維陣列
cvSetHistBinRanges(輸入CvHistogram資料結構,輸入float型別範圍二維陣列)

cvReleaseHist()
釋放CvHistogram直方圖資料結構記憶體位址
cvReleaseHist(目標CvHistogram直方圖資料結構)

cvCopyHist()
複製目標CvHistogram資料結構,包括設定直以及直方圖數據資料,第一個引數為輸入目標CvHistogram資料結構直方圖,第二個引數為輸出CvHistogram資料結構直方圖
cvCopyHist(輸入CvHistogram資料結構,輸出要被複製的CvHistogram資料結構)

cvThreshHist()
去除小於目標數據的直方圖區塊,第一個引數為輸入CvHistogram直方圖資料結構,第二個引數為輸入double型別要被刪除的直方圖區塊數據大小
cvThreshHist(輸入CvHistogram資料結構,輸入小於門檻值double型別的數據)



2008年10月23日 星期四

OpenCV 1.1

新版的OpenCV 1.1 ,在2008年10月15號發佈,基本上只是優化前一版的內容,刪除了cvcam函式庫,並且提供了一本歐萊里的工具書
http://sourceforge.net/project/showfiles.php?group_id=22870


Notes:

This is OpenCV 1.1pre1, the first intermediate update after 1.0.It features better stereo support, SURF features, better performance of some functions, DirectShow support in highgui etc. It also includes many bug-fixes.

Changes:

>>> New functionality/features: <<<

    - General:

        * Octave bindings have been added. See interfaces/swig/octave
         (for now, Linux only)

        * [Windows] OpenCV is now built with VS2005 with SSE2 and OpenMP support included
         (if you want to rebuild OpenCV using Express or Standard Edition of VS, use
         _make\opencv.vs2005.no_openmp.sln).

        * [Windows] Python bindings have been updated to use Python 2.6

        * [Windows] cvcam has been removed (as videoInput is now supported by highgui)

    - CXCORE, CV, CVAUX:

        * Speeded-up Robust Features (SURF), contributed by Liu Liu.
         see samples/c/find_obj.cpp and the documentation opencvref_cv.htm

        * Many improvements in camera calibration:
            - Added stereo camera calibration: cvStereoCalibrate, cvStereoRectify etc.
            - Single camera calibration now uses Levenberg-Marquardt method and supports extra flags to
             switch on/off optimization of individual camera parameters
            - The optional 3rd radial distortion parameter (k3*r^6) is now supported in every calibration-related
             function

        * 2 stereo correspondence algorithms:
            - very fast block matching method by Kurt Konolige
             (processes the Tsukuba stereo pair in <10ms on Core2Duo laptop)
            - slow but more accurate graph-cut based algorithm by Kolmogorov and Zabin

        * Better homography estimation algorithms (RANSAC and LMEDs)

        * new C++ template image classes contributed by Daniel Filip (Google inc.). see
            opencv/cxcore/include/cvwimage.h

        * Fast approximate nearest neighbor search (by Xavier Delacour)

        * Codebook method for background/foreground segmentation (by Gary Bradski)

        * Sort function (contributed by Shiqi Yu)

        * [OpenCV+IPP] Face Detection (cvHaarDetectObjects) now runs much faster (up to 2x faster) when
            using IPP 5.3 or higher.

        * Much faster (~4x faster) fixed-point variant of cvRemap has been added

    - MLL:

        * Python bindings for MLL have been added. There are no samples yet.

    - HighGUI:

        * [Windows, 32bit] Added support for videoInput library. Hence, cvcam is [almost] not needed anymore

        * [Windows, 32bit] FFMPEG can now be used for video decoding/encoding via ffopencv*.dll

        * [Linux] Added unicap support

        * Improved internal video capturing and video encoding APIs

    - Documentation:

        * OpenCV book has been published (sold separately :) see docs/index.htm)

        - New samples (opencv/samples):

        * Many Octave samples

        * find_obj.cpp (SURF), bgfg_codebook.cpp (Codebook BG/FG segmentation), stereo_calib.cpp
            (Stereo calibration and stereo correspondence)

>>> Bug fixes: <<<

    Many thanks to everybody who submitted bug reports and/or provided the patches!

    * added dma_unlisten to icvCloseCAM_DC1394 (thanks to Victor Benso)

    * LMEDs algorithm for cvFindFundamentalMat estimation has been fixed

    * Broken debug build of highgui in VS2005/2008 (SF #2056185, SF #1709435)

    * cvFindChessboardCorners memory leak and incorrect size checks
    (SF #1972896, SF #1910323)

    * disabling GTK causes v4l runtime error (SF #2088102)

    * cvsetmousecallback bug (SF #2053529)

    * libhighgui needed deprecated "img_convert" replacement (SF #2011753)

    * Segfault in createsamples caused by uninitialized variable (SF #1977989)

    * Data Alignment Issue in bgfg_gaussmix (SF #1961755)

    * libpng need to be updated (SF #1952793)

    * cvCreateVideoWriter_Win32 - identifier not found (SF #1944254)

    * Bug in cvmorph.cpp (SF #1908844)

    * dilate (cvDilate) works bogus with default kernel (SF #1887130)

    * CvEM non-default constructor declared but not defined (SF #1830346)

    * cvFloodFill (in ver 1.0) Hangs (SF #1790565)

    * double delete in CvImage (SF #1733437)

    * cvFilter2D anchor default value is not working properly (SF #1713779)

    * cvCvtColor - Bug? in converting HSV2RGB (SF #1676344)

    * Invalid selection of the MKL-dll version in cvUseOptimized() (SF #1633017)


>>> Known issues:

    * Borland compiler is not supported (but might work) in this update.

    * 64-bit Windows is not supported (but might work) in this update.

    * SF bug tracker still contains over 50 records of open bugs. Many of them will be addressed in the next
     update.




2008年10月19日 星期日

OpenCV統計應用-CvHistogram直方圖資料結構

CvHistogram可以提供直方圖的計算,並且可以支援多個維度的直方圖設計,但是在繪製直方圖圖形的時候就需要自己用繪圖函式來繪製,CvHistogram算是一個比較複雜的資料結構,由於它在維度小於二的可以用密集的資料結構,CvMatND來設計,可以當它的直方圖維度大於二的時候,就必須要用到稀疏矩陣,CvSparseMat的方式,下面就是用CvHistogram結構計算一維直方圖的例子

CvHistogram一維設計
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int HistogramBins = 256;
float HistogramRange1[2]={0,255};
float *HistogramRange[1]={&HistogramRange1[0]};

int main()
{
    IplImage *Image1;
    CvHistogram *Histogram1;
    IplImage *HistogramImage1;

    Image1=cvLoadImage("Riverbank.jpg",0);

    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(256,300),8,3);
    HistogramImage1->origin=1;

    cvCalcHist(&Image1,Histogram1);

    printf("type is : %d\n",Histogram1->type);
    printf("Low Bound is : %.f\n",Histogram1->thresh[0][0]);
    printf("Up Bound is : %.f\n",Histogram1->thresh[0][1]);
    printf("The Bins are : %d\n",((CvMatND *) Histogram1->bins)->dim[0].size);

    printf("\nGray Level Values:\n");
    for(int i=0;i<HistogramBins;i++)
    {
        printf("%.f \n",((CvMatND *) Histogram1->bins)->data.fl[i]);
        cvLine(HistogramImage1,cvPoint(i,0),cvPoint(i,(int)(cvQueryHistValue_1D(Histogram1,i)/10)),CV_RGB(127,127,127));
    }

    cvNamedWindow("Histogram",1);
    cvNamedWindow("Riverbank",1);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Histogram",HistogramImage1);
    cvWaitKey(0);
}

原始圖片:


執行結果:


跟前面直接用矩陣累加設計出的統計直方圖一樣,到最後還是要自己用繪圖函式自己畫,CvHistogram這個資料結構的特色,它可以設定要用幾個區塊來呈現,這邊設立為256個區塊,而實際上,它可以用比較模糊的方式設定直方圖的區域,也就是可以小於256個的區塊數,是利用切割成n等分的方式,然後它可以自行設定上界(Up Bound)以及下界(Low Bound),在灰階圖裡面數據的範圍為0~255,而CvHistogram資料結構可以設立為上界30,下界200這樣的方式縮小範圍,而0~29,201~266這範圍內的數據將不會被計算,下面是CvHistogram資料結構的內容,分別為CV_HIST_ARRAY及CV_HIST_SPARSE兩種

1.


2.


CvHistogram的type固定都為CV_HIST_MAGIC_VAL這的參數,與上面程式cvCreateHist()所設定的CV_HIST_ARRAY無關,這也許是OpenCV裡面對於CvHistogram這個結構設計不良的地方,而對於CvHistogram資料結構的參數,定義如下

#define CV_HIST_ARRAY 0
#define CV_HIST_SPARSE 1
#define CV_HIST_TREE CV_HIST_SPARSE


因此,它的參數只有CV_HIST_ARRAY的多維矩陣CvMatND以及CV_HIST_SPARSE的稀疏矩陣CvSparseMat資料結構,而它的thresh是放上下界的資料,thresh2是放動態的維度上下界資料,而一般的直方圖數據資料都是放在bins裡面.而CvHistogram內的CvMatND結構則是在做快速初始化,bins以及mat都是使用同一個記憶體空間,而將直方圖資料的提取就要用到cvQueryHistValue_1D()這個函式了.

接著是將直方圖的空間分區塊的方式實作,將它分為50塊,並且上界為30,下界為200

CvHistogram結構區塊與上下界
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int HistogramBins = 50;
int HistogramBinWidth;
float HistogramRange1[2]={30,200};
float *HistogramRange[1]={&HistogramRange1[0]};

int main()
{
    IplImage *Image1;
    CvHistogram *Histogram1;
    IplImage *HistogramImage1;
    CvPoint Point1;
    CvPoint Point2;

    Image1=cvLoadImage("Riverbank.jpg",0);

    Histogram1 = cvCreateHist(1,&HistogramBins,CV_HIST_ARRAY,HistogramRange);
    HistogramImage1 = cvCreateImage(cvSize(256,300),8,3);
    cvSetZero(HistogramImage1);
    HistogramImage1->origin=1;
    HistogramBinWidth=256/HistogramBins;
    printf("The Bolck Width is : %d\n",HistogramBinWidth);
    cvCalcHist(&Image1,Histogram1);

    printf("Gray Level Values:\n");
    for(int i=0;i<HistogramBins;i++)
    {
        printf("%.f \n",((CvMatND *) Histogram1->bins)->data.fl[i]);
        Point1=cvPoint(i*HistogramBinWidth,0);
        Point2=cvPoint((i+1)*HistogramBinWidth,(int)cvQueryHistValue_1D(Histogram1,i)/50);

        cvRectangle(HistogramImage1,Point1,Point2,CV_RGB(127,127,127));
    }

    cvNamedWindow("Histogram",1);
    cvNamedWindow("Riverbank",1);
    cvShowImage("Riverbank",Image1);
    cvShowImage("Histogram",HistogramImage1);
    cvWaitKey(0);
}

執行結果:


因此,這邊就用cvRectangle()的方式來表達,對於CvHistogram這個資料結構,為什麼可以任意定義上下界以及區塊的個數呢?因為CvHistogram使用的是Look-up table(LUT)的方式,也就是查表法,開一個256大小空間的陣列,利用比例的縮放,縮放的數據存入Look-up table裡面,在利用索引的方式對應,而使用Look-up table最大的缺點為,無法取得很精確的數據,由於它是被比例縮放過的,因此除了範圍為256的可以求的完整的數值,LUT的用法會在後面使用到.

對於其他維度的直方圖,它的做法如下

三維直方圖擷取
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int Histogram3DBins[3] = {256,256,256};
float HistogramRange1[6]={0,255,0,255,0,255};
float *HistogramRange[3]={&HistogramRange1[0],&HistogramRange1[2],&HistogramRange1[4]};

void Print3DHistogram(CvHistogram *Histogram,int BinSize);

int main()
{
    CvHistogram *Histogram1;
    IplImage *Image1=cvLoadImage("Riverbank.jpg",1);
    IplImage *RedImage=cvCreateImage(cvGetSize(Image1),8,1);
    IplImage *GreenImage=cvCreateImage(cvGetSize(Image1),8,1);
    IplImage *BlueImage=cvCreateImage(cvGetSize(Image1),8,1);
    IplImage *ImageArray[3]={RedImage,GreenImage,BlueImage};

    cvSplit(Image1,BlueImage,GreenImage,RedImage,0);
    Histogram1 = cvCreateHist(3,Histogram3DBins,CV_HIST_SPARSE,HistogramRange);

    cvCalcHist(ImageArray,Histogram1);

    printf("3D Historgram Data\n");
    Print3DHistogram(Histogram1,256);

    cvNamedWindow("Riverbank",1);
    cvShowImage("Riverbank",Image1);

    cvWaitKey(0);
}

void Print3DHistogram(CvHistogram *Histogram,int BinSize)
{
    for(int i=0;i<BinSize;i++)
    {
        for(int j=0;j<BinSize;j++)
        {
            for(int k=0;k<BinSize;k++)
            {
                if(cvQueryHistValue_3D(Histogram,i,j,k)>10)
                {
                    printf("%.f\n",cvQueryHistValue_3D(Histogram,i,j,k));
                }
            }
        }
    }
}

執行結果:


上面是三個維度的統計直方圖,分別為R維度,G維度,B維度,並且分別被量化成256等分的區塊,同樣的,也是使用LUT的方法,由於這個統計直方圖非常的大,它所佔的區域為256*256*256的大小,而實際上累積的分佈只有一點點,所以說,對於高維度的統計直方圖就要用到稀疏矩陣,要不然會浪費大量的記憶體空間,但是對於多維度的圖表呈現,OpenCV最多也只能支援到一維的方式,二維的方式可以用OpenGL(glut)呈現,當然高的維度本來就很難用視覺化的方式呈現.

cvCreateHist()
初始化CvHistogram資料結構,可以選擇密集矩陣(CvMatND)CV_HIST_ARRAY以及稀疏矩陣(CvSparseMat)CV_HIST_SPARSE,第一個引數為維度的選擇,第二個引數為要將直方圖切割為多少區塊,第三個引數為選擇cvCreateHist()函數的參數或代號,第四個引數為每一個維度的上下界
cvCreateHist(輸入int型別直方圖維度,輸入int型別直方圖區塊數,輸入參數或代號,輸入每個維度的上下界數據)

cvCalcHist()
計算直方圖累積數據,第一個引數為輸入目標IplImage圖形陣列資料結構,第二個引數輸出為CvHistogram資料結構
cvCalcHist(輸入目標IplImage圖形陣列資料結構,輸出為CvHistogram資料結構)

cvQueryHistValue_1D()
讀取CvHistogram資料結構一維空間的圖形直方圖資料,它在OpenCV的"cvcompat.h"函式庫被定義為

#define cvQueryHistValue_1D( hist, idx0 ) ((float)cvGetReal1D( (hist)->bins, (idx0)))

因此回傳的是float型別的資料,輸入CvHistogram資料結構,以及輸入一維資料結構的Index索引數據
cvQueryHistValue_1D(輸入CvHistogram資料結構,輸入一維int型別數據索引)

cvQueryHistValue_2D()
讀取CvHistogram資料結構二維空間的圖形直方圖資料,它在OpenCV的"cvcompat.h"函式庫被定義為

#define cvQueryHistValue_2D( hist, idx0, idx1 ) ((float)cvGetReal2D( (hist)->bins, (idx0), (idx1)))

回傳float型別的資料,第一個引數為輸入CvHistogram資料結構,第二個引數為輸入二維資料結構的x軸索引數據,第三個引數為y軸索引數據
cvQueryHistValue_2D(輸入CvHistogram資料結構,輸入二維int型別x軸數據索引,輸入二維int型別y軸數據索引)

cvQueryHistValue_3D()
讀取CvHistogram資料結構三維空間的圖形直方圖資料,它在OpenCV的"cvcompat.h"函式庫被定義為

#define cvQueryHistValue_3D( hist, idx0, idx1, idx2 ) ((float)cvGetReal3D( (hist)->bins, (idx0), (idx1), (idx2)))

回傳float型別的資料,第一個引數為輸入CvHistogram資料結構,第二個引數為輸入三維資料結構的x軸索引數據,第三個引數為輸入y軸索引數據,第四個引數為輸入z軸索引數據
cvQueryHistValue_3D(輸入CvHistogram資料結構,輸入三維int型別x軸數據索引,輸入三維int型別y軸數據索引,輸入三維int型別z軸數據索引)



2008年10月11日 星期六

OpenCV統計應用-極端值,cvReduce

在統計模型裡面,總是會有一些特別高或特別低的數值,這個數值已經脫離了統計分配的假設,不符合統計的模型,通常會有這種極大或是極小的極端值(outlier)有可能是因為量測誤差,或是在統計模型中那0.0001的機率發生,因此,在統計學裡,挑出極端值來做最精簡的量測也是很重要的,就好比一群低收入戶的人裡面出現了一位身價上億的人而拉高了一群人的平均所得,使得低收入戶的族群變成中高收入的族群,這邊要抓取那個最大最小值就用到了cvMinMaxLoc()的函式啦.在圖片裡面,也許很難有如此極端的事件產生,cvMinMaxLoc()可以處理一般的數據,亦可以處理圖片找出它最大最小值以及它的位置.

cvMinMaxLoc()的實作
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int main()
{
    IplImage *Image1=cvLoadImage("grotto.jpg",1);

    double MinValue;
    double MaxValue;

    CvPoint MinLocation;
    CvPoint MaxLocation;

    cvSetImageCOI(Image1,1);
    cvMinMaxLoc(Image1,&MinValue,&MaxValue,&MinLocation,&MaxLocation);

    printf("The Min number is : %.f\n",MinValue);
    printf("The position is : ( %d , %d )\n",MinLocation.x,MinLocation.y);
    printf("The Max number is : %.f\n",MaxValue);
    printf("The position is : (%d , %d )\n",MaxLocation.x,MaxLocation.y);

    cvNamedWindow("grotto",1);
    cvShowImage("grotto",Image1);
    cvWaitKey(0);
}

執行結果:


上面的結果是抓出這張圖片最大最小值的數據,選擇綠色這個通道,雖然這已經不算是極端值的意義了,不過它仍然是可以對一般圖形做處理,cvMinMaxLoc()可以同時找出最大最小值,也可以指出最大值的位置跟最小值的位置,而cvMinMaxLoc()必須對單通道做處理因此必須要用,cvSetImageCOI選定顏色,也可以支援ROI,甚至,cvMinMaxLoc()可以用遮罩的方式實作,使用的方法如下

#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int main()
{
    IplImage *Image1=cvLoadImage("grotto.jpg",1);
    IplImage *MaskImage1=cvLoadImage("grotto_Threshold.bmp",0);
    double MinValue;
    double MaxValue;

    CvPoint MinLocation;
    CvPoint MaxLocation;

    cvSetImageCOI(Image1,1);
    cvMinMaxLoc(Image1,&MinValue,&MaxValue,&MinLocation,&MaxLocation,MaskImage1);

    printf("The Min number is : %.f\n",MinValue);
    printf("The position is : ( %d , %d )\n",MinLocation.x,MinLocation.y);
    printf("The Max number is : %.f\n",MaxValue);
    printf("The position is : (%d , %d )\n",MaxLocation.x,MaxLocation.y);

    cvNamedWindow("grotto",1);
    cvShowImage("grotto",Image1);
    cvWaitKey(0);
}

執行結果:


上面的程式也只對grotto_Threshold.bmp白色的部份做處理,從白色區域找出它的最大最小值以及它的位置,跟前面的程式差不多.

cvReduce()為將矩陣維度降低為向量的維度,也就勢將該行或該列做加總,平均,找出最大,最小值,cvReduce()可使用的參數或代號如下

#define CV_REDUCE_SUM 0
#define CV_REDUCE_AVG 1
#define CV_REDUCE_MAX 2
#define CV_REDUCE_MIN 3


而它的數據要從圖形縮成行或縮成列,則輸入的向量就必須要跟它的行或列相等長度,而且是一維的形式,下面這個是求出每一列的RGB值的平均數,並且用圖表的方式

cvReduce()平均數向量
#include <cv.h>
#include <highgui.h>
#include <stdio.h>


int main()
{
    IplImage *Image1=cvLoadImage("grotto.jpg",1);
    IplImage *AVGImage=cvCreateImage(cvSize(Image1->width,255),IPL_DEPTH_8U,3);
    CvMat *AVGVector=cvCreateMat(1,Image1->width,CV_32FC3);

    cvReduce(Image1,AVGVector,0,CV_REDUCE_AVG);


    for(int i=0;i<Image1->width;i++)
    {
        printf("%f\t%f\t%f\t\n",cvGet1D(AVGVector,i).val[0],cvGet1D(SumVector,i).val[1],cvGet1D(SumVector,i).val[2]);
        if(i!=Image1->width-1)
        {
            cvLine(AVGImage,cvPoint(i,(int)cvGet1D(AVGVector,i).val[0]),cvPoint(i+1,(int)cvGet1D(AVGVector,i+1).val[0]),CV_RGB(0,0,255));
            cvLine(AVGImage,cvPoint(i,(int)cvGet1D(AVGVector,i).val[1]),cvPoint(i+1,(int)cvGet1D(AVGVector,i+1).val[1]),CV_RGB(0,255,0));
            cvLine(AVGImage,cvPoint(i,(int)cvGet1D(AVGVector,i).val[2]),cvPoint(i+1,(int)cvGet1D(AVGVector,i+1).val[2]),CV_RGB(255,0,0));
        }

    }

    cvNamedWindow("grotto",1);
    cvShowImage("grotto",Image1);
    cvNamedWindow("AVG Image",1);
    cvShowImage("AVG Image",AVGImage);

    cvWaitKey(0);
}

執行結果:



cvReduce()可以支援多通道,而AVGVector則是通道三的向量,然後圖表的呈現以cvLine()實作,cvReduce()第一個引數為目標要被統計的圖片,第二個引數為要被計算出來的向量,而第二個引數有一些特殊的規定,它的使用條件如下

1.通道數必須與輸入圖片的數目相同
2.所需要計算的列或欄的長度必須要與輸入圖片
3.一定要用浮點數型別來輸入,如CV_32FC3,CV64FC3,CV_32FC1等,由於uchar型別在做像是CV_REDUCE_SUM時數據一定會超過,因此cvReduce()會做檢查驗證

而且而CV_REDUCE_SUM則是在二值化的圖片的計算比較具有意義,它可以求出二值化圖片的投影向量,並且以圖表的方式呈現.下面就是以列投影以及欄投影的方式來做圖表的呈現.

二值化圖形投影計算
#include <cv.h>
#include <highgui.h>
#include <stdio.h>

int main()
{
    IplImage *Image1=cvLoadImage("grotto_Threshold.bmp",0);
    IplImage *RowProjectionImage=cvCreateImage(cvSize(Image1->width,300),IPL_DEPTH_8U,3);
    IplImage *ColumnProjectionImage=cvCreateImage(cvSize(300,Image1->height),IPL_DEPTH_8U,3);
    CvMat *RowSumVector=cvCreateMat(1,Image1->width,CV_32FC1);
    CvMat *ColumnSumVector=cvCreateMat(Image1->height,1,CV_32FC1);

    RowProjectionImage->origin=1;
    cvReduce(Image1,RowSumVector,0,CV_REDUCE_SUM);
    cvReduce(Image1,ColumnSumVector,1,CV_REDUCE_SUM);

    for(int i=0;i<Image1->width;i++)
    {
        cvLine(RowProjectionImage,cvPoint(i,(int)cvGet1D(RowSumVector,i).val[0]/(2*255)),cvPoint(i,0),CV_RGB(255,255,255));
    }
    for(int i=0;i<Image1->height;i++)
    {
        cvLine(ColumnProjectionImage,cvPoint((int)cvGet1D(ColumnSumVector,i).val[0]/(2*255),i),cvPoint(0,i),CV_RGB(255,255,255));
    }

    cvNamedWindow("grotto Threshold",1);
    cvShowImage("grotto Threshold",Image1);

    cvNamedWindow("Row Projection Image",1);
    cvShowImage("Row Projection Image",RowProjectionImage);

    cvNamedWindow("Column Projection Image",1);
    cvShowImage("Column Projection Image",ColumnProjectionImage);
    cvWaitKey(0);
}

執行結果:


上面的程式,由於是二值化的圖片,只有0跟255,所以必須要將累加的數值除以255才能知道它所累積的個數,在將該數目除以二平移,使他在圖表呈現上不會太大,對於投影的實作,可以藉由選取較高的區間,提出二值化裡面有意義的資料區塊,在人臉辨識可以藉由這種方法提取特定臉部器官的特徵.

cvMinMaxLoc()
找出圖片或一組數據中最大值及最小值的數據,以及最大值及最小值的位置,第一個引數為輸入IplImage資料結構或CvMat資料結構,第二個引數為輸出最小值double型別數據,第三個引數為輸出最大值double型別數據,第四個引數為輸出最小值位置CvPoint資料結構,第五個引數為輸出最大值位置CvPoint資料結構.
cvMinMaxLoc(輸入IplImage或CvMat資料結構,輸出double型別最小值數據,輸出double型別最大值數據,輸出最小值CvPoint資料結構,輸出最大值CvPoint資料結構)

cvReduce()
將二維圖形縮減成列向量或欄向量,cvReduce()具有四個參數,分別為CV_REDUCE_SUM總和,CV_REDUCE_AVG平均,CV_REDUCE_MAX最大值,CV_REDUCE_MIN最小值,可以由多通道的輸入,但輸出的行向量或欄向量一定要符合圖形的長度.第一個引數為輸入目標IplImage資料結構或CvMat資料結構,第二個引數為輸出目標CvMat結構向量,第三個引數為縮減為列向量或欄向量,0為欄向量,1為列向量,第四個引數為cvReduce()的參數或代號.
cvReduce(輸入IplImage或CvMat資料結構,輸出CvMat向量資料結構,輸入縮減欄向量或列向量代號,目標參數或代號)



Copyright 2008-2009,yester