直方图比较,是用一定的标准来判断两个直方图的相似度方法;
OpenCV中提供的API是:
对输入的两张图像计算得到直方图H1与H2,归一化到相同的尺度空间然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。
Opencv提供的比较方法有四种:
1.Correlation 相关性比较
2.Chi - Square 卡方比较
3.Intersection 十字交叉性
4.Bhattacharyya distance 巴氏距离
相关系数的标准(method=CV_COMP_CORREL) 值越大,相关度越高,最大值为1,最小值为0
卡方系数的标准(method=CV_COMP_CHISQR) 值越小,相关度越高,最大值无上界,最小值0
相交系数的标准(method=CV_COMP_INTERSECT)值越大,相关度越高,最大值为9.455319,最小值为0
巴氏系数的标准(method=CV_COMP_BHATTACHARYYA) 值越小,相关度越高,最大值为1,最小值为0
步骤:首先把图像从RGB色彩空间转换到HSV色彩空间cvtColor
计算图像的直方图,然后归一化到[0~1]之间calcHist和normalize;
使用上述四种比较方法之一进行比较compareHist
任何一种颜色都是由红绿蓝三原色(RGB)构成的,所以上图共有4张直方图(三原色直方图 + 最后合成的直方图)。
如果每种原色都可以取256个值,那么整个颜色空间共有1600万种颜色(256的三次方)。针对这1600万种颜色比较直方图,计算量实在太大了,因此需要采用简化方法。可以将0~255分成四个区:0~63为第0区,64~127为第1区,128~191为第2区,192~255为第3区。这意味着红绿蓝分别有4个区,总共可以构成64种组合(4的3次方)。
任何一种颜色必然属于这64种组合中的一种,这样就可以统计每一种组合包含的像素数量。
上图是某张图片的颜色分布表,将表中最后一栏提取出来,组成一个64维向量(7414, 230, 0, 0, 8, …, 109, 0, 0, 3415, 53929)。这个向量就是这张图片的特征值或者叫"指纹"。
于是,寻找相似图片就变成了找出与其最相似的向量。
优点:根据像素进行匹配,对于分辨率不同的图片效果较好,实验数据如下。
缺点:对于不同图片,但是相似,或是些许形变的图片,匹配效果并不好。
//直方图比较
#include
#include
#include
#include
using namespace cv;
using namespace std;
string convertToString(double d);
int main(int argc, char* argv)
{
Mat base, test1, test2;
Mat hsvbase, hsvtest1, hsvtest2;
base = imread("C:/Users/wenhaofu/Desktop/picture/NBA1.png");
test1 = imread("C:/Users/wenhaofu/Desktop/picture/NBA2.png");
test2 = imread("C:/Users/wenhaofu/Desktop/picture/NBA3.png");
if (!base.data)
{
printf("could not load image...\n");
return -1;
}
//步骤一:从RGB空间转换到HSV空间
cvtColor(base, hsvbase, CV_BGR2HSV);
cvtColor(test1, hsvtest1, CV_BGR2HSV);
cvtColor(test2, hsvtest2, CV_BGR2HSV);
//步骤二:计算直方图与归一化
int h_bins = 50;
int s_bins = 60;
int histsize[] = { h_bins,s_bins };
//hue varies from 0 to 179,saturation from 0 to 255
float h_ranges[] = { 0,180 };
float s_ranges[] = { 0,256 };
const float* histRanges[] = { h_ranges,s_ranges };
//use the 0-th and 1-st channels
int channels[] = { 0,1 };
MatND hist_base;
MatND hist_test1;
MatND hist_test2;
//计算直方图
calcHist(&hsvbase, 1, channels, Mat(), hist_base, 2, histsize, histRanges, true, false);
calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histsize, histRanges, true, false);
calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histsize, histRanges, true, false);
//归一化
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());//归一化
normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
//步骤三:比较直方图,并返回值
double basebase = compareHist(hist_base, hist_base, CV_COMP_BHATTACHARYYA);//比较直方图
double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_BHATTACHARYYA);
double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_BHATTACHARYYA);
double test1test2 = compareHist(hist_test1, hist_test2, CV_COMP_BHATTACHARYYA);
printf("test1 with test2 correlation value :%f", test1test2);
//在原图中显示相关性参数
putText(base, convertToString(basebase), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test1, convertToString(basetest1), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test2, convertToString(basetest2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test2, convertToString(test1test2), Point(100, 100), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
namedWindow("base", CV_WINDOW_AUTOSIZE);
namedWindow("test1", CV_WINDOW_AUTOSIZE);
namedWindow("test2", CV_WINDOW_AUTOSIZE);
imshow("base", base);
imshow("test1", test1);
imshow("test2", test2);
waitKey(0);
return 0;
}
//由于comparehist计算出来的相关性的值是一个double型,这个函数就是把double转变为string
string convertToString(double d)
{
ostringstream os;
if (os << d)
return os.str();
return "invalid conversion";
}
(1)在之前的C++代码上,在解决方案资源管理器中新建一个头文件(.h)
(2)在新建的头文件中,输入以下内容:
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
extern "C" __declspec(dllexport) std::string convertToString(double d);
extern "C" __declspec(dllexport) void histogram(char* s1, char* s2, char* s3);//因为c#与c++之间,string不能直接转换
extern "C"外部声明,表示函数和变量是按照C语言的方式编译和链接的。
_decspec(dllexport)的目的是为了将对应的函数放入到DLL动态库中。
extern “C” _declspec(dllexport)的目的是为了使用DllImport调用非托管C++的DLL文件。因为使用DllImport只能调用由C语言函数做的DLL。
c#与c++对应:
将string转为IntPtr:IntPtr
System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(string)
将IntPtr转为string:string
System.Runtime.InteropServices.MarshalPtrToStringAuto(IntPtr) c++
<----------> c# BSTR --------- StringBuilder LPCTSTR ---------
StringBuilder LPCWSTR --------- IntPtr handle---------IntPtr
hwnd-----------IntPtr char *----------string int * -----------ref int
int &-----------ref int void *----------IntPtr unsigned char *-----ref
byte Struct需要在C#里重新定义一个Struct CallBack回调函数需要封装在一个委托里,delegate static
extern int FunCallBack(string str); 注意在每个函数的前面加上public static extern
+返回的数据类型,如果不加public ,函数默认为私有函数,调用就会出错。
(3)在之前的C++文件中,直接修改代码
至于C++如何配置环境;如何写;具体的怎么做可以看VS2019+OpenCV4.4.0安装及整合详细步骤
//直方图比较
#include "Histogram.h"
using namespace cv;
using namespace std;
//由于comparehist计算出来的相关性的值是一个double型,这个函数就是把double转变为string
std::string convertToString(double d)
{
ostringstream os;
if (os << d)
return os.str();
return "invalid conversion";
}
void histogram(char *s1, char *s2, char *s3) {
Mat base, test1, test2;
Mat hsvbase, hsvtest1, hsvtest2;
base = imread(s1,1);
test1 = imread(s2,1);
test2 = imread(s3,1);
if (!base.data || !test1.data || !test2.data)
{
printf("could not load image...\n");
return;
}
//步骤一:从RGB空间转换到HSV空间
cvtColor(base, hsvbase, CV_BGR2HSV);
cvtColor(test1, hsvtest1, CV_BGR2HSV);
cvtColor(test2, hsvtest2, CV_BGR2HSV);
//步骤二:计算直方图与归一化
int h_bins = 50;
int s_bins = 60;
int histsize[] = { h_bins,s_bins };
//hue varies from 0 to 179,saturation from 0 to 255
float h_ranges[] = { 0,180 };
float s_ranges[] = { 0,256 };
const float* histRanges[] = { h_ranges,s_ranges };
//use the 0-th and 1-st channels
int channels[] = { 0,1 };
MatND hist_base;
MatND hist_test1;
MatND hist_test2;
//计算直方图
calcHist(&hsvbase, 1, channels, Mat(), hist_base, 2, histsize, histRanges, true, false);
calcHist(&hsvtest1, 1, channels, Mat(), hist_test1, 2, histsize, histRanges, true, false);
calcHist(&hsvtest2, 1, channels, Mat(), hist_test2, 2, histsize, histRanges, true, false);
//归一化
normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());//归一化
normalize(hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat());
normalize(hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat());
//步骤三:比较直方图,并返回值
double basebase = compareHist(hist_base, hist_base, CV_COMP_CORREL);//比较直方图
double basetest1 = compareHist(hist_base, hist_test1, CV_COMP_CORREL);
double basetest2 = compareHist(hist_base, hist_test2, CV_COMP_CORREL);
double test1test2 = compareHist(hist_test1, hist_test2, CV_COMP_CORREL);
printf("test1 with test1 correlation value :%f", basebase);
cout << endl;
printf("test1 with test2 correlation value :%f", basetest1);
cout << endl;
printf("test1 with test3 correlation value :%f", basetest2);
cout << endl;
printf("test2 with test3 correlation value :%f", test1test2);
//在原图中显示相关性参数
putText(base, convertToString(basebase), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test1, convertToString(basetest1), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test2, convertToString(basetest2), Point(50, 50), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
putText(test2, convertToString(test1test2), Point(100, 100), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255), 2, CV_AA);
namedWindow("base", CV_WINDOW_AUTOSIZE);
namedWindow("test1", CV_WINDOW_AUTOSIZE);
namedWindow("test2", CV_WINDOW_AUTOSIZE);
imshow("base", base);
imshow("test1", test1);
imshow("test2", test2);
waitKey(0);
}
(4)在源文件文件夹下新建模块定义文件(.def),输入以下内容:
LIBRARY Histogram
EXPORTS histogram
EXPORTS convertToString
EXPORTS后面的就是你要调用的方法名,生成.dll文件的同时也会生成.lib库。
(5)生成解决方案,这里可能生成没问题,但是最后运行C#会报错:
System.DllNotFoundException: Unable to load DLL 'XX.dll': 找不到指定的模块。 (Exception from HRESULT:
直接在这里解决了
这是运行库设置的问题,几个工程的运行库设置不一样。解决方法:
打开所引用的dll源码的项目属性页面,配置属性 -> C/C++ -> 代码生成 -> 运行库,将该项设置为 多线程调试 (/MTd),然后再重新生成解决方案,使用新的dll即可解决问题。
(6)再重新生成整个解决方案,可能又会出现以下错误:
这是因为输出文件拓展名的问题,解决方法如下:
【项目属性】→ 【配置属性】→【常规】→【目标文件扩展名】,将.exe,改为.dll
再重新生成解决方案
(7)找到解决方案所在目录,在Debug目录下即可找到生成的 .dll 文件。要注意的是,要到整个解决方案的目录下找Debug文件夹,如下图:
(2)把Histogram.dll文件复制到该工程的Debug目录下。
(3)在C#文件中引用该 .dll 文件,代码如下:
using Microsoft.AspNetCore.Http;
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp4
{
class Program
{
[DllImport("Histogram.dll")]
//[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public static extern void histogram(string s1,string s2,string s3);
static void Main(string[] args)
{
Console.WriteLine("C#调用dll");
string s1 = "C:/Users/wenhaofu/Desktop/picture/tubiao3.png";
string s2 = "C:/Users/wenhaofu/Desktop/picture/tubiao2.png";
string s3 = "C:/Users/wenhaofu/Desktop/picture/tubiao1.png";
histogram(s1,s2,s3);
Console.Read();
}
}
}