云函数分享:重复图片识别及图片地址返回的实现
一、应用场景:
简道云目前没有关于重复或相似图片的识别,但是我们在实际应用中会有相关的需求,如报销凭据、拍照打卡等。而目前该类信息的判定主要是通过人工进行判定,如果是在同一张表单中或者图片量少时,人工尚可,如是时间间隔较久或图片量较大,就可能出现漏失
那么如何对同一张图片进行判定呢?
二、解决方案:
通过搜索网络上成熟的解决方案
目前最简单的方法是使用加密哈希(例如MD5, SHA-1)判断。但是局限性非常大。例如一个txt文档,其MD5值是根据这个txt的二进制数据计算的,如果是这个txt文档的完全复制版,那他们的MD5值是完全相同的。但是,一旦改变副本的内容,哪怕只是副本的缩进格式,其MD5也会天差地别。因此加密哈希只能用于判断两个完全一致、未经修改的文件,如果是一张经过调色或者缩放的图片,根本无法判断其与另一张图片是否为同一张图片。
那么如何判断一张被PS过的图片是否与另一张图片本质上相同呢?比较简单、易用的解决方案是采用感知哈希算法(Perceptual Hash Algorithm)。
相似图片解决步骤:
分别计算两张图片的dHash值
通过dHash值计算两张图片的汉明距离(Hamming Distance),通过汉明距离的大小,判断两张图片的相似程度。
参考文章:1.相似图片检测:感知哈希算法之dHash的Python实现
2.【相似图片检测:哈希算法之dHash的Python实现】详解
在简道云上的解决步骤:
1.在主表中通过前端事件将图片信息推送至云函数,并通过云函数计算出各个图片的dHash值,返回对应图片的dHash值及地址至主表
2.在主表中统计图片个数并通过云函数创建新的子表单,并分别获取单个图片dHash值及地址;
3.通过智能助手将返回的图片dHash值和地址推送至新的子表中,用以配合主表做重复图片验证;
4.设置字段显隐,当出现重复时显示重复图片信息及地址;
注:本文目前仅作重复图片验证,相似图片验证可能涉及到数据库,暂未实施。
测试示例:
三、详细步骤
1.在主表中通过前端事件将图片信息推送至云函数并返回dHash值及其地址
1.1 创建表单
以报销单为例,创建一个报销单,包含报销编码及报销明细,报销明细中至少包含图片、dHash值(文本)、图片地址(文本);
1.2 设置云函数
设置方法,请参考@张明亮 相关帖子,此文不再赘述;
代码参考如下:
# -*- coding: utf8 -*-
from PIL import Image
import json
import requests
from io import BytesIO
from urllib.parse import unquote
def grayscale_Image(image,resize_width=9,resize_heith=8): #image为图片的请求网络路径信息,resize_width为缩放图片的宽度,resize_heith为缩放图片的高度
response = requests.get(image) #获取网络图片
im = Image.open(BytesIO(response.content)) #使用Image的open方法打开图片
smaller_image = im.resize((resize_width,resize_heith),Image.ANTIALIAS) #将图片进行缩放
grayscale_image = smaller_image.convert('L') #将图片灰度化
return grayscale_image
def hash_String(image,resize_width=9,resize_heith=8):
hash_string = "" #定义空字符串的变量,用于后续构造比较后的字符串
pixels = list(grayscale_Image(image,resize_width,resize_heith).getdata())
# 上一个函数grayscale_Image()缩放图片并返回灰度化图片,.getdata()方法可以获得每个像素的灰度值,使用内置函数list()将获得的灰度值序列化
for row in range(1,len(pixels)+1): #获取pixels元素个数,从1开始遍历
if row % resize_width : #因不同行之间的灰度值不进行比较,当与宽度的余数为0时,即表示当前位置为行首位,我们不进行比较
if pixels > pixels: #当前位置非行首位时,我们拿前一位数值与当前位进行比较
hash_string += '1' #当为真时,构造字符串为1
else:
hash_string += '0' #否则,构造字符串为0
#最后可得出由0、1组64位数字字符串,可视为图像的指纹
return int(hash_string,2) #把64位数当作2进制的数值并转换成十进制数值
#下面函数主要是针对简道云前端事件获取的信息进行处理
def dhash_String(src):
src_list = src.split('https') #先分组 谨防图片名称中包含"https"
src_list = #解码图片URL(二次解码,处理中文名称图片),去除空格、空元素影响 再建图片地址列表
dhash_list = #调用hash_String函数生成数值码
src_list = #给类表中每个元素(地址)添加指定字符||,防止图片命名干扰其分割列表
return ''.join(str(dhash_list)).replace(" ",""),''.join(str(src_list)).replace(" ","").replace("'","") #dhash列表转化为字符串 去除空格和,返回图片地址
class Factory:
def __init__(self,cs):
self.chuli={}
self.chuli = dhash_String(cs)
def Release(self):
return {
"isBase64Encoded": False,
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(self.chuli)
}
def main_handler(event, context):
# 处理获取到的参数
chuli = Factory(event)
# 返回处理结果
return chuli.Release()
注:由于刚接触Python,算法及代码书写不是很规范,实际运行中 可能会有卡顿,有功夫的道友可以帮忙提供优化建议。
1.3 创建前端事件
设置如下:
a.触发字段及请求设置
第二张图片中URL地址源于云函数 触发管理中的访问路径
在简道云请求设置的URL中,要在从云函数中复制的地址后面加上: ?cs=请求字段
b.返回值设置
点击添加 表单字段及对应返回值 其中 dHash 值为:$.release 、图片地址为:$.release
2.在主表中统计图片个数并通过云函数创建新的子表单,并分别获取单个图片dHash值及地址
2.1 创建dHash值集合及图片地址集合并计算出图片的数量
相关公式如下:
dHash值集合(文本):报销明细.dHash值
图片地址集合(文本):报销明细.图片地址
图片数量(数字):COUNT(SPLIT(dHash值集合,","))
2.2 创建 “重复图片判定” 子表单
相关公式设置如下:
序号(数字):#(为空,使用云函数)
dHash值(文本):SPLIT(dHash值集合,",")
图片位置信息(文本):CONCATENATE('报销单号:',报销编码,' 第',重复图片判定.序号,'图片') #可以自行设置
图片地址(文本):SPLIT(SPLIT(图片地址集合,"||,"),"||")
2.3 设置云函数,根据图片数量设置子表单
设置方法请参考@张明亮的帖子:《超爽:自建云函数+前端事件 激活你的更多使用场景》
3.通过智能助手将返回的图片dHash值和地址推送至新的子表中,用以配合主表重复图片验证;
3.1 创建一个表单:图片集 用于搜集所有图片的dHash值及位置信息
dHash值(文本)、图片位置信息(文本)、地址(文本)
3.2 设置智能助手推送
由于是测试,该实例中使用的是表单新增触发智能助手,用以演示,各位道友可自行设置
4.设置字段显隐,当出现重复时显示重复图片信息及地址
4.1设置关联数据
在报销单的“重复图片判定”中添加“重复图片位置信息”、“重复图片地址”,分别设置数据联动设置,关联上述“图片集”中的信息
数据联动设置
4.2 设置显隐字段
显隐字段的设置,主要是为了美观,各位道友可按照需要自行设置,以下仅为参考:
我们可以在“重复图片判定”的dHash值中设置不允许重复值,可以确保本次提交不允许重复图片
我们也可以在“重复图片判定”子表单上设置多行文本或单行文本:“重复图片信息”,并设置显隐规则,用以提示:
当出现重复提交图片时,可在其显示报销单中的重复图片位置信息及地址。
注意:
1.获取的地址包含token信息,请注意保密;
2.获取的图片地址,放在浏览器中可直接下载,用以人工判定。(目前尚未找到可以直接预览图片的方法)
3.文中使用的前端事件请求类型均是get,对数据安全性较高的,需要慎重。
4.子表单提交多张图片时 云函数反应稍有迟钝,使用时请注意不要因提交过快而导致dHash值为空数据(设置为必填项);
5.当报销明细中删除项目时,如删除掉一行子表单,下方重复图片判定中的子表单行数不会减少,此时可以刷新网页重新提交(有解决方案的道友可留言指导),避免智能助手提交空数据。
以上就是重复图片识别及图片地址返回的实现的步骤,下面是相似图片实现的思路
四、拓展:相似图片识别的解决思路与困惑
1.汉明距离
汉明距离表示将A修改成为B,需要多少个步骤。
比如字符串“abc”与“ab3”,汉明距离为1,因为只需要修改“c”为“3”即可。dHash中的汉明距离是通过计算差异值的修改位数。我们的差异值是用0、1表示的,可以看做二进制。二进制0110与1111的汉明距离为2。我们将两张图片的dHash值转换为二进制difference,并取异或。计算异或结果的“1”的位数,也就是不相同的位数,这就是汉明距离。
def Difference(dhash1, dhash2):
difference = dhash1 ^ dhash2 #将两个数值进行异或运算
return bin(difference).count('1') #异或运算后计算两数不同的个数,即个数<5,可视为同一或相似图片
2.思路
2.1 通过云函数将所有图片的dHash值提交至数据库;
2.2 将目标图片dHash值与数据库中所有dHash值进行汉明计算,小于5的可以认为是相同图片;
2.3 再次通过云函数将小于5的图片信息返回至主表,用以人工判定。
3.实际测试
3.1 个人在测试下面三张原图片时,
云函数测试的 dHash 值分别是:1885395423738997412、1885395423738997413、1885395973501627045
本地测试的 dHash 值 分别是:1012823547742809698,1012823547742809702,1012823547742809830
虽然值不相同(未找到具体原因,使用Postman测试是结果与上述也不相同,不知是不是因为转码原因),但是本地dHash值前两张图片汉明距离为1,第二张与第三张也是1,第一张与第三张为2;网络dHash值 前两张汉明距离为1,第一、三两张为5,第二、三为4。
测试结果有所波动,但均在理论范围内 认定为相同图片;
3.2 如果对图片进行截切(如下面三张图,从此文章下载到桌面上进行测试,云函数测试汉明距离,差距最小的是1、3两张图片,结果为12)
结果不是很理想。
综上,个人目前尚未有效掌握汉明距离的使用,期待各位道友的建议与分享。
2021.12.07更新说明
不知各位道友有没有这个问题:相同的图片本地测试的值与云函数的值不相同;
且云函数实际使用中出现,图片中仅金额有差异其他都相同的图片,最终dHash值也会相同,导致误判。
目前代码更新了一下(上文源代码已更新):
#将源码中下面代码
smaller_image = im.resize((resize_width,resize_heith)) #将图片进行缩放
#替换为
smaller_image = im.resize((resize_width,resize_heith),Image.ANTIALIAS) #将图片进行缩放
目前排查的结果是,下图红框中相同图片调试时 云函数中的灰度值 与本地的灰度值 几乎每个值都相差1,最终导致云函数中值与本地值 不相同。
目前该问题尚未解决,待解决后会及时更新!