天天看點

顔色特征提取(一)——顔色直方圖(opencv實作)

    直方圖——再講顔色直方圖之前,先簡單介紹一下直方圖。  直方圖作為一種簡單有效的基于統計特性的特征描述子,在計算機視覺領域廣泛使用。它的優點主要展現在兩個方面:一是對于任意一個圖像區域,直方圖特征的提取簡單友善;其二,直方圖表征圖像區域的統計特性,可以有效表示多模态的特征分布,并且本身具備一定的旋轉不變性。是以,在計算機視覺領域,基于不同底層特征的各種新穎直方圖描述子層出不窮,包括亮度直方圖,顔色直方圖,HOG,局部二值模式直方圖[等。其中顔色直方圖是目标跟蹤領域最為廣泛使用的描述子,然而傳統的顔色直方圖對光照變化敏感,同時目标區域内像素位置分布被完全忽略。

  顔色特征——是一種全局特征,描述了圖像或圖像區域所對應的景物的表面性質。一般顔色特征是基于像素點的特征,此時所有圖像或圖像區域的像素都有各自的貢獻。由于顔色對圖像或圖像區域的方向、大小等變化不敏感,是以顔色特征不能很好地捕捉圖像中對象的局部特征。另外,僅使用顔色特征查詢時,如果資料庫很大,常會将許多不需要的圖像也檢索出來。顔色直方圖是最常用的表達顔色特征的方法,其優點是不受圖像旋轉和平移變化的影響,進一步借助歸一化還可以不受圖像尺度變化的影響,其缺點是沒有表達出顔色空間分布的資訊。

(顔色直方圖):

       A color histogram of an image represents the distribution of the composition of colors in the image. It shows dif-ferent types of colors appeared and the number of pixels in each type of the colors appeared. The relation between a color histogram and a luminance histogram is that a color histogram can be also expressed as “Three Color Histogr-ams”, each of which shows the brightness distribution of each individual Red/Green/Blue color channel.

顔色直方圖在OpenCV的的實作:

    OpenCV裡面提供的計算圖像直方圖的API函數calcHist,用法:

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

OpenCV官網參數詳細注釋

parameters: 

第一個參數:源輸入(圖像)數組,必須是深度和大小均相同的CV_8U或者CV_32F(即uchar或者float)圖檔,每一個可以是任意通道的; 

第二個參數:源輸入數組中的圖檔個數; 

第三個參數:用來計算直方圖的通道維數的數組,第一張圖檔的通道由0到arrays[0].channels()-1列出,第二張圖檔的通道從arrays[0].channels()到arrays[0].channels()+arrays[1].channels()-1,以此類推,而該參數就是從通道序列中選取一子序列,參與直方圖計算的是子序列而不是所有通道序列; 

第四個參數:可選的掩模,如果該矩陣不是空的,則必須是8位的并且與arrays[i]的大小相等,掩模的非零值标記需要在直方圖中統計的數組元素; 

第五個參數:輸出直方圖,是一個稠密或者稀疏的dims維的數組; 

第六個參數:直方圖的維數,必須為正,并且不大于CV_MAX_DIMS(目前的OpenCV版本中為36,即最大可以統計36維的直方圖); 

第七個參數:用于指出直方圖數組每一維的大小的數組,即指出每一維的bin的個數的數組; 

第八個參數:用于指出直方圖每一維的每個bin的上下界範圍(數組)的數組,當直方圖是均勻的(uniform =true)時,對每一維i指定直方圖的第0個bin的下界和最後一個即第histSize[i]-1個bin的上界,也就是說對均勻直方圖來說,每一個ranges[i]都是一個兩個元素的數組【指出該維的上下界】。當直方圖不是均勻的時,每一個ranges[i]數組都包含histSize[i]+1個元素; 

第九個參數:直方圖是否均勻的标志;【指定直方圖每個bin統計的是否是相同數量的灰階級】; 

第十個參數:累加标志; 

calcHist函數的channels參數和narrays以及dims共同來确定用于計算直方圖的圖像;首先dims是最終的直方圖維數,narrays指出了arrays數組中圖像的個數,其中每一幅圖像都可以是任意通道的【隻要最終dims不超過36即可】。

如果channels參數為0,則narrays和dims必須相等,否則彈出assert,此時計算直方圖的時候取數組中每幅圖像的第0通道。當channels不是0的時候,用于計算直方圖的圖像是arrays中由channels指定的通道的圖像,channels與arrays中的圖像的對應關系,如channels的參數說明的,将arrays中的圖像從第0幅開始按照通道攤開排列起來,然後channels中的指定的用于計算直方圖的就是這些攤開的通道。

假設有arrays中隻有一幅三通道的圖像image,那麼narrays應該為1,如果是想計算3維直方圖【最大也隻能是3維的】,想将image的通道2作為第一維,通道0作為第二維,通道1作為第三維,則可以将channels設定為channesl={2,0,1};這樣calcHist函數計算時就按照這個順序來統計直方圖。

OpenCV實作計算HSV空間顔色直方圖程式:

#include "stdafx.h"
/*計算hsv圖像的直方圖*/
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

using namespace std;
using namespace cv;

class CalcHistogram
{
private:
	int histSize[3];         //直方圖項的數量
	float hranges[2];        //h通道像素的最小和最大值
	float sranges[2];
	float vranges[2];
	const float *ranges[3];  //各通道的範圍
	int channels[3];         //三個通道
	int dims;

public:
	CalcHistogram(int hbins = 30, int sbins = 32, int vbins = 32)
	{
		histSize[0] = hbins;
		histSize[1] = sbins;
		histSize[2] = vbins;
		hranges[0] = 0; hranges[1] = 180;
		sranges[0] = 0; sranges[1] = 256;
		vranges[0] = 0; vranges[1] = 256;
		ranges[0] = hranges;
		ranges[1] = sranges;
		ranges[2] = vranges;
		channels[0] = 0;
		channels[1] = 1;
		channels[2] = 2;
		dims = 3;
	}

	Mat getHistogram(const Mat &image);
	void getHistogramImage(const Mat &image);
};

Mat CalcHistogram::getHistogram(const Mat &image)
{
	Mat hist;
	calcHist(&image,
		1,
		channels,
		Mat(),
		hist,
		dims,
		histSize,
		ranges,
		true,      //直方圖每一維的histSize是均勻的
		false
	);

	return hist;
}

void CalcHistogram::getHistogramImage(const Mat &image)
{
	Mat hist = getHistogram(image);
	int scale = 4;
	int hbins = histSize[0];
	int sbins = histSize[1];
	int vbins = histSize[2];
	float *hist_sta = new float[sbins];
	float *hist_val = new float[vbins];
	float *hist_hue = new float[hbins];
	memset(hist_val, 0, vbins * sizeof(float));
	memset(hist_sta, 0, sbins * sizeof(float));
	memset(hist_hue, 0, hbins * sizeof(float));

	for (int s = 0; s < sbins; s++)
	{
		for (int v = 0; v < vbins; v++)
		{
			for (int h = 0; h<hbins; h++)
			{
				float binVal = hist.at<float>(h, s, v);
				hist_hue[h] += binVal;
				hist_val[v] += binVal;
				hist_sta[s] += binVal;
			}
		}
	}

	double max_sta = 0, max_val = 0, max_hue = 0;
	for (int i = 0; i<sbins; ++i)
	{
		if (hist_sta[i]>max_sta)
			max_sta = hist_sta[i];
	}
	for (int i = 0; i<vbins; ++i)
	{
		if (hist_val[i]>max_val)
			max_val = hist_val[i];
	}
	for (int i = 0; i<hbins; ++i)
	{
		if (hist_hue[i]>max_hue)
			max_hue = hist_hue[i];
	}

	Mat sta_img = Mat::zeros(240, sbins*scale + 20, CV_8UC3);
	Mat val_img = Mat::zeros(240, vbins*scale + 20, CV_8UC3);
	Mat hue_img = Mat::zeros(240, hbins*scale + 20, CV_8UC3);

	for (int i = 0; i<sbins; ++i)
	{
		int intensity = cvRound(hist_sta[i] * (sta_img.rows - 10) / max_sta);
		rectangle(sta_img, Point(i*scale + 10, sta_img.rows - intensity), Point((i + 1)*scale - 1 + 10, sta_img.rows - 1), Scalar(0, 255, 0), 1);
	}
	for (int i = 0; i<vbins; ++i)
	{
		int intensity = cvRound(hist_val[i] * (val_img.rows - 10) / max_val);
		rectangle(val_img, Point(i*scale + 10, val_img.rows - intensity), Point((i + 1)*scale - 1 + 10, val_img.rows - 1), Scalar(0, 0, 255), 1);
	}
	for (int i = 0; i<hbins; ++i)
	{
		int intensity = cvRound(hist_hue[i] * (hue_img.rows - 10) / max_hue);
		rectangle(hue_img, Point(i*scale + 10, hue_img.rows - intensity), Point((i + 1)*scale - 1 + 10, hue_img.rows - 1), Scalar(255, 0, 0), 1);
	}

	imshow("Shist", sta_img);
	imshow("Vhist", val_img);
	imshow("Hhist", hue_img);

	delete[] hist_sta;
	delete[] hist_val;
	delete[] hist_hue;
}

int main()
{
	Mat src = imread("C:\\Users\\59235\\Desktop\\image\\girl1.jpg"), hsv;
	if (!src.data)
	{
		cout << "error, the image is not built!" << endl;
		return -1;
	}
	cvtColor(src, hsv, CV_BGR2HSV);
	imshow("src", src);
	imshow("hsv", hsv);
	CalcHistogram h;
	h.getHistogram(hsv);
	h.getHistogramImage(hsv);
	waitKey();
	return 0;
}
           

亦或者是用實作RGB空間的顔色直方圖:

//計算圖像RGB空間的顔色直方圖
#include<opencv2\opencv.hpp>  
#include<iostream>  
using namespace std;
using namespace cv;

class HistogramND {
private:
	Mat image;//源圖像  
	int hisSize[1], hisWidth, hisHeight;//直方圖的大小,寬度和高度  
	float range[2];//直方圖取值範圍  
	const float *ranges;
	Mat channelsRGB[3];//分離的BGR通道  
	MatND outputRGB[3];//輸出直方圖分量  
public:
	HistogramND() {
		hisSize[0] = 256;
		hisWidth = 400;
		hisHeight = 400;
		range[0] = 0.0;
		range[1] = 255.0;
		ranges = &range[0];
	}

	//導入圖檔  
	bool importImage(String path) {
		image = imread(path);
		if (!image.data)
			return false;
		return true;
	}

	//分離通道  
	void splitChannels() {
		split(image, channelsRGB);
	}

	//計算直方圖  
	void getHistogram() {
		calcHist(&channelsRGB[0], 1, 0, Mat(), outputRGB[0], 1, hisSize, &ranges);
		calcHist(&channelsRGB[1], 1, 0, Mat(), outputRGB[1], 1, hisSize, &ranges);
		calcHist(&channelsRGB[2], 1, 0, Mat(), outputRGB[2], 1, hisSize, &ranges);

		//輸出各個bin的值  
		for (int i = 0; i < hisSize[0]; ++i) {
			cout << i << "   B:" << outputRGB[0].at<float>(i);
			cout << "   G:" << outputRGB[1].at<float>(i);
			cout << "   R:" << outputRGB[2].at<float>(i) << endl;
		}
	}

	//顯示直方圖  
	void displayHisttogram() {
		Mat rgbHist[3];
		for (int i = 0; i < 3; i++)
		{
			rgbHist[i] = Mat(hisWidth, hisHeight, CV_8UC3, Scalar::all(0));
		}
		normalize(outputRGB[0], outputRGB[0], 0, hisWidth - 20, NORM_MINMAX);
		normalize(outputRGB[1], outputRGB[1], 0, hisWidth - 20, NORM_MINMAX);
		normalize(outputRGB[2], outputRGB[2], 0, hisWidth - 20, NORM_MINMAX);
		for (int i = 0; i < hisSize[0]; i++)
		{
			int val = saturate_cast<int>(outputRGB[0].at<float>(i));
			rectangle(rgbHist[0], Point(i * 2 + 10, rgbHist[0].rows), Point((i + 1) * 2 + 10, rgbHist[0].rows - val), Scalar(0, 0, 255), 1, 8);
			val = saturate_cast<int>(outputRGB[1].at<float>(i));
			rectangle(rgbHist[1], Point(i * 2 + 10, rgbHist[1].rows), Point((i + 1) * 2 + 10, rgbHist[1].rows - val), Scalar(0, 255, 0), 1, 8);
			val = saturate_cast<int>(outputRGB[2].at<float>(i));
			rectangle(rgbHist[2], Point(i * 2 + 10, rgbHist[2].rows), Point((i + 1) * 2 + 10, rgbHist[2].rows - val), Scalar(255, 0, 0), 1, 8);
		}

		cv::imshow("R", rgbHist[0]);
		imshow("G", rgbHist[1]);
		imshow("B", rgbHist[2]);
		imshow("image", image);
	}
};


int main() {
	string path = "C:\\Users\\59235\\Desktop\\image\\girl1.jpg";
	HistogramND hist;
	if (!hist.importImage(path)) {
		cout << "Import Error!" << endl;
		return -1;
	}
	hist.splitChannels();
	hist.getHistogram();
	hist.displayHisttogram();
	waitKey(0);
	return 0;
}
           

實驗結果:

顔色特征提取(一)——顔色直方圖(opencv實作)
顔色特征提取(一)——顔色直方圖(opencv實作)

 H(hue):                    S(saturation):          V(value) :                  HSV空間的顔色直方圖:

顔色特征提取(一)——顔色直方圖(opencv實作)

RGB空間顔色直方圖:

顔色特征提取(一)——顔色直方圖(opencv實作)

RGB與HSV空間的比較:

(1)模型差別:

顔色特征提取(一)——顔色直方圖(opencv實作)

RBG模型:原點到白色頂點的中軸線是灰階線,r、g、b三分量相等,強度可以由三分量的向量表示

顔色特征提取(一)——顔色直方圖(opencv實作)

HSV模型:按色彩、深淺、明暗來描述的。H是色彩S是深淺, S = 0時,隻有灰階;V是明暗,表示色彩的明亮程度,但與光強無直接聯系。

(2)優缺點:

RGB空間:在圖像進行中,最常用的顔色空間是RGB模型,常用于顔色顯示和圖像處理,三維坐标的模型形式,非常容易被了解。但RGB顔色空間的均勻性非常差,且兩種顔色之間的直覺差異色差不能表示為改顔色空間中兩點間的距離。

HSV空間:相對于RGB空間,HSV空間能夠非常直覺的表達色彩的明暗,色調,以及鮮豔程度,友善進行顔色之間對的對比,也友善感情的傳達。但缺點是不能直接把顔色值傳達給顯示器,在轉換過程中消耗系統資源。