给你10万张图片,让你找出与其中某张图片最为近似的10张,你会怎么做?不要轻言放弃,也不用一张张浏览。使用Python,你也可以轻松搞定这个任务。
疑问
《如何用Python和深度神经网络识别图像?》一文写完后,我收到了不少读者的反馈。其中一个很普遍的疑问是:
识别相同或相似的图像,有什么好的方法么?
我虽然乐于帮助读者解决问题,但实话实说,一开始不太理解这种需求。
我文章里的样例图片(哆啦a梦和瓦力),都是从网络搜集来的。如果你需要从网上找到跟某张图片近似的图像,可以使用Google的“以图搜图”功能啊。
很快,我突然醒悟过来。
这种需求,往往不是为了从互联网上大海捞针,寻找近似图片。而是在一个私有海量图片集合中,找到近似图像。
这种图片集合,也许是你团队的科研数据。例如你研究鸟类。某天浏览野外拍摄设备传回来的图像时,突然发现一个新奇品种。
你于是很想搞清楚这种鸟类的出现时间、生活状态等。这就需要从大量图片里,找到与其近似的图片(最有可能是拍到了同一种鸟)。
这种图片集合,也许是社会安全数据。例如你在反恐部门,系统突然发现某个疑似恐怖分子出现在敏感区域。这家伙每一次现身,都伴随着恶性刑事案件的发生,给人民群众的生命财产安全带来严重威胁。
这时候无论对其衣着、外貌还是交通工具的相似度搜索,就显得至关重要了。
上述例子中,因为你都没有把图像上传到互联网,Google的“以图搜图”引擎功能再强大,也无能为力。
好吧,解决这个问题,很有意义。
下一个问题自然是:这种需求,解决起来复杂吗?
是不是需要跨过很高的技术门槛才能实现?是不是需要花大量经费雇佣专家才能完成?
本文,我为你展示如何用10几行Python代码,解决这个问题。
数据
为了讲解的方便,我们依然采用《如何用Python和深度神经网络识别图像?》一文中使用过的哆啦a梦和瓦力图片集合。
我给你准备好了119张哆啦a梦的照片,和80张瓦力的照片。图片已经上传到了这个Github项目。
请点击这个链接,下载压缩包。然后在本地解压。作为咱们的演示目录。
解压后,你会看到目录下有个image文件夹,其中包含两个子目录,分别是doraemon和walle。
doraemon的目录下,都是各式各样的蓝胖子图片。
瓦力目录下的图片是这个样子的:
数据已经有了,下面我们来准备一下环境配置。
环境
本文中,我们需要使用到苹果公司的机器学习框架TuriCreate。
请注意TuriCreate发布时间不久,目前支持的操作系统列表如下:
这就意味着,如果你用的操作系统是Windows 7及以下版本,那么目前TuriCreate还不支持。如需使用,有两种办法:
第一种,请升级到Windows 10,并且使用WSL。关于如何使用WSL,我帮你找到了一个中文教程。请按照教程一步步完成安装。
第二种,采用虚拟机。推荐采用Virtualbox虚拟机,开源免费。同样地,我也帮你找到了很详尽的Virtualbox安装Ubuntu Linux的中文教程。你可以参照它安装好Linux。
解决了系统兼容性问题,下面我们在TuriCreate支持的系统中,安装Python集成运行环境Anaconda。
请到这个网址 下载最新版的Anaconda。下拉页面,找到下载位置。根据你目前使用的系统,网站会自动推荐给你适合的版本下载。我使用的是macOS,下载文件格式为pkg。
下载页面区左侧是Python 3.6版,右侧是2.7版。请选择2.7版本。
双击下载后的pkg文件,根据中文提示一步步安装即可。
装好Anaconda后,我们安装TuriCreate。
请到你的“终端”下面,进入咱们刚刚下载解压后的样例目录。
执行以下命令,我们来创建一个Anaconda虚拟环境,名字叫做turi。如果你之前跟随我在《如何用Python和深度神经网络识别图像?》一文中创立过这个虚拟环境,此处请跳过。
conda create -n turi python=2.7 anaconda
然后,我们激活turi虚拟环境。
source activate turi
在这个环境中,我们安装(或者升级到)最新版的TuriCreate。
pip install -U turicreate
安装完毕后,执行:
jupyter notebook
这样就进入到了Jupyter笔记本环境。我们新建一个Python 2笔记本。
浏览器里出现了一个空白笔记本。
点击左上角笔记本名称,修改为有意义的笔记本名“demo-python-image-similarity”。
准备工作完毕,下面我们就可以开始编写程序了。
代码
首先,我们读入TuriCreate软件包。
import turicreate as tc
我们指定图像所在的文件夹image。让TuriCreate读取所有的图像文件,并且存储到data数据框。
data = tc.image_analysis.load_images('./image/')
我们来看看,data数据框的内容:
data
data包含两列信息,第一列是图片的地址,第二列是图片的长宽描述。
下面我们要求TuriCreate给数据框中每一行添加一个行号。这将作为图片的标记,好在后面查找图片时使用。
data = data.add_row_number()
再看看此时的data数据框内容:
data
我们来看看数据框里面的这些信息对应的图片。
data.explore()
TuriCreate会弹出一个页面,给我们展示数据框里面的内容。
把鼠标悬停在某张缩略图上面,就可以看到对应清晰大图。
第一张图片,是哆啦a梦:
第二张图片,是瓦力:
下面,是重头戏。我们让TuriCreate根据输入的图片集合,建立图像相似度判别模型。
model = tc.image_similarity.create(data)
这个语句执行起来,可能需要一些时间。如果你是第一次使用TuriCreate,它可能还需要从网上下载一些数据。请耐心等待。
Resizing images...
Performing feature extraction on resized images...
Completed 199/199
注意这里的提示,TuriCreate自动帮我们做了图片尺寸调整等预处理工作,并且对每一张图片,都做了特征提取。
经过或长或短的等待,模型已经成功建立。
下面,我们来尝试给模型一张图片,让TuriCreate帮我们从目前的图片集合里,挑出最为相似的10张来。
为了方便,我们就选择第一张图片作为查询输入。
我们利用show()
函数展示一下这张图片。
tc.Image(data[0]['path']).show()
确认无误,还是那张哆啦a梦。
下面我们来查询,我们让模型寻找出与这张图片最相似的10张。
similar_images = model.query(data[0:1], k=10)
很快,系统提示我们,已经找到了。
我们把结果存储在了similar_images
变量里面,下面我们来看看其中都有哪些图片。
similar_images
返回的结果一共有10行。跟我们的要求一致。
每一行数据,包含4列。分别是:
- 查询图片的标记
- 获得结果的标记
- 结果图片与查询图片的距离
- 结果图片与查询图片近似程度排序值
有了这些信息,我们就可以查看到底哪些图片与输入查询图片最为相似了。
注意其中的第一张结果图片,其实就是我们的输入图片本身。考虑它没有意义。
我们提取全部结果图片的标记(索引)值,忽略掉第一张(自身)。
similar_image_index = similar_images['reference_label'][1:]
剩余9张图片的标记都在结果中:
similar_image_index
dtype: int
Rows: 9
[194, 158, 110, 185, 5, 15, 79, 91, 53]
下面我们希望TuriCreate能够可视化帮我们展示这9张图片的内容。
我们要把上面9张图片的标记在所有图片的索引列表中过滤出来:
filtered_index = data['id'].apply(lambda x : x in similar_image_index)
看看过滤后的索引结果:
filtered_index
dtype: int
Rows: 199
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, ... ]
你可以自己数一数,其中标为1的那些图片位置,和我们存储在similar_image_index
中的数字是否一致。
验证完毕以后,请执行以下语句。我们再次调用TuriCreate的explore()
函数,展现相似度查询结果图片。
data[filtered_index].explore()
系统会弹出以下对话框:
我们可以看到,全部查询结果图片中,只出现了哆啦a梦。瓦力的图片,一张都没有出现。
近似图片查找成功!
随着本文操作样例数据后,你不妨换用自己的数据,来动手尝试一番。
原理
展示了如何用10几行Python代码帮你查找相似图形后,我们来聊聊这种强大、简洁背后的原理。
如果你对原理不感兴趣,请跳过这一部分,看“小结”。
虽然我们刚刚只是用了一条语句构建模型:
model = tc.image_similarity.create(data)
然而实际上,TuriCreate在后台为我们做了很多事情。
首先,它调用了一个非常复杂的,在庞大数据集上训练好的模型。
《如何用Python和深度神经网络识别图像?》一文中,我们介绍过,这个模型就是上图中的最后一行。它的名字叫做Resnet-50,足足有50层,训练的图片数以百万计,训练时长也很久。
这里,机智的你一定会问个问题:那些数以百万计的预训练图片里面,是否有哆啦a梦和瓦力呢?
没有。
那就怪了,不是吗?
如果这个复杂的模型之前根本就没有见过哆啦a梦和瓦力,那它怎么知道如何区分它们呢?又怎么能够判别两张哆啦a梦之间的差别,就一定比哆啦a梦和瓦力之间更小呢?
《如何用Python和深度神经网络识别图像?》一文里,我已经提示给你一个关键词:迁移学习(transfer learning)。
这里咱们就不深入技术细节了。我只给你在概念层次讲解一下。
还记得这张描述计算机视觉(卷积神经网络)的示意图吗?
在全连接层(Fully Connected Layer)之前,你可能进行了多次的卷积、抽样、卷积、抽样……这些中间层次,帮我们描绘了图片的一些基本特征,例如边缘大概是个什么形状,某个区块主要的颜色是哪些等。
到了全连接层,你只剩下了一组数据,这组数据可能很长,它抽取了你输入数据的全部特征。
如果你的输入是一只猫,此时的全连接层里就描述了这只猫的各种信息,例如毛发颜色、面部组成部分排列方式、边缘的形状……
这个模型可以帮你提取猫的特征,但它并不知道“猫”的概念是什么。
你自然可以用它帮你提取一条狗的特征。
同理,哆啦a梦的照片,与猫咪的照片一样,都是二维图片,都是用不同颜色分层。
那用其他图片训练的模型,能否提取哆啦a梦照片里的特征呢?
当然也可以!
使用迁移学习的关键,在于冻结中间过程的全部训练结果,直接把一幅图,利用在其他图片集合上训练的模型,转化为一个特征描述结果。
后面的工作,只把这个最后的特征描述(全连接层),用来处理分类和相似度计算。
前面的好几十层参数迭代训练,统统都被省却了。
难怪可以利用这么小的数据集获得如此高的准确度;也难怪可以在这么短的时间里,就获得整合后的模型结果。
把在某种任务上积累下的经验与认知,迁移到另一种近似的新任务上,这种能力就叫做迁移学习。
比起机器来,人的迁移学习能力更为强大。
雨果奖作者郝景芳在最近的一篇文章里,描述了人的这种强大学习能力:
小孩子可以快速学习,进行小数据学习,而且可以得到「类」的概念。小孩子轻易分得清「鸭子」这个概念,和每一只具体不同的鸭子,有什么不同。前者是抽象的「类」,后者是具体的东西。小孩子不需要看多少张鸭子的照片,就能得到「鸭子」这个抽象「类」的概念。
用成语来描述,大概就是“触类旁通”吧。
如果人类不善于迁移学习,把生活中的所有事物,全都当成新的东西从头学起,那后果简直不堪设想。对比我们一生中所能处理的信息总量,这种认知负荷将是无法承受的。
回到我们的问题里,如果模型可以帮我们把每一张图片,都变成全连接层上的那一长串数字(特征),那么我们分辨这些图片的相似程度,就变得太简单了。因为这变成了一个简单的空间向量距离问题。
处理这种简单的数值计算,我们人类可能觉得很繁琐。但是计算机算起来,那就很欢快了。
根据距离大小排序,找出其中最小的几个向量,它们描述的图片,就被模型判定为相似度最高的。
小结
在《如何用Python和深度神经网络识别图像?》一文的基础上,本文进一步介绍了以下内容:
- 如何利用TuriCreate快速构建图片相似度模型;
- 如何查询与某张图片最为相似的k张图片;
- 如何可视化展示查询图片集合结果;
- TuriCreate图形分类与相似度计算背后的原理;
- 迁移学习的基础概念。
如果你没有读过《如何用Python和深度神经网络识别图像?》,强烈建议你读一读。阅读过程可以帮助你更好地理解基于深度神经网络的计算机视觉工作原理。
讨论
你之前遭遇过大海捞针,寻找近似图片的工作吗?你是如何处理的?使用过哪些好的工具与方法?与本文相比较,它们的优势有哪些?欢迎留言,把你的经验和思考分享给大家,我们一起交流讨论。
如果你对我的文章感兴趣,欢迎点赞,并且微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。
如果本文可能对你身边的亲友有帮助,也欢迎你把本文通过微博或朋友圈分享给他们。让他们一起参与到我们的讨论中来。
网友评论
model = tc.image_similarity.create(data)这样的语句得到的model我想存下来,以便在其他电脑的程序中调用我训练好的东西(比如把这个程序拷贝到我的笔记本里运行),我该存成什么样的格式呢?我尝试了.sframe .model 都能存的下来,但是都没法重新用程序再次载入这个文件。所以我每次关机后重新开始就只能再重新训练一次,很苦恼,找了好几天都找不到方法,请问您知不知道该怎么做?非常感谢!
我是训练了一个目标检测的模型,语句是这样的model = tc.object_detector.create(data, annotations='annotations', feature='image', model='darknet-yolo', classes=None, max_iterations=0, verbose=True)
但是关闭jupyter notebook后再重新打开就没办法直接用model了,只能重新训练