首页 > 其他分享 >基于pHash+hammingdistance的图片相似度比较

基于pHash+hammingdistance的图片相似度比较

时间:2023-09-20 16:35:18浏览次数:36  
标签:distance 基于 assets image hammingdistance jpg pHash device match

参考文献

开源仓库


实现案例

比较两张图片的相似度,支持对图片进行分块裁剪之后比较每块的相似度。默认是整张图片比较。

安装依赖

pip install perception=0.6.8
pip install Pillow=9.4.0

代码实现

import itertools
import math
from typing import NamedTuple

import PIL
from PIL import Image
from perception import hashers


class ImageBlock(NamedTuple):
    row: int
    colum: int
    image_block: Image

    def __str__(self):
        return '(%d, %d)' % (self.row, self.colum)


class ImageBlockDistance(NamedTuple):
    left_image_block: ImageBlock
    right_image_block: ImageBlock
    distance: float

    def __str__(self):
        return '%s vs %s distance(%f)' % (self.left_image_block, self.right_image_block, self.distance)


class ImageDistance(NamedTuple):
    left_image_path: str
    right_image_path: str
    distances: list


class ImageCompare:

    def __init__(self, left_image, right_image, hasher=hashers.PHash(), block=1):
        self.left_image = left_image
        self.right_image = right_image
        self.hasher = hasher
        self.block = block
        self.__row_columns_size = int(math.sqrt(self.block))

    def similarity(self, threshold=0.5, ratio=0.75, enable_detail=False):
        """
        获取图片相似度信息
        :param threshold: 阈值
        :param ratio: 符合阈值的图片块占总图片块的比例
        :param enable_detail: 是否记录图片块详情
        :return: 相似度信息
        """
        image_distance = ImageCompare.compute_distance(self.left_image, self.right_image, self.__row_columns_size,
                                                       self.hasher)
        threshold_match = 0
        details = []
        total_distance = 0
        for item in image_distance.distances:
            total_distance = total_distance + item.distance
            if item.distance <= threshold:
                threshold_match = threshold_match + 1
            if enable_detail:
                details.append(str(item))
            else:
                details = None
        match_ratio = threshold_match / len(image_distance.distances)
        return {
            "match": match_ratio >= ratio,
            "match_number": threshold_match,
            "match_ratio": match_ratio,
            "total_distance": total_distance,
            "detail_distance": details
        }

    def __str__(self):
        return "left_image:%s right_image:%s hasher:%s, block:%s row_columns_size:%s" % (
            self.left_image, self.right_image, self.hasher.__class__.__name__, self.block, self.__row_columns_size)

    @staticmethod
    def compute_distance(left_image_path, right_image_path, row_columns_size=1, hasher=hashers.PHash()):
        """
        计算图片HammingDistance
        :param left_image_path: 图片1路径
        :param right_image_path: 图片2路径
        :param row_columns_size: 图片行列数
        :param hasher: 图片比对算法
        :return: 图片距离
        """
        left_image_blocks = ImageCompare.image_crop_blocks(Image.open(left_image_path), row_columns_size)
        right_blocks = ImageCompare.image_crop_blocks(Image.open(right_image_path), row_columns_size)
        distances = []
        for item in list(zip(left_image_blocks, right_blocks)):
            left_hash_value, right_hash_value = hasher.compute(item[0].image_block), hasher.compute(item[1].image_block)
            distance = hasher.compute_distance(left_hash_value, right_hash_value)
            distances.append(ImageBlockDistance(left_image_block=item[0], right_image_block=item[1], distance=distance))
        return ImageDistance(left_image_path=left_image_path, right_image_path=left_image_path,
                             distances=distances)

    @staticmethod
    def image_crop_blocks(image: PIL.Image, n=1):
        """
        图片裁剪成块
        :param image:
        :param n: 行列数(总块数=n^2)
        :return: 图片块集合
        """
        image_blocks = []
        if n == 1:
            image_blocks.append(ImageBlock(row=0, colum=0, image_block=image))
        else:
            width, height = image.size
            size = min(width, height)
            left, top, right, bottom = (int((width - size) / 2), int((height - size) / 2),
                                        int((width + size) / 2), int((height + size) / 2))
            square_image = image.crop((left, top, right, bottom))
            min_size = size // n
            for i in range(n):
                for j in range(n):
                    left, top, right, bottom = (j * min_size, i * min_size, (j + 1) * min_size, (i + 1) * min_size)
                    image_block = square_image.crop((left, top, right, bottom))
                    image_blocks.append(ImageBlock(row=i, colum=j, image_block=image_block))
        return image_blocks

测试用例

示例图片:人眼识别其中 device_yes_01.jpg和device_yes_02.jpg相似,device_no_01.jpg和device_no_02.jpg

基于pHash+hammingdistance的图片相似度比较_Hamming Distance

用例A

