网上365平台被黑提款-365外网足球-365体育平台网址

网上365平台被黑提款

相似图片的判定及海量图片快速查找相似图片的实现方案

2026-06-21 06:32:50 作者 admin 阅读 7247
相似图片的判定及海量图片快速查找相似图片的实现方案

前言本文所述相似图片,即为在视觉上一致的图片,因图片格式、形状变化、图片亮度等等变化,无法使用图片md5的方法来判断图片是否相似。在当前业务场景中,判断两张图片是否视觉一致,以及快速在海量图片中找到视觉相似的图片,有着迫切的需求,如下2个场景急需图片相似判定及快速查找的方案:

接口重构或者底层图片处理库的升级,需要使用线上请求,重放到测试环境中,使用图片的相似判定方法,对线上和测试环境下载的图片进行对比,看改动是否影响了下发的图片。审核到一张涉及黄反的图片,快速查找当前图片服务里是否还有相似的图片进行屏蔽。

本文所述方案包含:图片的相似判定方法,可用于接口变更时进行对比以保证变更不影响图片的下发。快速在海量图片中找到相似图片的方法,可用于相似黄反图片的快速查找。相似图片判定需要符合的要求视觉物体完全一致的图片,如下面2张图

图片格式不一样,也可以判定图片的亮度、对比度较小调整的情况下,仍然可以判定图片等比缩放后,仍可以判定图片压缩质量变化后,仍可以判定图片有变形拉伸等,仍可以判定到有一定相似度图片的相似判定方法目前常见的图片相似判定方法如下:

图像颜色直方图是一种经典、简单但实用的图像相似性判定方法,它基于图像中颜色或亮度的统计分布来衡量两张图像之间的相似度。感知哈希是一种用于图像相似性判断的轻量级算法,试图捕捉图像的"感知特征"——也就是肉眼看到的视觉结构。其先压缩图片,然后对压缩后的图片灰度化,再对灰度化后的图片提取特征(如 DCT),最后生成生成hash值,然后用汉明距离对hash值进行距离判定,低于10的说明有相似性,低于5的说明图片一致。常见的实现方式有均值哈希(aHash)、差值哈希(dHash)、感知哈希(pHash)和wHash(小波哈希)。结构相似度(SSIM, Structural Similarity Index)模拟人眼对亮度、对比度、结构等的感知,SSIM ∈ [0, 1],越接近1表示越相似,专注于图像整体结构信息,对像素级别变化较敏感。特征点匹配(局部特征)提取图像的关键点(角点、边缘),并进行匹配,常见方法:SIFT,SURF,ORB,通过匹配点的数量或匹配得分评估,鲁棒性强,抗旋转、缩放、部分遮挡,算法相对复杂。深度学习嵌入(CNN Embedding)用卷积神经网络(如 ResNet、Inception、EfficientNet)提取语义特征向量(一般为512或2048维),用余弦相似度、欧氏距离等度量,强语义理解能力,能区分图片内容而不仅仅是像素,较为复杂。本文主要叙述感知哈希用于图片相似度判定,图像颜色直方图方法判断准确率不高,而其余三种方法则实现复杂或者运行缓慢,不太适用于我们当前的业务场景需求。

感知哈希的四种实现方案,优缺点如下:

方法名原理特点aHash(平均哈希)灰度图均值对比快速但抗变形弱dHash(差值哈希)相邻像素比较差值对细节敏感pHash(感知哈希)DCT频域特征鲁棒性更强,推荐使用wHash(小波哈希)小波变换抗干扰性强,但较慢综合准确率和运行性能来看,选用pHash进行图片相似度判定是比较合适的。

基于DCT频域特征的pHash的核心原理图像缩放,将图像缩小为 32×32 像素(或其他固定尺寸),降低复杂度但保留大致结构转为灰度图 ,将 RGB 图像转为灰度,只保留亮度信息离散余弦变换(DCT),使用 DCT 将图像转换为频域数据。DCT 会把图像转换为一堆频率系数,其中前几个系数代表整体结构,后面代表细节。提取低频特征,截取 DCT 左上角的 8×8 共 64 个低频系数(去掉最左上角的 DC 分量)生成哈希,计算这 64 个值的平均值,然后将每个值与均值比较:

≥ 均值:置 1

< 均值:置 0得到一个 64 位的二进制字符串,即图像的 pHash示例输出(十六进制): 3c8f9ad3739e4aa5使用汉明距离比较两个pHash汉明距离(Hamming Distance)是一种度量两个等长字符串之间差异的指标,表示两个字符串在相同位置上不同字符的个数。它最早由理查德·汉明(Richard Hamming)提出,用于纠错编码领域,如海明码。

在图像相似性检测、感知哈希(aHash、dHash、pHash)中,它被广泛用于衡量两张图像的"相似度"。

汉明距离 = A 和 B 在对应位上不相同的位数:

A = 1011101

B = 1001001

