相似图片搜索原理一(ahash—c++实现)

2023-05-16

ahash,全称叫做average hash,应该是phash(perceptual hash, 感知哈希)算法的一种。是基于图像内容搜索最简单的一种(search image by image),因此也有很多的局限性。主要用于由图像的缩略图搜原图,对于图像的旋转、平移、对比度和微变形等都无能为力,所以很局限。此次讲解主要分为两个部分,理论部分主要参考是网上的资料,最核心的应该是自己的c++代码实现。

 

理论部分:

理论部分主要包括以下几个步骤:

<1> 图像缩放—将图像缩放到8*8大小

<2>灰度化—对8*8大小的图像进行灰度化

<3>计算均值—计算这8*8大小图片中64个像素的均值

<4>得到8*8图像的ahash8*8的像素值中大于均值的则用1表示,小于的用0表示,这样就得到一个64位二进制码作为该图像的ahash值。

<5>计算两幅图像ahash值的汉明距离,距离越小,表明两幅图像越相似;距离越大,表明两幅图像距离越大。

 

以下来自阮一峰blog的简介:http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html?20150415102912

 

上个月,Google"相似图片搜索"正式放上了首页。

你可以用一张图片,搜索互联网上所有与它相似的图片。点击搜索框中照相机的图标。


一个对话框会出现。


你输入网片的网址,或者直接上传图片,Google就会找出与其相似的图片。下面这张图片是美国女演员Alyson Hannigan


上传后,Google返回如下结果:


类似的"相似图片搜索引擎"还有不少,TinEye甚至可以找出照片的拍摄背景。


==========================================================

这种技术的原理是什么?计算机怎么知道两张图片相似呢?

根据Neal Krawetz博士的解释,原理非常简单易懂。我们可以用一个快速算法,就达到基本的效果。

这里的关键技术叫做"感知哈希算法"Perceptual hash algorithm),它的作用是对每张图片生成一个"指纹"fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。

下面是一个最简单的实现:

第一步,缩小尺寸。

将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。

 

第二步,简化色彩。

将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。

第三步,计算平均值。

计算所有64个像素的灰度平均值。

第四步,比较像素的灰度。

将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0

第五步,计算哈希值。

将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。

   =8f373714acfcf4d0

得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。在理论上,这等同于计算"汉明距离"Hamming distance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。

具体的代码实现,可以参见Wotepython语言写的imgHash.py。代码很短,只有53行。使用的时候,第一个参数是基准图片,第二个参数是用来比较的其他图片所在的目录,返回结果是两张图片之间不相同的数据位数量(汉明距离)。

这种算法的优点是简单快速,不受图片大小缩放的影响,缺点是图片的内容不能变更。如果在图片上加几个文字,它就认不出来了。所以,它的最佳用途是根据缩略图,找出原图。

实际应用中,往往采用更强大的pHash算法和SIFT算法,它们能够识别图片的变形。只要变形程度不超过25%,它们就能匹配原图。这些算法虽然更复杂,但是原理与上面的简便算法是一样的,就是先将图片转化成Hash字符串,然后再进行比较。

 

注意两点(英文原文)

1:The resulting hash won't change if the image is scaled or the aspect ratio changes. Increasing or decreasing the brightness or contrast, or even altering the colors won't dramatically change the hash value. And best of all: this is FAST!

就是说当图像被缩放或者分辨率及强度或者对比度甚至颜色改变对得到的hash值都不会有很明显的变化,关键是他非常的快!

2:With pictures, high frequencies give you detail, while low frequencies show you structure. A large, detailed picture has lots of high frequencies. A very small picture lacks details, so it is all low frequencies. 

就是说,像素点的值出现的频率高给你看到的是图片的细节信息,而低频则给你看到的是图片的轮廓结构,一副大的详细的图片会含有很多的高频,而一副小的则缺少细节, 大多都是低频。<翻译的有点烂>

C++代码实现:

