图片相似度计算
背景
要识别两张图片是否相似,首先我们可能会区分这两张图是人物照,还是风景照等……对应的风景照是蓝天还是大海……做一系列的分类。
从机器学习的的角度来说,首先要提取图片的特征,将这些特征进行分类处理,训练并建立模型,然后在进行识别。
但是让计算机去区分这些图片分别是哪一类是很不容易的,不过计算机可以知道图像的像素值的,因此,在图像识别过程中,通过颜色特征来识别是相似图片是我们常用的(当然还有其特征还有纹理特征、形状特征和空间关系特征等,这些有分为直方图,颜色集,颜色局,聚合向量,相关图等来计算颜色特征),
为了得到两张相似的图片,在这里通过以下几种简单的计算方式来计算图片的相似度:
- 直方图计算图片的相似度
- 通过哈希值,汉明距离计算
- 通过图片的余弦距离计算
- 通过图片结构度量计算
直方图
理论
比如有图像A和图像B,分别计算两幅图像的直方图,HistA,HistB,然后计算两个直方图的归一化相关系数(巴氏距离,直方图相交距离)等等。
这种思想是基于简单的数学上的向量之间的差异来进行图像相似程度的度量,这种方法是目前用的比较多的一种方法,第一,直方图能够很好的归一化,比如通常的256个bin条的。那么两幅分辨率不同的图像可以直接通过计算直方图来计算相似度很方便。而且计算量比较小。
这种方法的缺点:
- 直方图反映的是图像像素灰度值的概率分布,比如灰度值为200的像素有多少个,但是对于这些像素原来的位置在直方图中并没有体现,所以图像的骨架,也就是图像内部到底存在什么样的物体,形状是什么,每一块的灰度分布式什么样的这些在直方图信息中是被省略掉得。那么造成的一个问题就是,比如一个上黑下白的图像和上白下黑的图像其直方图分布是一模一样的,其相似度为100%。
- 两幅图像之间的距离度量,采用的是巴氏距离或者归一化相关系数,这种用分析数学向量的方法去分析图像本身就是一个很不好的办法。
- 就信息量的道理来说,采用一个数值来判断两幅图像的相似程度本身就是一个信息压缩的过程,那么两个256个元素的向量(假定直方图有256个bin条)的距离用一个数值表示那么肯定就会存在不准确性。
下面是一个基于直方图距离的图像相似度计算的Matlab Demo和实验结果.
1 | %计算图像直方图距离 |
通过上图可以看到这种计算图像相似度的方法确实存在很大的弊端。然而很多人也对于这种方法进行了修改,比如FragTrack算法,具体可以参见这篇论文《》。其中对图像分成横纵的小块,然后对于每一个分块搜索与之最匹配的直方图。来计算两幅图像的相似度,融入了直方图对应位置的信息。但是计算效率上很慢。
还有一种是计算一个图像外包多边形,一般得到跟踪图像的前景图后计算其外包多边形,根据外包多边形做Delauny三角形分解,然后计算每个三角形内部的直方图,对于这两个直方图组进行相似距离计算。这样就融入了直方图的位置信息。
操作
下面三张图片,分别是img1, img2, img3:
img1
img2
img3
可以看出上面这三张图是相似的,颜色上是差不多的,最相似的是哪两张大家可以猜猜看,看和我们计算的是否一样。
在 python 中利用 OpenCV 中的 calcHist()
方法获取其直方图数据,返回的结果是一个列表:
1 | import cv2 |
先计算img1的直方图,在对其归一化,最后在分别对img2,img3计算,做归一化,然后在利用python自带的compareHist()进行相似度的比较:
1 | # 利用compareHist()进行比较相似度 |
最后得到三张图片的直方图如下:
图像的x轴是指的图片的0255之间的像素变化,y轴指的是在这0255像素所占的比列。
我们可以明显的看出img2与img3的直方图的变化趋势是相符的有重合态的,运行结果如下:
1 | img1和img2的相似度:0.817515 |
通过运行结果知道img2和img3是值是最为相似的
上面的是直接调用opencv中的方法来实现的,下面还有自己写的方法:
首先是将图片转化为RGB格式,在这里是用的pillow中的Image来对图片做处理的:
1 | # 将图片转化为RGB |
在计算两图片的直方图:
1 | # 计算直方图 |
在计算其相似度:
1 | # 计算相似度 |
得到最终的运行结果:
1 | img1和img2的相似度:0.525800 |
两种方法的的结果还是有点差距的,可以看到img1和img3的结果相似度高些。
不过两者的相似度计算方法如下:
$$
\frac1N\sum_{i=1}^N(1-\frac{|g_i-s_i|}{\max(g_i,s_i)})
$$
$g_i$和$s_i$分别指的是两条曲线的第$i$个点。
总结
利用直方图计算图片的相似度时,是按照颜色的全局分布情况来看待的,无法对局部的色彩进行分析,同一张图片如果转化成为灰度图时,在计算其直方图时差距就更大了。
为了解决这个问题,可以将图片进行等分,然后在计算图片的相似度。不过在这里我就不叙述了,大家自行探讨!!!
哈希算法
感知哈希(Hash)算法
感知哈希算法是一类算法的总称,包括:aHash、pHash、dHash。顾名思义,感知哈希不是以严格的方式计算 Hash 值,而是以更加相对的方式计算 Hash 值,因为“相似”与否,就是一种相对的判定。算法输出的值越小,相似度越高,取值为$0\sim64$,即
几种hash值的比较
- aHash:平均值哈希。速度比较快,但是常常不太精确。
- pHash:感知哈希。精确度比较高,但是速度方面较差一些。
- dHash:差异值哈希。精确度较高,且速度也非常快
平均值哈希算法(aHash)
该算法是基于比较灰度图每个像素与平均值来实现。
aHash 的 Hamming 距离步骤
- 先将图片压缩成 $8*8$的小图
- 将图片转化为灰度图
- 计算图片的 Hash 值,这里的 Hash 值是64位,或者是32位01字符串
- 将上面的 Hash 值转换为16位的
- 通过 Hash值来计算汉明距离
感知哈希算法(pHash)
平均值哈希虽然简单,但是受均值影响大。如果对图像进行伽马校正或者进行直方图均值化都会影响均值,从而影响哈希值的计算。所以就有人提出更健壮的方法,通过离散余弦(DCT)进行低频提取。
离散余弦变换(DCT)是种图像压缩算法,它将图像从像素域变换到频率域。然后一般图像都存在很多冗余和相关性的,所以转换到频率域之后,只有很少的一部分频率分量的系数才不为0,大部分系数都为0(或者说接近于0)。
pHash的计算步骤
- 缩小图片:$32 * 32$ 是一个较好的大小,这样方便DCT计算转化为灰度图
- 计算DCT:利用 OpenCV 中提供的
dct()
方法,注意输入的图像必须是32位浮点型,所以先利用numpy
中的float32
进行转换 - 缩小DCT:DCT计算后的矩阵是 $3232$,保留左上角的 $88$,这些代表的图片的最低频率
- 计算平均值:计算缩小DCT后的所有像素点的平均值。
- 进一步减小DCT:大于平均值记录为1,反之记录为0.
- 得到信息指纹:组合64个信息位,顺序随意保持一致性。
- 最后比对两张图片的指纹,获得汉明距离即可。
差异值哈希算法(dHash)
相比 pHash,dHash 的速度要快的多,相比 aHash,dHash 在效率几乎相同的情况下的效果要更好,它是基于渐变实现的。
dHash 的 Hamming 距离步骤:
- 先将图片压缩成 $9*8$ 的小图,有72个像素点
- 将图片转化为灰度图
- 计算差异值:dHash算法工作在相邻像素之间,这样每行9个像素之间产生了8个不同的差异,一共8行,则产生了64个差异值,或者是32位01字符串。
- 获得指纹:如果左边的像素比右边的更亮,则记录为1,否则为0.
- 通过hash值来计算汉明距离
计算哈希值差异
- 图像指纹:和人的指纹一样,是身份的象征。而图像指纹简单点来讲,就是将图像按照一定的哈希算法,经过运算后得出的一组二进制数字。
- 算法中1和0顺序组合起来的即是图片的指纹hash。顺序不固定,但是比较的时候必须是相同的顺序。
- 汉明(Hamming)距离:就是一组二进制数据变成另一组数据所需的步骤数,显然,这个数值可以衡量两张图片的差异,汉明距离越小,则代表相似度越高。汉明距离为0,即代表两张图片完全一样。
- 假如一组二进制数据为101,另外一组为111,那么显然把第一组的第二位数据0改成1就可以变成第二组数据111,所以两组数据的汉明距离就为1。
- 对比两幅图的指纹,计算汉明距离,即两个64位的hash值有多少是不一样的,不同的位数越小,图片越相似
算法代码
1 | import cv2 |
最终的运行结果:
1 | --->aHash<--- |
通过上面运行的结果可以看出来,img1和img2的相似度高一些。
余弦相似度(cosin)
把图片表示成一个向量,通过计算向量之间的余弦距离来表征两张图片的相似度。
1 | from PIL import Image |
最终运行结果:
1 | img1 与 img2 的余弦相似度: |
结果显示img1和img2的相似度高一些,和计算hash值的汉明距离得到的结果是相一致的。
SSIM(结构相似度量)
SSIM是一种全参考的图像质量评价指标,分别从亮度、对比度、结构三个方面度量图像相似性。SSIM取值范围[0, 1],值越大,表示图像失真越小。在实际应用中,可以利用滑动窗将图像分块,令分块总数为N,考虑到窗口形状对分块的影响,采用高斯加权计算每一窗口的均值、方差以及协方差,然后计算对应块的结构相似度SSIM,最后将平均值作为两图像的结构相似性度量,即平均结构相似性SSIM。
1 | ssim1 = compare_ssim(img1, img2, multichannel=True) |
这个是scikit-image库自带的一种计算方法
运行结果:
1 | img1与img2的SSIM: |
可以看到img1和img2的相似度高。
互信息(Mutual Information)
通过计算两个图片的互信息来表征他们之间的相似度,如果两张图片尺寸相同,还是能在一定程度上表征两张图片的相似性的。但是,大部分情况下图片的尺寸不相同,如果把两张图片尺寸调成相同的话,又会让原来很多的信息丢失,所以很难把握。经过实际验证,此种方法的确很难把握。
1 | from sklearn import metrics as mr |
数学上的矩阵分解
图像本身就是一个矩阵,可以依靠数学上矩阵分解的一些知识来获取矩阵中一些代表这个矩阵元素值和分布的一些鲁棒性特征来对图像的相似度进行计算。
最常用的一般是SVD分解和NMF分解。
SVD分解
下面简单介绍下SVD分解的一些性质,如果需要探究的更深入一点网上有一些相关文献,读者可以去探究的更清楚:
奇异值的稳定性
奇异值的比例不变性
奇异值的旋转不变性
奇异值的压缩性
综上所述,可以看出奇异值分解是基于整体的表示。图像奇异值特征向量不但具有正交变换、旋转、位移、镜像映射等代数和几何上的不变性,而且具有良好的稳定性和抗噪性,广泛应用于模式识别与图像分析中。
对图像进行奇异值分解的目的是:
- 得到唯一、稳定的特征描述;降低特征空间的维数;
- 提高抵抗干扰和噪声的能力。
但是由于奇异值分解得到的奇异矢量中有负数存在所以不能很好的解释其物理意义。
非负矩阵分解(NMF)
NMF的主要思想是将非负矩阵分解为可以体现图像主要信息的基矩阵与系数矩阵,并且可以对基矩阵赋予很好的解释,比如对人脸的分割,得到的基向量正是人的“眼睛”,“鼻子”等主要概念特征,源图像表示为这些特征的加权组合。所以NMF算法也在人脸识别等场合中发挥着巨大的作用。
下面一个实验说明了SVD+NMF数学上的这些分解在图像相似度判定方面的应用,这个跟我目前的课题有关细节方面就不再透露更多了。
当然基于数学上的矩阵特征值计算的还有很多方法比如Trace变换,不变矩计算等等,当然如果有需要这方面资料的同学可以找我,我可以进行相关的帮助。
基于特征点
每一幅图像都有自己的特征点,这些特征点表征图像中比较重要的一些位置,比较类似函数的拐点那种,通常比较常用的有Harris角点和Sift特征点。那么将得到的图像角点进行比较,如果相似的角点数目较多,那么可以认为这两幅图像的相似程度较高。这里主要介绍基于Sift算子。
对于Sift的原理和代码可以参见David Lower的网站。
那么我们就可以通过找到匹配点的个数来判断两幅图像是否一致,这个算法的好处是对于一个物体,两个不同角度下得到的照片依然可以找到很多的匹配点,我也一直认为是一个综合来说结果相对较为准确的方法,但是由于每个特征点需要计算一个长度不小的特征值,也造成了该算法的时间消耗比较大。所以不常用于实时的视频处理。这个算法还有一个好处就是可以通过找到的匹配特征点进行图像校正。关于使用Sift做图像校正请参见我的另外一篇博文。可见Sift对于抗旋转和噪声的效果确实很好。对于Sift也不能全部相信,一般使用RANSAC对于错误匹配点去除可以达到更好的效果,当然目前也有很多对SIFT进行改进的算法。希望有这方面研究的可以多多交流。
参考文献
python图像识别———图片相似度计算 - 云+社区 - 腾讯云 (tencent.com)
(10条消息) Python计算图片之间的相似度_~小疯子~的博客-CSDN博客_python 图片相似度
(10条消息) python OpenCV 图片相似度 5种算法_追求卓越,做到专业-CSDN博客_python图片相似度识别算法