↑ ↑ (两处不同)

→ 汉明距离 = 2

当我们使用感知哈希算法(如 pHash)将一张图像转为一个定长的二进制哈希值(通常是 64 位),我们可以通过汉明距离来判断图像是否相似:

汉明距离图像相似性解读0几乎完全一致(可能是重复)1–5高度相似(可能是轻度编辑)6–10有一定相似性(结构、构图相似,但非同图)11–20差异明显,不同图片≥21明显不同为什么通常认为 5~10 是「可接受的相似范围」?因为:

感知哈希是模糊的特征摘要,轻度裁剪、缩放、亮度变化或压缩会导致部分位发生翻转;实践中发现,轻微变动通常造成 1–5 位哈希不同;6–10 表示局部较多的变化或是同类图片(如同一物体不同角度);超过 15 通常是完全不同的图像。实际使用建议:

如果你要「查重」:汉明距离 ≤ 5如果你要「推荐相似图」:可以接受 ≤ 10如果你要「反盗图检测」:可以考虑 ≤ 12~15,但需人工审核辅助使用pHash及汉明距离判定图片相似性示例代码(Go)package main

import (

"fmt"

"log"

// 图像格式支持

_ "image/jpeg"

_ "image/png"

"github.com/corona10/goimagehash"

"github.com/disintegration/imaging"

_ "golang.org/x/image/webp"

)

func main() {

// 加载图片

img1Path := "img1.jpg"

img2Path := "img2.webp"

img1, err := imaging.Open(img1Path)

if err != nil {

log.Fatalf("Failed to open image 1: %v", err)

}

img2, err := imaging.Open(img2Path)

if err != nil {

log.Fatalf("Failed to open image 2: %v", err)

}

// 生成 pHash

hash1, err := goimagehash.PerceptionHash(img1)

if err != nil {

log.Fatalf("Failed to compute pHash for img1: %v", err)

}

hash2, err := goimagehash.PerceptionHash(img2)

if err != nil {

log.Fatalf("Failed to compute pHash for img2: %v", err)

}

// 计算汉明距离

distance, err := hash1.Distance(hash2)

if err != nil {

log.Fatalf("Failed to calculate Hamming distance: %v", err)

}

fmt.Printf("pHash 1: %s\n", hash1.ToString())

fmt.Printf("pHash 2: %s\n", hash2.ToString())

fmt.Printf("汉明距离: %d\n", distance)

// 判断是否相似(阈值可以根据需求调整)

if distance <= 10 {

fmt.Println("这两张图片可能是相似的 ✅")

} else {

fmt.Println("这两张图片差异较大 ❌")

}

}

img1.jpg和img2.webp是完全一样的图片,只是格式不一样,输出结果如下:

pHash 1: p:f4b883c6338bc3cc

pHash 2: p:f4b883c6338bc3cc

汉明距离: 0

这两张图片可能是相似的

将img1.jpg从400x400,缩放成100x100,进行汉明距离计算,结果仍然为0。

将img1.jpg缩放拉伸成600x100之后,汉明距离依然为0。

说明pHash可以判定不同格式的图片,也可以判定等比缩放的图片,对拉伸的图片也具备一定的判定能力。

海量图片快速查找相似图片黄反内容筛查这个业务场景,查出一张黄色/反动图片,需要可以快速的查找图片上传服务中是否还有相似的图片,以防止遗漏造成政策风险。如果直接把海量图片重新过一次图片审核,则成本过高,利用图片相似度判定的pHash,可以较低成本解决此问题,但把海量照片一张一张来对比pHash,则效率过于低效。

基于 pHash(感知哈希) 和 LSH(局部敏感哈希)+ Redis 实现相似图片的快速查找,是一个实用且高效的解决方案。LSH(Locality Sensitive Hashing)将高维的pHash向量映射到多个桶中,使得相似的数据被映射到同一个桶,在查找时,只需比较同一个桶里的元素,降低计算量。

pHash + LSH + Redis 存储图片的过程大致如下(示例说明,具体使用过程存储结构会有所调整):

![[phash与redis的计算过程.png]]

![[相似图片海量查找.png]]

用python代码简单的实现来说明使用流程:

步骤一、计算图片的pHash

from PIL import Image

import imagehash

def compute_phash(image_path):

img = Image.open(image_path)

return imagehash.phash(img) # 返回的是ImageHash对象,可以转成int或str

步骤二、将pHash值输入LSH并构建倒排索引(存入Redis),简单实现(以分段LSH为例)

import redis

# 连接Redis

r = redis.StrictRedis(host='localhost', port=6379, db=0)

def lsh_buckets(phash_bin, band_size=8):

"""将64位phash切成8段,返回8个bucket key"""

return [phash_bin[i:i+band_size] for i in range(0, 64, band_size)]

def index_image(image_id, phash_bin):

buckets = lsh_buckets(phash_bin)

for i, bucket in enumerate(buckets):