网上有人用java代码将其实现了,可以看这里:http://blog.csdn.net/luohong722/article/details/7100058

我这里结合opencvc++代码将其实现了

<1>图片缩放与灰度化

直接调用opencv的函数就可以了:

 

Mat img = imread("E:\\algorithmZack\\ImageSearch\\image\\person.jpg", 1);
	if(!img.data){
		cout << "the image is not exist" << endl;
		return 0;
	}
	int size = 8;  // 图片缩放后大小
	
	resize(img, img, Size(size,size));      // 缩放到8*8
	cvtColor(img, img, COLOR_BGR2GRAY);       // 灰度化

<2>计算灰度化的均值

 

// 计算8*8图像的平均灰度
float calcAverage(Mat_<uchar> image, const int &size){
	float sum = 0;
	for(int i = 0 ; i < size; i++){
		for(int j = 0; j < size; j++){
			sum += image(i, j);
		}
	}
	return sum/(size*size);
}

<3>得到8*8图像的ahash

 

/* 计算hash值
	image:8*8的灰度图像
	size: 图像大小  8*8
	ahahs:存放64位hash值
	averagePix: 灰度值的平均值
*/
void fingerPrint(Mat_<uchar> image, const int &size, bitset<hashLength> &ahash, const float &averagePix){
	for(int i = 0; i < size; i++){
		int pos = i * size;
		for(int j = 0; j < size; j++){
			ahash[pos+j] = image(i, j) >= averagePix ? 1:0;
		}
	}
}

<4>计算汉明距离

 

/*计算汉明距离*/
int hammingDistance(const bitset<hashLength> &query, const bitset<hashLength> &target){
	int distance = 0;
	for(int i = 0; i < hashLength; i++){
		distance += (query[i] == target[i] ? 0 : 1);
	}
	return distance;
}

完整源代码:

#include <iostream>
#include <bitset>
#include <string>
#include <iomanip>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>

using namespace std;
using namespace cv;

#define hashLength 64


// 计算8*8图像的平均灰度
float calcAverage(Mat_<uchar> image, const int &size){
	float sum = 0;
	for(int i = 0 ; i < size; i++){
		for(int j = 0; j < size; j++){
			sum += image(i, j);
		}
	}
	return sum/(size*size);
}

/* 计算hash值
	image:8*8的灰度图像
	size: 图像大小  8*8
	ahahs:存放64位hash值
	averagePix: 灰度值的平均值
*/
void fingerPrint(Mat_<uchar> image, const int &size, bitset<hashLength> &ahash, const float &averagePix){
	for(int i = 0; i < size; i++){
		int pos = i * size;
		for(int j = 0; j < size; j++){
			ahash[pos+j] = image(i, j) >= averagePix ? 1:0;
		}
	}
}

/*计算汉明距离*/
int hammingDistance(const bitset<hashLength> &query, const bitset<hashLength> &target){
	int distance = 0;
	for(int i = 0; i < hashLength; i++){
		distance += (query[i] == target[i] ? 0 : 1);
	}
	return distance;
}

string bitTohex(const bitset<hashLength> &target){
	string str;
	for(int i = 0; i < hashLength; i=i+4){
		int sum = 0;
		string s;
		sum += target[i] + (target[i+1]<<1) + (target[i+2]<<2) + (target[i+3]<<3);
		stringstream ss;
		ss << hex <<sum;    // 以十六进制保存
		ss >> s;
		str += s;
	}
	return str;
}

