利⽤Python实现简单的相似图⽚搜索的教程
⼤概五年前吧,我那时还在为⼀家约会⽹站做开发⼯作。他们是早期创业公司,但他们也开始拥有了⼀些稳定⽤户量。不像其他约会⽹站,这家公司向来以洁⾝⾃好为主要市场形象。它不是⼀个供你⿁混的⽹站——是让你能到忠实伴侣的地⽅。
由于投⼊了数以百万计的风险资本(在US⼤萧条之前),他们关于真爱并寻灵魂伴侣的在线⼴告势如破⽵。Forbes(福布斯,美国著名财经杂志)采访了他们。全国性电视节⽬也对他们进⾏了专访。早期的成功促成了事业起步时让⼈垂涎的指数级增长现象——他们的⽤户数量以每⽉加倍的速度增长。对他们⽽⾔,⼀切都似乎顺风顺⽔。
但他们有⼀个严重的问题——⾊情问题。
该约会⽹站的⽤户中会有⼀些⼈上传⾊情图⽚,然后设置为其个⼈头像。这种⾏为破坏了很多其他⽤户的体验——导致很多⽤户取消了会员。
可能对于现在的⼀些约会⽹站随处可见⼏张⾊情图⽚也许并不能称之为是问题。或者可以说是习以为常甚⾄有些期待,只是⼀个被接受然后被⽆视的在线约会的副产品。
然⽽,这样的⾏为既不应该被接受也应该被忽视。
别忘了,这次创业可是将⾃⼰定位在优秀的约会天堂,免于⽤户受到困扰其他约会⽹站的污秽和垃圾的烦扰。简⽽⾔之,他们拥有很实在的以风险资本作为背后⽀撑的名声,⽽这也正是他们需要保持的风格。
该约会⽹站为了能迅速阻⽌⾊情图⽚的爆发可以说是不顾⼀切了。他们雇佣了图⽚论坛版主团队,真是不做其他事只是每天盯着监管页⾯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包:
1. ⽤于读取和载⼊图⽚
2. ,包括DHash的实现
3. 以及/,ImageHash的依赖包
你可以使⽤下列命令⼀键安装所需要的必备库:
1$ pip install pillow imagehash
第⼀步:为⼀个图⽚集建⽴指纹
第⼀步就是为我们的图⽚集建⽴指纹。
也许你会问,但我们不会,我们不会使⽤那些我为那家约会⽹站⼯作时的⾊情图⽚。相反,我创建了⼀个可供使⽤的⼈⼯数据集。
对计算机视觉的研究⼈员⽽⾔,数据集是⼀个传奇般的存在。它包含来⾃101个不同分类中的⾄少7500张图⽚,内容分别有⼈物,摩托车和飞机。
从这7500多张图⽚中,我随机的挑选了17张。
然后,从这17张随机挑选的图⽚中,以⼏个百分点的⽐例随机放⼤/缩⼩并创建N张新图⽚。这⾥我们
的⽬标是到这些近似副本的图⽚——有点⼤海捞针的感觉。
你也想创建⼀个类似的数据集⽤于⼯作吗?那就下载数据集,抽取⼤概17张图⽚即可,然后运⾏repo下的脚本⽂件gather.py。
回归正题,这些图⽚除了宽度和⾼度,其他各⽅⾯都是⼀样的。⽽且因为他们没有相同的形状,我们不能依赖简单的md5校验和。最重要的是,有相似内容的图⽚可能有完全不相同的md5哈希。然⽽,采取图⽚哈希,相似内容的图⽚也有相似的哈希指纹。
所以赶紧开始写代码为数据集建⽴指纹吧。创建⼀个新⽂件,命名为index.py,然后开始⼯作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17# import the necessary packages
from PIL import Image
import imagehash
import argparse
import shelve
python怎么读取py文件
import glob
# construct the argument parse and parse the arguments ap =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 database
db =shelve.open(args["shelve"], writeback =True)
要做的第⼀件事就是引⼊我们需要的包。我们将使⽤PIL或Pillow中的Image类载⼊硬盘上的图⽚。这个imagehash库可以被⽤于构建哈希算法。
Argparse库⽤于解析命令⾏参数,shelve库⽤作⼀个存储在硬盘上的简单键值对数据库(Python字典)。glob库能很容易的获取图⽚路径。然后传递命令⾏参数。第⼀个,—dataset是输⼊图⽚库的路
径。第⼆个,—shelve是shelve数据库的输出路径。
下⼀步,打开shelve数据库以写数据。这个db数据库存储图⽚哈希。更多的如下所⽰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14# loop over the image dataset
for 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 database
db.close()
以上就是⼤部分⼯作的内容了。开始循环从硬盘读取图⽚,创建图⽚指纹并存⼊数据库。现在,来看看整个范例中最重要的两⾏代码:
1 2filename =imagePath[imagePath.rfind("/") +1:] db[h] =db.get(h, []) +[filename]
正如本⽂早些时候提到的,有相同指纹的图⽚被认为是⼀样的。
因此,如果我们的⽬标是到近似图⽚,那就需要维护⼀个有相同指纹值的图⽚列表。
⽽这也正是这⼏⾏代码做的事情。
前⼀个代码段提取了图⽚的⽂件名。⽽后⼀个代码⽚段维护了⼀个有相同指纹值的图⽚列表。
为了从我们的数据库中提取图⽚指纹并建⽴哈希数据库,运⾏下列命令:
1$ python index.py —dataset images —shelve db.shelve
这个脚本会运⾏⼏秒钟,完成后,就会出现⼀个名为db.shelve的⽂件,包含了图⽚指纹和⽂件名的键值对。
这个基本算法正是⼏年前我为这家约会创业公司⼯作时使⽤的算法。我们获得了⼀个不雅图⽚集,为其中的每张图⽚构建⼀个图⽚指纹并将其存⼊数据库。当来⼀张新图⽚时,我只需简单地计算它的哈希值,检测数据库查看是否上传图⽚已被标识为⾮法内容。
下⼀步中,我将展⽰实际如何执⾏查询,判定数据库中是否存在与所给图⽚具有相同哈希值的图⽚。
第⼆步:查询数据集
既然已经建⽴了⼀个图⽚指纹的数据库,那么现在就该搜索我们的数据集了。
打开⼀个新⽂件,命名为search.py,然后开始写代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15# 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(args["dataset"] +"/"+filename)
image.show()
# close the shelve database
db.close()
⾸先打开数据库,然后载⼊硬盘上的图⽚,计算图⽚的指纹,到具有相同指纹的所有图⽚。
如果有图⽚具有相同的哈希值,会遍历这些图⽚并展⽰在屏幕上。
这段代码使我们仅仅使⽤指纹值就能判定图⽚是否已在数据库中存在。
结果
正如本⽂早些时候提到的,我从CALTECH-101数据集的7500多张图⽚中随机选取17张,然后通过任意缩放⼀部分点产⽣N张新的图⽚。
这些图⽚在尺⼨上仅仅是少数像素不同—但也是因为这⼀点我们不能依赖于⽂件的md5哈希(这⼀点已在“优化算法”部分进⾏了详尽的描述)。然⽽,我们可以使⽤图⽚哈希到近似图⽚。
打开你的终端并执⾏下述命令:
1$ python search.py —dataset images —shelve db.shelve —query images/84eba74d-38ae-4bf6-b8b
d-79ffa1dad23a.jpg
如果⼀切顺利你就可以看到下述结果:
左边是输⼊图⽚。载⼊这张图⽚,计算它的图⽚指纹,在数据库中搜索指纹查看是否存在有相同指纹的图⽚。
当然——正如右边所⽰,我们的数据集中有其他两张指纹相同的图⽚。尽管从截图中还不能⼗分明显的看出,这些图⽚,虽然有完全相同的视觉内容,也不是完全相同!这三张图⽚的⾼度宽度各不相同。
尝试⼀下另外⼀个输⼊图⽚:
1$ python search.py —dataset images —shelve db.shelve —query images/9d355a22-3d59-465e-ad14-138a4e3880bc.jpg
下⾯是结果:
左边仍然是我们的输⼊图⽚。正如右边展⽰的,我们的图⽚指纹算法能够出具有相同指纹的三张完全相同的图⽚。
最后⼀个例⼦:
1$ python search.py —dataset images —shelve db.shelve —query images/5134e0c2-34d3-40
这⼀次左边的输⼊图⽚是⼀个摩托车。拿到这张摩托车图⽚,计算它的图⽚指纹,然后在指纹数据库中查该指纹。正如我们在右边看到的,我们也能判断出数据库中有三张图⽚具有相同指纹。
优化算法
有很多可以优化本算法的⽅法——但最关键性的是要考虑到相似但不相同的哈希。
⽐如,本⽂中的图⽚仅仅是⼀⼩部分点重组了(依⽐例增⼤或减⼩)。如果⼀张图⽚以⼀个较⼤的因素调整⼤⼩,或者纵横⽐被改变了,对应的哈希就会不同了。
然⽽,这些图⽚应该仍然是相似的。
为了到相似但不相同的图⽚,我们需要计算汉明距离(Hamming distance).汉明距离被⽤于计算⼀个哈希中的不同位数。因此,哈希中只有⼀位不同的两张图⽚⾃然⽐有10位不同的图⽚更相似。
然⽽,我们遇到了第⼆个问题——算法的可扩展性。
考虑⼀下:我们有⼀张输⼊图⽚,⼜被要求在数据库中到所有相似图⽚。然后我们必须计算输⼊图⽚和数据库中的每⼀张图⽚之间的汉明距离。
随着数据库规模的增长,和数据库⽐对的时间也随着延长。最终,我们的哈希数据库会达到⼀个线性⽐对已经不实际的规模。
解决办法,虽然已超出本⽂范围,就是利⽤和将搜索问题的复杂度从线性减⼩到次线性。
总结
本⽂中我们学会了如何构建和使⽤图⽚哈希来完成相似图⽚的检测。这些图⽚哈希是使⽤图⽚的视觉内容构建的。
正如⼀个指纹可以识别⼀个⼈,图⽚哈希也能唯⼀的识别⼀张图⽚。
使⽤图⽚指纹的知识,我们建⽴了⼀个仅使⽤图⽚哈希就能到和识别具有相似内容的图⽚的系统。
然后我们⼜演⽰了图⽚哈希是如何应⽤于快速到有相似内容的图⽚。
从⽬录下下载代码。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。