基本概念
直方图是对数据进行统计的一种方法, 可以直观表现图像某属性的数值(频率)分布情况, 包括灰度直方图、RGB直方图等
数字直方图
图像直方图
相关概念
函数原型
CV_EXPORTS 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 );
/** @overload
this variant uses cv::SparseMat for output
*/
CV_EXPORTS void calcHist( const Mat* images, int nimages,
const int* channels, InputArray mask,
SparseMat& hist, int dims,
const int* histSize, const float** ranges,
bool uniform = true, bool accumulate = false );
1.dims: 需要统计得特征的数目,
只统计灰度值—dims=1
统计RGB值—dims=3
2.bins: 每个特征空间子区域段的数目,也
可称为组距(简单理解为直方图分
成几个柱子组成)
3.range: 每个特征空间的取值范围
如灰度值取值范围[0, 255]
4.计算直方图函数: calcHist()
images: const Mat* 类型, 输入数组(或数组集), 需要有相同的深度和尺寸
nimages: 输入数组的个数, 即第一个参数中存放图像个数
channels: 需要统计通道的索引, 表示要使用哪个通过或多个通道(属性)
mask: 可选的操作掩码, 如果不为空, 则必须为8位, 并且与图像有一样大小尺寸
hist: 输出的目标直方图
dims: 需要计算的直方图维度, 必须是正数
histSize: 存放每个维度的直方图尺寸的数组, 即bins
ranges: 表示每一维数值的取值范围
uniform: 直方图是否均匀的标识符, 默认值true
accumulate: 累计标识符, 默认值false, 若为true, 直方图在配置阶段不会被清零
代码一—-一维灰度直方图
#include "mainwindow.h"
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//一维灰度直方图
Mat img = imread("D:\\1\\1.png", 0); //以灰度方式打开
imshow("src", img);
Mat dstHist; //定义存储直方图变量
int dims = 1; //需要统计的特征数目(只统计灰度值)
float hranges[] = {0, 256}; //范围[0,256)注意是最大值加1
const float* ranges[] = {hranges};
int bins = 256;
int channels = 0;
//计算直方图
calcHist(&img, 1, &channels, Mat(), dstHist, dims, &bins, ranges);
int scale = 1;
Mat dstImg(bins * scale, bins*3, CV_8UC3, Scalar(0)); //定义直方图输出图像
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
int hpt = saturate_cast<int>(0.9*bins); //设置最大值并防止溢出
int j=0;
for(int i=0; i<256; i++)
{
float binValue = dstHist.at<float>(i);
//if(i>250)
cout<<"i="<<i<<"---Value="<<binValue<<endl;
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //归一化数据
//cout<<"i="<<i<<"---Value="<<realValue<<endl;
//line(dstImg, Point(i*scale, bins-1), Point(i*scale, bins-realValue), Scalar(0, 255, 0), 1, 8);
//rectangle(dstImg, Point(i*scale, bins-1), Point((i+1)*scale-1, bins-realValue), Scalar(0, 255, 0), 1);
//rectangle(dstImg, Point(j*scale, size-1), Point((j+1)*scale-1, size-realValue), Scalar(rand()%255, rand()%255, rand()%255), -1);
rectangle(dstImg, Point(j*scale, bins-1), Point((j+2)*scale-1, bins-realValue), Scalar(rand()%255, rand()%255, rand()%255), -1);
j=j+3;
}
imshow("Histogram", dstImg);
waitKey(0);
destroyAllWindows();
}
MainWindow::~MainWindow()
{
}
代码二—不均匀灰度直方图
#include "mainwindow.h"
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//不均匀直方图绘制
Mat img = imread("D:\\1\\1.png", 0); //以灰度方式打开
imshow("src", img);
Mat dstHist; //定义存储直方图变量
int channels = 0;
int histSize[1]={5};
float hranges[6]={0, 50, 100, 150, 200, 256};
const float* ranges[1]={hranges};
int size = 256;
//计算直方图
calcHist(&img, 1, &channels, Mat(), dstHist, 1, histSize, ranges, false);
int scale = 1;
Mat dstImg(size * scale, size, CV_8UC3, Scalar(0));
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
int hpt = saturate_cast<int>(0.9*size); //设置最大值并防止溢出
int j=0;
for(int i=0; i<5; i++)
{
float binValue = dstHist.at<float>(i);
//if(i>250)
cout<<"i="<<i<<"---Value="<<binValue<<endl;
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //
//cout<<"i="<<i<<"---Value="<<realValue<<endl;
//line(dstImg, Point(i*scale, size-1), Point(i*scale, size-realValue), Scalar(0, 255, 0), 1, 8);
//rectangle(dstImg, Point(i*scale, size-1), Point((i+1)*scale-1, size-realValue), Scalar(0, 255, 0), 1);
//rectangle(dstImg, Point(j*scale, size-1), Point((j+1)*scale-1, size-realValue), Scalar(rand()%255, rand()%255, rand()%255), -1);
rectangle(dstImg, Point(j*scale, size-1), Point((j+20)*scale-1, size-realValue), Scalar(0, 255, 0), -1);
//rectangle(dstImg, Point(j*scale, size-1), Point((j+2)*scale-1, size-realValue), Scalar(rand()%255, rand()%255, rand()%255), -1);
j=j+35;
}
imshow("Histogram", dstImg);
waitKey(0);
destroyAllWindows();
}
MainWindow::~MainWindow()
{
}
代码三—RGB三色直方图绘制
#include "mainwindow.h"
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
Mat src;
src=imread("D://1//1.png");
int bins = 256;
int hist_size[] = {bins};
float range[] = { 0, 256 };
const float* ranges[] = { range};
Mat hist_r,hist_g,hist_b;
int channels_r[] = {2};
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[] = {0};
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+5, 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( "src", src );
imshow( "RGB Histogram", hist_img );
waitKey(0);
destroyAllWindows();
}
MainWindow::~MainWindow()
{
}
代码四—二维直方图绘制
#include "mainwindow.h"
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//二维直方图绘制
Mat src,hsv;
src=imread("D:\\1\\1.png");
int Bbins = 256, Gbins = 256;
int histSize[] = {Bbins, Gbins};
float hranges[] = { 0, 256 };
float sranges[] = { 0, 256 };
const float* ranges[] = { hranges, sranges };
Mat hist;
int channels[] = {0, 1};
calcHist( &src, 1, channels, Mat(), hist, 2, histSize, ranges);
double maxVal=0;
minMaxLoc(hist, 0, &maxVal, 0, 0);
int scale = 2;
Mat histImg = Mat::zeros(Bbins*(scale), Bbins*(scale), CV_8UC3);
for( int h = 0; h < Bbins; h++ )
for( int s = 0; s < Bbins; s++ )
{
float binVal = hist.at<float>(h, s);
//cout<<"h="<<h<<"s="<<s<<"binValue="<<binVal<<endl;
//int intensity = cvRound(binVal*255/maxVal);
int intensity = int(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( "B-G Histogram", 1 );
imshow( "B-G Histogram", histImg );
waitKey(0);
destroyAllWindows();
}
MainWindow::~MainWindow()
{
}
直方图均衡化
直方图均衡化是灰度变换的一个重要应用, 它是通过拉伸像素强度分布范围来增强图像对比度的一种方法, 广泛应用于图像增强处理中。
函数原型
void equalizeHist( InputArray src, OutputArray dst );
代码1—灰度直方图均衡化
Mat srcImg = imread("D:\\1\\1.png", 0);
imshow("src", srcImg);
MatND dstHist; //定义存储直方图变量
int dims = 1; //需要统计的特征数目(只统计灰度值)
float hranges[] = {0, 256}; //范围[0,256)注意是最大值加1
const float* ranges[] = {hranges};
int bins = 256;
int channels = 0;
//计算直方图
calcHist(&srcImg, 1, &channels, Mat(), dstHist, dims, &bins, ranges);
int scale = 1;
Mat HistImg(bins * scale, bins*1, CV_8UC3, Scalar(0)); //定义直方图输出图像
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
int hpt = saturate_cast<int>(0.9*bins); //设置最大值并防止溢出
int j=0;
for(int i=0; i<256; i++)
{
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //归一化数据
line(HistImg, Point(i*scale, bins-1), Point(i*scale, bins-realValue), Scalar(0, 255, 0), 1, 8);
}
imshow("src_hist", HistImg);
Mat dstImg; //均衡化后的图像
equalizeHist(srcImg, dstImg);
imshow("dst", dstImg);
MatND dstHist2; //定义存储直方图变量
//计算直方图
calcHist(&dstImg, 1, &channels, Mat(), dstHist2, dims, &bins, ranges);
Mat HistImg2(bins * scale, bins*1, CV_8UC3, Scalar(0)); //定义直方图输出图像
minMaxLoc(dstHist2, &minValue, &maxValue, 0, 0);
for(int i=0; i<256; i++)
{
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //归一化数据
line(HistImg2, Point(i*scale, bins-1), Point(i*scale, bins-realValue), Scalar(0, 255, 0), 1, 8);
}
imshow("dst_hist", HistImg2);
waitKey(0);
代码一的运行结果
代码二—彩色直方图均衡化
Mat src = imread("D:\\1\\1.png");
imshow("src", src);
vector<Mat>channels;
split(src, channels);
Mat blueChannel, greenChannel, redChannel, dst;
blueChannel = channels.at(0);
greenChannel = channels.at(1);
redChannel = channels.at(2);
//分别对BGR通道做直方图均衡化
equalizeHist(blueChannel, blueChannel);
equalizeHist(greenChannel, greenChannel);
equalizeHist(redChannel, redChannel);
merge(channels, dst);
imshow( "dst", dst );
imwrite("dst.jpg", dst);
waitKey(0);
destroyAllWindows();
代码二–运行结果
直方图对比
直方图对比就是根据一定的标准来比较两幅图像的直方图的相似度, 近而确定图像的相似度, opencv提供对比直方图相似度的函数为: compareHist()
函数原型
CV_EXPORTS_W double compareHist( InputArray H1, InputArray H2, int method );
/** @overload */
CV_EXPORTS double compareHist( const SparseMat& H1, const SparseMat& H2, int method );
- H1: 需要比较的直方图1
- H2: 需要比较的直方图2
- method: 直方图对比的方法, 有如下四种:
CV_COMP_CORREL=0, —相关性方法(值越大匹配度越高)
CV_COMP_CHISQR=1, —卡方测量法(值越小匹配度越高)
CV_COMP_INTERSECT=2, —直方图相交法(值越大匹配度越高)
CV_COMP_BHATTACHARYYA =3, —Bhattacharyya测量法(小)
代码
Mat src1 = imread("a.jpg");
Mat src2 = imread("b.jpg");
imshow("src1", src1);
imshow("src2", src2);
MatND dstHist; //定义存储直方图变量
int dims = 1; //需要统计的特征数目(只统计灰度值)
float hranges[] = {0, 256}; //范围[0,256)注意是最大值加1
const float* ranges[] = {hranges};
int bins = 256;
int channels = 0;
//计算直方图
calcHist(&src1, 1, &channels, Mat(), dstHist, dims, &bins, ranges);
int scale = 1;
Mat HistImg(bins * scale, bins*1, CV_8UC3, Scalar(0)); //定义直方图输出图像
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0);
int hpt = saturate_cast<int>(0.9*bins); //设置最大值并防止溢出
int j=0;
for(int i=0; i<256; i++)
{
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //归一化数据
line(HistImg, Point(i*scale, bins-1), Point(i*scale, bins-realValue), Scalar(0, 255, 0), 1, 8);
}
imshow("src1_hist", HistImg);
MatND dstHist2; //定义存储直方图变量
//计算直方图
calcHist(&src2, 1, &channels, Mat(), dstHist2, dims, &bins, ranges);
Mat HistImg2(bins * scale, bins*1, CV_8UC3, Scalar(0)); //定义直方图输出图像
minMaxLoc(dstHist2, &minValue, &maxValue, 0, 0);
for(int i=0; i<256; i++)
{
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>(binValue*hpt/maxValue); //归一化数据
line(HistImg2, Point(i*scale, bins-1), Point(i*scale, bins-realValue), Scalar(0, 255, 0), 1, 8);
}
imshow("src2_hist", HistImg2);
//CV_COMP_CORREL =0,
//CV_COMP_CHISQR =1,
//CV_COMP_INTERSECT =2,
//CV_COMP_BHATTACHARYYA =3,
double matchValue0 = compareHist(dstHist, dstHist2, CV_COMP_CORREL); //值越大相似度越高
double matchValue1 = compareHist(dstHist, dstHist2, CV_COMP_CHISQR); //值越小相似度越高
double matchValue2 = compareHist(dstHist, dstHist2, CV_COMP_INTERSECT); //值越大相似度越高
double matchValue3 = compareHist(dstHist, dstHist2, CV_COMP_BHATTACHARYYA); //值越小相似度越高
cout<<"matchValue0(max_best)="<<matchValue0<<endl;
cout<<"matchValue1(min_best)="<<matchValue1<<endl;
cout<<"matchValue2(max_best)="<<matchValue2<<endl;
cout<<"matchValue3(min_best)="<<matchValue3<<endl;
反向投影
反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式, 简单来说, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在该特征的方法。
例如, 你有一个肤色直方图 ( Hue-Saturation 直方图 ),你可以用它来寻找图像中的肤色区域, 具体原理:
假设你已经通过下图得到一个肤色直方图(Hue-Saturation),旁边的直方图就是 模型直方图 ( 代表手掌的皮肤色调).你可以通过掩码操作来抓取手掌所在区域的直方图:
右图是另一张手掌图(测试图像) 以及对应的整张图像的直方图
步骤
我们要做的就是使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤:
①对测试图像中的每个像素 (p(I,j)),获取色调数据并找到该色调(hij, sij )在直方图中的bin的位置。
②查询 模型直方图 中对应的bin - - 并读取该bin的数值。
③将此数值储存在新的图像中(BackProjection)。你也可以先归一化 模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
④通过对测试图像中的每个像素采用以上步骤, 我们得到了下面的 BackProjection 结果图:
⑤使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率 。比如以上图为例, 亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
反向投影的作用
反向投影应用在输入图像(大)中查找与特定图像(模板图像), 最匹配的点或者区域, 也就是定位模板图像出现在输入图像的位置
函数原型
CV_EXPORTS void calcBackProject( const Mat* images, int nimages,
const int* channels, const SparseMat& hist,
OutputArray backProject, const float** ranges,
double scale = 1, bool uniform = true );
/** @overload */
CV_EXPORTS_W void calcBackProject( InputArrayOfArrays images, const std::vector<int>& channels,
InputArray hist, OutputArray dst,
const std::vector<float>& ranges,
double scale );
-images: 输入数组或数组集, 需要为相同深度和尺寸
- nimages: 输入数组个数, 也就是图像数量
- channels: 需要统计的通道索引
- hist: 输入的直方图
- backProject: 目标反向投影阵列, 需为单通道, 并且和image[0]有相同大小和深度
- ranges: 表示每一维度数组的每一维的边界阵列(取值范围)
- uniform: 直方图是否均匀标识, 默认值true
代码
#include "mainwindow.h"
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
#define WINDOW_NAME1 "inital Image"
Mat g_srcImage; Mat g_hsvImage; Mat g_hueImage;
int g_bins = 30;//直方图组距
void on_BinChange(int, void* );
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//反向投影
g_srcImage = imread( "D:\\1\\1.png", 1 );
//cvResize(g_srcImage, g_srcImage, Size(g_srcImage.cols/4, g_srcImage.rows/4));
cvtColor( g_srcImage, g_hsvImage, CV_BGR2HSV );
g_hueImage.create( g_hsvImage.size(), g_hsvImage.depth() );
int ch[ ] = { 0, 0 };
mixChannels( &g_hsvImage, 1, &g_hueImage, 1, ch, 1 );
namedWindow( WINDOW_NAME1 , CV_WINDOW_AUTOSIZE );
createTrackbar("色调组距 ", WINDOW_NAME1 , &g_bins, 180, on_BinChange );
on_BinChange(0, 0);
imshow( WINDOW_NAME1 , g_srcImage );
//waitKey(0);
}
void on_BinChange(int, void* )
{
MatND hist;
int histSize = MAX( g_bins, 2 );
float hue_range[] = { 0, 180 };
const float* ranges = { hue_range };
calcHist( &g_hueImage, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
MatND backproj;
calcBackProject( &g_hueImage, 1, 0, hist, backproj, &ranges, 1, true );
imshow( "TouYingTu", backproj );
int w = 400; int h = 400;
int bin_w = cvRound( (double) w / histSize );
Mat histImg = Mat::zeros( w, h, CV_8UC3 );
for( int i = 0; i < g_bins; i ++ )
{
rectangle( histImg, Point( i*bin_w, h ), Point( (i+1)*bin_w, h - cvRound( hist.at<float>(i)*h/255.0 ) ), Scalar( 100, 123, 255 ), -1 );
}
imshow( "histImage", histImg );
}
MainWindow::~MainWindow()
{
}