• "date": "2020-05-11"
  • "title": "基于Hash值的图片检索"

环境:Python 3.7 + Opencv 4.1.2 + Numpy 1.18.1

一、实验目的

图像的Hash技术是能够快速检索图片的技术之一,它能够快速度量出图像相似度,三种Hash算法都是通过获取图片的Hash值,再比较两张图片Hash值的距离,即对于两个图片的Hash值,各个bit相同的越多,则图片相似度越高。

实验流程:

1.由10张原图,创建90张实验图片

2.分别计算90张图片的aHash,dHash,pHash值

3.分别在三种算法下,从90图中,找出与每张原图最相似的10图

4.分析结果

二、代码模块

1、平均哈希算法(aHash)

首先要对图像进行灰度处理,使用opencv中的COLOR_BGR2GRAY模式,其计算公式为Gray = 0.1140*B + 0.5870*G + 0.2989*R。往后函数一致。

接着计算图像进行灰度处理后图片的所有像素点的平均值;再比较像素灰度值,遍历灰度图片每一个像素,如果大于平均值记录为1,否则为0,最后就能得到信息指纹,组合64个bit位,顺序随意保持一致性即可。

def fun(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 = ''
   avg = numpy.mean(gray)
   # 灰度大于平均值为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

2、感知哈希算法(pHash)

平均Hash算法过于严格,不够精确,更适合搜索缩略图,为了获得更精确的结果可以选择感知Hash算法,它采用的是DCT(离散余弦变换)来降低频率的方法。

DCT变换类似于离散傅里叶变换,但是只使用实数,相当于一个长度大概是它两倍的离散傅里叶变换,这个离散傅里叶变换是对一个实偶函数进行的(因为一个实偶函数的傅里叶变换仍然是一个实偶函数)。

先缩小图片至3232,便于DCT变换,转化为灰度图后进行DCT变换,保留其左上角集中图片最低频率的88块,计算缩小DCT后的所有像素点的均值,对DCT系数进行编码,大于均值计1,小于计0,得到指纹信息。

def fun(img):
    # 缩放32*32
    img = cv2.resize(img, (32, 32)) # interpolation=cv2.INTER_CUBIC
    # 转换灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 将灰度图转为浮点型,再进行dct变换
    dct = cv2.dct(numpy.float32(gray))
    # 取左上角8*8
    dct_roi = dct[0:8, 0:8]
    hash_str = ''
    avg = numpy.mean(dct_roi)
    for i in range(dct_roi.shape[0]):
      for j in range(dct_roi.shape[1]):
         if dct_roi[i, j] > avg:
           hash_str = hash_str + '1'
         else:
           hash_str = hash_str + '0'
    return hash_str

3、差异哈希算法(dHash)

差异Hash算法比前两种算法的速度要快得多,相比aHash,dHash在效率几乎相同的情况下的效果要更好,它是基于渐变实现的。

首先是将图片缩小至9*8,这样每行就有8个差值,转化为灰度图;接着计算差异值,dHash算法工作在相邻像素之间,这样每行9个像素之间产生了8个不同的差异,一共8行,则产生了64个差异值,如果左边的像素比右边的更亮,则记录为1,否则为0。

def fun(img):
    # 缩放9*8
    img = cv2.resize(img, (9, 8), interpolation=cv2.INTER_CUBIC)
    # 转换灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    hash_str = ''
    # 每行左边像素大于右边像素为1,相反为0,生成Hash
    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

4、相似度

对于两个图片的Hash值,各个bit相同的越多,则图片相似度越高。

n = 0
for m in range(len(x)):
# 不相等则n计数+1,n最终为相似度
if x[m] != y[m]:
    n = n + 1
return n

5、计算90图Hash值

读取图片并将图片名称存入列表p,计算图片Hash值存入列表hash_all_2(二进制)与hash_all(十六进制)。两个列表的索引为一一对应关系。

def cmp(x, y):
  n = 0
  for m in range(len(x)):
      # 不相等则n计数+1,n最终为相似度
      if x[m] != y[m]:
          n = n + 1
  return n


p = [] # 90张图
for i in range(10):
  p.append('pic/' + str(i) + '.jpg')
  p.append('pic/' + str(i) + '.png')
  p.append('pic/' + str(i) + '_small.jpg')
  p.append('pic/' + str(i) + '_small.png')
  p.append('pic/' + str(i) + '_wide.jpg')
  p.append('pic/' + str(i) + '_wide.png')
  p.append('pic/' + str(i) + 'water.jpg')
  p.append('pic/' + str(i) + 'water_small.png')
  p.append('pic/' + str(i) + 'water_wide.png')

hash_all_2 = []
hash_all = []
# with open('ahash.txt', 'a') as f1:
for k in range(90):
  img = cv2.imread(p[k])
  # hash_2 = aHash.fun(img) # Hash算法选择
  # hash_2 = pHash.fun(img)
  hash_2 = dHash.fun(img)
  hash_16 = hex(int(hash_2, 2))[2:].zfill(16)
  hash_all_2.append(hash_2)
  hash_all.append(hash_16)
  # key = p[k] + ' ' + hash_16 + '\n'
  # f1.write(key)
  # print(k, hash_16, 'save')

6、最相似10图

从原图索引列表中选中原图地址,将原图与90图的Hash值一一对比。由于两图Hash值相似度存在相等重复,故列出Hash值最近的20图,选出靠前10图。

f = xlwt.Workbook()
sheet = f.add_sheet('dHash', cell_overwrite_ok=True)
ori = [0, 9, 18, 27, 36, 45, 54, 63, 72, 81]  # 原图
for i in ori:
    hash_ori = hash_all_2[i]
    print(p[i], hash_all[i])
    dist = []  # 原图与90图的距离
    for j in range(90):
        hash_cmp = hash_all_2[j]
        dist.append(cmp(hash_ori, hash_cmp))
    print('距离:', dist)
    temp = []
    for k in range(20):  # 距离最小的20张图
        min_num = dist.index(min(dist))
        temp.append(min_num)
        print(k, p[min_num], 'hash值为', hash_all[min_num], '距离为', dist[min_num])
        sheet.write(k + (ori.index(i) * 20), 0, k)
        sheet.write(k + (ori.index(i) * 20), 1, p[min_num])
        sheet.write(k + (ori.index(i) * 20), 2, hash_all[min_num])
        sheet.write(k + (ori.index(i) * 20), 3, dist[min_num])
        dist[min_num] = 99
f.save('Hash.xls')

三、哈希值对比实验结果

以10为基准,两图距离超过10的判断为两张不一样的图,小于10的为同一张图,每个算法10张原图均作90次比较。

1、平均哈希算法

本实验中,平均哈希算法判断相似图片全部准确,不同图片的相差较大,距离均在20以上。aHash能区别相似图片和差异大的图片。

而在相似图片中,对改图的细节区分不明显,距离一般为0、1。其中3.jpg、7.jpg、8.jpg三张图甚至与其改图的距离全为0。只有2.jpg的多数改图距离在2以上,最高达到7。

2、感知哈希算法

本实验中,感知哈希算法判断相似图片全部准确,多数不同图片的相差较大,在18以上,而1.jpg与2.jpg的距离为12-13,差距较小。dHash能区别相似图片和差异大的图片。

而在相似图片中,对改图细节区分比aHash更明显,未出现原图与其改图距离全0的情况,且大多数情况下,加水印后的改图距离较其他改图距离更大。

3、差异哈希算法

本实验中,差异哈希算法判断相似图片全部准确,不同图片的相差较大,均在20以上,半数在25以上。dHash能区别相似图片和差异大的图片,且在三种算法中效果最好。

在相似图片中,对改图细节区分比aHash更明显,未出现原图与其改图距离全0的情况。

四、后记

起初寻找了Python中有关图像哈希编码的第三方库,通过实验发现ImageHash中的dHash编码中,其1、0的记录与课上所讲相反,有些微差别,且Pillow库与Opencv库的灰度图转换函数也有些许差别,得出的灰度图有微小不同。

俗话说得好:“消除恐惧的最好方法就是面对恐惧,坚持就是胜利。加油。奥力给!”