OpenCV书签 #差值哈希算法的原理与相似图片搜索实验

1. 介绍

差值哈希算法(Difference Hash Algorithm,简称dHash) 是哈希算法的一种,主要可以用来做以图搜索/相似图片的搜索工作。

 

2. 原理

差值哈希算法通过计算相邻像素的差异来生成哈希,即通过缩小图像的每个像素与平均灰度值的比较,生成一组哈希值。最后,利用两组图像的哈希值的汉明距离来评估图像的相似度。

 

3. 魔法

概括地讲,差值哈希算法一共可细分五步:

  1. 缩小图像: 调整输入图像的大小为 (hash_size + 1) 宽度和 hash_size 高度,通常为 9x8 像素,总共72个像素。
  2. 图像灰度化: 将彩色图像转换为灰度图像,以便进行灰度差值计算。
  3. 计算差异值: 差值算法工作在相邻像素之间,如果左边的像素比右边的更亮,则记录为1,否则为0,这样每行9个像素通过左右像素的两两比较,会产生8个不同的差异值,一共8行,则会产生64个差异值。
  4. 生成哈希值: 由于64位的二进制值(差异值)太长,所以按每4个字符为1组,由2进制转成16进制。这样就转为一个长度为16的字符串。这个字符串也就是这个图像可识别的哈希值,也叫图像指纹,即这个图像所包含的特征。
  5. 哈希值比较: 通过比较两个图像的哈希值的汉明距离(Hamming Distance),就可以评估图像的相似度,距离越小表示图像越相似。

 

4. 实验

4.1 魔法

第一步:缩小图像

调整输入图像的大小为 (hash_size + 1) 宽度和 hash_size 高度,通常为 9x8 像素,总共72个像素,以便进行后续的差值计算。

1)读取原图

# 测试图片路径
img_path = 'img_test/apple-01.jpg'
 
# 通过OpenCV加载图像
img = cv2.imread(img_path)
plt.imshow(img, cmap='gray')
plt.show()

# 通道重排,从BGR转换为RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb, cmap='gray')
plt.show()

dhash-0001

2)缩小原图

# 缩小图像:使用OpenCV的resize函数将图像缩放为9x8像素,采用Cubic插值方法进行图像重采样
img_resize = cv2.resize(img_rgb, (9, 8), cv2.INTER_CUBIC)

# 打印 img.shape 可以获取图像的形状信息,即 (行数, 列数, 通道数)
# 通道数:灰度图像通道数为 1,彩色图像通道数为 3
print(img_resize.shape)
plt.imshow(img_resize, cmap='gray')
plt.show()

输出打印:

(8, 9, 3)

dhash-0002
将图像 img 调整大小为 (9, 8) 的尺寸,并使用 cv2.INTER_CUBIC 插值方法进行图像的缩放。在这里,原始图像 img 将被调整为 9 像素宽和 8 像素高。
打印 img.shape 可以获取图像的形状信息,即(行数, 列数, 通道数)。通道数取决于原始图像的通道数(如灰度图像通道数为 1,彩色图像通道数为 3)。

第二步:图像灰度化

将彩色图像转换为灰度图像,以便进行灰度差值计算。

# 图像灰度化:将彩色图像转换为灰度图像。
img_gray = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)

# 打印出了灰度图像的行数和列数,因为灰度图像只有一个通道,所以不会显示通道数
print(img_gray.shape)
plt.imshow(img_gray, cmap='gray')
plt.show()

输出打印:

(8, 9)

dhash-0003
使用 OpenCV 的 cvtColor 函数将彩色图像 img 转换为灰度图像。cv2.COLOR_BGR2GRAY 是颜色空间转换标志,它指示将图像从 BGR(彩色)颜色空间转换为灰度颜色空间。

打印 img_gray.shape 包含图像的维度信息,通常是(行数, 列数)或(行数, 列数, 通道数)。这里只有灰度图像的行数和列数,因为灰度图像只有一个通道,所以不会显示通道数。

第三步:计算差异值

差值算法工作在相邻像素之间,如果左边的像素比右边的更亮,则记录为1,否则为0,这样每行9个像素通过左右像素的两两比较,会产生8个不同的差异值,一共8行,则会产生64个差异值。

