用直方圖統計像素

https://blog.csdn.net/qq_30815237/article/details/86768547

用直方圖統計像素

圖像是由不同數值(顏色)的像素構成的,
像素值在整幅圖像中的分佈情況是該圖像的一個重要屬性。
本章將介紹圖像直方圖的概念,你將學會如何計算直方圖、如何用直方圖修改圖像的外觀,
還可以用直方圖來標識圖像的內容,檢測圖像中特定的物體或紋理。
本章將講解其中的部分技術。

計算圖像直方圖

圖像由各種數值的像素構成。例如在單通道灰度圖像中,每個像素都有一個 0(黑色)~255(白色)的整數。
對於每個灰度,都有不同數量的像素分佈在圖像內,具體取決於圖片內容。
直方圖是一個簡單的表格,表示一幅圖像(有時是一組圖像)中具有某個值的像素的數量。
因此,灰度圖像的直方圖有 256 個項目,也叫箱子(bin)。
0 號箱子提供值爲 0 的像素的數量,1 號箱子提供值爲 1 的像素的數量,以此類推。
很明顯,如果把直方圖的所有箱子進行累加,得到的結果就是像素的總數。
你也可以把直方圖歸一化,即所有箱子的累加和等於 1。
這時,每個箱子的數值表示對應的像素數量佔總數的百分比。

如何實現

要在 OpenCV 中計算直方圖,可簡單地調用 cv::calcHist 函數。
這是一個通用的直方圖計算函數,可處理包含任何值類型和範圍的多通道圖像。
爲了簡化,這裏指定一個專門用於處理單通道灰度圖像的類。
cv::calcHist 函數非常靈活,在處理其他類型的圖像時都可以直接使用它。

// 創建灰度圖像的直方圖
class Histogram1D {
private:
int histSize[1]; // 直方圖中箱子的數量
float hranges[2]; // 值範圍
const float* ranges[1]; //值範圍的指針
int channels[1]; //要檢查的通道數量
public:
Histogram1D() {
// 準備一維直方圖的默認參數
histSize[0]= 256;//256 個箱子
hranges[0]= 0.0;//從 0 開始(含)
hranges[1]= 256.0;//到 256(不含)
ranges[0]= hranges;
channels[0]= 0;//先關注通道 0
}

定義好成員變量後,就可以用下面的方法計算灰度直方圖了:

// 計算一維直方圖
cv::Mat getHistogram(const cv::Mat &image) {
cv::Mat hist;
// 用 calcHist 函數計算一維直方圖
cv::calcHist(&image, 1, // 僅爲一幅圖像的直方圖
channels, // 使用的通道
cv::Mat(), // 不使用掩碼
hist, // 作爲結果的直方圖
1, // 這是一維的直方圖
histSize, // 箱子數量
ranges // 像素值的範圍
);
return hist;
}

程序只需要打開一幅圖像,創建一個 Histogram1D 實例,然後調用 getHistogram 方法
即可:

// 讀取輸入的圖像
cv::Mat image= cv::imread("group.jpg", 0); // 以黑白方式打開
// 直方圖對象
Histogram1D h;
// 計算直方圖
cv::Mat histo= h.getHistogram(image);

這裏的 histo 對象是一個一維數組,包含 256 個項目。因此只需遍歷這個數組,就可以讀
取每個箱子:

// 循環遍歷每個箱子
for (int i=0; i<256; i++)
cout << "Value " << i << " = "
<<histo.at<float>(i) << endl;
使用本章開始時的圖像,部分顯示的值如下所示:
Value 7 = 159
Value 8 = 208
Value 9 = 271
Value 10 = 288
Value 11 = 340
Value 12 = 418
Value 13 = 432
Value 14 = 472
Value 15 = 525

顯然,只看這一系列數值很難得到任何有意義的信息。因此比較實用的做法是以函數的方式
顯示直方圖,例如用柱狀圖。用下面這幾種方法可創建這種圖形:

// 計算一維直方圖,並返回它的圖像
cv::Mat getHistogramImage(const cv::Mat &image, int zoom=1) {
// 先計算直方圖
cv::Mat hist= getHistogram(image);
// 創建圖像
return getImageOfHistogram(hist, zoom);
}
// 創建一個表示直方圖的圖像(靜態方法)
static cv::Mat getImageOfHistogram (const cv::Mat &hist, int zoom) {
// 取得箱子值的最大值和最小值
double maxVal = 0;
double minVal = 0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);
// 取得直方圖的大小
int histSize = hist.rows;
// 用於顯示直方圖的方形圖像
cv::Mat histImg(histSize*zoom, histSize*zoom,CV_8U, cv::Scalar(255));
// 設置最高點爲 90%(即圖像高度)的箱子個數
int hpt = static_cast<int>(0.9*histSize);
// 爲每個箱子畫垂直線
for (int h = 0; h < histSize; h++) {
float binVal = hist.at<float>(h);
if (binVal>0) {
int intensity = static_cast<int>(binVal*hpt / maxVal);
cv::line(histImg, cv::Point(h*zoom, histSize*zoom),
cv::Point(h*zoom, (histSize - intensity)*zoom),
cv::Scalar(0), zoom);
}
}
return histImg;
}

