opencv——圖像直方圖與反向投影


引言

在圖像處理中,對於直方圖這個概念,肯定不會陌生。但是其原理真的可以信手拈來嗎?

本文篇幅有點長,在此列個目錄,大家可以跳着看:

  • 分析圖像直方圖的概念,以及opencv函數calcHist()對於RGB圖像的直方圖的繪制
  • 在其基礎上自已定義函數實現對灰度圖像直方圖的簡單繪制
  • 直方圖均衡化
  • 直方圖的反向投影

圖像直方圖分析以及opencv函數實現

(一)直方圖的介紹

直方圖到底可以干什么呢?我覺得最明顯的作用就是有利於很直觀的對圖像進行分析了,直方圖就像我們常用的統計圖,直方圖可以用來描述各種不同的事情,如物體的色彩分布、物體邊緣梯度模板,以及表示目標位置的概率分布。

 例如:我們統計了一個有11個學生的班級的身高和體重情況,身高為160cm的有5人,170cm的有4人,180cm的有2人。然后看體重,體重160斤的有3人,170斤的有5人,180斤的有3人。

用直方圖統計就是這樣:

 在opencv中,對於圖像的直方圖來說。對應上圖數據,也有三個參數:

  1. dims:需要統計的特征的數目。如上面例子里有身高和體重兩個特征。
  2. bins:每個特征空間子區段數目。如身高有160,170,180所以子區段數目為3。
  3. range:每個特征空間的取值范圍。例如:身高的取值范圍就是[160,180]

直方圖的意義:

        1. 直方圖是圖像中像素強度分布的圖形表達方式。 
        2. 直方圖統計了每一個強度值所具有的像素個數。

任一幅圖像,都能唯一地算出一幅與它對應的直方圖。但不同的圖像,可能有相同的直方圖。即圖像與直方圖之間是多對一的映射關系。

(二)直方圖API

直方圖是對數據的統計,並把統計值顯示到事先設定好的bin(如上表,設置160,170,180)中,bin中的數值是從數據中計算出的特征的統計量。總之,直方圖獲取的是數據分布的統計圖,通常直方圖的維數要低於原始數據。

在OpenCV中封裝了直方圖的計算函數calcHist,為了更為通用,該函數的參數有些復雜,其聲明如下:

 calcHist(
        const Mat*   images,    //輸入圖像的數組(CV_8U,CV_16U,CV_32F)
        int          nimages,   //輸入數組個數
        const int*   channels,  //通道索引,可以傳一個數組 {0, 1} 表示計算第0通道與第1通道的直方圖,此數組長度要與histsize,ranges 數組長度一致
        InputArray   mask;      //Mat(),  //不使用腌膜
        OutputArray  hist,      //輸出的目標直方圖,一個二維數組
        int       dims,      //需要計算的直方圖的維數  例如:灰度,R,G,B,H,S,V等數據
        congst int*  histSize,  // 在每一維上直方圖的個數。(簡單把直方圖看作一個一個的豎條的話,就是每一維上豎條的個數。)
        const float**    ranges, //每一維數組的取值范圍數組
        bool          uniform=true,   
        bool          accumulate = false
          );

opencv實現:

Mat src = imread("D:/opencv練習圖片/src1.jpg");
    imshow("原圖片", src);
    // //步驟一:分通道顯示
    vector<Mat> bgr_plane;
    split(src, bgr_plane);
    //split//把多通道圖像分為多個單通道圖像 (const Mat &src, //輸入圖像 Mat* mvbegin //輸出的通道圖像數組)

    //步驟二:計算直方圖
       // 定義參數變量
    const int channels[1] = { 0 };
    const int bins[1] = { 256 };
    float hranges[2] = { 0,255 };
    const float* ranges[1] = { hranges };
    Mat b_hist;
    Mat g_hist;
    Mat r_hist;
       // 計算Blue, Green, Red通道的直方圖
    calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
    calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
    calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
       // 顯示直方圖
    int hist_w = 512;
    int hist_h = 400;
    int bin_w = cvRound((double)hist_w / bins[0]);//直方圖的等級
    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
       // 歸一化直方圖數據(范圍在0-400)
    normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
    normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());

    //步驟三:繪制直方圖並顯示
    for (int i = 1; i < bins[0]; i++) {
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
        line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
    }
      // 顯示直方圖
    namedWindow("Histogram Demo", WINDOW_AUTOSIZE);
    imshow("Histogram Demo", histImage);


 自定義函數實現灰度圖像直方圖繪制

