利用Python实现简单的相似图片搜索
【搞了好几天,终于把程序复原调试通过,特此在这里把技术文档贴出来,尤其是环境配置的说明,供大家分享。】
写作本文的目是发现建立网站的时候,很多用户用相同的头像,这导致识别度降低,为了防止用户上传相同的图片作为自己的头像以及上传不当的图像文件,作者研究了这个图像指纹的问题。
每个人都有属于他自己的指纹,指纹能够识别人,那么图片的指纹也可以用来识别图片。这促使了一个三阶段算法的实现:
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
利用Python实现简单的相似图片搜索相关推荐
- 利用Python实现简单的相似图片搜索的教程
大概五年前吧,我那时还在为一家约会网站做开发工作.他们是早期创业公司,但他们也开始拥有了一些稳定用户量.不像其他约会网站,这家公司向来以洁身自好为主要市场形象.它不是一个供你鬼混的网站--是让你能找到 ...
- python爬虫简单实例-Python 利用Python编写简单网络爬虫实例3
利用Python编写简单网络爬虫实例3 by:授客 QQ:1033553122 实验环境 python版本:3.3.5(2.7下报错 实验目的 获取目标网站"http://bbs.51tes ...
- 利用python发送邮件_利用python实现简单的邮件发送客户端示例
脚本过于简单,供学习和参考.主要了解一下smtplib库的使用和超时机制的实现.使用signal.alarm实现超时机制. #!/usr/bin/env python # -*- coding: ut ...
- Python 散点图线性拟合_机器学习之利用Python进行简单线性回归分析
前言:在利用机器学习方法进行数据分析时经常要了解变量的相关性,有时还需要对变量进行回归分析.本文首先对人工智能/机器学习/深度学习.相关分析/因果分析/回归分析等易混淆的概念进行区分,最后结合案例介绍 ...
- python实现邮件客户端_利用python实现简单的邮件发送客户端示例
脚本过于简单,供学习和参考.主要了解一下smtplib库的使用和超时机制的实现.使用signal.alarm实现超时机制. #!/usr/bin/env python # -*- coding: ut ...
- 利用python进行简单条件选股策略
标题: 利用python进行简单条件选股策略 """ 目的是如何用python演示条件选股. 根据标的公司所处的行业进行分类,从本行业中选出高成长性.净资产收益率高.估值 ...
- 利用Python进行简单杜邦分析
利用Python进行简单杜邦分析 "巧妇难为无米之炊",找不到数据,量化分析.财务报表分析也就无从谈起.对于分析者来说,获取数据是量化分析的第一步.Python的一个强大功能之一就 ...
- 利用 Python 实现简单的主题爬虫
利用 Python 实现简单的主题爬虫 利用 Python 实现简单的主题爬虫,主要是通过对指定的 主题 和 网站 进行深度爬取,获取对应网页的标题和 url ,仅供学习参考. 爬取结果: 实验源 ...
- python做线性回归_利用python实现简单的线性回归
Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 用pytho ...
- 如何利用python实现简单的作图
* 如何利用python实现简单的作图* 首先,了解一下python的海龟绘图法. 绘图法指的是一只类似于海龟的机器人.我们可以利用机器人进行前进后退及向左向右的旋转,而机器人行走的路径就是你所要绘制 ...
最新文章
- html5 颜色弹窗 位置,HTML5之placeholder属性以及如何更改placeholder属性中文字颜色大小位置...
- Xamarin XAML语言教程基本页面ContentPage占用面积(二)
- python mongodb 异步_【转】Python操作MongoDB数据库
- c 语言 文本处理范例
- Java高级语法笔记-普通异常处理
- python编辑编程器_用Python制作编辑器
- 《梦断代码》读书笔记——第3、4、5章
- linux音频alsa-uda134x驱动文档阅读之一(over-view)
- 【C / EasyX】十字消除游戏的实现方法
- UCOS操作系统——任务内嵌信号量(十一)
- 【面试准备】计算机体系结构
- 【python】将多个txt文件合并为一个txt文件
- SQL 数据库学习路线推荐
- Python repr()函数
- 页面跳转之前显示等待 wating ------
- gitbucket push卡住
- ATM和电路交换和分组交换区别
- 搜狗输入法切换全角半角
- 《Braid》碎片式台词
- Unity创建和修改本地Word文档
热门文章
- html5画布动态时钟,HTML5之canvas绘制动态时钟
- Laravel 源码解读
- EPSG是什么?WKT是什么?SRID是什么?EPSG、WKT、SRID概念
- EPSG:900913 与 EPSG:4326 转换方法
- 电脑怎么分区硬盘分区方法
- AudioTunes FLAC, APE, WMA Converter for Mac(音频转换工具)
- 有监督学习(supervised learning))与无监督学习(unsupervised learning)之间有何区别?
- padavan固件获取网络地图中的客户端状态
- 线段树学习总结 - 关于猹 ACM 生涯的第一次受苦受难
- 如何准备一场Java面试?