文章出自:http://blog.sina.com.cn/s/blog_69e7ab31010191h7.html
http://blog.csdn.net/abcjennifer/article/details/7424971
计算机视觉的特征提取算法研究至关重要。在一些算法中,一个高复杂度特征的提取可能能够解决问题(进行目标检测等目的),但这将以处理更多数据,需要更高的处理效果为代价。而颜色特征无需进行大量计算。只需将数字图像中的像素值进行相应转换,表现为数值即可。因此颜色特征以其低复杂度成为了一个较好的特征。
在图像处理中,我们可以将一个具体的像素点所呈现的颜色分多种方法分析,并提取出其颜色特征分量。比如通过手工标记区域提取一个特定区域(region)的颜色特征,用该区域在一个颜色空间三个分量各自的平均值表示,或者可以建立三个颜色直方图等方法。下面我们介绍一下颜色直方图和颜色矩的概念。
颜色直方图:
颜色直方图用以反映图像颜色的组成分布,即各种颜色出现的概率。Swain和Ballard最先提出了应用颜色直方图进行图像特征提取的方法[40],首先利用颜色空间三个分量的剥离得到颜色直方图,之后通过观察实验数据发现将图像进行旋转变换、缩放变换、模糊变换后图像的颜色直方图改变不大,即图像直方图对图像的物理变换是不敏感的。因此常提取颜色特征并用颜色直方图应用于衡量和比较两幅图像的全局差。另外,如果图像可以分为多个区域,并且前景与背景颜色分布具有明显差异,则颜色直方图呈现双峰形。
颜色直方图也有其缺点:由于颜色直方图是全局颜色统计的结果,因此丢失了像素点间的位置特征。可能有几幅图像具有相同或相近的颜色直方图,但其图像像素位置分布完全不同。因此,图像与颜色直方图得多对一关系使得颜色直方图在识别前景物体上不能获得很好的效果。
考虑到颜色直方图的以上问题,主色调直方图便产生了。所谓主色调直方图基于假设少数几个像素的值能够表示图像中的绝大部分像素,即出现频率最高的几个像素被选为主色,仅用主色构成的主色调直方图描述一幅图像。这样的描述子并不会降低通过颜色特征进行匹配的效果,因为从某种角度将,频度出现很小的像素点可以被视为噪声。
颜色矩:
颜色矩是一种有效的颜色特征,由Stricker和Orengo提出[41],该方法利用线性代数中矩的概念,将图像中的颜色分布用其矩表示。利用颜色一阶矩(平均值Average)、颜色二阶矩(方差Variance)和颜色三阶矩(偏斜度Skewness)来描述颜色分布。与颜色直方图不同,利用颜色矩进行图像描述无需量化图像特征。由于每个像素具有颜色空间的三个颜色通道,因此图像的颜色矩有9个分量来描述。由于颜色矩的维度较少,因此常将颜色矩与其他图像特征综合使用。
颜色集:
以上两种方法通常用于两幅图像间全局或region之间的颜色比较、匹配等,而颜色集的方法致力于实现基于颜色实现对大规模图像的检索。颜色集的方法由Smith和Chang提出[42],该方法将颜色转化到HSV颜色空间后,将图像根据其颜色信息进行图像分割成若干region,并将颜色分为多个bin,每个region进行颜色空间量化建立颜色索引,进而建立二进制图像颜色索引表。为加快查找速度,还可以构造二分查找树进行特征检索。
对于颜色的研究还真不太好搞,因为我们在处理图片的时候通常第一件事就是将图片灰度化,这样什么所谓的色彩信息便全部丢失,但是有的时候,当灰度图效果不佳时,可以采用不同色彩的不同通道来突出ROI(region of interest),这里先简绍下常见的色彩空间和几个实例。
颜色空间按照基本结构可以分两大类:基色颜色空间(RGB)和色、亮分离颜色空间(CIE系列),其常见的有如下几个(经过本人总结整理,很多没有研究透彻,这里只做基本介绍):
颜色空间 | 描述 | 特点 |
RGB | 三原色光模式,采用加法混色法,它是描述各种“光”通过何种比例来产生颜色。 | 面向硬件的颜色空间,RGB颜色空间适合于显示系统,却并不适合于图像处理 |
CIE YUV | 是通过亮度,色差来描述颜色的颜色空间。亮度信号经常被称作Y,色度信号是由两个互相独立的信号组成 | |
CIE Lab | 用于计算机色调调整和彩色校正。它独立于设备的彩色模型实现。这一方法用来把设备映射到模型及模型本身的彩色分布质量变化 | Lab颜色空间可以直接通过使用颜色空间内的几何距离来做不同颜色之间的比较分析,所以可以有效地、方便地用在测量较小的色差上。可是尽管非线性变换空间可以消除其各个颜色分量之间存在的相关性,可用于数字图像处理,但因为是非线性变换,所以计算量比较大,并且颜色空间同样存在奇异点的问题。 |
CIE | 第一次基于人眼对于色彩感知度量建立色彩空间的尝试,它是几乎所有其它色彩空间的基础 | 国际照明委员会(CIE) |
CMYK | 印刷四分色模式,使用减法混色法,因为它描述的是需要使用何种油墨,通过光的反射显示出颜色。它是在一种白色介质(画板,页面等)上使用油墨来体现图像。CMYK描述的是青,品红,黄和黑四种油墨的数值。根据不同的油墨,介质,和印刷特性,存在多种CMYK色彩空间。(可以通过色点扩张或者转换各种油墨数值从而得到不同的外观). | |
HSB(HSV,HSI,HSL) | 使用色相(X轴)、饱和度(Y轴)和明度(Z轴)表示颜色的方法,HSV(色相hue,饱和度saturation,明度value),也称HSB (B指brightness),是艺术家们常用的,因为与加法减法混色的术语相比,使用色相,饱和度等概念描述色彩更自然直观 | 亮度(明度)分量与图像的彩色信息无关,所以对彩色处理只要考虑其它两个分量即可;②H与S分量与人眼感受颜色的方式很接近。可是,HSI颜色空间存在奇异点,在距离奇异点较近处,如果R、G、B分量有微小地变化就可能会导致H分量的巨大波动,致使系统不稳定。另一方面,HSI变换与RGB变换都是非线性变换,耗时多,无法满足机器手进行采摘工作的实时性需要 |
RG Chromaticity | 是用于计算机视觉的色彩空间,它可以显示光线的颜色,如红、黄、绿等,但是不能显示它的亮度如暗与亮 | |
YIQ | 属于NTSC系统。这里Y是指颜色的明视度,即亮度。其实Y就是图像灰度值,I和Q都指的是指色调,即描述图像色彩与饱和度的属性。 | RGB和YIQ的对应关系用下面的方程式表示: Y = 0.299R + 0.587G + 0.114B I = 0.596R - 0.275G - 0.321B Q = 0.212R - 0.523G + 0.311B |
当研究对象是依据人的眼睛进行分类识别时候的,由于计算机表示颜色的方式通常是面向硬件的RGB色彩空间,所以可以尝试使用与人眼更相近的色彩空间对烟叶颜色进行分析,这里考虑四个颜色空间, RGB, HSV, HLS, Lab其各自属性如上表所述。
用不同的色彩空间描述一图片的效果如下图所示,由此可见,不同的空间描述侧重点不同。例如直观的看HSV更偏重于RGB中最大量的显示,而HLS侧重于各个颜色的均值,Lab则更加倾向于色差区分:
RGB
HSV
HLS
Lab
说道EGB我想起来一件事,在openCV中采用的是BGR色彩格式与matlab的RGB有所不同,大家一定要注意注意再注意,第一次搞的我郁闷了好半天。
不知道相关程序放在哪好,CSDN,CodeSoSo,pudn 还是SourceForge上会比较方便,以后再说吧。。。
to be continued。。。
上述空间与RGB转换方式如下所示HSV((1)~(3)):
HLS((4)~(8)):
总结:颜色特征并不如别的特征(纹理与形状)用的那么多而且计算较为复杂,甚至有非线性计算部分,不如只含有一层通道的灰度图计算的简单,且获得彩色信息本就比较复杂(考虑到工业相机,而不是简单的数码相机,有机会专门写个工业相机文章,记得第一次搞相机的接口程序时候,搞的头都大了!),如果不采用专门的相机会导致色彩的失真,还不如仅用灰度图来的划算。
用颜色的特征还需对色彩知识有一定的理解,所以通常在两种情况下会用色彩特征1)特征太少2)色彩是区分的非常重要指标3)图像较为清晰,色彩较为准确(是拜耳算法获得的颜色信息就要仔细考虑了,否则一用就跪了可能)4)你不是色盲或者最后一种情况,你在搞科研堆文章创新点。
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
//绘制灰度直方图
//int main()
//{
// Mat src, gray;
// src = imread("g:\\AI\\电路图9.jpg");
// cvtColor(src, gray, CV_RGB2GRAY);
// int bins = 256;
// int hist_size[] = { bins };
// float range[] = { 0, 256 };
// const float* ranges[] = { range };
// MatND hist;
// int channels[] = { 0 };
//
// calcHist(&gray, 1, channels, Mat(), // do not use mask
// hist, 1, hist_size, ranges,
// true, // the histogram is uniform
// false);
//
// double max_val;
// minMaxLoc(hist, 0, &max_val, 0, 0);
// int scale = 2;
// int hist_height = 256;
// Mat hist_img = Mat::zeros(hist_height, bins*scale, CV_8UC3);
// for (int i = 0; i<bins; i++)
// {
// float bin_val = hist.at<float>(i);
// int intensity = cvRound(bin_val*hist_height / max_val); //要绘制的高度
// rectangle(hist_img, Point(i*scale, hist_height - 1),
// Point((i + 1)*scale - 1, hist_height - intensity),
// CV_RGB(255, 255, 255));
// }
// imshow("Source", src);
// imshow("Gray Histogram", hist_img);
// waitKey(10000000000);
// return 0;
//}
//绘制RGB三色分量直方图
//int main()
//{
// Mat src;
// src = imread("g:\\AI\\lena.jpg");
// int bins = 256;
// int hist_size[] = { bins };
// float range[] = { 0, 256 };
// const float* ranges[] = { range };
// MatND hist_r, hist_g, hist_b;
// int channels_r[] = { 0 };
//
// calcHist(&src, 1, channels_r, Mat(), // do not use mask
// hist_r, 1, hist_size, ranges,
// true, // the histogram is uniform
// false);
//
// int channels_g[] = { 1 };
// calcHist(&src, 1, channels_g, Mat(), // do not use mask
// hist_g, 1, hist_size, ranges,
// true, // the histogram is uniform
// false);
//
// int channels_b[] = { 2 };
// calcHist(&src, 1, channels_b, Mat(), // do not use mask
// hist_b, 1, hist_size, ranges,
// true, // the histogram is uniform
// false);
// double max_val_r, max_val_g, max_val_b;
// minMaxLoc(hist_r, 0, &max_val_r, 0, 0);
// minMaxLoc(hist_g, 0, &max_val_g, 0, 0);
// minMaxLoc(hist_b, 0, &max_val_b, 0, 0);
// int scale = 1;
// int hist_height = 256;
// Mat hist_img = Mat::zeros(hist_height, bins * 3, CV_8UC3);
// for (int i = 0; i<bins; i++)
// {
// float bin_val_r = hist_r.at<float>(i);
// float bin_val_g = hist_g.at<float>(i);
// float bin_val_b = hist_b.at<float>(i);
// int intensity_r = cvRound(bin_val_r*hist_height / max_val_r); //要绘制的高度
// int intensity_g = cvRound(bin_val_g*hist_height / max_val_g); //要绘制的高度
// int intensity_b = cvRound(bin_val_b*hist_height / max_val_b); //要绘制的高度
// rectangle(hist_img, Point(i*scale, hist_height - 1),
// Point((i + 1)*scale - 1, hist_height - intensity_r),
// CV_RGB(255, 0, 0));
//
// rectangle(hist_img, Point((i + bins)*scale, hist_height - 1),
// Point((i + bins + 1)*scale - 1, hist_height - intensity_g),
// CV_RGB(0, 255, 0));
//
// rectangle(hist_img, Point((i + bins * 2)*scale, hist_height - 1),
// Point((i + bins * 2 + 1)*scale - 1, hist_height - intensity_b),
// CV_RGB(0, 0, 255));
//
// }
// imshow("Source", src);
// imshow("RGB Histogram", hist_img);
// waitKey(10000000000);
// return 0;
//}
//
绘制H-S二维直方图
//int main()
//{
// Mat src, hsv;
// src = imread("g:\\AI\\lena.jpg");
// cvtColor(src, hsv, CV_BGR2HSV);
// // Quantize the hue to 30 levels
// // and the saturation to 32 levels
// int hbins = 256, sbins = 180;
// int histSize[] = { hbins, sbins };
// // hue varies from 0 to 179, see cvtColor
// float hranges[] = { 0, 180 };
// // saturation varies from 0 (black-gray-white) to
// // 255 (pure spectrum color)
// float sranges[] = { 0, 256 };
// const float* ranges[] = { hranges, sranges };
// MatND hist;
// // we compute the histogram from the 0-th and 1-st channels
// int channels[] = { 0, 1 };
// calcHist(&hsv, 1, channels, Mat(), // do not use mask
// hist, 2, histSize, ranges,
// true, // the histogram is uniform
// false);
// double maxVal = 0;
// minMaxLoc(hist, 0, &maxVal, 0, 0);
// int scale = 2;
// Mat histImg = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);
// for (int h = 0; h < hbins; h++)
// for (int s = 0; s < sbins; s++)
// {
// float binVal = hist.at<float>(h, s);
// int intensity = cvRound(binVal * 255 / maxVal);
// rectangle(histImg, Point(h*scale, s*scale),
// Point((h + 1)*scale - 1, (s + 1)*scale - 1),
// Scalar::all(intensity),
// CV_FILLED);
// }
// namedWindow("Source", 1);
// imshow("Source", src);
// namedWindow("H-S Histogram", 1);
// imshow("H-S Histogram", histImg);
// waitKey(10000000000);
// return 0;
//}
//
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <opencv2\opencv.hpp>
//#pragma comment(lib, "opencv_core231d.lib")
//#pragma comment(lib, "opencv_highgui231d.lib")
//#pragma comment(lib, "opencv_imgproc231d.lib")
using namespace cv;
using namespace std;
#define HIST_DIM1
int main(int argc, char** argv)
{
#ifdef HIST_DIM1
//----------------------example 1-------------------------------//
Mat src, dst;
/// Load image
src = imread("G:\\AI\\lena.jpg");
if (!src.data)
{
cout << "load image failed" << endl;
return -1;
}
/// Separate the image in 3 places ( R, G and B )
vector<Mat> rgb_planes;
#define SHOW_HSV
#ifdef SHOW_HSV
Mat hsv;
cvtColor(src, hsv, COLOR_BGR2HSV);
split(hsv, rgb_planes);
#else
split(src, rgb_planes);
#endif
/// Establish the number of bins
int histSize = 256;
/// Set the ranges ( for R,G,B) )
float range[] = { 0, 255 };
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat r_hist, g_hist, b_hist;
/// Compute the histograms:
calcHist(&rgb_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
// Draw the histograms for R, G and B
int hist_w = 600; int hist_h = 400;
int bin_w = cvRound((double)hist_w / histSize);
Mat rgb_hist[3];
for (int i = 0; i<3; ++i)
{
rgb_hist[i] = Mat(hist_h, hist_w, CV_8UC3, Scalar::all(0));
}
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));
/// Normalize the result to [ 0, histImage.rows-10]
normalize(r_hist, r_hist, 0, histImage.rows - 10, NORM_MINMAX);
normalize(g_hist, g_hist, 0, histImage.rows - 10, NORM_MINMAX);
normalize(b_hist, b_hist, 0, histImage.rows - 10, NORM_MINMAX);
/// Draw for each channel
for (int i = 1; i < histSize; i++)
{
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
Scalar(0, 0, 255), 1);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
Scalar(0, 255, 0), 1);
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0), 1);
}
for (int j = 0; j<histSize; ++j)
{
int val = saturate_cast<int>(r_hist.at<float>(j));
rectangle(rgb_hist[0], Point(j * 2 + 10, rgb_hist[0].rows), Point((j + 1) * 2 + 10, rgb_hist[0].rows - val), Scalar(0, 0, 255), 1, 8);
val = saturate_cast<int>(g_hist.at<float>(j));
rectangle(rgb_hist[1], Point(j * 2 + 10, rgb_hist[1].rows), Point((j + 1) * 2 + 10, rgb_hist[1].rows - val), Scalar(0, 255, 0), 1, 8);
val = saturate_cast<int>(b_hist.at<float>(j));
rectangle(rgb_hist[2], Point(j * 2 + 10, rgb_hist[2].rows), Point((j + 1) * 2 + 10, rgb_hist[2].rows - val), Scalar(255, 0, 0), 1, 8);
}
/// Display
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE);
namedWindow("wnd");
imshow("calcHist Demo", histImage);
imshow("wnd", src);
imshow("R", rgb_hist[0]);
imshow("G", rgb_hist[1]);
imshow("B", rgb_hist[2]);
#else
//----------------------example 2-------------------------------//
Mat src, hsv;
if (!(src = imread("d:/picture/lena.bmp")).data)
return -1;
cvtColor(src, hsv, CV_BGR2HSV);
// Quantize the hue to 30 levels
// and the saturation to 32 levels
int hbins = 60, sbins = 64;
int histSize[] = { hbins, sbins };
// hue varies from 0 to 179, see cvtColor
float hranges[] = { 0, 180 };
// saturation varies from 0 (black-gray-white) to
// 255 (pure spectrum color)
float sranges[] = { 0, 256 };
const float*ranges[] = { hranges, sranges };
MatND hist;
// we compute the histogram from the 0-th and 1-st channels
int channels[] = { 0, 1 };
calcHist(&hsv, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
double maxVal = 0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 8;
Mat histImg = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);
for (int h = 0; h < hbins; h++)
{
for (int s = 0; s < sbins; s++)
{
float binVal = hist.at<float>(h, s);
int intensity = cvRound(binVal * 255 / maxVal);
rectangle(histImg, Point(h*scale, s*scale), Point((h + 1)*scale - 1, (s + 1)*scale - 1), Scalar::all(intensity), CV_FILLED);
}
}
namedWindow("Source", 1);
imshow("Source", src);
namedWindow("H-S Histogram", 1);
imshow("H-S Histogram", histImg);
#endif
//-------------------------------------------------------------------------//
waitKey(0);
destroyAllWindows();
return 0;
}
//#include <opencv\cv.h>
//#include <opencv\highgui.h>
//#include <iostream>
//using namespace std;
//
//int main(int argc, char** argv)
//{
// IplImage * src = cvLoadImage("g:\\AI\\电路图9.jpg", 1);
//
// IplImage* hsv = cvCreateImage(cvGetSize(src), 8, 3); //第一个为size,第二个为位深度(8为256度),第三个通道数
// IplImage* h_plane = cvCreateImage(cvGetSize(src), 8, 1);
// IplImage* s_plane = cvCreateImage(cvGetSize(src), 8, 1);
// IplImage* v_plane = cvCreateImage(cvGetSize(src), 8, 1);
// IplImage* planes[] = { h_plane, s_plane, v_plane };
//
// //** H 分量划分为16个等级,S分量划分为8个等级 */
// int h_bins = 16, s_bins = 8, v_bins = 8;
// int hist_size[] = { h_bins, s_bins, v_bins };
//
// //** H 分量的变化范围 */
// float h_ranges[] = { 0, 180 };
//
// //** S 分量的变化范围*/
// float s_ranges[] = { 0, 255 };
// float v_ranges[] = { 0, 255 };
//
// float* ranges[] = { h_ranges, s_ranges, v_ranges };
//
// //** 输入图像转换到HSV颜色空间 */
// cvCvtColor(src, hsv, CV_BGR2HSV);
// cvCvtPixToPlane(hsv, h_plane, s_plane, v_plane, 0);
//
// //** 创建直方图,二维, 每个维度上均分 */
// CvHistogram * hist = cvCreateHist(3, hist_size, CV_HIST_ARRAY, ranges, 1);
// //** 根据H,S两个平面数据统计直方图 */
// cvCalcHist(planes, hist, 0, 0);
//
// //** 获取直方图统计的最大值,用于动态显示直方图 */
// float max_value;
// cvGetMinMaxHistValue(hist, 0, &max_value, 0, 0);
//
//
// //** 设置直方图显示图像 */
// int height = 100;
// int width = (h_bins*s_bins*v_bins * 5);
// IplImage* hist_img = cvCreateImage(cvSize(width, height), 8, 3);
// cvZero(hist_img);
//
// //** 用来进行HSV到RGB颜色转换的临时单位图像 */
// IplImage * hsv_color = cvCreateImage(cvSize(1, 1), 8, 3);
// IplImage * rgb_color = cvCreateImage(cvSize(1, 1), 8, 3);
// int bin_w = width / (h_bins * s_bins);
// for (int h = 0; h < h_bins; h++)
// {
// for (int s = 0; s < s_bins; s++)
// {
// for (int v = 0; v < v_bins; v++)
// {
// int i = h*s_bins + s*v_bins + v;
// /** 获得直方图中的统计次数,计算显示在图像中的高度 */
// float bin_val = cvQueryHistValue_3D(hist, h, s, v);
// int intensity = cvRound(bin_val*height / max_value);
//
// /** 获得当前直方图代表的颜色,转换成RGB用于绘制 */
// cvSet2D(hsv_color, 0, 0, cvScalar(h*180.f / h_bins, s*255.f / s_bins, v*255.f / v_bins, 0));
// cvCvtColor(hsv_color, rgb_color, CV_HSV2BGR);
// CvScalar color = cvGet2D(rgb_color, 0, 0);
//
// cvRectangle(hist_img, cvPoint(i*bin_w, height),
// cvPoint((i + 1)*bin_w, height - intensity),
// color, -1, 8, 0);
// }
// }
// }
//
// cvNamedWindow("Source", 1);
// cvShowImage("Source", src);
// cvNamedWindow("H-S-V Histogram", 0);
// cvShowImage("H-S-V Histogram", hist_img);
// cvWaitKey(0);
//}