opencv 直方圖的學習記錄

    一個圖像是由不同顏色的像素組成。像素值在圖像中的分佈情況是這幅圖像的一個重要特徵。我們將學習如何計算並使用直方圖來修改圖像的外觀。直方圖也可用於描述圖像的內容,並且檢測圖像中特定的對象或紋理。

    1【計算圖像的直方圖】

    圖像是由像素組成的,在一個單通道的灰度圖像中,每個像素的值介於0(黑色)~255(白色)之間。根據圖像的內容,我們會發現每個灰度值的像素數目是不同的。直方圖是一個簡單的表,它給出了一幅圖像或一組圖像中擁有給定數值的像素數量。因此,灰度圖像的直方圖有256個條目(容器)。0號容器給出值爲0的像素個數,1號容器給出值爲1的像素個數,以此類推。顯然,如果我們對直方圖的所有項求和,會得到像素的總數。直方圖也可以被歸一化,歸一化後的所有項之和等於1。在該情況下,每一項給出的都是擁有特定數值的像素在圖像中佔的比例。

    在opencv中計算直方圖,通過cv::calcHist函數。這是一個通用函數,可以計算任意像素類型的多通道圖像。下面代碼是實現一個單通道直方圖的簡單代碼實現。

cv::MatND CowisHistogram1D::getHistogram(const cv::Mat &image)
{
    cv::MatND hist;

    cv::calcHist(&image,//輸入的圖像

 1, //計算幾張圖像

channels,//通道數量

 cv::Mat(),//掩碼

 hist, //返回直方圖的矩陣

1, //通道數量

histSize,//項的數量

 rang);//像素值的範圍

    return hist;

}

獲取到直方圖的數據是一個矩陣,方便我們觀察,我們把數據畫出來,主要代碼如,運行的效果如下圖所示

cv::Mat CowisHistogram1D::gethistogramImage(const cv::Mat &image)
{
    cv::MatND hist = getHistogram(image);
    double maxVal  = 0;
    double minVal  = 0;
    cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
    cv::Mat histImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
    int hpt = static_cast<int>(0.9 * histSize[0]);


    for(int h = 0; h < histSize[0]; h++)
    {
        float binVal = hist.at<float>(h);
        int intensity = static_cast<int>(binVal * hpt / maxVal);
        cv::line(histImg, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
    }
    return histImg;

}


    從直方圖可以直觀的看到,灰度的中間位置存在一個大的峯值,同時有大量深色的像素。這兩組像素基本對應的是圖像的背景和前景。通過這兩組像素之間的過渡處進行閾值可以證實這一點。有一個方便的函數cv::threshold函數。當需要使用一個閾值來創建二值圖像時可以使用該函數。生成的圖像清晰的顯示了分段的背景與前景。


    cv::calcHist有許多參數,大多數時候,我們的直方圖將是一個單通道或者三通道圖像。然而,該函數允許我們指定一個分佈在幾個圖像中的多通道圖像。這就是爲何一組圖像被作爲該函數的輸入。第6個參數指定直方圖的維度,例如1指的是一維直方圖。直方圖計算中要考慮的通道列在一個數組中,它有指定的維度。在實現單通道時,默認使用的是通道0(第3個參數)。直方圖本身是通過每個維度的條目數量,每個維度的最小及最大值進行描述的,前者位於第7個參數,是一組整數,後者位於第8個參數,是一組每項包含兩個元素的數組。可以指定一個掩碼,指明哪些像素需要進行統計。還可以指定兩個額外的可選參數,都是布爾值。第一個表面直方圖是否歸一化的(默認是true),第2個值允許積累多個直方圖計算結果。如果這個參數爲true,那麼圖像的像素統計值會加到輸入直方圖中當前的值上。需要主要的是,直方圖中的值是浮點數。

    下面是計算三通道直方圖的代碼。

    std::vector<cv::Mat> bgr_planes;
    cv::split(image, bgr_planes);
    cv::Mat hist1;
    cv::calcHist(&bgr_planes[0], 1, channels, cv::Mat(), hist1, 1, histSize, ranges);
    cv::Mat hist2;
    cv::calcHist(&bgr_planes[1], 1, channels, cv::Mat(), hist2, 1, histSize, ranges);
    cv::Mat hist3;
    cv::calcHist(&bgr_planes[2], 1, channels, cv::Mat(), hist3, 1, histSize, ranges);


    cv::Mat imageChann1(histSize[0], histSize[0], CV_8U, cv::Scalar(255));
    cv::Mat imageChann2(histSize[1], histSize[1], CV_8U, cv::Scalar(255));
    cv::Mat imageChann3(histSize[2], histSize[2], CV_8U, cv::Scalar(255));
    double minVal = 0.;
    double maxVal = 0.;
    cv::minMaxLoc(hist1, &minVal, &maxVal, 0 ,0);


    int hpt = static_cast<int>(0.9 * histSize[0]);
    for(int i = 0; i < 3; i++)
    {
        for(int h = 0; h < histSize[0]; h++)
        {
            if(i == 0)
            {
                float binVal = hist1.at<float>(h);
                int intensity = static_cast<int>(binVal * hpt / maxVal);
                cv::line(imageChann1, cv::Point(h, histSize[0]),cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
            }
            else if(i == 1)
            {
                float binVal = hist2.at<float>(h);
                int intensity = static_cast<int>(binVal * hpt / maxVal);
                cv::line(imageChann2, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
            }
            else {
                float binVal = hist3.at<float>(h);
                int intensity = static_cast<int>(binVal * hpt / maxVal);
               cv::line(imageChann3, cv::Point(h, histSize[0]), cv::Point(h, histSize[0] - intensity), cv::Scalar::all(0));
            }


        }
    }
    cv::namedWindow("Channl1");
    cv::namedWindow("Channl2");
    cv::namedWindow("Channl3");
    cv::imshow("Channl1", imageChann1);
    cv::imshow("Channl2", imageChann2);

    cv::imshow("Channl3", imageChann3);

運行結果如下