1. 程式人生 > >opencv提取影象中的顏色直方圖(RGB、HSV)

opencv提取影象中的顏色直方圖(RGB、HSV)

本篇部落格主要介紹利用opencv工具提取一幅影象中的顏色直方圖特徵。所謂顏色直方圖,指的是一幅影象中的顏色分佈,與影象中的特定的物體無關,只是用來表示人的眼睛觀察到的影象中的顏色分佈情況,例如說,一幅圖中紅色佔了多少比例,綠色佔了多少比例等。

我們知道,計算機色彩顯示器採用R、G、B相加混色的原理,通過發射出三種不同強度的電子束,使螢幕內側覆蓋的紅、綠、藍磷光材料發光而產生色彩。在RGB顏色空間中,任意色光F都可以用RGB三色不同分量的相加混合而成。也就是說,影象中每個畫素的顏色值都可以用一個三元值(R,G,B)來表示,例如(0,0,0)表示黑色,(0,0,255)表示藍色,……每個分量的大小從0~255.

那麼,我們可以知道一張彩色影象可以由R、G、B三個通道堆疊而成。可以想象成將三張大小相同的紙疊放,然後人眼從上往下看到的影象就是原來的彩色影象。這裡,假設影象的解析度為720*576,那麼每個通道的長為720,寬為576,單位是畫素,總的畫素個數就是3*720*576. 

下面具體介紹顏色直方圖,橫軸表示bins,也就是顏色分為多少塊。假設bins=256,也就是橫座標上每個點表示一個數值,縱座標表示一幅圖中有多少個畫素點的值是該數值。如果bins=16,也就是把顏色範圍0~255從小到大分為16塊,然後影象中落在每塊對應的範圍的畫素點的個數就是每塊對應的直方圖的高度。

因為opencv預設一張影象是由R、G、B三個通道堆疊形成的,所以首先提取出每個通達的直方圖,最後將三個直方圖揉在一塊。

其中,最主要的就是一個calcHist函式,void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false )
引數較多。第一個引數表示輸入的影象;第二個引數表示輸入的影象個數,一般是 1;第三個引數表示需要處理的是影象的第幾個通道;第四個引數表示掩模,一般是Mat();第五個引數是要輸出的直方圖陣列

;第六個引數表示需要統計的直方圖通道個數,一般是1;第七個引數histSize表示劃分的區間數,即bins的個數第八個引數表示畫素的變化範圍;後兩個引數分別表示是否歸一化,和是否累計計算畫素個數。

重點關注我標紅的引數即可。

程式碼展示:

#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
	//讀取本地的一張圖片
	Mat srcimage = imread("F:\\6030.png");
	imshow("原圖", srcimage);
	int channels = 0;
	int histsize[] = { 256 };
	float midranges[] = { 0,255 };
	const float *ranges[] = { midranges };
	MatND  dsthist;    //要輸出的直方圖
	//重點關注calcHist函式,即為計算直方圖的函式
	calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);  

	Mat b_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	double g_dhistmaxvalue;
	minMaxLoc(dsthist, 0, &g_dhistmaxvalue, 0, 0);
	for (int i = 0;i < 256;i++) {
		//這裡的dsthist.at<float>(i)就是每個bins對應的縱軸的高度
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue)); 
		line(b_drawImage, Point(i, b_drawImage.rows - 1), Point(i, b_drawImage.rows - 1 - value), Scalar(255, 0, 0));
	}
	imshow("B通道直方圖", b_drawImage);

	channels = 1;
	calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
	Mat g_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	for (int i = 0;i < 256;i++) {
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
		line(g_drawImage, Point(i, g_drawImage.rows - 1), Point(i, g_drawImage.rows - 1 - value), Scalar(0, 255, 0));
	}
	imshow("G通道直方圖", g_drawImage);

	channels = 2;
	calcHist(&srcimage, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
	Mat r_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	for (int i = 0;i < 256;i++) {
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
		line(r_drawImage, Point(i, r_drawImage.rows - 1), Point(i, r_drawImage.rows - 1 - value), Scalar(0, 0, 255));
	}
	imshow("R通道直方圖", r_drawImage);

	add(b_drawImage, g_drawImage, r_drawImage);   //將三個直方圖疊在一塊
	imshow("RGB直方圖", r_drawImage);
	waitKey(0);
	return 0;
}

