哈希算法-图片相似度计算

哈希算法实现图片相似度计算
实现图片相似度比较的哈希算法有三种:均值哈希算法,差值哈希算法,感知哈希算法

1.均值哈希算法
一张图片就是一个二维信号,它包含了不同频率的成分。亮度变化小的区域是低频成分,它描述大范围的信息。而亮度变化剧烈的区域(比如物体的边缘)就是高频的成分,它描述具体的细节。或者说高频可以提供图片详细的信息,而低频可以提供一个框架。 而一张大的,详细的图片有很高的频率,而小图片缺乏图像细节,所以都是低频的。所以我们平时的下采样,也就是缩小图片的过程,实际上是损失高频信息的过程。均值哈希算法就是利用图片的低频信息。
具体步骤:
(1)缩小尺寸:将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。
(2)简化色彩:将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
(3)计算平均值:计算所有64个像素的灰度平均值
(4)比较像素的灰度:将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。
(5)计算哈希值:将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
最后得到两张图片的指纹信息后,计算两组64位数据的汉明距离,即对比数据不同的位数,不同位数越少,表明图片的相似度越大。
分析: 均值哈希算法计算速度快,不受图片尺寸大小的影响,但是缺点就是对均值敏感,例如对图像进行伽马校正或直方图均衡就会影响均值,从而影响最终的hash值。

2.感知哈希算法
感知哈希算法是一个比均值哈希算法更为健壮的一种算法,与均值哈希算法的区别在于感知哈希算法是通过DCT(离散余弦变换)来获取图片的低频信息。
离散余弦变换(DCT)是种图像压缩算法,它将图像从像素域变换到频率域。然后一般图像都存在很多冗余和相关性的,所以转换到频率域之后,只有很少的一部分频率分量的系数才不为0,大部分系数都为0(或者说接近于0)。经过DCT变换后的系数矩阵从左上角到右下角频率越来越高,因此图片的能量主要保留在左上角的低频系数上了。
具体步骤:
(1)缩小尺寸:pHash以小图片开始,但图片大于8x8,32x32是最好的。这样做的目的是简化了DCT的计算,而不是减小频率。
(2)简化色彩:将图片转化成灰度图像,进一步简化计算量。
(3)计算DCT:计算图片的DCT变换,得到32x32的DCT系数矩阵。
(4)缩小DCT:虽然DCT的结果是32x32大小的矩阵,但我们只要保留左上角的8x8的矩阵,这部分呈现了图片中的最低频率。
(5)计算平均值:如同均值哈希一样,计算DCT的均值。
(6)计算hash值:这是最主要的一步,根据8x8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1”,小于DCT均值的设为“0”。组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。
分析: 结果并不能告诉我们真实性的低频率,只能粗略地告诉我们相对于平均值频率的相对比例。只要图片的整体结构保持不变,hash结果值就不变。能够避免伽马校正或颜色直方图被调整带来的影响。对于变形程度在25%以内的图片也能精准识别。

3.差值哈希算法
比pHash,dHash的速度要快的多,相比aHash,dHash在效率几乎相同的情况下的效果要更好,它是基于渐变实现的。
主要步骤:
(1)缩小尺寸:收缩到8x9(高x宽)的大小,一遍它有72的像素点
(2)转化为灰度图:把缩放后的图片转化为256阶的灰度图。
(3)计算差异值:dHash算法工作在相邻像素之间,这样每行9个像素之间产生了8个不同的差异,一共8行,则产生了64个差异值
(4)获得指纹:如果左边的像素比右边的更亮,则记录为1,否则为0.

代码实现如下:

import cv2
import numpy as np
#感知哈希算法
def pHash(image): 
    image = cv2.resize(image,(32,32), interpolation=cv2.INTER_CUBIC) 
    image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) 
#     cv2.imshow('image', image)
#     cv2.waitKey(0)
#     cv2.destroyAllWindows()
    # 将灰度图转为浮点型,再进行dct变换 
    dct = cv2.dct(np.float32(image))
#     print(dct)
    # 取左上角的8*8,这些代表图片的最低频率 
    # 这个操作等价于c++中利用opencv实现的掩码操作 
    # 在python中进行掩码操作,可以直接这样取出图像矩阵的某一部分 
    dct_roi = dct[0:8,0:8]  
    avreage = np.mean(dct_roi) 
    hash = [] 
    for i in range(dct_roi.shape[0]): 
        for j in range(dct_roi.shape[1]): 
            if dct_roi[i,j] > avreage: 
                hash.append(1) 
            else: 
                hash.append(0) 
    return hash

#均值哈希算法
def aHash(image):
    #缩放为8*8
    image=cv2.resize(image,(8,8),interpolation=cv2.INTER_CUBIC)
    #转换为灰度图
    image=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    avreage = np.mean(image) 
    hash = [] 
    for i in range(image.shape[0]): 
        for j in range(image.shape[1]): 
            if image[i,j] > avreage: 
                hash.append(1) 
            else: 
                hash.append(0) 
    return hash

