8

【声明】度量两张图片的相似度有许多算法,本文将对常用的图片相似度算法进行汇总。部分数据、资料来源于各技术网站,如有侵权烦请联系删除。
常用的算法有几类:

一、Hash算法

  • Hash算法常用的有三种,分别为平均哈希算法(aHash)、感知哈希算法你(pHash)和差异哈哈希算法(dHash);还有一种是小波哈希算法(whash)。
  • Hash算法都是通过获取图片的hash值,再比较两张图片hash值的汉明距离来度量两张图片是否相似。两张图片越相似,那么两张图片的hash数的汉明距离越小。

1、aHash-平均哈希

算法步骤

平均哈希算法是三种Hash算法中最简单的一种,它通过下面几个步骤来获得图片的Hash值:

(1) 缩放图片;
(2) 转灰度图; 
(3) 算像素均值;
(4) 根据相似均值计算指纹.

image.png
得到图片的ahash值后,比较两张图片ahash值的汉明距离,通常认为汉明距离小于10的一组图片为相似图片。

举栗

我们以一张动物的图片为例,详解ahash算法过程:
原图:
husky.jpg

缩放图片
先将图片缩放为8*8的图片,得到下图:
image.png

图片灰度
得到灰度图:
image.png
得到单通道灰度图的各个像素值
image.png

计算平均值
求得的平均值为152.4375

每个像素点与平均值比较
image.png
那么该图片的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); 

举栗

原图:
husky.jpg

缩放图片
先将图片缩放为9*8的图片,得到下图:
image.png
图片灰度
得到灰度图:
image.png

得到单通道灰度图的各个像素值
image.png

每个像素点与后面一个像素点比较值
image.png

那么该图片的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值。

举栗

原图:
husky.jpg

缩放图片
先将图片缩放为32*32的图片,得到下图:
image.png

图片灰度
得到灰度图:
image.png

计算图片DCT
得到DCT图:
image.png

得到图片低频DCT
得到低频DCT图:
image.png
低频DCT值:
image.png
平均值为: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已经成为广播和有线电视中广为使用的一种衡量视频质量的方法。在超分辨率,图像去模糊 中都有广泛的应用。

更多的是应用于同一张照片,在传输过程前后的对比。

image.png

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)

image.png

参考文章:

https://cloud.tencent.com/dev...
https://www.cnblogs.com/Kalaf...
https://blog.csdn.net/u010977...


Sunflower
171 声望8 粉丝

Keep Hungery!