🙂在明白直方圖的原理以后,那我們可不可以自己構造函數去實現對灰度圖像的一維直方圖繪制呢?

函數實現思想:(思想很重要!!)

  1. 遍歷整幅圖像的像素點,統計灰度值0-256的像素點個數並存到數組img_num[]中
  2. 遍歷這個img_num[]數組,對灰度值進行歸一化,計算出的高度為各灰度值所占的比值
  3. 用畫直線函數進行繪制

opencv實現:

🧡主函數:

int main(int argc, char** argv)
{
    Mat histogram_draw(Mat img, int *img_num);
    Mat src = imread("D:/opencv練習圖片/直方圖.png");
    imshow("原圖片", src);
    cvtColor(src, src, COLOR_RGB2GRAY);
    int img_num[256] = { 0 };  //定義一個存放統計數據的一維數組(256位,每位初始化為0)
    Mat histogram; //定義直方圖
    histogram = histogram_draw(src, img_num);
    imshow("直方圖", histogram);
    waitKey(0);
    return 0;
}

💛自定義直方圖函數:

//自定義直方圖函數
  //img:需要計算的圖像
  //img_num[]:計算直方圖的特征空間子區段的數目
Mat histogram_draw(Mat img, int *img_num)
{
    int r = 200; //定義高
    int w = 1000; //定義寬
    Mat histogram = Mat(r, w, CV_8UC3); //直方圖畫布
    int row = img.rows;  //圖片的高度
    int col = img.cols;  //圖片的寬度
    for (int i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            int num = img.at<uchar>(i, j); //讀取圖片像素位置(i,j)處的灰度值
            img_num[num]++;  //將對應灰度值的個數加一(統計0-255的像素值的出現個數)        
        }
    }
    int all = row * col;
    for (int i = 0; i < 256; i++)  //對灰度值0-255循環處理
    {
        int hight = int(double(img_num[i]) / double(all)*r); //(出現個數與總個數的比值)*高[即各灰度值所占的比值的高度]
        //opencv圖像的像素坐標系原點在左上角
        Point ps(i * 4, r);
        Point pe(i * 4, r - hight*10);
        line(histogram, pe, ps, Scalar(0, 0, 255));
    }
    return histogram;
}


直方圖均衡化

直方圖均衡化是灰度變換的一個重要應用,廣泛應用於圖像增強處理中。它是通過拉伸像素強度分布范圍來增強圖像對比度的一種方法。

說得更清楚一些, 以直方圖為例, 你可以看到像素主要集中在中間的一些強度值上. 直方圖均衡化要做的就是 拉伸 這個范圍. 見下面左圖: 綠圈圈出了 少有像素分布其上的 強度值. 對其應用均衡化后, 得到了中間圖所示的直方圖。

直方圖均衡化的API非常簡單:(輸入圖像必須是單通道)

void equalizeHist(InputArray src, OutputArray dst)
//第一個參數,源圖像,需為8位單通道圖像
//第二個參數,輸出圖像,尺寸、類型和源圖像一致

 🤨opencv對於彩色(三通道)的圖像如何均衡化呢? 

 灰常簡單:先將三通道拆分為3個單通道split(),再分別對其均衡化,最后合並為三通道merge()

    Mat dst;
    Mat src = imread("D:/opencv練習圖片/直方圖.png");
    imshow("原圖片", src);
    //分割通道
    vector<Mat>channels;
    split(src, channels);
    Mat blue, green, red;
    blue = channels.at(0);
    green = channels.at(1);
    red = channels.at(2);
    //分別對BGR通道做直方圖均衡化
    equalizeHist(blue, blue);
    equalizeHist(green, green);
    equalizeHist(red, red);
    //合並通道
    merge(channels, dst);
    imshow("output", dst);


 直方圖的反向投影

直方圖反向投影用於圖像分割或在圖像中用直方圖查找感興趣的對象。

直方圖在一定程度上可以反應圖像的特征,我們截取一個有固定特征的樣本圖,比如草地,然后計算該塊草地的直方圖,然后用這個直方圖去和整幅圖像的直方圖做對比,根據一定的判斷條件,就能得出相似的即為草地。

是不是看起來像是語義分割?