测试参数:[block]图片分块为1,表示整体比较; [threshold]阈值:0.45 表示图片相似度距离小于0.45认为相似。

def path(name):
    return './assets/' + name


if __name__ == '__main__':
    image_name_set = ['device_yes_01.jpg', 'device_yes_02.jpg', 'device_no_01.jpg', 'device_no_02.jpg']
    for g in [(path(item[0]), path(item[1])) for item in
              list(itertools.combinations_with_replacement(image_name_set, 2))]:
        left_image_path, right_image_path = g[0], g[1]
        print(left_image_path, " vs ", right_image_path)
        x = ImageCompare(left_image_path, right_image_path, block=1)
        # print(x)
        print(x.similarity(threshold=0.45, enable_detail=False))
        print("--------------------------")

输出结果:从结果上分析和人眼识别的结论是一致的。其中:device_yes_01.jpg vs device_yes_02.jpg 的距离0.3437 远小于

device_yes_01.jpg vs device_no_01.jpg 的距离0.53125,这组参数配置能够区分出来图片的相似性。

./assets/device_yes_01.jpg  vs  ./assets/device_yes_01.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_yes_02.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.34375, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_no_01.jpg
{'match': False, 'match_number': 0, 'match_ratio': 0.0, 'total_distance': 0.53125, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_no_02.jpg
{'match': False, 'match_number': 0, 'match_ratio': 0.0, 'total_distance': 0.53125, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_yes_02.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_no_01.jpg
{'match': False, 'match_number': 0, 'match_ratio': 0.0, 'total_distance': 0.65625, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_no_02.jpg
{'match': False, 'match_number': 0, 'match_ratio': 0.0, 'total_distance': 0.59375, 'detail_distance': None}
--------------------------
./assets/device_no_01.jpg  vs  ./assets/device_no_01.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_no_01.jpg  vs  ./assets/device_no_02.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.125, 'detail_distance': None}
--------------------------
./assets/device_no_02.jpg  vs  ./assets/device_no_02.jpg
{'match': True, 'match_number': 1, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
用例B

测试参数:[block]图片分块为16(4*4); [threshold]阈值:0.45 表示图片相似度距离小于0.45认为相似; [ratio]相似图片块占比:0.75 表示匹配。

def path(name):
    return './assets/' + name


if __name__ == '__main__':
    image_name_set = ['device_yes_01.jpg', 'device_yes_02.jpg', 'device_no_01.jpg', 'device_no_02.jpg']
    for g in [(path(item[0]), path(item[1])) for item in
              list(itertools.combinations_with_replacement(image_name_set, 2))]:
        left_image_path, right_image_path = g[0], g[1]
        print(left_image_path, " vs ", right_image_path)
        x = ImageCompare(left_image_path, right_image_path, block=16, )
        # print(x)
        print(x.similarity(threshold=0.45, ratio=0.75, enable_detail=False))
        print("--------------------------")

输出结果:从结果上分析相同图片,或者非常近似的图片结论和人眼判断的一致。另外存在无法区分的图片,比如:device_yes_01.jpg vs device_yes_02.jpg的结果与device_yes_01.jpg vs device_no_01.jpg的结果很接近,而且给出了都不相似的结论。这里主要的干扰因素有:block,threshold, ratio。所以进行图片分块的相似度比较之后的结果二次处理不能如上实现简单的比较来完成。比如:两组图片块的结果分别是0.1512和0.4420,它们都符合阈值设置,但是对整体图片的相似度的影响(权重)明显是不同的。

./assets/device_yes_01.jpg  vs  ./assets/device_yes_01.jpg
{'match': True, 'match_number': 16, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_yes_02.jpg
{'match': False, 'match_number': 6, 'match_ratio': 0.375, 'total_distance': 7.8125, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_no_01.jpg
{'match': False, 'match_number': 6, 'match_ratio': 0.375, 'total_distance': 7.5625, 'detail_distance': None}
--------------------------
./assets/device_yes_01.jpg  vs  ./assets/device_no_02.jpg
{'match': False, 'match_number': 4, 'match_ratio': 0.25, 'total_distance': 7.75, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_yes_02.jpg
{'match': True, 'match_number': 16, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_no_01.jpg
{'match': False, 'match_number': 5, 'match_ratio': 0.3125, 'total_distance': 7.90625, 'detail_distance': None}
--------------------------
./assets/device_yes_02.jpg  vs  ./assets/device_no_02.jpg
{'match': False, 'match_number': 3, 'match_ratio': 0.1875, 'total_distance': 7.96875, 'detail_distance': None}
--------------------------
./assets/device_no_01.jpg  vs  ./assets/device_no_01.jpg
{'match': True, 'match_number': 16, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
./assets/device_no_01.jpg  vs  ./assets/device_no_02.jpg
{'match': True, 'match_number': 15, 'match_ratio': 0.9375, 'total_distance': 5.40625, 'detail_distance': None}
--------------------------
./assets/device_no_02.jpg  vs  ./assets/device_no_02.jpg
{'match': True, 'match_number': 16, 'match_ratio': 1.0, 'total_distance': 0.0, 'detail_distance': None}
--------------------------
建议

通常情况下建议采用用例A的方式进行图片相似性比较,通过大量的图片测试进行阈值的调节来找到合适的设置。图片分块的比较复杂度较高,特别是影响因子比整体图片比较多,而且二次处理上需要根据应用场景来设计。

标签:distance,基于,assets,image,hammingdistance,jpg,pHash,device,match
From: https://blog.51cto.com/aiilive/7539855

相关文章

  • 学法减分-基于搜索引擎
    缘由违章扣分较多,通过学法减分减少扣分操作线上申请:12123操作线上学习:文档或视频线上考试:一次考试,2次补考线上考试可以通过搜索引擎指定网站的功能搜索考题关键字,基本能过示例最大允许总质量百分之三十site:https://www.jiakaobaodian.com/site:https://www.jiak......
  • HFile详解-基于HBase0.90.5
    1.HFile详解HFile文件分为以下六大部分 序号名称描述1数据块由多个block(块)组成,每个块的格式为:[块头]+[key长]+[value长]+[key]+[value]。2元数据块元数据是key-value类型的值,但元数据快只保存元数据的value值,元数据的key值保存在第五项(元数据索引块)中。该块由多个元数......
  • 【WCH蓝牙系列芯片】-基于CH582开发板—四种低功耗模式电流测试
    ---------------------------------------------------------------------------------------------------------------------在WCH沁恒官方提供的CH583的EVT资源包中,找到BLE文件中找到PW这个工程文件,这是一个系统睡眠模式并唤醒例程;其中GPIOA_5作为唤醒源,共4种功耗等级。芯片提......
  • 基于微信小程序的在线点餐平台
    如今的信息时代,对信息的共享性,信息的流通性有着较高要求,因此传统管理方式就不适合。为了让管理模式进行升级,也为了更好的维护信息,在线点餐(堂食)平台的开发运用就显得很有必要。并且通过开发在线点餐(堂食)平台,不仅可以让所学的微信小程序技术得到实际运用,也可以掌握MySQL的使用方法,对......
  • 基于微信平台的报刊订阅小程序
    计算机网络发展到现在已经好几十年了,在理论上面已经有了很丰富的基础,并且在现实生活中也到处都在使用,可以说,经过几十年的发展,互联网技术已经把地域信息的隔阂给消除了,让整个世界都可以即时通话和联系,极大的方便了人们的生活。所以说,基于微信平台的报刊订阅小程序用计算机技术来进行......
  • 66基于java的志愿者服务管理系统设计与实现(配套lun文,PPT,可参考做毕业设计)
    本章节给大家带来一个基于java志愿者服务管理系统设计与实现,可适用于校园志愿者活动服务平台,校园爱心志愿者活动,爱心活动管理信息系统,大学志愿者服务平台,大学生志愿者服务平台,大学生爱心活动系统,在线志愿者活动平台,校园志愿者活动,大学志愿者活动平台等等;引言现如今,校园志愿者......
  • 基于已知点云数据的最小外接圆matlab函数
    基于已知点云数据的最小外接圆matlab函数–MATLAB中文论坛(ilovematlab.cn) %该函数是在其他网站看到的,以此共享。有两种方法(函数)实现。%第一种比较费时:function[xc,yc,r]=smallestcircle(x,y)%Thisfindsthecircleofsmallestareacontainingall%thepoint......
  • m基于码率兼容打孔LDPC码BP译码算法的matlab误码率仿真
    1.算法仿真效果matlab2022a仿真结果如下:2.算法涉及理论知识概要码率兼容打孔LDPC码BP译码算法是一种改进的LDPC译码算法,能够在不同码率下实现更好的译码性能。该算法通过在LDPC码中引入打孔操作,使得码率可以灵活地调整,同时利用BP(BeliefPropagation)译码算法进行迭代译码,提高了......
  • m基于码率兼容打孔LDPC码BP译码算法的matlab误码率仿真
    1.算法仿真效果matlab2022a仿真结果如下:   2.算法涉及理论知识概要       码率兼容打孔LDPC码BP译码算法是一种改进的LDPC译码算法,能够在不同码率下实现更好的译码性能。该算法通过在LDPC码中引入打孔操作,使得码率可以灵活地调整,同时利用BP(BeliefPropagation)译......
  • 基于wsl的ubuntu vscode调试环境搭建--Apple的学习笔记
    一,前言正好在网上搜索文章的时候看到了wsl,我想起来它也是一个虚拟机环境,所以我要用用,没想要一用,感觉比vmware还要方便。二,环境搭建A,在wsl中安装ubuntu1.     首先打开powershell 输入命令wsl--list–online,来查看支持安装的ubuntu版本。 注意:若提示连接超时,则是自动ip......