結果展示:

接下來,繪製HSV顏色空間的直方圖。有了RGB顏色直方圖,還要HSV幹什麼。下面介紹HSV顏色空間模型。

HSV是一種將RGB色彩空間中的點在倒圓錐體中的表示方法。HSV即色相(Hue)、飽和度(Saturation)、亮度(Value),色相即顏色的基本屬性,飽和度指色彩的純度,越高則色彩越純,亮度指色彩的明亮程度。

在RGB顏色空間中,三種顏色分量的取值與所生成的顏色之間的聯絡並不直觀。而HSV顏色空間,更類似於人類感覺顏色的方式,封裝了關於顏色的資訊:“這是什麼顏色?深淺如何?明暗如何?”

HSV顏色空間規定的取值範圍: H:0~360,S:0~1,V:0~1

在opencv中,H:0~180,S:0~255,V:0~255, H/2,S*255,V*255

也就是說,這裡H通道的取值是和其他兩個通道有所不同的,它的範圍是0~180,因為如果為了堆疊三個通道,強行設定256個bins,那麼在第180~255個bins上,H通道上會出現為空的情況。

HSV顏色空間特徵的提取方法和RGB類似,關鍵一點就是要將原影象轉化為HSV顏色空間的影象,之後再對三個通道分別進行直方圖繪製操作即可。

程式碼展示:

#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
	Mat srcimage = imread("F:\\6030.png");
	imshow("原圖", srcimage);
	Mat srcimageHSV;
	//影象轉化HSV顏色空間影象
	cvtColor(srcimage, srcimageHSV, COLOR_BGR2HSV);
	imshow("HSV空間影象", srcimageHSV);
	int channels = 0;
	int histsize[] = { 256 };
	float midranges[] = { 0,255 };
	const float *ranges[] = { midranges };
	MatND  dsthist;
	calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
	Mat b_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);

	double g_dhistmaxvalue;   
	minMaxLoc(dsthist, 0, &g_dhistmaxvalue, 0, 0);
	for (int i = 0;i < 256;i++) {
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
		line(b_drawImage, Point(i, b_drawImage.rows - 1), Point(i, b_drawImage.rows - 1 - value), Scalar(255, 0, 0));
	}
	imshow("H通道直方圖", b_drawImage);
	
	channels = 1;
	calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
	Mat g_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	for (int i = 0;i < 256;i++) {
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
		line(g_drawImage, Point(i, g_drawImage.rows - 1), Point(i, g_drawImage.rows - 1 - value), Scalar(0, 255, 0));
	}
	imshow("S通道直方圖", g_drawImage);

	channels = 2;
	calcHist(&srcimageHSV, 1, &channels, Mat(), dsthist, 1, histsize, ranges, true, false);
	Mat r_drawImage = Mat::zeros(Size(256, 256), CV_8UC3);
	for (int i = 0;i < 256;i++) {
		int value = cvRound(256 * 0.9 *(dsthist.at<float>(i) / g_dhistmaxvalue));
		line(r_drawImage, Point(i, r_drawImage.rows - 1), Point(i, r_drawImage.rows - 1 - value), Scalar(0, 0, 255));
	}
	imshow("V通道直方圖", r_drawImage);
	add(b_drawImage, g_drawImage, r_drawImage);
	imshow("HSV直方圖", r_drawImage);
	waitKey(0);
	return 0;
}

結果展示:

說明:HSV三個通道的直方圖繪製,我為了區分,用了紅綠藍三種顏色,和RGB通道沒有關係。顏色可以自己在程式中更改。其實每個通道都是從不同的角度(色相、飽和度、亮度)來分析影象的。