# 计算差异值:获得图像二进制字符串
img_hash_str = ''
# img_hash_arr = []
# 遍历图像的像素,比较相邻像素之间的灰度值,根据强弱增减差异情况生成一个二进制哈希值
# 外层循环,遍历图像的行(垂直方向),范围是从0到7
for i in range(8):
    # 内层循环,遍历图像的列(水平方向),范围也是从0到7
    for j in range(8):
        # 比较当前像素 img[i, j] 与下一个像素 img[i, j + 1] 的灰度值
        if img_gray[i, j] > img_gray[i, j + 1]:
            # 如果当前像素的灰度值大于下一个像素的灰度值(灰度值增加),将1添加到名为 hash 的列表中
            # img_hash_arr.append(1)
            img_hash_str += '1'
        else:
            # 否则灰度值弱减,将0添加到名为 hash 的列表中
            # img_hash_arr.append(0)
            img_hash_str += '0'
print(f"图像的二进制哈希值={img_hash_str}")

输出打印:

图像的二进制哈希值=0000000000110000001100000010000001110000001000000011000001110000

这段代码的目的是遍历图像的每一行和每一列,逐个比较相邻像素之间的灰度值,根据比较结果生成一个二进制哈希值。如果像素之间的灰度值增加,就将1添加到哈希值中,如果减少或保持不变,就将0添加。这个生成的哈希值可用于图像相似性比较,用于检测图像中的局部特征。

第四步:生成哈希值

由于64位二进制值太长,所以按每4个字符为1组,由2进制转成16进制。这样就转为一个长度为16的字符串。这个字符串也就是这个图像可识别的哈希值,也叫图像指纹,即这个图像所包含的特征。

# 生成哈希值:生成图像可识别哈希值
img_hash = ''
for i in range(0, 64, 4):
    img_hash += ''.join('%x' % int(img_hash_str[i: i + 4], 2))
print(f"图像可识别的哈希值={img_hash}")

输出打印:

图像可识别的哈希值=0030302070203070

同样的,将目标素材图像进行上述计算,亦可得到一个图像可识别的哈希值。

第五步:哈希值比较

通过两个等长字符串在相同位置上不同字符的数量,计算两个等长字符串之间的汉明距离(Hamming Distance),就可以评估图像的相似度,距离越小表示图像越相似。

# 汉明距离:通过两个等长字符串在相同位置上不同字符的数量,计算两个等长字符串之间的汉明距离
def hamming_distance(s1, s2):
    # 检查这两个字符串的长度是否相同。如果长度不同,它会引发 ValueError 异常,因为汉明距离只适用于等长的字符串
    if len(s1) != len(s2):
        raise ValueError("Input strings must have the same length")
    
    distance = 0
    for i in range(len(s1)):
        # 遍历两个字符串的每个字符,比较它们在相同位置上的值。如果发现不同的字符,将 distance 的值增加 1
        if s1[i] != s2[i]:
            distance += 1
    return distance

汉明距离: 两个等长字符串在相同位置上不同字符的数量。即一组二进制数据变成另一组数据所需要的步骤数。汉明距离越小,则相似度越高。汉明距离为0,即两张图片完全一样。

dhash-0004

 

4.2 测试

实验场景

通过 opencv,使用差值哈希算法查找目标图像素材库中所有相似图像,并列出相似值。

实验素材

这里,我准备了10张图片,其中9张是苹果,但形态不一;1张梨子。
dhash-0005

实验代码

"""
以图搜图:差值哈希算法(Difference Hash Algorithm,简称dHash)的原理与实现
测试环境:win10 | python 3.9.13 | OpenCV 4.4.0 | numpy 1.21.1
实验场景:通过 opencv,使用差值哈希算法查找目标图像素材库中所有相似图像
实验时间:2023-10-31
实验名称:dhash_v5_all.py
"""

import os
import cv2
import time

