图像直方图比较
图像直方图比较,就是计算两幅图像的直方图数据,比较两组数据的相似性,从而得到两幅图像之间的相似程度,直方图比较在早期的CBIR中是应用很常见的技术手段,通常会结合边缘处理、词袋等技术一起使用。其实现原理为:对输入的两张图像进行计算得到直方图H1与H2,将它们归一化到相同的尺度空间,然后通过计算H1与H2之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。OpenCV中提供的比较方法有四种:
- Correlation 相关性比较
- Chi - Square 卡方比较
- Intersection 十字交叉性
- Bhattacharyya distance 巴氏距离
进行直方图比较的步骤如下:
- 首先把图像从RGB色彩空间转换到HSV色彩空间,使用函数为 cvtColor();
- 计算图像的H和S通道的直方图,然后归一化到[0~1]之间,使用的函数为 calcHist() 和 normalize();
- 进行直方图比较,使用的函数为 compareHist(),可使用上述四种比较方法之一进行比较,相关公式如下图:
介绍下OpenCV中的API函数:
CV_EXPORTS_W double compareHist( InputArray H1, InputArray H2, int method );
其中,
- H1 为直方图矩阵图像,一般需要先做归一化处理;
- H2 为待比较的直方图矩阵图像,大小深度等与H1 相同;
- method 为比较方式,总共有如上所示的4种;
直方图比较的测试效果如下图所示:
这里我使用的是 Correlation 比较的方法,该方法对比较的两张图像有如下定义:-1 表示完全不匹配、0表示无关联、1表示完全匹配。从图中可以看出数字5的两幅图片匹配度很高为1,表示这两幅图几乎完全相似,而lena人像与猫的图像的匹配度很接近0,说明这两幅图像没有关联性。
图像直方图反向投影
图像直方图的反向投影是一种首先寻找某一特征的直方图模型,然后根据这个模型去寻找图像中是否存在这个特征的解决方案。反向投影储存的亮度值,代表测试图像中该像素属于某个特征的概率,也就是说,亮度值相同的位置,属于同一个特征的概率越大,亮起的地方概率更大,内部和边缘之间的阴影影响了检测的精度。反向投影的作用是在输入图像中寻找特定图像中最匹配的点或者区域,也就是定位模版图像在输入图像的位置。投影的结果以每个输入图像像素为起点的直方图对比结果,可以看作是单通道浮点型图像,或者是一个二维的概率数组集合。
简单理解如下:
假设一张 5 × 5 图像的灰度图矩阵为
I
m
a
g
e
=
[
0
1
2
3
4
4
5
6
7
8
8
9
10
11
12
8
9
3
1
14
]
Image= \left[ \begin{matrix} 0 & 1 & 2 & 3 & 4 \\ 4 & 5 & 6 & 7 & 8 \\ 8 & 9 & 10 & 11 & 12 \\ 8 & 9 & 3 & 1 & 14 \end{matrix} \right]
Image=⎣⎢⎢⎡048815992610337111481214⎦⎥⎥⎤
假设 bin 指定的区间为[0,3],[4,7],[8,11],[12,15] 则该 5×5 图像灰度图的直方图为
H
i
s
t
o
g
r
a
m
=
[
6
5
7
2
]
Histogram= \left[ \begin{matrix} 6 & 5 & 7 & 2 \\ \end{matrix} \right]
Histogram=[6572]
该 5×5 图像的反向投影图为
B
a
c
k
P
r
o
j
e
c
t
i
o
n
=
[
6
6
6
6
5
5
5
5
5
7
7
7
7
7
2
7
7
6
6
2
]
Back_Projection= \left[ \begin{matrix} 6 & 6 & 6 & 6 & 5 \\ 5 & 5 & 5 & 5 & 7 \\ 7 & 7 & 7 & 7 & 2 \\ 7 & 7 & 6 & 6 & 2 \end{matrix} \right]
BackProjection=⎣⎢⎢⎡65776577657665765722⎦⎥⎥⎤
例如位置(0,0)上的像素值为0,对应的bin为[0,3],所以反向直方图在该位置上的值这个bin的值6。可以看出,反向投影实际上是将原图像的256个灰度值替换为直方图各bin中对应的值,具体值为多少要看把0~255划分为多少个区间!这里只划分了4个bin。反向投影矩阵中某点的值就是它对应的原图像中的点所在区间的灰度直方图值。所以我们可以看出,一个区间点越多,在反向投影矩阵中就越亮。
反向投影在OpenCV中的API函数为:
CV_EXPORTS void calcBackProject( const Mat* images, int nimages,
const int* channels, InputArray hist,
OutputArray backProject, const float** ranges,
double scale = 1, bool uniform = true );
其中,
- images 为输入图像,图像深度必须位CV_8U, CV_16U或CV_32F中的一种;
- images 为输入图像的数量;
- channels 为用于计算反向投影的通道列表,通道数必须与直方图维度相匹配;
- hist 为输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse);
- backProject 为目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度;
- ranges为直方图中每个维度bin的取值范围;
- scale 为输出反向投影的比例因子;
- uniform 为直方图是否均匀分布(uniform)的标识符,有默认值true;
反向投影的测试效果如下图所示:
图像直方图模板匹配
从一幅图像中寻找和模版最相似的部分的技术叫做模版匹配,但并不是基于直方图的匹配技术,而是类似图像卷积操作一样,通过在输入图像上滑动模板图像,对实际的图像块和输入图像进行匹配的一种匹配方法。
OpenCV中的API函数为:
CV_EXPORTS_W void matchTemplate( InputArray image, InputArray templ,
OutputArray result, int method, InputArray mask = noArray() );
其中,
- image 为 输入图像;
- templ 为模版图像;
- result 为匹配结果的映射图像;
- method 为指定的匹配方法;
- mask 为图像掩模,默认为0;
这里重点说明下 method 的类型:
cv::TM_SQDIFF:该方法使用平方差进行匹配,因此最佳的匹配结果在结果为0处,值越大匹配结果越差。
cv::TM_SQDIFF_NORMED:该方法使用归一化的平方差进行匹配,最佳匹配也在结果为0处。
cv::TM_CCORR:相关性匹配方法,该方法使用源图像与模板图像的卷积结果进行匹配,因此,最佳匹配位置在值最大处,值越小匹配结果越差。
cv::TM_CCORR_NORMED:归一化的相关性匹配方法,与相关性匹配方法类似,最佳匹配位置也是在值最大处。
cv::TM_CCOEFF:相关性系数匹配方法,该方法使用源图像与其均值的差、模板与其均值的差二者之间的相关性进行匹配,最佳匹配结果在值等于1处,最差匹配结果在值等于-1处,值等于0直接表示二者不相关。
cv::TM_CCOEFF_NORMED:归一化的相关性系数匹配方法,正值表示匹配的结果较好,负值则表示匹配的效果较差,也是值越大,匹配效果也好。
模板匹配的测试效果如下图所示:
代码实现
// HistogramCompare.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
//直方图比较
void HistogramCompare(void)
{
Mat src1 = imread("figure1.jpg");
Mat src2 = imread("figure2.jpg");
Mat src3 = imread("human.jpg");
Mat src4 = imread("cat.jpg");
namedWindow("input1", WINDOW_AUTOSIZE);
namedWindow("input2", WINDOW_AUTOSIZE);
namedWindow("input3", WINDOW_AUTOSIZE);
namedWindow("input4", WINDOW_AUTOSIZE);
imshow("input1", src1);
imshow("input2", src2);
imshow("input3", src3);
imshow("input4", src4);
Mat hsv1, hsv2, hsv3, hsv4;
cvtColor(src1, hsv1, COLOR_BGR2HSV);
cvtColor(src2, hsv2, COLOR_BGR2HSV);
cvtColor(src3, hsv3, COLOR_BGR2HSV);
cvtColor(src4, hsv4, COLOR_BGR2HSV);
int h_bins = 60; int s_bins = 64;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
Mat hist1, hist2, hist3, hist4;
calcHist(&hsv1, 1, channels, Mat(), hist1, 2, histSize, ranges, true, false);
calcHist(&hsv2, 1, channels, Mat(), hist2, 2, histSize, ranges, true, false);
calcHist(&hsv3, 1, channels, Mat(), hist3, 2, histSize, ranges, true, false);
calcHist(&hsv4, 1, channels, Mat(), hist4, 2, histSize, ranges, true, false);
normalize(hist1, hist1, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist2, hist2, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist3, hist3, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist4, hist4, 0, 1, NORM_MINMAX, -1, Mat());
double src1_src2 = compareHist(hist1, hist2, HISTCMP_CORREL);
double src3_src4 = compareHist(hist3, hist4, HISTCMP_CORREL);
printf("Method_HISTCMP_CORREL: src1_src2: %f, src3_src4: %f, \n", src1_src2, src3_src4);
}
//反向投影
void BackProjection(void)
{
Mat srcImage = imread("cat.jpg");
Mat hsvImage, backProjImage;
cvtColor(srcImage, hsvImage, COLOR_BGR2HSV);
int h_bins = 32; int s_bins = 32;
int histSize[] = { h_bins, s_bins };
float h_ranges[] = { 0, 180 };
float s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
Mat hist;
calcHist(&hsvImage, 1, channels, Mat(), hist, 2, histSize, ranges, true, false);
normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat());
calcBackProject(&hsvImage, 1, channels, hist, backProjImage, ranges, 1.0);
namedWindow("src", WINDOW_AUTOSIZE);
namedWindow("backProjection", WINDOW_AUTOSIZE);
imshow("src", srcImage);
imshow("backProjection", backProjImage);
}
//模板匹配
void TemplateMatch(void)
{
Mat SrcImage, TemplateImage, ResultImage;
SrcImage = imread("cat.jpg");
TemplateImage = imread("Template.jpg");
matchTemplate(SrcImage, TemplateImage, ResultImage, TM_CCOEFF_NORMED);
normalize(ResultImage, ResultImage, -1, 1, NORM_MINMAX);
double MinVal = 0, MaxVal = 0;
Point MinLoc(0, 0), MaxLoc(0, 0);
minMaxLoc(ResultImage, &MinVal, &MaxVal, &MinLoc, &MaxLoc);
circle(SrcImage, Point(MaxLoc.x + TemplateImage.cols / 2, MaxLoc.y + TemplateImage.rows / 2), 30, Scalar(0, 0, 255), 2, 8);
namedWindow("src", WINDOW_AUTOSIZE);
namedWindow("Template", WINDOW_AUTOSIZE);
namedWindow("Result_CCOEFF_NORMED", WINDOW_AUTOSIZE);
imshow("src", SrcImage);
imshow("Template", TemplateImage);
imshow("Result_CCOEFF_NORMED", ResultImage);
}
//获取ROI图片
void GetROIImage(void)
{
Mat SrcImage = imread("cat.jpg");
int Height = SrcImage.rows;
int Width = SrcImage.cols;
Rect rect(Width / 2 - 40, Height / 6 - 40, 100, 100);
Mat ROIImage = SrcImage(rect);
//ROIImage.setTo(Scalar(50, 0, 0));
namedWindow("src", WINDOW_AUTOSIZE);
namedWindow("roi", WINDOW_AUTOSIZE);
imshow("roi", ROIImage);
imshow("src", SrcImage);
//imwrite("Template.jpg", ROIImage);
}
int main(int artc, char** argv)
{
//GetROIImage();
//HistogramCompare();
//BackProjection();
TemplateMatch();
while (true)
{
if (waitKey(10) == 27)
break;
}
destroyAllWindows();
return 0;
}