使用 getImageOfHistogram 方法可以得到直方圖圖像。它用線條畫成,以柱狀圖形式
展現:

// 以圖像形式顯示直方圖
cv::namedWindow("Histogram");
cv::imshow("Histogram",h.getHistogramImage(image));

得到的結果如下圖所示。

在這裏插入圖片描述從上面圖形化的直方圖可以看出,在中等灰度值處有一個大的尖峯,
並且比中等值更黑的像素有很多。巧的是,這兩部分像素分別對應了圖像的背景和前景。
要驗證這點,可以在這兩部分的匯合處進行閾值化處理。
OpenCV 中的 cv::threshold 函數可以實現這個功能。
上一章介紹過,它是一個很實用的函數。我們取直方圖中在升高爲尖峯之前的最小值的位置(灰度值爲 70 ),
對其進行閾值化處理,得到二值圖像:

cv::Mat thresholded; // 輸出二值圖像
cv::threshold(image,thresholded,70,255, // 閾值
cv::THRESH_BINARY); // 對超過閾值的像素

圖像直方圖

圖像直方圖提供了利用現有像素強度值進行場景渲染的方法。
通過分析圖像中像素值的分佈情況,你可以利用這個信息來修改圖像,甚至提高圖像質量。
解釋如何用一個簡單的映射函數(稱爲查找表)來修改圖像的像素值。

物體的圖片主要靠形狀和紋理區分彼此,
這些特徵可以用方向梯度直方圖(Histogram of14.4 行人檢測Oriented Gradients,HOG)模型表示。
正如其名,這種模型的基礎就是圖像梯度的直方圖;
具體來說是梯度方向的分佈圖,因爲我們更加關注形狀和紋理。
此外,爲了觀察這些梯度的空間分佈,需要把圖像劃分成網格,並以此計算多個直方圖。
構建 HOG 模型的第一步就是計算圖像的梯度。
把圖像分割成小的單元格(例如 8 像素×8 像素),並針對每個單元格計算方向梯度直方圖。
方向的值會被分割成多個箱子。通常只考慮梯度的方向,不考慮正負(稱作無符號梯度)。
這裏的方向值範圍是 0 度~180 度。採用 9 個箱子的直方圖,方向值的分割間距爲 20 度。
每個單元格的梯度向量產生一個箱子,該箱子的權重對應梯度的幅值。

圖像 HOG 模型的向量的維度非常高。
這個向量就代表了圖像的特徵,可用於各種物體圖像的分類。
爲此,我們需要一種能處理這種高維向量的機器學習方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章