int main(){
	Mat img = imread("E:\\algorithmZack\\ImageSearch\\image\\person.jpg", 1);
	if(!img.data){
		cout << "the image is not exist" << endl;
		return 0;
	}
	int size = 8;  // 图片缩放后大小
	
	resize(img, img, Size(size,size));      // 缩放到8*8
	cvtColor(img, img, COLOR_BGR2GRAY);       // 灰度化
	float averagePix = calcAverage(img, size);    // 计算灰度化的均值
	//cout << averagePix << endl;
	bitset<hashLength> ahash;               
	fingerPrint(img, size, ahash, averagePix);    // 得到均值hash
	//cout << ahash << endl;
	cout << bitTohex(ahash) << endl;

	string img_dir = "E:\\algorithmZack\\ImageSearch\\image\\";
	for(int i = 1; i <= 8; i++){
		string pos;
		stringstream ss;
		ss << i;
		ss >> pos;
		string img_name = img_dir + "person" + pos +".jpg"; 
		Mat target = imread(img_name, 1);
		if(!target.data){
			cout << "the target image" << img_name << " is not exist" << endl;
			continue;
		}
		resize(target, target, Size(size,size));
		cvtColor(target, target, COLOR_BGR2GRAY);
		float averagePix2 = calcAverage(target, size);
		bitset<hashLength> ahash2;
		fingerPrint(target, size, ahash2, averagePix2);

		//cout << averagePix2 << endl;
		int distance = hammingDistance(ahash, ahash2);      // 计算汉明距离
		cout <<"【" << i <<"-" <<  distance << "】 ";
	}
	cout << endl;
	return 0;
	
}

测试的图片为:


结果为:

其中【i-j】, i代表personi, j代表personi与person的汉明距离。

参考文献:

1http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html英文原始资料

2http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html?20150415102912阮一峰blog

3http://blog.csdn.net/luohong722/article/details/7100058java实现版本


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

相似图片搜索原理一(ahash—c++实现) 的相关文章

