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資料結構)



1 意見:

softboy 提到...

其實cvCalcBackProject所做出的圖形中每個像素值就是代表 "出現的機率"。當一個Histogram是根據目標物所做出時,則被反投影的圖中每一個像素值就變成了"相對於目標物中該像素值出現的機率",例如: 目標物的Histogram為241,127代表目標物中2有127個,因此被反投影的圖中2就對應到127,代表相對於目標物而言2的出現機率是127。最後,做完Mask(原始圖與被投影圖),我們即可將原始圖中跟目標物無關的像素(出現機率為0)排除.....

Copyright 2008-2009,yester