文章目录
前言
在进行物体图像和视频信息分析的过程中,我们常常会习惯于将眼中看到的物体用于直方图(histogram)表示出来,得到比较直观的数据感官展示,直方图可以用来描述各种不同的参数和事物,如物体的色彩分布、物体边缘梯度模板,以及表示目标位置的当前假设的概率分布。
- 什么是直方图
- 直方图的计算与绘制
- 如何进行直方图的对比
- 反向投影技术
- 模板匹配技术
1、图像直方图
直方图就是对数据进行统计的一种方法,在统计学中,直方图(Histogram)是一种对数据分布情况的图形表示,是一种二维统计表。
图像直方图(Image Histogram)是用以表示数字图像中亮度分布的直方图,标绘了图像中每个亮度值的像素数。可借助观察该直方图了解需要如何调整亮度分布。 这种直方图中,横坐标的左侧为纯黑、较暗的区域,而右侧为较亮、纯白的区域。因此,一张较暗图片的图像直方图中的数据多集中于左侧和中间部分,而整体明亮、只用少量阴影的图像则相反。计算机领域常借助图像直方图来实现图像的二值化。
直方图的意义:
- 直方图是图像中像素强度分布的图形表达方式。
- 它统计了每一个强度值所具有的像素个数
2、直方图的计算与绘制
直方图的计算在OpenCV中可以使用calHist()函数,而计算完成之后,可以采用OpenCV中的绘图函数,如绘制矩形的rectangle()函数,绘制线段的line()来完成。
(1) H-S二维直方图的绘制
#include <opencv2/opencv.hpp>
using namespace cv;
static void test()
{
//【1】载入源图,转化为HSV颜色模型
Mat srcImage, hsvImage;
srcImage = imread("1.jpg");
cvtColor(srcImage, hsvImage, COLOR_BGR2HSV);
system("color 2F");
//【2】参数准备
//将色调量化为30个等级,将饱和度量化为32个等级
int hueBinNum = 30;//色调的直方图直条数量
int saturationBinNum = 32;//饱和度的直方图直条数量
int histSize[] = { hueBinNum, saturationBinNum };
// 定义色调的变化范围为0到179
float hueRanges[] = { 0, 180 };
//定义饱和度的变化范围为0(黑、白、灰)到255(纯光谱颜色)
float saturationRanges[] = { 0, 256 };
const float* ranges[] = { hueRanges, saturationRanges };
MatND dstHist;
//参数准备,calcHist函数中将计算第0通道和第1通道的直方图
int channels[] = { 0, 1 };
//【3】正式调用calcHist,进行直方图计算
calcHist(&hsvImage,//输入的数组
1, //数组个数为1
channels,//通道索引
Mat(), //不使用掩膜
dstHist, //输出的目标直方图
2, //需要计算的直方图的维度为2
histSize, //存放每个维度的直方图尺寸的数组
ranges,//每一维数值的取值范围数组
true, // 指示直方图是否均匀的标识符,true表示均匀的直方图
false);//累计标识符,false表示直方图在配置阶段会被清零
//【4】为绘制直方图准备参数
double maxValue = 0;//最大值
minMaxLoc(dstHist, 0, &maxValue, 0, 0);//查找数组和子数组的全局最小值和最大值存入maxValue中
int scale = 10;
Mat histImg = Mat::zeros(saturationBinNum*scale, hueBinNum * 10, CV_8UC3);
//【5】双层循环,进行直方图绘制
for (int hue = 0; hue < hueBinNum; hue++)
for (int saturation = 0; saturation < saturationBinNum; saturation++)
{
float binValue = dstHist.at<float>(hue, saturation);//直方图组距的值
int intensity = cvRound(binValue * 255 / maxValue);//强度
//正式进行绘制
rectangle(histImg, Point(hue*scale, saturation*scale),
Point((hue + 1)*scale - 1, (saturation + 1)*scale - 1),
Scalar::all(intensity), FILLED);
}
//【6】显示效果图
imshow("素材图", srcImage);
imshow("H-S 直方图", histImg);
waitKey();
}
int main()
{
test();
system("pause");
return 0;
}
结果
(2) 一维直方图的绘制
#include<opencv2/opencv.hpp>
using namespace cv;
static void test()
{
//1、载入图像,并显示
std::string imagePath = "80.jpg";
std::string windowNameSrc = "原图";
const Mat srcImage = imread(imagePath);
imshow(windowNameSrc, srcImage);
if (!srcImage.data) {
std::cout << "fail to load image" << std::endl;
return;
}
//2、定义变量
MatND dstHist; //在cv中用CvHistogram * hist = cvCreateHist
int dims = 1;
float hranges[] = { 0,255 };
const float *ranges[] = { hranges }; //这里需要为const类型
int size = 256;
int channels = 0;
//3、计算图形的直方图
calcHist(&srcImage, //输入数组
1, //数组个数
&channels, //通道索引
Mat(), //不使用掩码
dstHist, //输出的目标直方图
dims, //需要计算的直方图的维度为1
&size, //存放每个维度的直方图尺寸的数组
ranges //每一维数值的取值范围数组
);
int scale = 1;
Mat dstImage(size*scale, size, CV_8U, Scalar(0));
//4、获取最大值和最小值
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist, &minValue, &maxValue, 0, 0); //在cv中用到是cvGetMinMaxHistValue
//5、绘制出直方图
int hpt = saturate_cast<int>(0.9*size);
for (int i = 0; i < 256; i++) {
float binValue = dstHist.at<float>(i);
int realValue = saturate_cast<int>(binValue*hpt / maxValue);
rectangle(dstImage, Point(i*scale, size - 1), Point((i + 1)*scale - 1, size - realValue), Scalar(255));
}
std::string windowNameDst = "一维直方图";
imshow(windowNameDst,dstImage);
waitKey(0);
}
int main()
{
test();
system("pause");
return 0;
}
结果
(3) RGB三色直方图的绘制
#include<opencv2/opencv.hpp>
using namespace cv;
static void test()
{
//1、载入素材图并显示
std::string path_image = "81.jpg";
std::string name_image_src = "素材图";
Mat srcImage = imread(path_image);
imshow(name_image_src, srcImage);
//2、参数准备
int bins = 256;
int hist_size[] = { bins };
float range[] = { 0,256 };
const float*ranges[] = { range };
MatND redHist, grayHist, blueHist;
int channels_r[] = { 0 };
//3、进行直方图的计算(红色部分)
calcHist(&srcImage, 1, channels_r, Mat(), redHist, 1, hist_size, ranges, true, false);
//4、进行直方图的计算(绿色部分)
int channels_g[] = { 1 };
calcHist(&srcImage, 1, channels_g, Mat(), grayHist, 1, hist_size, ranges, true, false);
//5、进行直方图的计算(蓝色部分)
int channels_b[] = { 2 };
calcHist(&srcImage, 1, channels_b, Mat(), blueHist, 1, hist_size, ranges, true, false);
//绘制出三色直方图
//参数准备
double maxValue_red, maxValue_green, maxValue_blue;
minMaxLoc(redHist, 0, &maxValue_red, 0, 0);
minMaxLoc(grayHist, 0, &maxValue_green, 0, 0);
minMaxLoc(blueHist, 0, &maxValue_blue, 0, 0);
int scale = 1;
int histHeight = 256;
Mat histImage = Mat::zeros(histHeight, bins * 3, CV_8UC3);
//正式开始绘制
for (int i = 0; i < bins; i++) {
//参数准备
float binValue_red = redHist.at<float>(i);
float binValue_green = grayHist.at<float>(i);
float binValue_blue = blueHist.at<float>(i);
//绘制高度
int intensity_red = cvRound(binValue_red*histHeight / maxValue_red);
int intensity_green = cvRound(binValue_green*histHeight / maxValue_green);
int intensity_blue = cvRound(binValue_blue*histHeight / maxValue_blue);
//绘制红色分量的直方图
rectangle(histImage,Point(i*scale,histHeight -1),
Point((i + 1)*scale -1,histHeight - intensity_red),Scalar(255, 0, 0));
//绘制绿色分量的直方图
rectangle(histImage, Point((i + bins)*scale, histHeight - 1),
Point((i + bins+1)*scale - 1, histHeight - intensity_green), Scalar(0, 255, 0));
//绘制蓝色分量的直方图
rectangle(histImage, Point((i + bins*2)*scale, histHeight - 1),
Point((i + bins*2 + 1)*scale - 1, histHeight - intensity_blue), Scalar(0, 0, 255));
}
//在窗口中显示出绘制好的直方图
std::string histogram_rgb = "图像的RGB直方图";
imshow(histogram_rgb, histImage);
waitKey(0);
}
int main()
{
test();
system("pause");
return 0;
}
结果
3、直方图对比
对于直方图来说,一个不可或缺的工具便是用某些具体的标准来比较两个直方图的相似度。
对比直方图: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 );
可采用四种方法比较
- 相关
- 卡方
3. 直方图相交
4. Bhattacharyya距离
直方图对比
#include<opencv2/opencv.hpp>
using namespace cv;
static void test()
{
//1、声明存储基准图像和另外两张对比图像的矩阵(RGB和HSV)
Mat srcImage_base, hsvImage_base;
Mat srcImage_test1, hsvImage_test1;
Mat srcImage_test2, hsvImage_test2;
Mat hsvImage_halfDown;
//2、载入基准图像(srcImage_base)和两张测试图像srcImage_test1,srcImage_test2,并显示
srcImage_base = imread("82_1.jpg", 1);
srcImage_test1 = imread("82_2.jpg", 1);
srcImage_test2 = imread("82_3.jpg", 1);
//显示载入的3张图像
imshow("基准图像", srcImage_base);
imshow("测试图像1", srcImage_test1);
imshow("测试图像2", srcImage_test2);
//3、将图像由BGR色彩空间转换到HSV色彩空间
cvtColor(srcImage_base, hsvImage_base, COLOR_BGR2HSV);
cvtColor(srcImage_test1, hsvImage_test1, COLOR_BGR2HSV);
cvtColor(srcImage_test2, hsvImage_test2, COLOR_BGR2HSV);
//4、创建包含基准图像下半部分的半身图像(HSV格式)
hsvImage_halfDown = hsvImage_base(Range(hsvImage_base.rows / 2, hsvImage_base.rows - 1), Range(0, hsvImage_base.cols - 1));
//5、初始化计算直方图需要的实参
//对hue通道使用30个bin,对saturation通道使用32个bin
int h_bins = 50, s_bins = 60;
int histSize[] = { h_bins,s_bins };
//hue的取值范围从0到256,saturation取值范围从0到180
float h_ranges[] = { 0,256 };
float s_ranges[] = { 0,180 };
const float* ranges[] = { h_ranges,s_ranges };
//使用第0和第1通道
int channels[] = { 0,1 };
//6、创建储存直方图的MatND类的实例
MatND baseHist, halfDownHist, testHist1, testHist2;
//7、计算基准图像,两张测试图像,半身基准图像的HSV直方图
calcHist(&hsvImage_base, 1, channels, Mat(), baseHist, 2, histSize, ranges, true, false);
normalize(baseHist, baseHist, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvImage_halfDown, 1, channels, Mat(), halfDownHist, 2, histSize, ranges, true, false);
normalize(halfDownHist, halfDownHist, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvImage_test1, 1, channels, Mat(), testHist1, 2, histSize, ranges, true, false);
normalize(testHist1, testHist1, 0, 1, NORM_MINMAX, -1, Mat());
calcHist(&hsvImage_test2, 1, channels, Mat(), testHist2, 2, histSize, ranges, true, false);
normalize(testHist2, testHist2, 0, 1, NORM_MINMAX, -1, Mat());
//8、按顺序使用4种对比标准将基准图像的直方图与其余直方图进行对比
for (int i = 0; i < 4; i++) {
//进行图像直方图的对比
int compare_method = i;
double base_base = compareHist(baseHist, baseHist, compare_method);
double base_half = compareHist(baseHist, halfDownHist, compare_method);
double base_test1 = compareHist(baseHist, testHist1, compare_method);
double base_test2 = compareHist(baseHist, testHist2, compare_method);
//输出结果
printf(" 方法 [%d] 的匹配结果如下:\n\n 【基准图 - 基准图】:%f, 【基准图 - 半身图】:%f,【基准图 - 测试图1】: %f, 【基准图 - 测试图2】:%f \n-----------------------------------------------------------------\n", i, base_base, base_half, base_test1, base_test2);
}
std::cout << "检测结束" << std::endl;
waitKey(0);
}
int main()
{
test();
system("pause");
return 0;
}
结果
4、反向投影
反向投影就是一种记录给定图像种的像素点如何适应直方图模型像素的分布,也就是首先计算一种特征的直方图模型,然后使用模型去寻找图像中存在的特征方法。
#include<opencv2/opencv.hpp>
#include<string>
using namespace cv;
const std::string window_name1 = "【原始图】";
Mat g_srcImage, g_hsvImage, g_hueImage;
int g_bins = 30; //直方图组距
void on_BinChange(int, void*);
static void test()
{
//1、读取原图像,并转换为HSV空间
g_srcImage = imread("83.jpg", 1);
if (!g_srcImage.data)
{
std::cout << "image not load \n";
return ;
}
cvtColor(g_srcImage, g_hsvImage,COLOR_BGR2HSV);
//2、分离Hue色调通道
g_hueImage.create(g_hsvImage.size(), g_hsvImage.depth());
int ch[] = { 0,0 };
mixChannels(&g_hsvImage, 1, &g_hueImage, 1, ch, 1);
//3、创建Trackbar来输入bin的数目
namedWindow(window_name1, WINDOW_AUTOSIZE);
createTrackbar("色调组距", window_name1, &g_bins, 180, on_BinChange);
on_BinChange(0, 0); //进行一次初始化
//4、显示效果图
imshow(window_name1, g_srcImage);
//等待用户按键
waitKey(0);
}
int main()
{
test();
system("pause");
return 0;
}
//响应滑动条移动消息的回调函数
void on_BinChange(int, void *)
{
//1、参数准备
MatND hist;
int histSize = MAX(g_bins, 2);
float hue_range[] = { 0,180 };
const float* ranges = { hue_range };
//2、计算直方图并归一化
calcHist(&g_hueImage, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false);
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());
//3、计算反向投影
MatND backproj;
calcBackProject(&g_hueImage, 1, 0, hist, backproj, &ranges, 1, true);
//4、显示反向投影
imshow("反向投影图", backproj);
//5、绘制直方图的参数准备
int w = 400, h = 400;
int bin_w = cvRound((double)w / histSize);
Mat histImg = Mat::zeros(w, h, CV_8UC3);
//6、绘制直方图
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);
}
//7、显示直方图窗口
imshow("直方图", histImg);
}
结果
5、模板匹配
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。在OpenCV2和OpenCV3中,模板匹配由MatchTemplate()函数完成
实现模板匹配:matchTemplate()函数
void matchTemplate(
InputArray image,
InputArray templ,
OutputArray result,
int method,
InputArray mask = noArray()
);
OpenCV为我们提供了 6种图像匹配的方法可供使用
#include<opencv2/opencv.hpp>
#include<string>
using namespace cv;
const std::string window_name1("【原始图片】");
const std::string window_name2("【效果窗口】");
static Mat g_srcImage, g_templateImage, g_resultImage;
int g_nMatchMethod, g_nMaxTrackbarNum = 5;
void on_Matching(int, void*);
static void ShowHelpText();
static void test()
{
ShowHelpText();
//1、载入原图和模板块
g_srcImage = imread("84_1.jpg", 1);
g_templateImage = imread("84_2.jpg", 1);
//2、创建窗口
namedWindow(window_name1, WINDOW_AUTOSIZE);
namedWindow(window_name2, WINDOW_AUTOSIZE);
//3、创建滑动条并进行一次初始化
createTrackbar("方法", window_name1, &g_nMatchMethod, g_nMaxTrackbarNum, on_Matching);
on_Matching(0, 0);
waitKey(0);
}
int main()
{
test();
system("pause");
return 0;
}
static void ShowHelpText()
{
printf("\n\n\t请调整滑动条观察图像效果\n\n");
printf("\n\t滑动条对应的方法数值说明: \n\n"
"\t\t方法【0】- 平方差匹配法(SQDIFF)\n"
"\t\t方法【1】- 归一化平方差匹配法(SQDIFF NORMED)\n"
"\t\t方法【2】- 相关匹配法(TM CCORR)\n"
"\t\t方法【3】- 归一化相关匹配法(TM CCORR NORMED)\n"
"\t\t方法【4】- 相关系数匹配法(TM COEFF)\n"
"\t\t方法【5】- 归一化相关系数匹配法(TM COEFF NORMED)\n");
}
void on_Matching(int, void *)
{
//1、给局部变量初始化
Mat srcImage;
g_srcImage.copyTo(srcImage);
//2、初始化用于结果输出的矩阵
int resultImage_cols = g_srcImage.cols - g_templateImage.cols + 1;
int resultImage_rows = g_srcImage.rows - g_templateImage.rows + 1;
g_resultImage.create(resultImage_cols, resultImage_rows, CV_32FC1);
//3、进行匹配和标准化
matchTemplate(g_srcImage, g_templateImage, g_resultImage, g_nMatchMethod);
normalize(g_resultImage, g_resultImage, 0, 1, NORM_MINMAX, -1, Mat());
//4、通过函数minMaxLoc 定位最匹配的位置
double minValue, maxValue;
Point minLocation, maxLocation, matchLocation;
minMaxLoc(g_resultImage, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
//5、对于方法SQDIFF和DQDIFF_NORMED,越小的数值有着更高的匹配结果,
//而其余的方法,数值越大匹配效果越好
if (g_nMatchMethod == TM_SQDIFF || g_nMatchMethod == TM_SQDIFF_NORMED)
matchLocation = minLocation;
else
matchLocation = maxLocation;
//6、绘制出矩形,并显示最终结果
rectangle(srcImage, matchLocation,
Point(matchLocation.x + g_templateImage.cols,
matchLocation.y + g_templateImage.rows),
Scalar(0, 0, 255), 2, 8, 0
);
rectangle(g_resultImage, matchLocation,
Point(matchLocation.x + g_templateImage.cols,
matchLocation.y + g_templateImage.rows),
Scalar(0, 0, 255), 2, 8, 0);
imshow(window_name1, srcImage);
imshow(window_name2, g_resultImage);
}