利用Python实现简单的相似图片搜索的教程
大概五年前吧,我那时还在为一家约会网站做开发工作。他们是早期创业公司,但他们也开始拥有了一些稳定用户量。不像其他约会网站,这家公司向来以洁身自好为主要市场形象。它不是一个供你鬼混的网站——是让你能找到忠实伴侣的地方。
由于投入了数以百万计的风险资本(在US大萧条之前),他们关于真爱并找寻灵魂伴侣的在线广告势如破竹。Forbes(福布斯,美国著名财经杂志)采访了他们。全国性电视节目也对他们进行了专访。早期的成功促成了事业起步时让人垂涎的指数级增长现象——他们的用户数量以每月加倍的速度增长。对他们而言,一切都似乎顺风顺水。
但他们有一个严重的问题——色情问题。
该约会网站的用户中会有一些人上传色情图片,然后设置为其个人头像。这种行为破坏了很多其他用户的体验——导致很多用户取消了会员。
可能对于现在的一些约会网站随处可见几张色情图片也许并不能称之为是问题。或者可以说是习以为常甚至有些期待,只是一个被接受然后被无视的在线约会的副产品。
立即学习“Python免费学习笔记(深入)”;
然而,这样的行为既不应该被接受也应该被忽视。
别忘了,这次创业可是将自己定位在优秀的约会天堂,免于用户受到困扰其他约会网站的污秽和垃圾的烦扰。简而言之,他们拥有很实在的以风险资本作为背后支撑的名声,而这也正是他们需要保持的风格。
该约会网站为了能迅速阻止色情图片的爆发可以说是不顾一切了。他们雇佣了图片论坛版主团队,真是不做其他事只是每天盯着监管页面8个小时以上,然后移除任何被上传到社交网络的色情图片。
毫不夸张的说,他们投入了数万美元(更不用说数不清的人工小时)来解决这个问题,然而也仅仅只是缓解,控制情况不变严重而不是在源头上阻止。
色情图片的爆发在2009年的七月达到了临界水平。8个月来第一次用户量没能翻倍(甚至已经开始减少了)。更糟糕的是,投资者声称若该公司不能解决这个问题将会撤资。事实上,污秽的潮汐早已开始冲击这座象牙塔了,将它推翻流入大海也不过是时间问题。
正在这个约会网站巨头快要撑不住时,我提出了一个更鲁棒的长期解决方案:如果我们使用图片指纹来与色情图片的爆发斗争呢?
你看,每张图片都有一个指纹。正如人的指纹可以识别人,图片的指纹能识别图片。
这促使了一个三阶段算法的实现:
1. 为不雅图片建立指纹,然后将图片指纹存储在一个数据库中。
2. 当一个用户上传一份新的头像时,我们会将它与数据库中的图片指纹对比。如果上传图片的指纹与数据库任意一个不雅图片指纹相符,我们就阻止用户将该图片设置为个人头像。
3. 当图片监管人标记新的色情图片时,这些图片也被赋予指纹并存入我们的数据库,建立一个能用于阻止非法上传且不断进化的数据库。
我们的方法,尽管不十分完美,但是也卓有成效。慢慢地,色情图片爆发的情况有所减慢。它永远不会消失——但这个算法让我们成功将非法上传的数量减少了80%以上。
这也挽回了投资者的心。他们继续为我们提供资金支持——直到萧条到来,我们都失业了。
回顾过去时,我不禁笑了。我的工作并没持续太久。这个公司也没有坚持太久。甚至还有几个投资者卷铺盖走人了。
但有一样确实存活了下来。提取图片指纹的算法。几年之后,我把这个算法的基本内容分享出来,期望你们可以将它应用到你们自己的项目中。
但最大的问题是,我们怎么才能建立图片指纹呢?
继续读下去一探究竟吧。
即将要做的事情
我们打算用图片指纹进行相似图片的检测。这种技术通常被称为“感知图像hash”或是简单的“图片hash”。
什么是图片指纹/图片哈希
图片hash是检测一张图片的内容然后根据检测的内容为图片建立一个唯一值的过程。
比如,看看本文最上面的那张图片。给定一张图片作为输入,应用一个hash函数,然后基于图片的视觉计算出一个图片hash。相似的图片也应当有相似的hash值。图片hash算法的应用使得相似图片的检测变得相当简单了。
特别地,我们将会使用“差别Hash”或简单的DHash算法计算图片指纹。简单来说,DHash算法着眼于两个相邻像素之间的差值。然后,基于这样的差值,就建立起一个hash值了。
为什么不使用md5,sha-1等算法?
不幸的是,我们不能在实现中使用加密hash算法。由于加密hash算法的本质使然,输入文件中非常微小的差别也能造成差异极大的hash值。而在图片指纹的案例中,我们实际上希望相似的输入可以有相似的hash输出值。
图片指纹可以用在哪里?
正如我上面举的例子,你可以使用图片指纹来维护一个保存不雅图片的数据库——当用户尝试上传类似图片时可以发出警告。
你可以建立一个图片的逆向搜索引擎,比如TinEye,它可以记录图片以及它们出现的相关网页。
你还可以使用图片指纹帮助管理你个人的照片收集。假设你有一个硬盘,上面有你照片库的一些局部备份,但需要一个方法删除局部备份,一张图片仅保留一份唯一的备份——图片指纹可以帮你做到。
简单来说,你几乎可以将图片指纹/哈希用于任何需要你检测图片的相似副本的场景中。
需要的库有哪些?
为了建立图片指纹方案,我们打算使用三个主要的Python包:
- PIL/Pillow用于读取和载入图片
- ImageHash,包括DHash的实现
- 以及NumPy/SciPy,ImageHash的依赖包
你可以使用下列命令一键安装所需要的必备库:
$ pip install pillow imagehash
第一步:为一个图片集建立指纹
第一步就是为我们的图片集建立指纹。
也许你会问,但我们不会,我们不会使用那些我为那家约会网站工作时的色情图片。相反,我创建了一个可供使用的人工数据集。
对计算机视觉的研究人员而言,数据集CALTECH-101 是一个传奇般的存在。它包含来自101个不同分类中的至少7500张图片,内容分别有人物,摩托车和飞机。
从这7500多张图片中,我随机的挑选了17张。
然后,从这17张随机挑选的图片中,以几个百分点的比例随机放大/缩小并创建N张新图片。这里我们的目标是找到这些近似副本的图片——有点大海捞针的感觉。
你也想创建一个类似的数据集用于工作吗?那就下载CALTECH-101 数据集,抽取大概17张图片即可,然后运行repo下的脚本文件gather.py。
回归正题,这些图片除了宽度和高度,其他各方面都是一样的。而且因为他们没有相同的形状,我们不能依赖简单的md5校验和。最重要的是,有相似内容的图片可能有完全不相同的md5哈希。然而,采取图片哈希,相似内容的图片也有相似的哈希指纹。
所以赶紧开始写代码为数据集建立指纹吧。创建一个新文件,命名为index.py,然后开始工作:
# import the necessary packagesfrom PIL import Imageimport imagehashimport argparseimport shelveimport glob # construct the argument parse and parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-d", "--dataset", required = True,help = "path to input dataset of images")ap.add_argument("-s", "--shelve", required = True,help = "output shelve database")args = vars(ap.parse_args()) # open the shelve databasedb = shelve.open(args["shelve"], writeback = True)
要做的第一件事就是引入我们需要的包。我们将使用PIL或Pillow中的Image类载入硬盘上的图片。这个imagehash库可以被用于构建哈希算法。
Argparse库用于解析命令行参数,shelve库用作一个存储在硬盘上的简单键值对数据库(Python字典)。glob库能很容易的获取图片路径。
然后传递命令行参数。第一个,—dataset是输入图片库的路径。第二个,—shelve是shelve数据库的输出路径。
下一步,打开shelve数据库以写数据。这个db数据库存储图片哈希。更多的如下所示:
# loop over the image datasetfor imagePath in glob.glob(args["dataset"] + "/*.jpg"): # load the image and compute the difference hash image = Image.open(imagePath) h = str(imagehash.dhash(image)) # extract the filename from the path and update the database # using the hash as the key and the filename append to the # list of values filename = imagePath[imagePath.rfind("/") + 1:] db[h] = db.get(h, []) + [filename] # close the shelf databasedb.close()