图像相似度度量的应用场景很多,包括以图搜图,相似图像去重等多种功能,目前在项目中的场景是针对大量重复类似的图片,需要进行筛选剔除,自然需要用到图像相似度,简单调研了下图像相似度的方法,包括传统图像方法和机遇深度学习的方法。
有个很 naive 的搞法是拿随便一个什么 imagenet 网络的倒数第二层结果做 feature vector。但是因为这是基于分类的结果,必然是丢弃纹理而取高层语义的。那么如果你的需求就是比较一堆比方说大理石纹理……我猜这个 feature 都未必比得上直接比直方图。
还有比如使用AM-softmax进行分类训练,把训练好的网络去掉最后一层分类层,选取最后一个隐藏层输出作为一个数据的特征。两个数据特征使用余弦相似度进行计算,简单来说,拿一个在imagenet上预训练的vgg模型提取图像特征,然后用余弦比较两个特征向量的相似度。
传统图像方法,主要是哈希算法,分别有以下几种:
平均哈希算法(ahash):
此算法是基于比较灰度图每个像素与平均值来实现的,最适用于缩略图,放大图搜索。
1.缩放图片:为了保留结构去掉细节,去除大小、横纵比的差异,把图片统一缩放到8*8,共64个像素的图片。
2.转化为灰度图:把缩放后的图片转化为256阶的灰度图。
附上灰度图相关算法(R = red, G = green, B = blue)
1.浮点算法:Gray=R*0.3+G*0.59+B*0.11
2.整数方法:Gray=(R*30+G*59+B*11)/100
3.移位方法:Gray =(R*76+G*151+B*28)>>8;
4.平均值法:Gray=(R+G+B)/3;
5.仅取绿色:Gray=G;
3.计算平均值: 计算进行灰度处理后图片的所有像素点的平均值。
4.比较像素灰度值:遍历灰度图片每一个像素,如果大于平均值记录为1,否则为0.
5.得到信息指纹:组合64个bit位,顺序随意保持一致性即可。
6.对比指纹:计算两幅图片的指纹,计算汉明距离(从一个指纹到另一个指纹需要变几次),汉明距离越大则说明图片越不一致,反之,汉明距离越小则说明图片越相似,当距离为0时,说明完全相同。(通常认为距离>10 就是两张完全不同的图片)
感知哈希算法(phash):
平均哈希算法过于严格,不够精确,更适合搜索缩略图,为了获得更精确的结果可以选择感知哈希算法,它采用的是DCT(离散余弦变换)来降低频率的方法
1.缩小图片:32 * 32是一个较好的大小,这样方便DCT计算
2.转化为灰度图:把缩放后的图片转化为256阶的灰度图。(具体算法见平均哈希算法步骤)
3.计算DCT:DCT把图片分离成分率的集合
4.缩小DCT:DCT是32*32,保留左上角的8*8,这些代表的图片的最低频率
5.计算平均值:计算缩小DCT后的所有像素点的平均值。
6.进一步减小DCT:大于平均值记录为1,反之记录为0.
7.得到信息指纹:组合64个信息位,顺序随意保持一致性即可
8.对比指纹:计算两幅图片的指纹,计算汉明距离(从一个指纹到另一个指纹需要变几次),汉明距离越大则说明图片越不一致,反之,汉明距离越小则说明图片越相似,当距离为0时,说明完全相同。(通常认为距离>10 就是两张完全不同的图片)
感知哈希算法示例代码:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File :sample_diff.py
@Date :2020/03/31 10:18:00
@Author :mrwang
@Version :1.0
'''
from PIL import Image
from os import listdir
def picPostfix(): # 图片后缀的集合
postFix = set()
postFix.update(['bmp', 'jpg', 'png', 'tiff', 'gif', 'pcx', 'tga', 'exif',
'fpx', 'svg', 'psd', 'cdr', 'pcd', 'dxf', 'ufo', 'eps', 'JPG', 'raw', 'jpeg'])
return postFix
def getDiff(width, height, image): # 将要裁剪成w*h的image照片 得到渐变序列
diff = []
im = image.resize((width, height))
imgray = im.convert('L') # 转换为灰度图片 便于处理
pixels = list(imgray.getdata()) # 得到像素数据 灰度0-255
for row in range(height):
rowStart = row * width # 起始位置行号
for index in range(width - 1):
leftIndex = rowStart + index # 当前位置号
rightIndex = leftIndex + 1
diff.append(pixels[leftIndex] > pixels[rightIndex])
return diff
def getHamming(diff=[], diff2=[]):
# print len(diff)
hamming_distance = 0
for i in range(len(diff)):
if diff[i] != diff2[i]:
hamming_distance += 1
return hamming_distance
if __name__ == '__main__':
width = 32
height = 32
thre = 100 #判别的汉明距离阈值,根据自己情况设置
dirName = "/home/workspace/lost/Images" # 图片路径
allDiff = []
postFix = picPostfix()
dirList = listdir(dirName)
cnt = 0
for i in dirList:
cnt += 1
print('cnt:', cnt)
if str(i).split('.')[-1] in postFix: # 判断后缀是不是照片格式
im = Image.open(r'%s/%s' % (dirName, str(i)))
diff = getDiff(width, height, im)
allDiff.append((str(i), diff))
print(len(allDiff))
for i in range(len(allDiff)):
for j in range(i + 1, len(allDiff)):
if i != j:
ans = getHamming(allDiff[i][1], allDiff[j][1])
if ans <= thre:
print(allDiff[i][0], "and", allDiff[j][0], "maybe same photo.")
差异哈希算法(dhash):
相比pHash,dHash的速度要快的多,相比aHash,dHash在效率几乎相同的情况下的效果要更好,它是基于渐变实现的。
1.缩小图片:收缩到9*8的大小,一遍它有72的像素点
2.转化为灰度图:把缩放后的图片转化为256阶的灰度图。(具体算法见平均哈希算法步骤)
3.计算差异值:dHash算法工作在相邻像素之间,这样每行9个像素之间产生了8个不同的差异,一共8行,则产生了64个差异值
4.获得指纹:如果左边的像素比右边的更亮,则记录为1,否则为0.
需要说明的是这种指纹算法不仅可以应用于图片搜索,同样适用于其他多媒体形式。除此之外,图片搜索特征提取方法有很多,很多算法还有许多可以改进的地方,比如对于人物可以先进行人脸识别,再在面部区域进行局部的哈希,或者背景是纯色的可以先过滤剪裁等等。
深度学习的方法,主要是孪生网络:
Siamese networks(孪生网络)
首先我们会喂给神经网络成对的图片,然后训练网络让它去猜测这两个图片是否属于同一类别。然后在给定测试集的样本时,神经网络可以将该样本与测试集中的样本配对并且选择出最有可能和测试集同类别的样本,因此我们需要的神经网络模型的输入是两张图片,然后输出它们之间属于同一类别的可能性。
假设
如果我们将两个样本连接(concate操作)在一起形成单个输入并且将其喂给神经网络,那么每个合成样本(调换两个样本concate的顺序,整个合成矩阵的结构就不一样了)的矩阵元素会和不同的一系列权重值相称或卷积,这就打破了“对称性”的原则。换句话说,即使是对不同顺序的输入最后都能学习到相同的weights参数,因此我们可以设计一种相同的网络结构并且两者之间权重共享,每种网络结构对应于一个input(因为两者完全相同,因此两个input即使调换位置也不会影响模型的学习)。然后用绝对值距离作为输入喂给线性分类器得到output。这就是Siamese network 的由来。
基于孪生网络的方法有个短板就是训练数据要有label。cvpr2016年有一篇论文是基于无监督的,Learning Compact Binary Descriptors with Unsupervised Deep Neural Networks cvpr,重点是无监督,图像可以是无label的,实际上是大量的样本自己去产生类似的hash值,隐含的聚类。