云函数分享:重复图片识别及图片地址返回的实现

楼主
我是社区第104675位番薯,欢迎点我头像关注我哦~

一、应用场景:

      简道云目前没有关于重复或相似图片的识别,但是我们在实际应用中会有相关的需求,如报销凭据、拍照打卡等。而目前该类信息的判定主要是通过人工进行判定,如果是在同一张表单中或者图片量少时,人工尚可,如是时间间隔较久或图片量较大,就可能出现漏失

那么如何对同一张图片进行判定呢?

二、解决方案:

通过搜索网络上成熟的解决方案

     目前最简单的方法是使用加密哈希(例如MD5, SHA-1)判断。但是局限性非常大。例如一个txt文档,其MD5值是根据这个txt的二进制数据计算的,如果是这个txt文档的完全复制版,那他们的MD5值是完全相同的。但是,一旦改变副本的内容,哪怕只是副本的缩进格式,其MD5也会天差地别。因此加密哈希只能用于判断两个完全一致、未经修改的文件,如果是一张经过调色或者缩放的图片,根本无法判断其与另一张图片是否为同一张图片。

那么如何判断一张被PS过的图片是否与另一张图片本质上相同呢?比较简单、易用的解决方案是采用感知哈希算法(Perceptual Hash Algorithm)。
 
相似图片解决步骤:
  1. 分别计算两张图片的dHash值
  2. 通过dHash值计算两张图片的汉明距离(Hamming Distance),通过汉明距离的大小,判断两张图片的相似程度。
 
在简道云上的解决步骤:
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[row-1] > pixels[row]: #当前位置非行首位时,我们拿前一位数值与当前位进行比较
                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 = ['https'+unquote(unquote(str(x))).strip() for x in src_list if len(x)>0]  #解码图片URL(二次解码,处理中文名称图片),去除空格、空元素影响 再建图片地址列表    
    dhash_list = [hash_String(str(image)) for image in src_list]     #调用hash_String函数生成数值码
    src_list = [x.strip()+'||' for x in src_list if len(x)>0]    #给类表中每个元素(地址)添加指定字符||,防止图片命名干扰其分割列表
    return ''.join(str(dhash_list)).replace(" ","")[1:-1],''.join(str(src_list)).replace(" ","").replace("'","")[1:-1]     #dhash列表转化为字符串 去除空格和[],返回图片地址

class Factory:   
    def __init__(self,cs):                   
        self.chuli={}
        self.chuli["release"] = 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['queryString']['cs'])
    # 返回处理结果
    return chuli.Release()

注:由于刚接触Python,算法及代码书写不是很规范,实际运行中 可能会有卡顿,有功夫的道友可以帮忙提供优化建议。

1.3 创建前端事件

设置如下:

a.触发字段及请求设置

     

  • 第二张图片中URL地址源于云函数 触发管理中的访问路径
  • 在简道云请求设置的URL中,要在从云函数中复制的地址后面加上: ?cs=请求字段

b.返回值设置

点击添加 表单字段及对应返回值 其中 dHash 值为:$.release[0] 、图片地址为:$.release[1]

     

2.在主表中统计图片个数并通过云函数创建新的子表单,并分别获取单个图片dHash值及地址
2.1 创建dHash值集合及图片地址集合并计算出图片的数量

相关公式如下:

dHash值集合(文本):报销明细.dHash值

图片地址集合(文本):报销明细.图片地址

图片数量(数字):COUNT(SPLIT(dHash值集合,","))

2.2 创建 “重复图片判定” 子表单 

相关公式设置如下:

序号(数字):#(为空,使用云函数)

dHash值(文本):SPLIT(dHash值集合,",")[重复图片判定.序号-1]

图片位置信息(文本):CONCATENATE('报销单号:',报销编码,' 第',重复图片判定.序号,'图片')    #可以自行设置

图片地址(文本):SPLIT(SPLIT(图片地址集合,"||,")[重复图片判定.序号-1],"||")[0]

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,最终导致云函数中值与本地值 不相同。
目前该问题尚未解决,待解决后会及时更新!
分享扩散:
参与人数 +1 F币 +15 理由
云团 + 15 太棒了,给你32个赞,么么哒

查看全部评分

沙发
发表于 2021-11-23 11:58:38
牛P
板凳
发表于 2021-11-23 11:59:25
您好 我是云团,能方便加您下微信嘛
地板
发表于 2021-11-23 12:02:08
牛哇
5楼
发表于 2021-11-27 22:49:29
  
6楼
发表于 2021-12-6 19:24:44
&#128077;&#127995;
7楼
发表于 2021-12-7 10:54:16
8楼
发表于 2021-12-13 15:27:50
牛P,直播客来一节
9楼
发表于 2022-1-4 12:19:10
这个好,相当有用,感谢大佬
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

10回帖数 1关注人数 5341浏览人数
最后回复于:2022-2-23 09:25

返回顶部 返回列表