#差值感知算法
def dHash(image):
    #缩放9*8
    image=cv2.resize(image,(9,8),interpolation=cv2.INTER_CUBIC)
    #转换灰度图
    image=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#     print(image.shape)
    hash=[]
    #每行前一个像素大于后一个像素为1,相反为0,生成哈希
    for i in range(8):
        for j in range(8):
            if image[i,j]>image[i,j+1]:
                hash.append(1)
            else:
                hash.append(0)
    return hash

#计算汉明距离
def Hamming_distance(hash1,hash2): 
    num = 0
    for index in range(len(hash1)): 
        if hash1[index] != hash2[index]: 
            num += 1
    return num
if __name__ == "__main__":
    image_file1 = './data/cartoon1.jpg'
    image_file2 = './data/cartoon3.jpg'
    img1 = cv2.imread(image_file1)
    img2 = cv2.imread(image_file2)
    hash1 = pHash(img1)
    hash2 = pHash(img2)
    dist = Hamming_distance(hash1, hash2)
    #将距离转化为相似度
    similarity = 1 - dist * 1.0 / 64 
    print(dist)
    print(similarity)
  • 21
    点赞
  • 162
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一种基于哈希函数比较图片相似度的 Java 实现: 1. 首先,我们需要读取两张图片并将其转换为灰度图像: ```java import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class ImageCompare { public static void main(String[] args) throws IOException { // 读取图片1 File file1 = new File("image1.jpg"); BufferedImage image1 = ImageIO.read(file1); BufferedImage grayImage1 = toGrayImage(image1); // 读取图片2 File file2 = new File("image2.jpg"); BufferedImage image2 = ImageIO.read(file2); BufferedImage grayImage2 = toGrayImage(image2); // TODO: 计算哈希值并比较相似度 } // 将彩色图像转换为灰度图像 private static BufferedImage toGrayImage(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage grayImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int rgb = image.getRGB(x, y); int r = (rgb >> 16) & 0xFF; int g = (rgb >> 8) & 0xFF; int b = rgb & 0xFF; int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b); grayImage.setRGB(x, y, (gray << 16) | (gray << 8) | gray); } } return grayImage; } } ``` 2. 接下来,我们可以使用 DCT(离散余弦变换)算法计算哈希值。DCT 算法可以将图像分解成若干个频率分量,我们只需要保留其中的高频分量,然后将其转化为 64 位二进制数即可。在计算哈希值时,我们可以将每个像素的灰度值作为 DCT 算法的输入,然后将输出结果的前 64 个元素进行二值化(即大于平均值为 1,小于平均值为 0),得到一个 64 位的二进制数,作为该图像的哈希值。 ```java import org.apache.commons.math3.complex.Complex; import org.apache.commons.math3.transform.*; import java.util.Arrays; public class ImageCompare { public static void main(String[] args) throws IOException { // 读取图片1 File file1 = new File("image1.jpg"); BufferedImage image1 = ImageIO.read(file1); BufferedImage grayImage1 = toGrayImage(image1); // 读取图片2 File file2 = new File("image2.jpg"); BufferedImage image2 = ImageIO.read(file2); BufferedImage grayImage2 = toGrayImage(image2); // 计算哈希值并比较相似度 long hash1 = calculateHash(grayImage1); long hash2 = calculateHash(grayImage2); double similarity = compareHash(hash1, hash2); System.out.println("Hash1: " + Long.toBinaryString(hash1)); System.out.println("Hash2: " + Long.toBinaryString(hash2)); System.out.println("Similarity: " + similarity); } // 计算哈希值 private static long calculateHash(BufferedImage grayImage) { int width = grayImage.getWidth(); int height = grayImage.getHeight(); double[][] data = new double[height][width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { data[y][x] = grayImage.getRGB(x, y) & 0xFF; } } DctNormalization normalization = DctNormalization.ORTHOGONAL_DCT_I; RealMatrix2D matrix = new DenseRealMatrix2D(data); RealMatrix2D dct = new FastCosineTransformer2D(normalization).transform(matrix); double[] vector = new double[64]; double sum = 0; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { vector[i * 8 + j] = dct.getEntry(i, j); sum += vector[i * 8 + j]; } } double mean = sum / 64; long hash = 0; for (int i = 0; i < 64; i++) { if (vector[i] > mean) { hash |= (1L << i); } } return hash; } // 比较哈希值的相似度 private static double compareHash(long hash1, long hash2) { int diff = Long.bitCount(hash1 ^ hash2); return 1.0 - (double) diff / 64.0; } } ``` 3. 在上面的代码中,我们使用了 Apache Commons Math 库中的 DCT 实现。我们首先将图像数据以二维数组的形式传递给 DCT 算法,然后将其转化为一维数组。在计算哈希值时,我们只需要保留前 64 个元素,然后将其转化为 64 位的二进制数即可。在比较哈希值时,我们可以使用 Hamming 距离,即两个二进制数对应位不同的数量。相似度可以用 1 减去 Hamming 距离除以总位数的结果得到。 需要注意的是,哈希函数比较图片相似度并不是一个精确的方法,可能会存在误差。在实际应用中,我们可以根据具体需求调整阈值,以达到较好的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值