def get_dHash(img_path):
    # 读取图像:通过OpenCV的imread加载RGB图像
    img_rgb = cv2.cvtColor(cv2.imread(img_path), cv2.COLOR_BGR2RGB)
    # 缩小图像:使用OpenCV的resize函数将图像缩放为9x8像素,采用Cubic插值方法进行图像重采样
    img_resize = cv2.resize(img_rgb, (9, 8), cv2.INTER_CUBIC)
    # 图像灰度化:将彩色图像转换为灰度图像
    img_gray = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)

    # 计算差异值:获得图像二进制字符串
    img_hash_str = ''
    # 遍历图像的像素,比较相邻像素之间的灰度值,根据强弱增减差异情况生成一个二进制哈希值
    # 外层循环,遍历图像的行(垂直方向),范围是从0到7
    for i in range(8):
        # 内层循环,遍历图像的列(水平方向),范围也是从0到7
        for j in range(8):
            # 比较当前像素 img[i, j] 与下一个像素 img[i, j + 1] 的灰度值
            if img_gray[i, j] > img_gray[i, j + 1]:
                # 如果当前像素的灰度值大于下一个像素的灰度值(灰度值增加),将1添加到名为 hash 的列表中
                img_hash_str += '1'
            else:
                # 否则灰度值弱减,将0添加到名为 hash 的列表中
                img_hash_str += '0'
    # print(f"图像的二进制哈希值={img_hash_str}")

    # 生成哈希值:生成图像可识别哈希值
    img_hash = ''.join(map(lambda x:'%x' % int(img_hash_str[x : x + 4], 2), range(0, 64, 4)))
    return img_hash


# 汉明距离:通过两个等长字符串在相同位置上不同字符的数量,计算两个等长字符串之间的汉明距离
def hamming_distance(str1, str2):
    # 检查这两个字符串的长度是否相同。如果长度不同,它会引发 ValueError 异常,因为汉明距离只适用于等长的字符串
    if len(str1) != len(str2):
        raise ValueError("Input strings must have the same length")
    
    distance = 0
    for i in range(len(str1)):
        # 遍历两个字符串的每个字符,比较它们在相同位置上的值。如果发现不同的字符,将 distance 的值增加 1
        if str1[i] != str2[i]:
            distance += 1
    return distance


# ------------------------------------------------- 测试 -------------------------------------------------
if __name__ == "__main__":
    time_start = time.time()

    # 指定测试图像库目录
    img_dir = 'img_test'
    # 指定测试图像文件扩展名
    img_suffix = ['.jpg', '.jpeg', '.png', '.bmp', '.gif']

    # 获取当前执行脚本所在目录
    script_dir = os.path.dirname(__file__)
    # 获取目标测试图像的全路径
    img_org_path = os.path.join(script_dir, img_dir, 'apple-01.jpg')
    # 获取目标图像可识别哈希值(图像指纹)
    org_img_hash = get_dHash(img_org_path)
    print(f"目标图像:{os.path.relpath(img_org_path)},图像HASH:{org_img_hash}")

    # 获取测试图像库中所有文件
    all_files = os.listdir(os.path.join(script_dir, img_dir))
    # 筛选出指定后缀的图像文件
    img_files = [file for file in all_files if any(file.endswith(suffix) for suffix in img_suffix)]

    img_hash_all = []
    # 遍历测试图像库中的每张图像
    for img_file in img_files:
        # 获取相似图像文件路径
        img_path = os.path.join(script_dir, img_dir, img_file)
        # 获取相似图像可识别哈希值(图像指纹)
        img_hash = get_dHash(img_path)
        # 获取相似图像与目标图像的汉明距离
        distance = hamming_distance(org_img_hash, img_hash)
        # 存储相似图像的相对路径、哈希值、汉明距离
        img_hash_all.append((os.path.relpath(img_path), img_hash, distance))

    for img in img_hash_all:
        print(f"图像名称:{os.path.basename(img[0])},图像HASH:{img[1]},与目标图像的近似值(汉明距离):{img[2]}")

    time_end = time.time()
    print(f"耗时:{time_end - time_start}")

输出打印:

目标图像:..\..\P1_Hash\03_dHash\img_test\apple-01.jpg,图像HASH:0030302070203070
图像名称:apple-01.jpg,图像HASH:0030302070203070,与目标图像的近似值(汉明距离):0
图像名称:apple-02.jpg,图像HASH:2048502430301000,与目标图像的近似值(汉明距离):9
图像名称:apple-03.jpg,图像HASH:0030705070506020,与目标图像的近似值(汉明距离):5
图像名称:apple-04.jpg,图像HASH:3030303038301000,与目标图像的近似值(汉明距离):7
图像名称:apple-05.jpg,图像HASH:0818206840602830,与目标图像的近似值(汉明距离):11
图像名称:apple-06.jpg,图像HASH:00004cccd0c8eeec,与目标图像的近似值(汉明距离):12
图像名称:apple-07.jpg,图像HASH:5af53928b158dc1e,与目标图像的近似值(汉明距离):14
图像名称:apple-08.jpg,图像HASH:87868a060c081e2c,与目标图像的近似值(汉明距离):16
图像名称:apple-09.jpg,图像HASH:0040285060602070,与目标图像的近似值(汉明距离):7
图像名称:pear-001.jpg,图像HASH:0204367274f07060,与目标图像的近似值(汉明距离):10
耗时:0.09773826599121094

 

