直方圖規定化

直方圖等化可以獲得具有均勻直方圖的輸出影像,主要用於增強影像對比度,操作簡單且結果可預知。但有時候使用者希望能控制轉換的方式,或者能夠指定直方圖的輸出影像,這樣就能選擇性的增強某個灰度範圍內的對比度,或者讓影像灰度滿足某種特定的分布,這種產生特定直方圖影像的方法就叫直方圖規定化。

直方圖規定化為產生特定的輸出直方圖,可以把直方圖等化看成是直方圖規定化的一種特例,只是將輸出直方圖定為一個常數,映射得到新的像素強度,以下為直方圖規定化的流程,將輸入圖的直方圖匹配成目標的直方圖:

  1. 將輸入圖進行直方圖等化,得到輸入圖的變換關係 s=f(r)。
  2. 將目標圖進行直方圖等化,得到目標的變換關係 v=g(z)。
  3. 計算反轉換函式 v=g-1(z)=g-1(s)=g-1(f(r))。
  4. 將輸入影像所有像素,進行上述流程3的變換,得到輸出圖像。

影像比對(matchTemplate、minMaxLoc)

OpenCV的compareHist()函式進行直方圖比較,可得到直方圖的相似程度,OpenCV提供更高階的matchTemplate()函式,計算想搜尋的影像(小圖),在原始影像(大圖)各個位置的直方圖比較值。

以下為matchTemplate()函式的介紹:

  1. 輸入兩張影像,分別是原始圖(大圖),和搜尋目標圖(小圖),我們的目標就是在原始圖,找到目標圖的位置。
  2. 不斷滑動目標圖,得到原始圖上各個位置的比較值,比較值代表重疊區域的相似程度,並將結果存到這時目標圖左上角的位置。
  3. 完成後可得到比較值的結果圖,可用minMaxLoc()函式,找出結果圖的最大或最小值,定位出搜尋位置。

我們可以用相同的概念,自行用compareHist()函式,得到影像各個位置的直方圖比對值,之後再找出最相近的位置,但搜尋的時間會增加很多,因此如果要用這概念進行搜索時,建議直接用matchTemplate()函式即可。


OpenCV影像比對

void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method)

  • image:輸入圖,必須為 8位元或浮點數圖。
  • templ:輸入的template,尺寸必須小於輸入圖,形態需相同。
  • result:比較結果圖,必須為單通道32位元浮點數圖,如果image的尺寸為W x H,templ的尺寸為w x h,則result的尺寸為(W-w+1)x(H-h+1)。
  • method:比較方法,有以下六種方法可選擇:

method=CV_TM_SQDIFF

enter image description here


method=CV_TM_SQDIFF_NORMED

enter image description here


method=CV_TM_CCORR

enter image description here


method=CV_TM_CCORR_NORMED

enter image description here


method=CV_TM_CCOEFF

enter image description here


method=CV_TM_CCOEFF_NORMED

enter image description here

當我們的參數為CV_TM_SQDIFF時,計算結果較小時相似度較高,當我們參數為CV_TM_CCORR、CV_TM_CCOEF時,計算結果較大時相似度較高。


OpenCV影像比對

當我們得到比較圖後,根據由比較方式,選擇比較圖最小或最大值的地方,就是目標影像的位置。

void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray())

  • src:輸入圖。
  • minVal:極小值,可輸入NULL表示不需要。
  • maxVal :極大值,可輸入NULL表示不需要。
  • minLoc:極小值的位置,可輸入NULL表示不需要。
  • maxLoc:極大值的位置,可輸入NULL表示不需要。
  • mask:可有可無的遮罩。

以下程式碼一開始輸入原始圖(大圖)和搜尋目標圖(小圖),呼叫matchTemplate()找出小圖在大圖影像上各個位置的比較值,接著呼叫minMaxLoc()找出最相似的位置並畫出:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

