以下内容引自百度百科:
感知哈希算法(Perceptual hash algorithm)是哈希算法的一类,主要用来做相似图片的搜索工作。图片所包含的特征被用来生成一组指纹(不过它不是唯一的), 而这些指纹是可以进行比较的。
概括地讲,感知哈希算法一共分两步:
1、把图片转化为字符串 ,这个字符串就是图片的hash值,又称指纹
2、求两个字符串之间的相似度,字符串越相似说明两个图像越相似~
本文的目标是,实现一个最简单的感知哈希,下面看看详细的实现步骤
测试图像为:
(1) 把图像缩小为8*8,并转化为灰度图
# Step1. 把图像缩小为8 * 8,并转化为灰度图
src = cv2.imread(path, 0)
src = cv2.resize(src, (8, 8), cv2.INTER_LINEAR)
这64个像素的灰度值如下:
(2) 计算64个像素的平均灰度值
# Step2. 计算64个像素的灰度均值
avg = sum([sum(src[i]) for i in range(8)]) / 64
(3) 与平均值比较,生成01字符串
# Step3. 与平均值比较,生成01字符串
string = ""
for i in range(8):
string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))
print(''.join(string))
# lambda表达式可读性太差了, 等价于下面的形式
'''
for i in range(8):
for j in range(8):
if src[i][j] <= avg:
src[i][j] = 0
else:
src[i][j] = 1
'''
64位01字符串是:
1111101111110011110000111100001110000011110000111111001111110111
(4) 计算hash值
对上面的64位字符串,每4个为一组,转化为16进制
# Step4. 计算hash值
result = ''
for i in range(0, 64, 4):
result += ''.join('%x' % int(string[i: i + 4], 2))
return result
得到的结果为:fbf3c3c383c3f3f7
字符串相似度计算使用汉明距离:
以下引自百度百科:
汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。
例如:
1011101 与 1001001 之间的汉明距离是 2。
2143896 与 2233796 之间的汉明距离是 3。
“toned” 与 “roses” 之间的汉明距离是 3。
def hamming(str1, str2):
if len(str1) != len(str2): # 要求两个字符串的长度相等
return
count = 0
for i in range(0, len(str1)):
if str1[i] != str2[i]:
count += 1
return count
测试使用了三张图片,测试leaf1与leaf2、leaf3之间的相似度。
leaf1 与leaf2的汉明距离是10,与leaf3的汉明距离是12,说明leaf1与leaf2更相似 ~
完整的代码如下:
# -*- coding:utf-8 -*-
"""
感知哈希的实现
author:lijialin(李家霖)
data:2017-08-04
"""
import cv2
def p_hash(path):
# Step1. 把图像缩小为8 * 8,并转化为灰度图
src = cv2.imread(path, 0)
src = cv2.resize(src, (8, 8), cv2.INTER_LINEAR)
# Step2. 计算64个像素的灰度均值
avg = sum([sum(src[i]) for i in range(8)]) / 64
# Step3. 与平均值比较,生成01字符串
string = ''
for i in range(8):
string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))
# Step4. 计算hash值
result = ''
for i in range(0, 64, 4):
result += ''.join('%x' % int(string[i: i + 4], 2))
return result
def hamming(str1, str2):
if len(str1) != len(str2):
return
count = 0
for i in range(0, len(str1)):
if str1[i] != str2[i]:
count += 1
return count
# 读取三张图片,进行测试
h1 = p_hash('images/leaf1.jpg')
h2 = p_hash('images/leaf2.jpg')
h3 = p_hash('images/leaf3.jpg')
print(hamming(h1, h2)) # 10
print(hamming(h1, h3)) # 12
上面的算法由于使用了像素平均值,因此又叫均值哈希。均值哈希虽然简单,但对图像灰度的平均值特别敏感,也不具备旋转不变性。余弦感知哈希算法是对均值哈希上的提升,能够更好地处理旋转图形。“可以识别变形程度在25%以内的图片。”
余弦感知哈希算法步骤如下:
1、缩小尺寸:将图像缩小到32 * 32,并转化为灰度
2、计算DCT:对图像进行二维离散余弦变换
3、缩小DCT:只保留矩阵左上角8 * 8区域,对这个区域求哈希均值,并生成01字符串
4、计算hash值:与前一个方法相同
余弦hash的代码如下:
"""
余弦感知哈希算法
"""
def p_hash(path):
# Step1. 把图像缩小为32 * 32,并转化为灰度图
src = cv2.imread(path, 0)
src = cv2.resize(src, (32, 32), cv2.INTER_LINEAR)
# Step2. 对图像进行余弦变换
h, w = src.shape[:2]
arr = np.zeros((h, w), np.float32)
arr[:h, :w] = src
src = cv2.dct(cv2.dct(arr)) # 离散余弦变换
src.resize(8, 8)
# Step3. 计算64个像素的灰度均值
avg = sum([sum(src[i]) for i in range(8)]) / 64
# Step4. 与平均值比较,生成01字符串
string = ''
for i in range(8):
string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))
# Step5. 计算hash值
result = ''
for i in range(0, 64, 4):
result += ''.join('%x' % int(string[i: i + 4], 2))
return result
参考
http://blog.csdn.net/zouxy09/article/details/17471401
http://yshblog.com/blog/43
#include
#include
using namespace cv;
using namespace std;
/************* 函数声明部分 start ************/
string p_hash(string path); //计算图像的哈希字符串
int hamming(string str1, string str2);// 计算汉明距离
string str_to_hex(string s); // 字符串转16进制
/************* 函数声明部分 end ************/
int main() {
string path0 = "images/image0.jpg";
string path1 = "images/image1.jpg";
string h0 = p_hash(path0);
string h1 = p_hash(path1);
cout << hamming(h0, h1) << endl;
}
// 输入图像的路径
// 感知哈希算法的实现
string p_hash(string path) {
// Step1. 把图像缩小为 8 * 8
Mat src = imread(path, 0);
resize(src, src, Size(8, 8), 0, 0, CV_INTER_LINEAR);
// Step2. 计算64个像素的灰度均值
double avg = mean(src)[0];
// Step3. 与平均值比较,生成01字符串
string str = "";
for (int i = 0; i < 8; i++) {
unsigned char* data = src.ptr(i);
for (int j = 0; j < 8; j++) {
if (data[j] <= avg) {
str += "0";
}else {
str += "1";
}
}
}
// Step4. 计算哈希值
string h = "";
for (int i = 0; i < 61; i+=4) {
string s = str.substr(i,4);
h += str_to_hex(s);
}
return h;
}
// 字符串转16进制
string str_to_hex(string s) {
string result = "";
int num = 0, p = 3;
for (int i = 0; i < 4; i++) {
if (s[i] == '1') {
num += pow(2, p);
}
p--;
}
if (num == 10) { // 这里写得很渣很渣。。。
result += "a";
}else if (num == 11) {
result += "b";
}else if (num == 12) {
result += "c";
}else if (num == 13) {
result += "d";
}else if (num == 14) {
result += "e";
}
else if (num == 15) {
result += "f";
}else {
stringstream ss;
ss << num;
result += ss.str();
}
return result;
}
// 计算汉明距离
int hamming(string str1, string str2) {
int count = 0;
for (int i = 0; i < 16; i++) {
if (str1[i] != str2[i]) {
count++;
}
}
return count;
}
今天就到这里吧, 拜拜~