写作本文的目是发现建立网站的时候,很多用户用相同的头像,这导致识别度降低,为了防止用户上传相同的图片作为自己的头像以及上传不当的图像文件,作者研究了这个图像指纹的问题。
每个人都有属于他自己的指纹,指纹能够识别人,那么图片的指纹也可以用来识别图片。这促使了一个三阶段算法的实现:
1. 为图片建立指纹,然后将图片指纹存储在一个数据库中。
2. 当一个用户上传一份新的头像时,我们会将它与数据库中的图片指纹对比。如果上传的图片的指纹与数据任意一个图片指纹相符,我们就阻止用户将该图片设置为个人头像。
3. 当图片监管人标记新的图片时,这些图片也被赋予指纹并存入我们的数据库,建立一个能用于阻止使用与库内相同图片且不断进化的数据库。
现在,我把这个算法的基本内容分享出来,期望可以将它应用到你们自己的项目中。
但最大的问题是,我们怎么才能建立图片指纹呢?继续读下去一探究竟吧。
即将要做的事情
我们打算用图片指纹进行相似图片的检测。这种技术通常被称为“感知图像hash”或是简单的“图片hash”。 【参阅《感知图像hash》,问题:为什么hash是图片的唯一?】
什么是图片指纹/图片哈希
图片hash是检测一张图片的内容然后根据检测的内容为图片建立一个唯一值的过程。
比如,看看本文最上面的那张图片。给定一张图片作为输入,应用一个hash函数,然后基于图片的视觉计算出一个图片hash。相似的图片也应当有相似的hash值。图片hash算法的应用使得相似图片的检测变得相当简单了。
特别地,我们将会使用“差别Hash”或简单的DHash算法计算图片指纹。简单来说,DHash算法着眼于两个相邻像素之间的差值。然后,基于这样的差值,就建立起一个hash值了。 【阅读:DHash算法计算图片指纹】
为什么不使用md5,sha-1等算法?
不幸的是,我们不能在实现中使用加密hash算法。由于加密hash算法的本质使然,输入文件中非常微小的差别也能造成差异极大的hash值。而在图片指纹的案例中,我们实际上希望相似的输入可以有相似的hash输出值。
图片指纹可以用在哪里?
1、正如我上面举的例子,你可以使用图片指纹来维护一个保存不雅图片的数据库——当用户尝试上传类似图片时可以发出警告。
2、你可以建立一个图片的逆向搜索引擎,比如TinEye,它可以记录图片以及它们出现的相关网页。
3、你还可以使用图片指纹帮助管理你个人的照片收集。假设你有一个硬盘,上面有你照片库的一些局部备份,但需要一个方法删除局部备份,一张图片仅保留一份唯一的备份——图片指纹可以帮你做到。
简单来说,你几乎可以将图片指纹/哈希用于任何需要你检测图片的相似副本的场景中。
需要的库有哪些?
为了建立图片指纹方案,我们打算使用三个主要的Python包:
1. PIL/Pillow用于读取和载入图片
2. ImageHash,包括DHash的实现
3. 以及NumPy/SciPy,ImageHash的依赖包
你可以使用下列命令一键安装所需要的必备库:
pip install pillow imagehash第一步:为一个图片集建立指纹
第一步就是为我们的图片集建立指纹。
数据集collect包含n多张图片,我随机的挑选了几张。然后,从这几张随机挑选的图片中,以几个百分点的比例随机放大/缩小并创建N张新图片。这里我们的目标是找到这些近似副本的图片——有点大海捞针的感觉。这些图片除了宽度和高度,其他各方面都是一样的。采取图片哈希,相似内容的图片也有相似的哈希指纹。
所以赶紧开始写代码为数据集建立指纹吧。创建一个新文件,命名为index.py,然后开始工作:
# coding=utf-8
# 导入必要的包
import argparse
import shelve
import imagehash
import glob
from PIL import Image
# 构建参数解析,并分析参数
ap =argparse.ArgumentParser()
ap.add_argument("-d","--dataset", required=True, help="照片数据集的路径")
ap.add_argument("-s","--shelve",required=True, help="shelve数据集的输出")
args =vars(ap.parse_args())
# 打开shelve数据集
db = shelve.open(args["shelve"],writeback=True)
要做的第一件事就是引入我们需要的包。我们将使用PIL或Pillow中的Image类载入硬盘上的图片。这个imagehash库可以被用于构建哈希算法。Argparse库用于解析命令行参数,shelve库用作一个存储在硬盘上的简单键值对数据库(Python字典)。glob库能很容易的获取图片路径。然后传递命令行参数。第一个,--dataset是输入图片库的路径。第二个,--shelve是shelve数据库的输出路径。
下一步,打开shelve数据库db以写数据,这个db数据库存储图片哈希。更多的如下所示:
# 在图像数据集中循环
for imagePath in glob.glob(args["dataset"] + "/*.jpg"):
# 加载图片并计算哈希值的差异
image =Image.open(imagePath)
h = str(imagehash.dhash(image))
# 提取路径中的文件名并更新数据库
# 用散列作为字典的键,文件名添加到值列表
filename = imagePath[imagePath.rfind("/") + 1:]
db[h] = db.get(h, []) + [filename]
# 关闭shelf数据集
db.close()
以上就是大部分工作的内容了。开始循环从硬盘读取图片,创建图片指纹并存入数据库。
现在,来看看整个范例中最重要的两行代码:
1 2 |
filename = imagePath[imagePath.rfind("/") + 1:] db[h] = db.get(h, []) + [filename] |
正如本文提到的,有相同指纹的图片被认为是一样的。
因此,如果我们的目标是找到近似图片,那就需要维护一个有相同指纹值的图片列表。
而这也正是这几行代码做的事情。
前一个代码段提取了图片的文件名。而后一个代码片段维护了一个有相同指纹值的图片列表。
为了从我们的数据库中提取图片指纹并建立哈希数据库,命令提示符下运行下列命令:
python index.py -d image -s db这个脚本会运行几秒钟,完成后,就会出现一个名为db的几个相关文件,它包含了图片指纹和文件名的键值对。
我们获得了一个图片集,为其中的每张图片构建一个图片指纹并将其存入数据库。当来一张新图片时,我只需简单地计算它的哈希值,检测数据库查看是否上传图片已被标识为非法内容。
下一步中,我将展示实际如何执行查询,判定数据库中是否存在与所给图片具有相同哈希值的图片。
第二步:查询数据集
既然已经建立了一个图片指纹的数据库,那么现在就该搜索我们的数据集了。
打开一个新文件,命名为search.py,然后开始写代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
# import the necessary packages from PIL import Image import imagehash import argparse import shelve
# construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-d","--dataset",required = True,help = "path to dataset of images") ap.add_argument("-s","--shelve", required = True,help = "output shelve database") ap.add_argument("-q","--query", required = True,help = "path to the query image") args = vars(ap.parse_args()) |
我们需要再一次导入相关的包。然后转换命令行参数。需要三个选项,--dataset初始图片集的路径,--shelve,保存键值对的数据库的路径,--query,查询/上传的图片文件名(含路径)。我们的目标是对于每个查询图片,判定数据库中是否已经存在。
现在,写代码执行实际的查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# open the shelve database db = shelve.open(args["shelve"])
# load the query image, compute the difference image hash, and # and grab the images from the database that have the same hash # value query = Image.open(args["query"]) h = str(imagehash.dhash(query)) filenames = db[h] print("Found %d images" % (len(filenames)))
# loop over the images for filename in filenames: image = Image.open(filename) print(image)#为了便于观察,这里打印出来 image.show()
# close the shelve database db.close() |
首先打开数据库,然后载入硬盘上的图片,计算图片的指纹,找到具有相同指纹的所有图片。
如果有图片具有相同的哈希值,会遍历这些图片并展示在屏幕上。
这段代码使我们仅仅使用指纹值就能判定图片是否已在数据库中存在。
结果
正如本文早些时候提到的,我从collect数据集的n多张图片中随机选取几张,然后通过任意缩放一部分点产生几张新的图片。
这些图片在尺寸上仅仅是少数像素不同—但也是因为这一点我们不能依赖于文件的md5哈希(这一点已在“优化算法”部分进行了详尽的描述)。然而,我们可以使用图片哈希找到近似图片。
打开你的终端并在命令提示符下执行下述命令:
python search.py -d image -s db -q 1.jpg如果一切顺利你就可以看到下述结果:
运行后的结果:
优化算法
有很多可以优化本算法的方法——但最关键性的是要考虑到相似但不相同的哈希。
比如,本文中的图片仅仅是一小部分点重组了(依比例增大或减小)。如果一张图片以一个较大的因素调整大小,或者纵横比被改变了,对应的哈希就会不同了。
然而,这些图片应该仍然是相似的。
为了找到相似但不相同的图片,我们需要计算汉明距离(Hammingdistance).汉明距离被用于计算一个哈希中的不同位数。因此,哈希中只有一位不同的两张图片自然比有10位不同的图片更相似。
然而,我们遇到了第二个问题——算法的可扩展性。
考虑一下:我们有一张输入图片,又被要求在数据库中找到所有相似图片。然后我们必须计算输入图片和数据库中的每一张图片之间的汉明距离。
随着数据库规模的增长,和数据库比对的时间也随着延长。最终,我们的哈希数据库会达到一个线性比对已经不实际的规模。
解决办法,虽然已超出本文范围,就是利用K-d trees和VP trees将搜索问题的复杂度从线性减小到次线性。
总结
本文中我们学会了如何构建和使用图片哈希来完成相似图片的检测。这些图片哈希是使用图片的视觉内容构建的。
正如一个指纹可以识别一个人,图片哈希也能唯一的识别一张图片。
使用图片指纹的知识,我们建立了一个仅使用图片哈希就能找到和识别具有相似内容的图片的系统。
然后我们又演示了图片哈希是如何应用于快速找到有相似内容的图片。
环境说明:本程序在python3.5下运行的,我将文件全部放在d:/code下,包括index.py、search.py、1.jpg,以及图片文件夹image。image下面有5张照片image_0001.jpg、image_0002.jpg、image_0011.jpg、image_0047.jpg和image_0055.jpg,其中1.jpg、image_0001.jpg和image_0002.jpg是相同的图片,仅仅是文件名不一样。
code下载:http://pan.baidu.com/s/1skSHcBV