2019-08-11
哈希算法不论准确性还是实效性,均高于直接使用灰度直方图或者rgb直方图。但由于测试数据集需要手动处理,所以数据集较小。以至于第一次测试效果中,三种哈希算法差异性不大。
图像指纹与音频指纹的表现形式类似,都是将原始文件安装一定的哈希算法进行转换。最后得出一个或者一组能够代表图像特征的值。 针对于图像指纹的提取,常见的哈希算法有几种:平均哈希算法(aHash)、感知哈希算法(pHash)、差异值哈希算法(dHsh)以及最为简单的加密哈希MD5. 由于MD5算法的敏感度极高,内容稍微有所差异,哈希出来的结果都会有极大的差异。所以我们只能将其作为最表层的检查方式。在提取图像指纹中,我们只考虑aHash、pHash与dHash。 同时为了比较效果,引入普通的直方图判断作为参考
aHash理论实现方法如下:
因为aHash最终的结果只保留了实际值对于自身均值的比较结果。在实际应用中过于严格与简单。唯一的优点在于实现简单、速度较快,不受图片大小缩放的影响,但是图片的内容不能变更。如果在图片上加几个文字,它就认不出来了。所以,它的最佳用途在于根据缩略图,找出原图,与我们视频判重的应用场景不是非常的符合。
与aHash在一定程度上类似,也是与均值相比较得出最终的结果。但是为了弥补aHash精度上的不足,pHash使用离散余弦变换(DCT)来获取图片的低频成分。DCT能将图像从像素域变换到频率域,一般图像都存在很多冗余和相关性的,转换到频率域之后,只有很少的一部分频率分量的系数才不为0,大部分系数都为0(或者说接近于0)。所以我们只用保留左上角的8*8大小的部分。
实验设计,将图片分为三类:基础图片、重复图片、不重复图片 基础图片:作为重复图片与不重复图片的依据,40张 重复图片:基础图片中出现过的图片,以及添加水印后的图片,10张,包括3张未变动,3张添加水印,2张添加滤镜,2张截取部分 不重复图片:没有在基础图片中出现过的图片
计分标准:不重复图片未命中基础图片误伤性+1,重复图片命中基础图片准确度+1,统计记录三种哈希算法的总耗时
单通道直方图: 在重合度>0.6时记为命中,误伤4,准确8,总耗时5.87s,单次比对耗时0.010s
三通道直方图: 在重合度>0.6时记为命中,误伤3,准确8,总耗时8.89s,单次比对耗时0.014s
aHash:汉明距离小于5记为命中,误伤0,准确7,总耗时4.39s,单次比对耗时0.0065s
pHash:汉明距离小于5记为命中,误伤0,准确7,总耗时4.48s,单次比对耗时0.0063s
dHash:汉明距离小于5记为命中,误伤0,准确7,总耗时4.11s,单次比对耗时0.0058s
测试代码:
import cv2
import numpy as np
import os
import time
#灰度直方图
def classify_gray_hist(image1, image2, size=(256, 256)):
# 先调整大小
image1 = cv2.resize(image1, size)
image2 = cv2.resize(image2, size)
hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0])
hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0])
# 计算直方图的重合度
degree = 0
for i in range(len(hist1)):
if hist1[i] != hist2[i]:
degree = degree + (1 - (abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])))
else:
degree = degree + 1
degree = degree / len(hist1)
return degree
def classify_hist_with_split(image1, image2, size=(256, 256)):
# 将图像resize后,分类为三个通道,再计算每个通道的相似值
image1 = cv2.resize(image1, size)
image2 = cv2.resize(image2, size)
sub_image1 = cv2.split(image1)
sub_image2 = cv2.split(image2)
sub_data = 0
for im1, im2 in zip(sub_image1, sub_image2):
sub_data += calculate(im1, im2)
sub_data = sub_data / 3
return sub_data
def calculate(im1, im2):
hist1 = cv2.calcHist([im1], [0], None, [256], [0.0, 255.0])
hist2 = cv2.calcHist([im2], [0], None, [256], [0.0, 255.0])
degree = 0
for i in range(len(hist1)):
if hist1[i] != hist2[i]:
degree = degree + (1 - (abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i])))
else:
degree = degree + 1
degree = degree / len(hist1)
return degree
# 平均哈希算法计算
def classify_aHash(image1, image2):
image1 = cv2.resize(image1, (8, 8))
image2 = cv2.resize(image2, (8, 8))
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # 切换至灰度图
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
hash1 = getHash(gray1)
hash2 = getHash(gray2)
return Hamming_distance(hash1, hash2)
def classify_pHash(image1, image2):
image1 = cv2.resize(image1, (32, 32))
image2 = cv2.resize(image2, (32, 32))
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # 切换至灰度图
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
# 将灰度图转为浮点型,再进行dct变换
dct1 = cv2.dct(np.float32(gray1))
dct2 = cv2.dct(np.float32(gray2))
# 取左上角的8*8,这些代表图片的最低频率
# 这个操作等价于c++中利用opencv实现的掩码操作
# 在python中进行掩码操作,可以直接这样取出图像矩阵的某一部分
dct1_roi = dct1[0:8, 0:8]
dct2_roi = dct2[0:8, 0:8]
#print(dct1)
hash1 = getHash(dct1_roi)
hash2 = getHash(dct2_roi)
return Hamming_distance(hash1, hash2)
# 输入灰度图,返回hash
def getHash(image):
avreage = np.mean(image) # 计算像素平均值
hash = []
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if image[i, j] > avreage:
hash.append(1)
else:
hash.append(0)
return hash
# 计算汉明距离
def Hamming_distance(hash1, hash2):
num = 0
for index in range(len(hash1)):
if hash1[index] != hash2[index]:
num += 1
return num
# 差异值哈希算法
def dhash(image1, image2):
image1 = cv2.resize(image1, (9, 8))
image2 = cv2.resize(image2, (9, 8))
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY) # 切换至灰度图
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
hash1 = dhashcaulate(gray1)
hash2 = dhashcaulate(gray2)
return Hamming_distance(hash1, hash2)
def dhashcaulate(gray):
hash_str = ''
for i in range(8):
for j in range(8):
if gray[i, j] > gray[i, j + 1]:
hash_str = hash_str + '1'
else:
hash_str = hash_str + '0'
return hash_str
if __name__ == '__main__':
base_path = './base'
repeat_path = './repeat'
unrepeat_path = './unrepeat'
A = 0
cnt = 0
B = 0
T = time.time()
for unrepeat in os.listdir(unrepeat_path):
imgobj1 = cv2.imread('./unrepeat/' + unrepeat)
for base in os.listdir(base_path):
imgobj2 = cv2.imread('./base/' + base)
cnt = cnt + 1
#degree = classify_gray_hist(imgobj1,imgobj2) #单通道直方图
#degree = classify_hist_with_split(imgobj1,imgobj2) #三通道直方图
#degree = classify_aHash(imgobj1,imgobj2) #平均哈希法
#degree = classify_pHash(imgobj1,imgobj2) #感知哈希法
degree = dhash(imgobj1, imgobj2) # 差异值哈希算法
if degree < 5:
A = A + 1
break
for repeat in os.listdir(repeat_path):
imgobj1 = cv2.imread('./repeat/' + repeat)
for base in os.listdir(base_path):
imgobj2 = cv2.imread('./base/' + base)
cnt = cnt + 1
#degree = classify_gray_hist(imgobj1,imgobj2) #单通道直方图
#degree = classify_hist_with_split(imgobj1,imgobj2) #三通道直方图
#degree = classify_aHash(imgobj1,imgobj2) #平均哈希法
#degree = classify_pHash(imgobj1,imgobj2) #感知哈希法
degree = dhash(imgobj1, imgobj2) # 差异值哈希算法
if degree < 5 :
#print (degree)
B = B + 1
break
FT = time.time();
print("unrepeat: " + str(A) +", repeat:"+ str(B) + ", time: " + str(FT - T)+", avg_time:" + str((FT-T)/cnt) )