int main(){
    Mat src = imread("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    Mat roiImg = imread("temp.jpg", CV_LOAD_IMAGE_GRAYSCALE);
    Mat displayImg = src.clone();
    Mat result;
    result.create(src.rows-roiImg.rows+1, src.cols-roiImg.cols+1, CV_32FC1);

    matchTemplate(src, roiImg, result, CV_TM_SQDIFF);
    double minVal; 
    Point minLoc;
    minMaxLoc(result, &minVal, 0, &minLoc, 0);

    rectangle(displayImg, minLoc, Point(minLoc.x+roiImg.cols , minLoc.y+roiImg.rows), Scalar::all(0), 3);
    imshow("origin", src);
    imshow("roi", roiImg);
    imshow("result", displayImg);
    waitKey(0);

    return 0;
}

matchTemplate

matchTemplate

matchTemplate

繼續閱讀 影像比對(matchTemplate、minMaxLoc)

直方圖等化(equalizeHist)

我們可透過拉伸直方圖,使直方圖覆蓋所有強度範圍,這種方法的確能提高影像對比度,但是在多數情況,影像模糊不是因為過窄的強度範圍,而是某區間的像素強度比例過高,這時可以製作一個映射表,使得調整之後的影像,能平均使用所有的強度,進而增加影像的整體對比度。

這就是直方圖等化的概念,以一個8位元強度範圍0~255的影像來說,意味著調整之後的影像,50%的像素強度低於128,25%的像素強度低於64,其他強度比例依此類推。


OpenCV直方圖等化

void equalizeHist(InputArray src, OutputArray dst)

  • src:輸入圖,8位元單通道圖。
  • dst:輸出圖,和輸入圖尺寸、型態相同。

equalizeHist()函式可得到直方圖等化後的影像,以下為流程,OpenCV已將流程封裝好,使用時只要直接呼叫equalizeHist()函式即可:

  1. 計算輸入圖的直方圖。
  2. 將直方圖歸一到所有bin的總合為255。
  3. 計算直方圖累計表。
  4. 用直方圖累計表完成各強度的映射,所以假設強度30所累積的比例為20%,映射的強度即為255*0.2,由於我們直方圖歸一化到255,所以假設強度30所累積的值為20,映射的強度即為20。

以下程式碼使用直方圖等化,讓影像對比度增加:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

int main(){
    Mat src = imread("src.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    Mat dst;
    equalizeHist(src,dst);
    imshow("window1", src);
    imshow("window2", dst);
    waitKey(0);

    return 0;
}

hist image

hist image

繼續閱讀 直方圖等化(equalizeHist)

直方圖比較(compareHist)

直方圖是影像內容的一個重要特性,兩個直方圖的相似程度,也可以看成是兩張影像的相似程度,OpenCV用compareHist()函式進行直方圖比較,讓我們以量化的方式,得到兩個直方圖的相似程度,compareHist()函式根據輸入參數,有四種比較方式可以選擇。

大多數直方圖比較是逐個容器(bin)進行比較,並不會考慮相臨的情況,因此容易受到雜訊或光線的影響,比較前通常會先減色,類似將8位元每個bin儲存1個強度範圍,共256個bin的直方圖,改成每個bin儲存16個強度範圍,共16個bin的直方圖,再用更改後的直方圖比較相似程度。


OpenCV直方圖比較

double compareHist(InputArray H1, InputArray H2, int method)

  • H1:第一個直方圖。
  • H2:第二個直方圖,須和第一個直方圖尺寸相同。
  • method:比較方法,有以下四種方法可選擇,不論選擇何種,皆會返回一個比較值:

CV_COMP_CORREL:信號處理中的歸一化互相關方法,N為bin的總個數

enter image description here


CV_COMP_CHISQR:歸一化的平方和

enter image description here


CV_COMP_INTERSECT :比較每個直方圖bin的值,總和較小的那個,也就是假如兩個直方圖沒有共同的值,計算結果為0,完全相同的直方圖,返回值等於像素的個數。

enter image description here


CV_COMP_BHATTACHARYYA:統計學中用於評估兩個概率分布相似性

enter image description here

均值漂移(meanShift、CamShift)

均值漂移主要用在視覺跟蹤,作法為從反投影直方圖的概率圖,得到目標影像出現在原始影像各個位置的概率,假設我們已知物體的大概位置,從這最初的位置,迭代移動來得到最大概率的位置,就是目標影像的精確位置,所以一開始的大概位置會影響到計算結果和花費時間。

OpenCV提供meanShift()函式處理均值漂移,計算預定窗口的加權平均值,將窗口中心移動到數據點的重心處,並重複這個過程來鎖定局部最大值,直到窗口重心收斂到一個穩定點,meanShift()函式定義了兩種終止條件,分別是迭代次數以及窗口中心的位移量,這個終止條件保存在TermCriteria中。

OpenCV另外有CamShift算法,這是一個改良的均值漂移算法,會調整搜尋窗口的尺寸和方向角度。


OpenCV均值漂移

int meanShift(InputArray probImage, Rect& window, TermCriteria criteria)

  • probImage:輸入反投影圖。
  • window:一開始的搜尋窗口。
  • criteria:停止條件。
  • window最後變更為搜尋結果的位置,函式返回迭代次數。

TermCriteria(int type, int maxCount, double epsilon) //TermCriteria類別的建構式

  • type:停止型態,COUNT、EPS或COUNT + EPS。
  • maxCount:迭代次數的最大值。
  • epsilon:停止條件,迭代在多少的準確度下停止。

OpenCV均值漂移

RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)

  • probImage:輸入反投影圖。
  • window:一開始的搜尋窗口。
  • criteria:停止條件。
  • 函式返回搜尋結果的旋轉矩形。

直方圖反投影(calcBackProject)

直方圖本質上是一個統計圖,統計這個影像的強度分配情形,從這可以得到每個強度所佔全體的比例,也就是每個強度的發生機率。這也是直方圖反投影的概念,在影像檢索時,我們由已知ROI的直方圖,比較原始影像每個像素,看相對位置的強度屬於這個直方圖的機率有多少。


OpenCV反投影

void calcBackProject(const Mat* images, int nimages, const int* channels, InputArray hist, OutputArray backProject, const float**ranges, double scale=1, bool uniform=true)

  • images:輸入圖,可以一個或多個圖,深度必須為CV_8U或CV_32F,可為任意通道數,但是每張圖的尺寸和深度必須相同。
  • nimages:有幾張輸入圖。
  • channels:直方圖通道清單。
  • hist: 輸入比較的直方圖。
  • backProject:輸出的反投影結果圖,單通道且尺寸、深度都和images[0]相同。
  • ranges:直方圖的範圍,以8位元無負號的影像,就是[0,255]。
  • scale:縮放因子。
  • uniform:各維度取值是否一致。

關於calcBackProject()這個函式,輸出的反投影結果圖backProject是一幅影像,每個像素代表原本強度在直方圖的機率值,所以假如輸入的直方圖hist是歸一化的,生成的值會在0.0到1.0之間,我們可以將縮放因子scale設成255.0,如此一來可以當作一般的8位元圖秀出結果,愈大的值代表屬於這個直方圖的機會越高。

實際上做影像搜尋時,通常會考慮顏色訊息,畢竟單純強度的話資訊量不足,很難得到理想的結果,且直方圖反透影的結果,是得到影像各位置屬於此直方圖的概率,所以通常會搭配其他演算法使用。

直方圖(calcHist)

直方圖是一個影像像素的統計表,橫軸為影像中所有可能的像素值,假使為8位元圖,範圍即為0到255,縱軸為此橫軸強度的像素個數,直方圖可以被歸一化,歸一化後所有項和為一,在這種情況下,縱軸值表示此強度的像素佔影像的比例。

histogram

直方圖是影像的一個重要特性,我們可以從這看出強度分布狀況,像是否太暗或過曝,或者分布太過集中,進而評估影像的品質。在影像檢索時,直方圖可以當成是一段獨特的紋理,或者是獨特的物體,透過直方圖的比較協助我們搜尋物體。


OpenCV計算直方圖

OpenCV的calcHist()函式可得到一個影像的直方圖,為了使用上的彈性,參數有點複雜。

void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false)

  • images:輸入圖,可以一個或多個圖,深度必須為CV_8U或CV_32F,可為任意通道數,但是每張圖的尺寸和深度必須相同。
  • nimages:有幾張輸入圖。
  • channels:直方圖通道清單。
  • mask:可有可無的遮罩。
  • hist:輸出的直方圖
  • dims:直方圖維度,必須為正數且不能超過CV_MAX_DIMS(目前為32),假設為灰階圖的直方圖,每個像素只有強度資料,此時維度為1。
  • histSize:直方圖橫軸(也稱bin)數目。
  • ranges:直方圖的強度範圍,以8位元無負號的影像,就是[0,255]。
  • uniform:各維度取值是否一致。
  • accumulate:如果設定為true的話,在呼叫calcHist()這函式的時候,hist的內容不會被清掉,方便我們做多次的直方圖計算的累加。

