给你10万张图片,让你找出与其中某张图片最为近似的10张,你会怎么做?不要轻言放弃,也不用一张张浏览。使用Python,你也可以轻松搞定这个任务。
识别相同或相似的图像,有什么好的方法么?
加vx:tanzhouyiwan 或qq群813622576免费领取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梦照片里的特征呢?
当然也可以!
使用迁移学习的关键,在于冻结中间过程的全部训练结果,直接把一幅图,利用在其他图片集合上训练的模型,转化为一个特征描述结果。
后面的工作,只把这个最后的特征描述(全连接层),用来处理分类和相似度计算。
前面的好几十层参数迭代训练,统统都被省却了。
难怪可以利用这么小的数据集获得如此高的准确度;也难怪可以在这么短的时间里,就获得整合后的模型结果。
把在某种任务上积累下的经验与认知,迁移到另一种近似的新任务上,这种能力就叫做迁移学习。
比起机器来,人的迁移学习能力更为强大。
雨果奖作者郝景芳在最近的一篇文章里,描述了人的这种强大学习能力:
小孩子可以快速学习,进行小数据学习,而且可以得到「类」的概念。小孩子轻易分得清「鸭子」这个概念,和每一只具体不同的鸭子,有什么不同。前者是抽象的「类」,后者是具体的东西。小孩子不需要看多少张鸭子的照片,就能得到「鸭子」这个抽象「类」的概念。
用成语来描述,大概就是“触类旁通”吧。
如果人类不善于迁移学习,把生活中的所有事物,全都当成新的东西从头学起,那后果简直不堪设想。对比我们一生中所能处理的信息总量,这种认知负荷将是无法承受的。
回到我们的问题里,如果模型可以帮我们把每一张图片,都变成全连接层上的那一长串数字(特征),那么我们分辨这些图片的相似程度,就变得太简单了。因为这变成了一个简单的空间向量距离问题。
处理这种简单的数值计算,我们人类可能觉得很繁琐。但是计算机算起来,那就很欢快了。
根据距离大小排序,找出其中最小的几个向量,它们描述的图片,就被模型判定为相似度最高的。
加vx:tanzhouyiwan 或qq群813622576免费领取Python学习资料一套哦!