5. 总结

经过实验和测试,差值哈希算法(dHash)是一种非常简单的算法,易于实现和理解,且计算速度快,适用于大规模图像数据相似性处理。

特点: 传统,属于一种外观相似哈希算法。
优点: 简单、相对准确、计算效率高;在同等测试样本下,相比pHash,dHash的速度要更快;适用于快速图像相似性搜索。
缺点: 对于图像的旋转和缩放敏感,不适用于检测嵌入式水印或复杂的变换,即使是微小的旋转或缩放也会导致哈希值大幅度改变,因为它主要用于检测左右局部像素级别的变化。

 

6. 系列书签

OpenCV书签 #均值哈希算法的原理与相似图片搜索实验
OpenCV书签 #感知哈希算法的原理与相似图片搜索实验
OpenCV书签 #差值哈希算法的原理与相似图片搜索实验

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/350551.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于IDEA中sout、fori无法自动生成的解决方案

方案一.通过idea中的设置修改 搜索栏搜索sout 点击change,全部勾选Java下的选项 最后别忘了点击右下角的Apply 方案二:写main函数 不能直接在类里生成,需要写一个main函数。(我就是忘了写main函数才无法生成的。&#xff09…

Soul CEO张璐积极履行反诈责任,倡导共建安全网络

近期,备受期待的反诈电影《鹦鹉杀》热映,深入剖析杀猪盘这一网络诈骗行为。为协助更多人增强反诈意识,备受欢迎的社交应用Soul App积极响应,在Soul CEO张璐的带领下,邀请电影中的演员和平台的反诈中心共同参与反诈宣传。此外,一旦用户在平台搜索“诈骗”、“杀猪盘”、“鹦鹉杀…

Spring Boot3整合MyBatis Plus

目录 1.前置条件 2.导坐标 3.配置数据源 4.mybatis-plus基础配置 5.配置mapper扫描路径 6.MyBatis Plus代码生成器整合 1.导坐标 2.编写代码生成逻辑 7.整合Druid连接池 1.前置条件 已经初始化好一个spring boot项目且版本为3X,项目可正常启动 初始化教程…

Halcon指定区域的形状匹配

Halcon指定区域的形状匹配 文章目录 Halcon指定区域的形状匹配1.在参考图像中选择目标2.创建模板3.搜索目标 在这个实例中,会介绍如何根据选定的ROI选择合适的图像金字塔参数,创建包含这个区域的形状模板,并进行精确的基于形状模板的匹配。最…

echarts-wordcloud词云

echarts-wordcloud是基于echarts的一个插件,所以我们要首先安装echarts包,然后再安装echarts-wordcloud的包,这里我的练习项目安装的版本;当然,你可以随意安装你需要的版本; “echarts”: “^5.3.3”, “ec…

PCB制板基础知识

一、PCB概念 PCB(PrintedCircuitBoard),中文名称为印制电路板,又称印刷电路板、印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气连接的提供者。由于它是采用电子印刷术制作…

前端工程化基础(一):Node模块化

Node模块化 Node.js是什么 官方定义:Node.js是一个基于V8 JavaScript引擎的JavaScript运行时的环境 Node.js基于V8引擎来执行 JavaScript代码,但是Node.js中不仅仅有V8 我们知道,V8可以嵌入到C应用程序中,因此无论是Chrome还是No…

Redis快的秘密,高性能设计epoll和IO多路复用探究

Redis快的原因,高性能设计epoll和IO多路复用探究 1、多路复用需要解决的问题 并发多客户端连接,在多路复用之前最简单和典型的方案:同步阻塞网络IO模型 这种模式的特点就是用一个进程来处理一个网络连接(即一个用户请求&#x…

windows 下docker-compose 试玩 paperlsess

第一步安装:(假设docker-compose已安装好) 下载 docker-compose.yml ,docker-compose.env: github下载地址 在docker-paperless.env增加环境变量,设置管理员账户信息: PAPERLESS_ADMIN_USER: admin PAPERLESS_ADMIN_P…

分页查询的使用

背景 在业务中我们在前端总是需要展示数据,将后端得到的数据进行分页处理,通过pagehelper实现动态的分页查询,将查询页数和分页数通过前端发送到后端,后端使用pagehelper,底层是封装threadlocal得到页数和分页数并动态…

[Linux基础开发工具---vim]关于vim的介绍、vim如何配置及vim的基本操作方法

一、vim及其三种模式的简单介绍 Vim是一个类似于Vi的文本编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面,如多级撤销、多窗口操作、崩溃后也可以恢复、增加了稳定性、关键字自动补全、上下文自动补全等…

力扣1143. 最长公共子序列

动态规划 思路: 假设 dp[i][j] 是 text1[0:i] 和 text2[0:j] 最长公共子序列的长度;则 dp[0][j] 0,(空字符串和任何字符串的最长公共子序列的长度都是 0);同理 dp[i][j] 0;状态转移方程&…

List使用addAll()方法报错

当使用Arrays.asList方式创建出来的list&#xff0c;在使用addAll方法的时候报错如下&#xff1a; Exception in thread "main" java.lang.UnsupportedOperationException 这个问题记录下&#xff0c;以防以后忘记。 下面是代码 List<String> objects new A…

股票交易维度和概念

股票&#xff1a;股份公司为筹集资金而发行给各个股东作为持股凭证并借以取得股息和红利的一种有价证券 好处&#xff1a;分红、送股配股、交易收益、本金少、易变现、避免货币贬值 金融标的投资风险与收益 股票分类 蓝筹股 经营业绩长期稳定增长的大公司&#xff0c;一般是…

【C++】入门基础

前言&#xff1a;C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等。熟悉C语言之后&#xff0c;对C学习有一定的帮助&#xff0c;因此从今天开始们将进入&#xff23;的学习。 &#x1f496; 博主CSDN主页:…

Java如何对OSS存储引擎的Bucket进行创建【OSS学习】

在前面学会了如何开通OSS&#xff0c;对OSS的一些基本操作&#xff0c;接下来记录一下如何通过Java代码通过SDK对OSS存储引擎里面的Bucket存储空间进行创建。 目录 1、先看看OSS&#xff1a; 2、代码编写&#xff1a; 3、运行效果&#xff1a; 1、先看看OSS&#xff1a; 此…

老师打学生违法吗该怎么处理

老师打学生&#xff1a;一个需要深入探讨的敏感话题。老师&#xff0c;肩负着教书育人的重任&#xff0c;面对学生的时候&#xff0c;法律、职业道德和个人修养常常需要我们做出权衡。那么&#xff0c;当老师打了学生这一行为发生时&#xff0c;我们该如何看待和处理呢&#xf…

蓝桥杯备战——6.串口通讯

1.分析原理图 由上图我们可以看到串口1通过CH340接到了USB口上&#xff0c;通过串口1我们就能跟电脑进行数据交互。 另外需要注意的是STC15F是有两组高速串口的&#xff0c;而且可以切换端口。 2.配置串口 由于比赛时间紧&#xff0c;我们最好不要去现场查寄存器手册&#x…

HCIP-BGP实验

实验拓扑 实验需求 1.r1上有两个换汇分别为192.168.1.0/24和192.168.2.0/24只允许学到汇总和1.0 2.r7上有两个还回172.16.1.0/24和172.16.2.0/24要求全部宣告&#xff0c;但是只有2.0可以通过 3.全网可达 实验思路 配置IP地址 BGP配置 实验步骤 配置IP地址 BGP配置 在…

驱动开发-系统移植

一、Linux系统移植概念 需要移植三部分东西&#xff0c;Uboot ,内核 &#xff0c;根文件系统 &#xff08;rootfs&#xff09; &#xff0c;这三个构成了一个完整的Linux系统。 把这三部分学明白&#xff0c;系统移植就懂点了。 二、Uboot uboot就是引导程序下载的一段代…