繪製直方圖

OpenCV本身沒有提供繪製直方圖的函式,因此我們這邊自行撰寫drawHistImg()函式,輸入從得到的直方圖,輸出直方圖視覺化影像,流程如下:

  1. 找出輸入直方圖的最大值。
  2. 將最高點設定成輸出影像高度的0.9倍(也就是256*0.9)。
  3. 算出直方圖內各個像素強度,所對應的高度。
  4. 逐行畫出各強度的高度。

以下程式碼我們讀入一張圖,用calcHist()計算出直方圖,再用drawHistImg()將直方圖畫出:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

void drawHistImg(const Mat &src, Mat &dst);

int main(){
    Mat src = imread("lena.jpg",CV_LOAD_IMAGE_GRAYSCALE);
    int histSize = 256;
    float range[] = {0, 255} ;
    const float* histRange = {range};
    Mat histImg;
    calcHist(&src, 1, 0, Mat(), histImg, 1, &histSize, &histRange);

    Mat showHistImg(256,256,CV_8UC1,Scalar(255));  //把直方圖秀在一個256*256大的影像上
    drawHistImg(histImg, showHistImg);
    imshow("window1", src);
    imshow("window2", showHistImg);
    waitKey(0);

    return 0;
}

void drawHistImg(const Mat &src, Mat &dst){
    int histSize = 256;
    float histMaxValue = 0;
    for(int i=0; i<histSize; i++){
        float tempValue = src.at<float>(i);
        if(histMaxValue < tempValue){
            histMaxValue = tempValue;
        }
    }

    float scale = (0.9*256)/histMaxValue;
    for(int i=0; i<histSize; i++){
        int intensity = static_cast<int>(src.at<float>(i)*scale);
        line(dst,Point(i,255),Point(i,255-intensity),Scalar(0));
    }
}

hist image

hist image 繼續閱讀 直方圖(calcHist)