飘忽不定的失踪人口又来了(~ ̄▽ ̄)~ 吐槽:害最近是真的烦,好不容易找到了感觉还不错的3+1实习,甚至已经谈好了下个月就去入职培训了,结果学校突然通知说疫情原因禁止学生暑期出去实习。这也就算了,咱以个人名义出去总可以了吧?可是个人与企业的协议又得上交学校,暑期时间内的话学校又不同意,必须把协议日期放到九月份后,然而这样企业方面又不太同意,我真的是无语了。。。又是漫长的等通知。。。天。。。而且企业那边使用halcon偏多一些,最近都在看这方面的内容,我这OpenCV的笔记呀,放下的有点久了唉~罢了罢了有空想写啥就写点啥吧。
吐槽完毕,步入正题,今天要整理的是图像的基于灰度值的均值哈希算法(aHash)。那么什么是均值哈希呢,其实是图像哈希算法的一种,通过某种特定的运算来得到一副图像特有的哈希指纹,可以用于图像的匹配或者是图像搜索。感觉有点类似于对一副图像进行整体的特征提取吧。
图像哈希算法主要分为均值哈希、差异值哈希、感知哈希三种算法,其中精确度由低到高分别是:均值哈希 < 差异值哈希 < 感知哈希,而计算速度从低到高则是:感知哈希 < 差异值哈希 < 均值哈希,可见精确度和计算速度是成反比的。那在这里,主要是实现均值哈希算法。
均值哈希算法的主要步骤是: (1)缩小尺寸:将图像缩小到Width x Height的尺寸,总共pixel_num个像素。目的是去除图像的细节,只保留结构或者明暗关系等基本信息,摒弃不同尺寸或比例带来的图像差异; (2)计算平均值:计算所有pixel_num个像素的灰度平均值; (3)比较像素的灰度值:将每个像素的灰度值,与平均值进行比较,大于或等于平均值记为1,小于平均值记为0; (4)计算哈希值:将上一步的比较结果,组合在一起,就构成了一个pixel_num位的整数,即是该图像的哈希指纹。
哈希指纹的生成位序可以是随意的,但是必须保证所有用来匹配的图像指纹都采用了同样位序,这样才具有可比性。通过不同图像的哈希指纹之间的差异位数来对比、计算得出汉明距离,从而得出不同的图像之间的相似度。
在进行图像缩放时,如果把图像尺寸规定为8x8的话是比较推荐的,因为这样计算出来的哈希指纹就刚好是64bit,当然了这个并不是一定的,看具体需要而定把。
首先,我们规定缩放后的图像尺寸,以及总的像素数量。注意规定的缩放尺寸越小,检测速度越快,对差异越敏感,即相似度会下降,但是难以准确定位差异位置。我这里其实没有进行缩放,用的还是图像原尺寸,这是因为可以直接定位到差异位置,精准度更高,当然了运算。速度也会变慢。
vector<int> image_shape(Mat template_image) { Mat template_image_gray; cvtColor(template_image, template_image_gray, COLOR_BGR2GRAY); // height = 8 // width = 8 int width = template_image_gray.cols; int height = template_image_gray.rows; int pixel_num = width * height; vector<int> shape; shape.push_back(height); shape.push_back(width); shape.push_back(pixel_num); return shape }随后分别定义计算模板图像和测试图像哈希指纹的函数,这两个函数其实大同小异
vector<int> template_image_hash_fingers(Mat template_image) { cout << "模板图像" << endl; namedWindow("template_image"); imshow("template_image", template_image); vector<int>shape = image_shape(template_image); int height = shape[0]; int width = shape[1]; int pixel_num = shape[2]; Mat template_image_gray; cvtColor(template_image, template_image_gray, COLOR_BGR2GRAY); Mat template_image_WxH; resize(template_image_gray, template_image_WxH, Size(width, height)); int pixelSum = 0; uchar* current = template_image_WxH.data; for (; current != template_image_WxH.dataend; current++) { int pixelVal = int(*current); pixelSum += pixelVal; } double template_gray_average = double(pixelSum) / pixel_num; cout << "平均灰度值" << template_gray_average << endl; vector<int> template_fingers; for (current = template_image_WxH.data; current != template_image_WxH.dataend; current++) { int template_finger = 0; if (*current >= template_gray_average) { template_finger = 1; } template_fingers.push_back(template_finger); } return template_fingers; } vector<int> test_image_hash_fingers(Mat test_image) { cout << "测试图像" << endl; namedWindow("test_image"); imshow("test_image", test_image); vector<int>shape = image_shape(test_image); int height = shape[0]; int width = shape[1]; int pixel_num = shape[2]; Mat test_image_gray; cvtColor(test_image, test_image_gray, COLOR_BGR2GRAY); Mat test_image_WxH; resize(test_image_gray, test_image_WxH, Size(width, height)); int pixelSum =0; uchar* current = test_image_WxH.data; for (; current != test_image_WxH.dataend; current++) { int pixelVal = int(*current); pixelSum += pixelVal; } double test_gray_average = double(pixelSum) / double(pixel_num); cout << "平均灰度值" << test_gray_average << endl; vector<int> test_fingers; for (current = test_image_WxH.data; current != test_image_WxH.dataend; current++) { int test_finger = 0; if (*current >= test_gray_average) { test_finger = 1; } test_fingers.push_back(test_finger); } return test_fingers; }最后定义两幅图像进行哈希指纹对比的函数
void image_contrast(Mat template_image,Mat test_image) { int64 start = getTickCount(); vector<int> template_fingers = template_image_hash_fingers(template_image); vector<int> test_fingers = test_image_hash_fingers(test_image); vector<int> shape; shape = image_shape(template_image); int height = shape[0]; int width = shape[1]; int pixel_num = shape[2]; Mat result = test_image.clone(); int different_num = 0; for(int i=0;i<pixel_num;i++) { if (template_fingers[i] != test_fingers[i]) { different_num += 1; int col = int(i % width); int row = int((i - col) / width); result.at<Vec3b>(row, col) = Vec3b(0, 0, 255); } } imshow("test_result", result); double similarity_ratio = (1 - (double(different_num) / double(pixel_num))) * 100; int64 stop = getTickCount(); double test_time = double(stop - start) / getTickFrequency(); cout << "哈希指纹间差异位数" << different_num << endl; cout << "相似度为" << similarity_ratio << "%" << endl; cout << "消耗测试时间为" << test_time << "s" << endl; }在main函数中进行调用
int main() { Mat template_image = imread("C:/Users/iamxb/Desktop/Product_information_detection/Printing detection/template/true_texts.png"); Mat test_image = imread("C:/Users/iamxb/Desktop/Product_information_detection/Printing detection/test/uncompleted_issue/single_char_issue/4.png"); if (template_image.empty() || test_image.empty()) { cout << "读入图像为空" << endl; exit(-1); } image_contrast(template_image, test_image); waitKey(); return 0; }所用的模板图像和测试图像如下: 上面两幅图像从肉眼应该很难看出差异,下面看看对比均值哈希指纹后找出的差异区域。 可以看到,从两幅肉眼看上去很相似的图像中找出了细小的标点符号以及字体粗细差异。运行信息如下:
下面再看模板图像和另一幅测试图像,以及测试效果。
上面就是通过均值哈希算法进行图像匹配的一个c++实现,下面我们再通过相同算法比较下python和c++的速度差异,因为这部分内容我开始的时候是使用python写的,感觉运行速度很慢,后来改写成c++顿时觉得速度飞起。。从上面可以看到进行两幅图像匹配的时间大概是0.6~0.7s,那对同样尺寸大小的图像,使用python又是怎样呢,我们直接看一下运行输出的结果。 emmmmmm这个消耗时间对比够明显了吧。。。主要是c++的指针在像素遍历访问这块比使用python要快得多,当图像尺寸越大这里的速度差距就更明显了。
好的那本次关于均值哈希算法的整理就到此结束~
PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!