1.目的
(1)如何使用OpenCV函數 split 將圖像分割成單通道數組。
(2)如何使用OpenCV函數 calcHist 計算圖像陣列的直方圖。
(3)如何使用OpenCV函數 normalize 歸一化數組。
2.原理
[1]直方圖
直方圖是對數據的集合 統計 ,並將統計結果分佈於一系列預定義的 bins 中。這裏的 數據 不僅僅指的是灰度值 (如上一篇您所看到的), 統計數據可能是任何能有效描述圖像的特徵。
假設有一個矩陣包含一張圖像的信息 (灰度值 0-255):如果我們按照某種方式去 統計 這些數字,會發生什麼情況呢? 既然已知數字的 範圍 包含 256 個值, 我們可以將這個範圍分割成子區域(稱作 bins), 如:
然後再統計掉入每一個 bini 的像素數目。採用這一方法來統計上面的數字矩陣,我們可以得到下圖( x軸表示 bin, y軸表示各個bin中的像素個數)。
以上只是一個說明直方圖如何工作以及它的用處的簡單示例。直方圖可以統計的不僅僅是顏色灰度, 它可以統計任何圖像特徵 (如 梯度, 方向等等)。
[2]直方圖的細節:
a. dims: 需要統計的特徵的數目, 在上例中, dims = 1 因爲我們僅僅統計了灰度值(灰度圖像)。
b. bins: 每個特徵空間 子區段 的數目,在上例中, bins = 16
c. range: 每個特徵空間的取值範圍,在上例中, range = [0,255]
PS:統計兩個特徵,直方圖是3維的,x,y分別代表一個特徵, z代表落入(x,y)中的樣本數目。同樣的方法適用於更高維的情形 。
3.部分代碼解釋
(1)calcHist
/*
calcHist參數解釋(10個參數)
&rgb_planes[0]: 輸入數組(或數組集)
1: 輸入數組的個數 (這裏我們使用了一個單通道圖像,我們也可以輸入數組集 )
0: 需要統計的通道 (dim)索引 ,這裏我們只是統計了灰度 (且每個數組都是單通道)所以只要寫 0 就行了。
Mat(): 掩碼( 0 表示忽略該像素), 如果未定義,則不使用掩碼
r_hist: 儲存直方圖的矩陣
1: 直方圖維數
histSize: 每個維度的bin數目
histRange: 每個維度的取值範圍,必須是const,一個二維數組
uniform 和 accumulate: bin大小相同,清除直方圖痕跡
*/
calcHist(&rgb_plane[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_plane[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_plane[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
(2)normalize
/*
normalize參數解釋
r_hist:歸一化圖像
r_hist:歸一化結果
0-hist_image.rows:歸一化範圍
NORM_MINMAX:歸一化方法
-1:表示歸一化結果數組和歸一化前數組的類型一致
Mat():掩碼,Mat()表示無掩碼操作
*/
normalize(r_hist, r_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
normalize(b_hist, b_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
4.完整代碼
(1)CommonInclude.h
#ifndef COMMON_INCLUDE
#define COMMON_INCLUDE
#include<iostream>
#include<vector>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
#endif
(2)Hist.cpp
#include"CommonInclude.h"
int main(int argc, char** argv){
if(argc < 2){
cout << "more parameters are required!!!" << endl;
return(-1);
}
Mat src;
vector<Mat> rgb_plane;
Mat r_hist, g_hist, b_hist;
src = imread(argv[1]);
if(!src.data){
cout << "error to read image!!!" << endl;
return(-1);
}
//分離rgb通道
split(src, rgb_plane);
//直方圖參數
int histSize = 255;//bins個數
float range[] = {0,255};//取值範圍
const float* histRange = {range};//必須是const,否則calcHist會報錯
bool uniform = true;
bool accumulate = true;
/*
calcHist參數解釋(10個參數)
&rgb_planes[0]: 輸入數組(或數組集)
1: 輸入數組的個數 (這裏我們使用了一個單通道圖像,我們也可以輸入數組集 )
0: 需要統計的通道 (dim)索引 ,這裏我們只是統計了灰度 (且每個數組都是單通道)所以只要寫 0 就行了。
Mat(): 掩碼( 0 表示忽略該像素), 如果未定義,則不使用掩碼
r_hist: 儲存直方圖的矩陣
1: 直方圖維數
histSize: 每個維度的bin數目
histRange: 每個維度的取值範圍,必須是const,一個二維數組
uniform 和 accumulate: bin大小相同,清除直方圖痕跡
*/
calcHist(&rgb_plane[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_plane[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_plane[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
//創建畫布
int h = 400, w = 400;
int bin_w = cvRound((double)w/histSize);
Mat hist_image = Mat(w, h, CV_8UC3, Scalar(0,0,0));
//歸一化
/*
normalize參數解釋
r_hist:歸一化圖像
r_hist:歸一化結果
0-hist_image.rows:歸一化範圍
NORM_MINMAX:歸一化方法
-1:表示歸一化結果數組和歸一化前數組的類型一致
Mat():掩碼,Mat()表示無掩碼操作
*/
normalize(r_hist, r_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
normalize(b_hist, b_hist, 0, hist_image.rows, NORM_MINMAX, -1, Mat());
//繪製直方圖
for(int i=1; i<histSize; i++){
line(hist_image, Point(bin_w*(i-1), h-cvRound(r_hist.at<float>(i-1))), Point(bin_w*i, h-cvRound(r_hist.at<float>(i))), Scalar(0,0,255), 1, 8);
line(hist_image, Point(bin_w*(i-1), h-cvRound(g_hist.at<float>(i-1))), Point(bin_w*i, h-cvRound(g_hist.at<float>(i))), Scalar(0,255,0), 1, 8);
line(hist_image, Point(bin_w*(i-1), h-cvRound(b_hist.at<float>(i-1))), Point(bin_w*i, h-cvRound(b_hist.at<float>(i))), Scalar(255,0,0), 1, 8);
}
namedWindow("RGB Histogram", CV_WINDOW_AUTOSIZE);
imshow("RGB Histogram", hist_image);
waitKey(0);
return(0);
}