相似图片检测:感知哈希算法之aHash,dHash,pHash的Python实现
原文:http://blog.sina.com.cn/s/blog_56fd58ab0102xpqf.html
感知哈希算法是一类算法的总称,包括aHash、pHash、dHash。顾名思义,感知哈希不是以严格的方式计算Hash值,而是以更加相对的方式计算哈希值,因为“相似”与否,就是一种相对的判定。某些情况下,我们需要检测图片之间的相似性,进行我们需要的处理:删除同一张图片、标记盗版等。
如何判断是同一张图片呢?最简单的方法是使用加密哈希(例如MD5, SHA-1)判断。但是局限性非常大。例如一个txt文档,其MD5值是根据这个txt的二进制数据计算的,如果是这个txt文档的完全复制版,那他们的MD5值是完全相同的。但是,一旦改变副本的内容,哪怕只是副本的缩进格式,其MD5也会天差地别。因此加密哈希只能用于判断两个完全一致、未经修改的文件,如果是一张经过调色或者缩放的图片,根本无法判断其与另一张图片是否为同一张图片。
那么如何判断一张被PS过的图片是否与另一张图片本质上相同呢?比较简单、易用的解决方案是采用感知哈希算法(Perceptual Hash Algorithm)。
- aHash:平均值哈希。速度比较快,但是常常不太精确。
- pHash:感知哈希。精确度比较高,但是速度方面较差一些。
- dHash:差异值哈希。Amazing!精确度较高,且速度也非常快。因此我就选择了dHash作为我图片判重的算法。
compare_ssim比较图像相似,光照不一样,图像不相似
phash:光照变化会过滤掉,64*64需要1ms左右
dhash需要2ms左右
import datetime
path=r"D:\data\similar"
a=path+"/0720_090352_840781.jpg"
b=path+"/0720_133954_832176.jpg"
# b=path+"/0720_085858_768830.jpg"
import cv2
import numpy as np
import time
#Hash值对比
def cmpHash(hash1,hash2):
n=0
#hash长度不同则返回-1代表传参出错
if len(hash1)!=len(hash2):
return -1
#遍历判断
for i in range(len(hash1)):
#不相等则n计数+1,n最终为相似度
if hash1[i]!=hash2[i]:
n=n+1
return 1 - n / 64
def pHash(img):
"""get image pHash value"""
#加载并调整图片为32x32灰度图片
# img=cv2.imread(imgfile, 0)
#创建二维列表
h, w = img.shape[:2]
vis0 = np.zeros((h,w), np.float32)
vis0[:h,:w] = img #填充数据
#二维Dct变换
vis1 = cv2.dct(cv2.dct(vis0))
#cv.SaveImage('a.jpg',cv.fromarray(vis0)) #保存图片
vis1.resize(32,32)
#把二维list变成一维list
img_list=vis1.flatten()
#计算均值
avg = sum(img_list)*1./len(img_list)
avg_list = ['0' if i else '1' for i in img_list]
#得到哈希值
return ''.join(['%x' % int(''.join(avg_list[x:x+4]),2) for x in range(0,32*32,4)])
'''
cv2.imread
flags>0时表示以彩色方式读入图片
flags=0时表示以灰度图方式读入图片
flags<0时表示以图片的本来的格式读入图片
interpolation - 插值方法。共有5种:
1)INTER_NEAREST - 最近邻插值法
2)INTER_LINEAR - 双线性插值法(默认)
3)INTER_AREA - 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能是一个更好的方法。但如果是放大图像时,它和最近邻法的效果类似。
4)INTER_CUBIC - 基于4x4像素邻域的3次插值法
5)INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos插值
'''
def hammingDist(s1, s2):
#assert len(s1) == len(s2)
return 1 - sum([ch1 != ch2 for ch1, ch2 in zip(s1, s2)])*1. / (32*32/4)
if __name__ == '__main__':
img1 = cv2.imread(a,0)
img2 = cv2.imread(b,0)
img1 = cv2.resize(img1, (64, 64), interpolation=cv2.INTER_CUBIC)
img2 = cv2.resize(img2, (64, 64), interpolation=cv2.INTER_CUBIC)
time1 = datetime.datetime.now()
hash1 =pHash(img1)
hash2 = pHash(img2)
n = cmpHash(hash1, hash2)
print('均值哈希算法相似度:', n, "--time=", (datetime.datetime.now() - time1).microseconds)
time1 = time.time()
import datetime
path=r"D:\data\similar"
a=path+"/0720_090352_840781.jpg"
b=path+"/0720_133954_832176.jpg"
# b=path+"/0720_085858_768830.jpg"
import cv2
import numpy as np
import time
#均值哈希算法
def aHash(img):
#缩放为8*8
img=cv2.resize(img,(8,8),interpolation=cv2.INTER_CUBIC)
#转换为灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#s为像素和初值为0,hash_str为hash值初值为''
s=0
hash_str=''
#遍历累加求像素和
for i in range(8):
for j in range(8):
s=s+gray[i,j]
#求平均灰度
avg=s/64
#灰度大于平均值为1相反为0生成图片的hash值
for i in range(8):
for j in range(8):
if gray[i,j]>avg:
hash_str=hash_str+'1'
else:
hash_str=hash_str+'0'
return hash_str
#差值感知算法
def dHash(img):
#缩放8*8
img=cv2.resize(img,(9,8),interpolation=cv2.INTER_CUBIC)
#转换灰度图
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
hash_str=''
#每行前一个像素大于后一个像素为1,相反为0,生成哈希
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
#Hash值对比
def cmpHash(hash1,hash2):
n=0
#hash长度不同则返回-1代表传参出错
if len(hash1)!=len(hash2):
return -1
#遍历判断
for i in range(len(hash1)):
#不相等则n计数+1,n最终为相似度
if hash1[i]!=hash2[i]:
n=n+1
return 1 - n / 64
def pHash(imgfile):
"""get image pHash value"""
#加载并调整图片为32x32灰度图片
# img=cv2.imread(imgfile, 0)
img=cv2.resize(imgfile,(64,64),interpolation=cv2.INTER_CUBIC)
#创建二维列表
h, w = img.shape[:2]
vis0 = np.zeros((h,w), np.float32)
vis0[:h,:w] = img #填充数据
#二维Dct变换
vis1 = cv2.dct(cv2.dct(vis0))
#cv.SaveImage('a.jpg',cv.fromarray(vis0)) #保存图片
vis1.resize(32,32)
#把二维list变成一维list
img_list=vis1.flatten()
#计算均值
avg = sum(img_list)*1./len(img_list)
avg_list = ['0' if i else '1' for i in img_list]
#得到哈希值
return ''.join(['%x' % int(''.join(avg_list[x:x+4]),2) for x in range(0,32*32,4)])
'''
cv2.imread
flags>0时表示以彩色方式读入图片
flags=0时表示以灰度图方式读入图片
flags<0时表示以图片的本来的格式读入图片
interpolation - 插值方法。共有5种:
1)INTER_NEAREST - 最近邻插值法
2)INTER_LINEAR - 双线性插值法(默认)
3)INTER_AREA - 基于局部像素的重采样(resampling using pixel area relation)。对于图像抽取(image decimation)来说,这可能是一个更好的方法。但如果是放大图像时,它和最近邻法的效果类似。
4)INTER_CUBIC - 基于4x4像素邻域的3次插值法
5)INTER_LANCZOS4 - 基于8x8像素邻域的Lanczos插值
'''
def hammingDist(s1, s2):
#assert len(s1) == len(s2)
return 1 - sum([ch1 != ch2 for ch1, ch2 in zip(s1, s2)])*1. / (32*32/4)
if __name__ == '__main__':
img1 = cv2.imread(a)
img2 = cv2.imread(b)
time1 = datetime.datetime.now()
hash1 = dHash(img1)
hash2 = dHash(img2)
n = cmpHash(hash1, hash2)
print('均值哈希算法相似度:', n, "--time=", (datetime.datetime.now() - time1).microseconds)
time1 = time.time()