【声明】度量两张图片的相似度有许多算法,本文将对常用的图片相似度算法进行汇总。部分数据、资料来源于各技术网站,如有侵权烦请联系删除。
常用的算法有几类:
一、Hash算法
- Hash算法常用的有三种,分别为平均哈希算法(aHash)、感知哈希算法你(pHash)和差异哈哈希算法(dHash);还有一种是小波哈希算法(whash)。
- Hash算法都是通过获取图片的hash值,再比较两张图片hash值的汉明距离来度量两张图片是否相似。两张图片越相似,那么两张图片的hash数的汉明距离越小。
1、aHash-平均哈希
算法步骤
平均哈希算法是三种Hash算法中最简单的一种,它通过下面几个步骤来获得图片的Hash值:
(1) 缩放图片;
(2) 转灰度图;
(3) 算像素均值;
(4) 根据相似均值计算指纹.
得到图片的ahash值后,比较两张图片ahash值的汉明距离,通常认为汉明距离小于10的一组图片为相似图片。
举栗
我们以一张动物的图片为例,详解ahash算法过程:
原图:
缩放图片
先将图片缩放为8*8的图片,得到下图:
图片灰度
得到灰度图:
得到单通道灰度图的各个像素值
计算平均值
求得的平均值为152.4375
每个像素点与平均值比较
那么该图片的ahash值即为:1111100011110000111111001111011011100111111011101111000000001000
ahash代码
import cv2
import numpy as np
import copy
def ahash_process(pic_path):
'''
ahash算法过程
:param pic_path: 图片路径
:return:
'''
img = cv2.imread(pic_path, cv2.IMREAD_UNCHANGED)
img_resize = cv2.resize(img, (8, 8), cv2.INTER_AREA)
gray_img = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
print('单通道灰度图:', gray_img)
avg = np.mean(gray_img)
print('平均值:', avg)
dis = copy.deepcopy(gray_img)
dis_hash_str = ''
for x_index, x in enumerate(gray_img):
for y_index, y in enumerate(x):
if y >= avg:
dis[x_index, y_index] = 1
else:
dis[x_index, y_index] = 0
dis_hash_str += str(dis[x_index, y_index])
print('与平均值比较:', dis)
print('该图片的ahash值:', dis_hash_str)
cv2.namedWindow('resize', 0)
cv2.resizeWindow('resize', 600, 600)
cv2.imshow('resize', img_resize)
cv2.namedWindow('gray', 0)
cv2.resizeWindow('gray', 600, 600)
cv2.imshow('gray', gray_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
两张图片对比自然需要另外一张图片,同理通过上述方法,计算出hash值后,计算汉明距离即可。
2、dHash-差异哈希
算法步骤
dhash和ahash算法的差别在于,ahash使用每个像素点与平均像素做对比得出布尔值,dhash是使用当前像素与后一个像素点做对比得到布尔值,正是这个原因,所以dhash算法需要将图片缩放为9*8个像素点。
(1)图片缩放为9*8,保留结构,出去细节;
(2)灰度化:转换为256阶灰度图;
(3)求平均值:计算灰度图所有像素的平均值;
(4)比较:像素值大于后一个像素值记作1,相反记作0。本行不与下一行对比,每行9个像素,八个差值,有8行,总共64位 ;
(5)生成hash:将上述步骤生成的1和0按顺序组合起来既是图片的指纹(hash);
举栗
原图:
缩放图片
先将图片缩放为9*8的图片,得到下图:
图片灰度
得到灰度图:
得到单通道灰度图的各个像素值
每个像素点与后面一个像素点比较值
那么该图片的dhash值即为:0011011010111011010110101111001011010011101110011110101111001101
dHash代码
import cv2
def dhash_process(pic_path):
img = cv2.imread(pic_path, cv2.IMREAD_UNCHANGED)
img_resize = cv2.resize(img, (9, 8), cv2.INTER_AREA)
gray_img = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
print('单通道灰度图:', gray_img)
dis = [[] for x in range(8)]
dis_hash_str = ''
for row in range(8):
for col in range(8):
if gray_img[row, col] >= gray_img[row, col + 1]:
dis[row].append(1)
dis_hash_str += str(1)
else:
dis[row].append(0)
dis_hash_str += str(0)
print('比较值:', dis)
print('该图片的dhash值:', dis_hash_str)
cv2.namedWindow('resize', 0)
cv2.resizeWindow('resize', 600, 600)
cv2.imshow('resize', img_resize)
cv2.namedWindow('gray', 0)
cv2.resizeWindow('gray', 600, 600)
cv2.imshow('gray', gray_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3、pHash-感知哈希
算法步骤
dhash是三种Hash算法中较为复杂的一种,它是基于DCT(离散余弦变换)来得到图片的hash值:
(1)缩小图片:32*32是一个较好的大小,这样方便DCT计算;
(2)灰度化:转换为256阶灰度图;
(3)计算DCT:DCT把图片分离成分率的集合,DCT(离散余弦变换);
(4)缩小DCT:DCT计算后的矩阵是32 * 32,保留左上角的8 * 8,这些代表的图片的最低频率;
(5)计算平均值:计算缩小DCT后的所有像素点的平均值;
(6)比较平均值:大于平均值记录为1,反之记录为0,得到phash值。
举栗
原图:
缩放图片
先将图片缩放为32*32的图片,得到下图:
图片灰度
得到灰度图:
计算图片DCT
得到DCT图:
得到图片低频DCT
得到低频DCT图:
低频DCT值:
平均值为:97.10013
低频DCT值与平均值比较
得到phash值为:1110011010100100000010001101001100010000110000001001000000010000
pHash代码
import cv2
import numpy as np
def phash_process(pic_path):
img = cv2.imread(pic_path, cv2.IMREAD_UNCHANGED)
img_resize = cv2.resize(img, (32, 32), cv2.INTER_AREA)
gray_img = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
print('单通道灰度图:', gray_img)
gray_img_dct = cv2.dct(np.float32(gray_img))
gray_img_low_dct = gray_img_dct[0:8, 0:8]
print('低频DCT图值:', gray_img_low_dct)
avg = np.mean(gray_img_low_dct)
print('低频DCT图平均值:', avg)
dis = [[] for x in range(8)]
dis_hash_str = ''
for row_index, row in enumerate(gray_img_low_dct):
for col_index, col in enumerate(row):
print(row_index, col_index)
if col >= avg:
dis[row_index].append(1)
dis_hash_str += '1'
else:
dis[row_index].append(0)
dis_hash_str += '0'
print('phash值:', dis_hash_str)
cv2.namedWindow('resize', 0)
cv2.resizeWindow('resize', 600, 600)
cv2.imshow('resize', img_resize)
cv2.namedWindow('gray', 0)
cv2.resizeWindow('gray', 600, 600)
cv2.imshow('gray', gray_img)
cv2.namedWindow('DCT', 0)
cv2.resizeWindow('DCT', 600, 600)
cv2.imshow('DCT', gray_img_dct)
cv2.namedWindow('low_DCT', 0)
cv2.resizeWindow('low_DCT', 600, 600)
cv2.imshow('low_DCT', gray_img_low_dct)
cv2.waitKey(0)
cv2.destroyAllWindows()
4、wHash-小波哈希
wavelet hash是频表示的另一种形式。whash相当于将phash中的DCT变换改为DWT。
离散小波变换(DWT)是频表示的另一种形式。流行的DCT和傅立叶变换使用余弦函数作为sin\cos的基础:sin(x),sin(2x),sin(3x)等等。与此相反,DWT使用一个单一的功能作为基础,但在不同的形式:缩放和移动。
小波hash在平时用的不多,在此不展开。
以上是所有hash算法的总结
5、彩蛋
那我们在平时使用过程中,是不需要自己手写hash算法过程的。为什么写在最后,其实是想让大家能够耐心看完前面的内容,对算法底层有一定的了解,求不挨打!
有现有的包(imagehash)供大家使用:
pip install imagehash
from imagehash import phash
def image_similar_compare(img1, img2):
# 以phash为栗子,其余hash方法也可以直接引用的
hash1 = phash(Image.open(img1))
hash2 = phash(Image.open(img2))
# 计算汉明距离
return 1 - (hash1 - hash2) / len(hash1.hash) ** 2
二、SSIM算法
SSIM(结构相似性度量),这是一种全参考的图像质量评价指标,分别从亮度、对比度、结构三个方面度量图像相似性。 均值作为亮度的估计,标准差作为对比度的估计,协方差作为结构相似程度的度量。 SSIM取值范围[0,1],值越大,表示图像失真越小,越相似。
计算步骤
在实际应用中,可以利用滑动窗将图像分块,令分块总数为N,考虑到窗口形状对分块的影响,采用高斯加权计算每一窗口的均值、方差以及协方差;然后计算对应块的结构相似度SSIM,最后将平均值作为两图像的结构相似性度量,即平均结构相似性MSSIM。
优点
结构相似度指数从图像组成的角度将结构信息定义为独立于亮度、对比度的反映场景中物体结构的属性,并将失真建模为亮度、对比度和结构三个不同因素的组合。
应用
SSIM已经成为广播和有线电视中广为使用的一种衡量视频质量的方法。在超分辨率,图像去模糊 中都有广泛的应用。
更多的是应用于同一张照片,在传输过程前后的对比。
uX、uY分别表示图像X和Y的均值,σX、σY分别表示图像X和Y的标准差,σXσX、σYσY(实在打不出上标啊,理解万岁)分别表示图像X和Y的方差。σXY代表图像X和Y协方差。C1,C2和C3为常数,是为了避免分母为0而维持稳定。通常取C1=(K1L)^2, C2=(K2L)^2, C3=C2/2, 一般地K1=0.01, K2=0.03, L=255( 是像素值的动态范围,一般都取为255)
参考文章:
https://cloud.tencent.com/dev...
https://www.cnblogs.com/Kalaf...
https://blog.csdn.net/u010977...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。