key = f"lsh_bucket:{i}:{bucket}"

r.sadd(key, image_id) # 把图片ID加入桶中

步骤三、查找相似图片

def query_similar_images(query_phash_bin, threshold=10):

candidate_ids = set()

buckets = lsh_buckets(query_phash_bin)

for i, bucket in enumerate(buckets):

key = f"lsh_bucket:{i}:{bucket}"

ids = r.smembers(key)

candidate_ids.update(ids)

# 过滤阶段:计算海明距离

similar_images = []

query_int = int(query_phash_bin, 2)

for cid in candidate_ids:

cid = cid.decode() # Redis返回bytes

stored_phash = r.get(f"phash:{cid}")

if stored_phash:

stored_int = int(stored_phash.decode(), 2)

dist = bin(query_int ^ stored_int).count("1")

if dist <= threshold:

similar_images.append((cid, dist))

# 按距离排序

return sorted(similar_images, key=lambda x: x[1])

步骤4:存储图片pHash(辅助步骤),在图片首次索引时,同时存储其pHash

def store_phash(image_id, phash_bin):

r.set(f"phash:{image_id}", phash_bin)

我们从一张图像中提取出 pHash 值(通常是 64 位二进制字符串,例如):

pHash = "1101010110010010010110100100101010100110010110101010101010101010"

将 pHash 切分为多个 bands(即 LSH 的子哈希段):

bands = [

'11010101', # band 0

'10010010', # band 1

'01011010', # ...

'01001010',

'10100110',

'01011010',

'10101010',

'10101010'

]

为每个 band 生成 Redis 中的桶 key(中间的数据即为哈希段的序号,分为8段):

lsh_bucket:0:11010101

lsh_bucket:1:10010010

...

lsh_bucket:7:10101010

以上述的 lsh_bucket:x:xxxxxxxx 为Redis桶的key,桶的存储类型为Set,值是图片id。pHash分成8段,每段按序生成桶key,取出桶key对应的图片id的集合,然后找到每个图片的pHash,进行汉明距离的计算,找到相似图片。

分成8个段,通过8个桶的联合查找,提高查全率。

基于LSH查找相似图片的查全率

$$

P(s) = 1 - (1 - s^r)^b

$$上述公式是在描述基于分段哈希 (banding technique) 的 LSH 方法中,估计具有相似度 s 的两个元素在至少一个 band 中发生碰撞的概率的常见且被广泛使用的公式。其中:

s 是两个元素的相似度(1 - 距离/总位数),汉明距离=6,总位数为64,则s=1-6/64=0.906r 是每 band 的位数b 是 band 数量

举例:如果两张图片海明距离是 6(相似度约 90.6%)

假设 r = 8, b = 8假设 r = 8, b = 8则被 LSH 检出的概率 ≈ 1 - (1 - 0.906⁸)⁸ ≈ 99.21%如何选择 r 和 b?一般经验法则(以 64 位 pHash 为例):

目标建议设置相似图像查全率高(Recall 优先)r = 8, b = 8(即 64 位分成 8 个 band,每 band 8 位)精度更高(减少误报)r = 16, b = 4(更严格 band 规则)模糊匹配更宽松(召回多)r = 4, b = 16(宽容 band)实例对比(假设 s = 0.9,汉明距离=6.4):

(r, b)P_hit (s=0.9)候选图数量趋势精度(4, 16)≈ 99.8%多(宽松)低(8, 8)≈ 98.8%适中中(16, 4)≈ 89.0%少高(32, 2)≈ 66.0%很少很高越小的 r,每个 band 越容易碰撞 → 查全率高但误报多越大的 r,要求越严格 → 误报少但漏掉一些相似图像

推荐组合总结(64位 pHash 场景):

场景推荐参数 (r, b)默认使用(平衡 recall/precision)(8, 8)想召回更多相似图(对 recall 要求高)(4, 16)精度要求极高(减少误判)(16, 4)假设你有 1,000,000 张图,使用 64 位 pHash,分 8 个 band。

如果 r = 4:每个 band 有 2⁴ = 16 种可能 → 分 bucket 粒度很粗,一个 bucket 可能有几万个图,查询时要对成千上万张图做 Hamming 比较(慢)

如果 r = 8:每个 band 有 2⁸ = 256 种可能 → 分得细,每个 bucket 只有几百张图,查询候选少,速度更快

综上,使用每band的位数r=8,band的数量b=8,在64位pHash的使用场景中,查全率和查找速度都是相对均衡的。

总结利用图片的pHash进行相似图片的查找,计算速度较快,准确率高,在配合图片宽高数据等,可以用于图片下发服务流量对比测试,确保接口重构、优化过程中没有下发错误的照片。而使用LSH + Redis,则可以实现海量图片中相似图片的快速查找,在图片的审核上有较好的应用场景,可以快速找到图片上传服务里相似的黄反图片,且成本低廉。

Last modified on 2025-10-09

相关文章