颜色特征提取(1)——颜色直方图(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;
}

实验结果:



 H(hue):                    S(saturation):          V(value) :                  HSV空间的颜色直方图:


RGB空间颜色直方图:


RGB与HSV空间的比较:

(1)模型区别:

RBG模型:原点到白色顶点的中轴线是灰度线,r、g、b三分量相等,强度可以由三分量的向量表示

HSV模型:按色彩、深浅、明暗来描述的。H是色彩S是深浅, S = 0时,只有灰度;V是明暗,表示色彩的明亮程度,但与光强无直接联系。

(2)优缺点:

RGB空间:在图像处理中,最常用的颜色空间是RGB模型,常用于颜色显示和图像处理,三维坐标的模型形式,非常容易被理解。但RGB颜色空间的均匀性非常差,且两种颜色之间的直觉差异色差不能表示为改颜色空间中两点间的距离。

HSV空间:相对于RGB空间,HSV空间能够非常直观的表达色彩的明暗,色调,以及鲜艳程度,方便进行颜色之间对的对比,也方便感情的传达。但缺点是不能直接把颜色值传达给显示器,在转换过程中消耗系统资源。

猜你喜欢

转载自blog.csdn.net/zhu_hongji/article/details/80443585