随机推荐

  • 标准GPS数据格式NEMA解析

    NEMA定位数据是一种标准的GPS数据格式 xff0c 其中包含了位置 速度 航向等信息 它的格式如下 xff1a GPGGA 123519 4807 038 N 01131 000 E 1 08 0 9 545 4 M 46 9 M 47
  • AI人工智能在电信运营商有哪些应用?怎么应用?

    AI人工智能在电信运营商有哪些应用 xff1f 怎么应用 xff1f
  • RTTHREAD软件包目录

    RTTHREAD软件包目录 这边统计下RTTHREAD软件包的各项内容 IOT 包名技术标签依赖平台备注abup fotaOTATCP UDP本软件包是用于 Abup FOTA 升级的固件下载器agile jsmnjsonC库jsmn是一个
  • DSP28系列—MPU6050漂移补偿方案的探索

    说在前面的话 xff1a 大家都知道 xff0c 陀螺仪是一种能测量角速度的器件 xff0c 是姿态解算 角度测量等任务中无法缺少的工具 单轴的陀螺仪配合单轴的加速度计 xff0c 就能结算出一个姿态角 在平衡车的应用中 xff0c 一维的
  • 【前端工程化】 剖析npm的包管理机制(完整版)

    现如今 xff0c 前端开发的同学已经离不开 npm 这个包管理工具 xff0c 其优秀的包版本管理机制承载了整个繁荣发展的NodeJS社区 xff0c 理解其内部机制非常有利于加深我们对模块开发的理解 各项前端工程化的配置以加快我们排查问
  • 学习无人机-C01小四轴无人机初体验

    开机时 xff0c M3对应的蓝色指示灯先亮 xff0c M4那头对应的LED09一直亮 xff08 只要板子上电 xff0c 就会一直亮 xff09 充电时 xff0c 红色LED11一直亮 xff0c 没有变暗或者灭掉的现象 并且 xf
  • 姿态传感器的原理与应用文献笔记

    64 TOC 姿态传感器的原理与应用文献笔记 20210526 一 高精度姿态传感器x 1 姿态传感器的定义和意义 姿态传感器以嵌入式系统为核心 xff0c 基于 固态 结构 xff0c 采用先进的倾角测量技术分 xff38 xff39 两
  • vim无法安装问题

    1 用root账户登录Ubuntu xff0c 命令行中输入vim xff0c 如果未安装会得到下面的提示 xff1a 程序 vim 已包含在下列软件包中 xff1a vim gnome vim tiny vim gtk vim nox 请
  • k8s操作手册

    1 kubernetes 组件 1 1 Kubernetes 组件介绍 一个 kubernetes 集群主要由控制节点 xff08 master xff09 工作节点 xff08 node xff09 构成 xff0c 每个节点上都会安装不
  • 已知一指针p,你可以确定该指针是否指向一个有效的对象吗?如果可以,如何确定?如果不可以,请说明原因。

    这个问题我的思路是 xff1a 首先用 p将其值输出来 xff0c 如果编译器报错 xff0c 证明p指向一个无效的对象 xff0c 要么p 61 0要么p未进行初始化 xff0c 此时可以用if p 61 61 NULL 进行判断即可 x
  • opencv学习_10 (图像和轮廓的匹配(hu矩))

    图像和轮廓的匹配 hu矩 1 hu矩的概念 xff0c 我也总结了但是我不过多的阐述 xff0c 因为我也不是太理解 xff0c 只知道它具有平移 xff0c 旋转 xff0c 尺度不变性 xff0c 详细见别人的这篇 blog xff1a
  • opencv学习_11 (模板匹配(包括单模板和多模板))

    模板匹配 在一幅图像中匹配与模板相似的单个或者多个目标 1 目标匹配函数 xff1a cvMatchTemplate const CvArr image constCvArr templ CvArr result int method Im
  • opencv学习_11 (moravec角点检测及缺点)

    1 首先我们来看三幅图片理解什么是角点 xff1a 我们在图片以某像素点为中心 xff0c 取一窗口 xff0c 当窗口向各个方向移动时 xff0c 其内部灰度值变化不是很明显 xff0c 则该点即处在平坦区域 如左边图 xff1b 当其内
  • 2013学习总结

    时间飞逝 xff0c 很快又要过年了 xff0c 马上就要回家了 xff0c 2013年工作也接近尾声了 下面好好总结下2013 学习与工作 lt 1 gt 863农产品推荐系统 一个字形容 xff1a 水 可能国家项目都是这样的 不管怎样
  • 机器学习实战笔记2(k-近邻算法)

    1 xff1a 算法简单描述 给定训练数据样本和标签 xff0c 对于某测试的一个样本数据 xff0c 选择距离其最近的k个训练样本 xff0c 这k个训练样本中所属类别最多的类即为该测试样本的预测标签 简称kNN 通常k是不大于20的整数
  • 智源青年科学家候选人 | 张祥雨:挑战自动化深度学习系统

    4月16日 xff0c 北京智源人工智能研究院发布 智源学者计划 xff0c 宣布重点支持四类人才 xff1a 智源科学家首席 xff08 CS xff09 智源研究项目经理 xff08 PM xff09 智源研究员 xff08 PI xf
  • 可导一定连续,连续不一定可导

    今天在群里面看到大家发了这句可导一定连续 xff0c 连续不一定可导 大家应该都很熟悉 xff0c 包括我自己 xff0c 但是真正理解有多少呢 xff0c 我当时就没想明白 xff0c 中午吃饭的时候也在想 xff0c 最后还是想明白了
  • PCA降维简介

    PCA 全称为 principal component analysis xff0c 即主成成分分析 xff0c 用于降维 对数据进行降维有很多原因 比如 xff1a 1 xff1a 使得数据更易显示 xff0c 更易懂 2 xff1a 降
  • 2014学习总结

    本来想将2014的总结推迟几天写的 xff0c 可是看到csdn的博客活动 xff0c 故而提前几天写了 http blog csdn net lu597203933 article details 18421101这是我个人2013写的学
  • 相似图片搜索原理一(ahash—c++实现)

    ahash xff0c 全称叫做 average hash 应该是 phash perceptual hash 感知哈希 算法的一种 是基于图像内容搜索最简单的一种 search image by image xff0c 因此也有很多的局限