其實一定意義上這就是語義分割,這不過直方圖反向分割的依據是人為計算的(直方圖),后者分割的依據是靠在神經網絡中學習得來的。

💛先來一看下opencv中直方圖反向投影的API:

void calcBackProject(
  const Mat *  images, //要進行投影的輸入圖像的地址,注意該API要求輸入的是地址
  int          nimages,//輸入圖像的數目
  const int *  channels,//要進行投影的通道數
  InputArray   hist,//樣本的直方圖
  OutputArray  backProject,//輸出得反向投影,為Mat類型
  const float ** ranges, //輸入直方圖得特征空間的取值范圍
  double         scale = 1,
  bool          uniform = true
                     )

🧡該API實現的原理:

假設我們現在有一個四行四列得灰度圖,它得灰度值如下圖:

說這幅圖有什么特征呢?直觀上看類似於一個邊角,但這是直觀上,怎么表示出來呢?深度學習是靠神經網絡黑箱計算出來得,我們可以用直方圖。

那我們就計算這幅灰度圖得直方圖,如果以組距為1計算直方圖並反向投影到原圖,得到得為下圖:

 

大概表述一下邊角的特征:左下角有6個像素值相同得三角形區域,中間斜向下有四個像素值相同得邊界線,以此類推。這就是用直方圖得到邊角的特征。

那如果以組距為2計算直方圖呢?反向投影后為:

可以看到特征描述得更為廣泛了,就像深度學習里,提取更高層次的特征,雖然更為普適,但也會忽略掉一些細節特征。

我們就是拿這個反向投影所表達的特征信息,去和整幅圖做對比,來得到特征相似的部分,達到分割的效果。

💚 利用反向投影進行語義分割(opencv實現)

先看一下我們今天要處理得圖片:                                                                                     

目的:將公路提取出來

 樣本圖片:


 (一)先讀取原圖以及樣本圖,並轉換為HSV格式。 


    Mat src1 = imread("D:/opencv練習圖片/直方圖反向投影.jpg");//原圖
    Mat src2 = imread("D:/opencv練習圖片/樣本圖.jpg");//樣本圖
    imshow("原圖", src1);
    //轉換為HSV圖像
    Mat HsvImage, RoiImage_HSV;
    cvtColor(src1, HsvImage, COLOR_BGR2HSV);
    cvtColor(src2, RoiImage_HSV, COLOR_BGR2HSV);

 為什么轉HSV呢?

因為HSV表達顏色更為方便區分,我們今天只對前兩個通道直方圖統計:H(色調)和S(飽和度),不用V(亮度)。


 (二)計算樣本圖的直方圖並進行歸一化


    Mat roiHist; //直方圖對象
    int dims = 2;  //特征數目(直方圖維度)
    float hranges[] = { 0,180 }; //特征空間的取值范圍
    float Sranges[] = { 0,256 };
    const float *ranges[] = { hranges,Sranges };
    int size[] = { 20,32 };  //存放每個維度的直方圖的尺寸的數組
    int channels[] = { 0,1 };  //通道數
    calcHist(&RoiImage_HSV, 1, channels, Mat(), roiHist, dims, size, ranges);
    //直方圖歸一化
    normalize(roiHist, roiHist, 0, 255, NORM_MINMAX);

為什么要歸一化呢,直方圖反向投影到原圖后,原圖各位置表示的是整幅圖中等於該點像素值的數量,歸一化后就變成概率了。


 (三)將計算的歸一化后的直方圖進行反向投影


//反向投影
Mat proImage; //投影輸出圖像
calcBackProject(&HsvImage, 1, channels, roiHist, proImage, ranges);
imshow("反向投影", proImage);

 可以看到,公路的輪廓以及提取出來了(雖然效果不是太好,肯定不如深度學習。。。)


 (四)用掩碼將公路給摳出來顯示


//圖像掩碼Mask操作
    threshold(proImage, proImage, 50, 255, THRESH_BINARY);//對mask進行二值化,將mask進一步處理
    Mat dstImage = Mat::zeros(src1.size(), CV_8UC3);
    src1.copyTo(dstImage, proImage);
    imshow("結果", dstImage);

 后期再加上些邊緣檢測和霍夫直線變換,和一些其他騷操作,就可以提取公路啊,車道線啥的了。

 

參考鏈接:(8條消息) 直方圖的反向投影的原理_michaelhan3的博客-CSDN博客_直方圖反向投影


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM