图像堆栈和 iPhone 架 - 构建一个互联网规模的表情包搜索引擎
任何在互联网上花费过时间的人都知道,网络用语中的梗使用已经变得多么普遍。在最新事件中寻找新的梗并与不同朋友群体分享幽默,是我长期喜爱的消遣方式。在技术和信息安全领域工作,让我意外地拥有了一群“终日在线”的朋友,他们都有同样的爱好。
然而,大多数迷因都存在着一种讽刺的双重性:它们越小众,往往就越有趣。一些最好的迷因只是我朋友之间愚蠢的内部笑话,或者来自极其小众的信息安全行业。
这个问题非常普遍:我总是在最需要的时候找不到想要发送给别人的特定梗图。在对话中,即兴的梗图总是难以找到。结果发现,在手机里翻阅数百张保存的图片并不高效,所以我决定尝试更好地解决这个问题。
OCR 实现
之前的尝试编写表情包搜索引擎最终导致一个核心问题:缺乏可扩展的 OCR。所有现有的解决方案要么在识别大多数表情包中扭曲且高度变化的文本方面非常糟糕,要么成本过高。
例如,Tesseract OCR 是一个免费的开源库,用于从图像中提取文本。当使用这个库进行测试时,它在识别具有非常标准字体和色彩方案的迷因时表现良好:
示例易于 OCR 的迷因,Tesseract 结果: i'm supposed to feel
refreshed after waking up from a nap but instead i end up feeling like
this
然而,恶搞、加水印和重新分享表情包使它们的格式根本不标准。以下这个表情包为例:
Tesseract 表示,此表情包的 OCR 文本为: 30 BLUE
man41;? S4-5?'flew/ — V \[IL ' . ",2; g" .'Sj /B"f;T"EArmDand \[red\]
mvslmunlm: sawmills
。这与实际文本相去甚远,任何人类都能看出。似乎我的选择只能是昂贵的云 OCR 服务,或者像这样的性能不佳的解决方案。
然而,有一天晚上,当我试图用我的 iPhone 发送一张老式 CAPTCHA 图像给某人时,我有了一个大领悟:
不小心选中了上一代 reCAPTCHA 图像中的混淆文本。
令我惊讶的是,iOS 非常乐意突出显示故意打乱和扭曲的 CAPTCHA 图像文本。更令人惊讶的是,它完美地解码了文本:
粘贴复制的 reCAPTCHA 文本。
如果它在故意混淆的文本图像上做得这么好,那么面对大多数表情包的多种格式,它会表现如何?在我手机里测试了一堆保存的表情包后,答案似乎是“非常好”。
更好的是,经过一番快速搜索,我发现这个功能在 iOS Vision 框架中已经公开。这意味着这个 OCR 可以完全自动化,以自定义 iOS 应用的形式实现。最后,似乎有一个可扩展的 OCR 解决方案可以解决我一直面临的问题!
便宜且可扩展的 OCR,适用于数百万个表情包
尽管我写过很多代码,但我从未用 Swift 或 Objective-C 写过任何严肃的东西。我找不到任何 Vision Framework 插件用于 Apache Cordova,所以我也不能只用 JavaScript 编写应用程序。看起来是时候硬着头皮用 Swift 编写一个 OCR iOS 服务器了。
通过结合强大的谷歌搜索能力、逆向工程 GitHub 上的各种 Swift 仓库,以及偶尔向我的 iOS 朋友询问 Xcode 问题,我设法拼凑出了一个可行的解决方案:
一个非常基础的在 iPhone 上运行的 iOS 视觉 OCR 服务器。
我的初步速度测试在我的 Macbook 上相当慢。然而,一旦我将应用部署到实际的 iPhone 上,OCR 的速度就非常令人鼓舞(可能是因为 Vision 框架使用了 GPU)。我随后能够迅速对数千张图片进行极其精确的 OCR 处理,甚至在预算型 iPhone 型号如第二代 SE 上也是如此。
总体来说,建立在 GCDWebServer 之上的 API 服务器工作得相当不错,但确实存在轻微的内存泄漏问题。在 OCR 了 20K-40K 张图片后,应用程序通常会崩溃,这相当令人烦恼。再次,我对 Swift 的熟悉程度与金毛犬对金融的理解相当,因此调试问题证明相当棘手。在调查了更多“黑客”选项后,我意识到可以利用 iOS 上的“辅助功能”在应用程序崩溃时自动重启。这本质上就像一个守护进程,确保 OCR 服务器继续处理请求,并防止由损坏的图像引起的其他未知崩溃中断流程。
全文搜索与 ElasticSearch
现在有了从所有表情包图片中正确提取文本的方法,问题变成了如何快速搜索大量文本。使用 Postgres 全文搜索索引功能的初步测试表明,在超过一百万张图片的规模下,即使分配了适当的硬件资源,速度仍然非常慢。
我决定尝试一下 ElasticSearch,因为它基本上是为这个问题量身定做的。在阅读文档、进行初步测试以及阅读关于其在实际应用中的博客文章后,我对我使用案例中的实现得出了一些结论:
-
ElasticSearch 对 RAM 和系统资源需求很大,尤其是在运行多个节点时。拥有多个节点可以在出现故障时提供容错能力,这在任何分布式系统中都很常见。 -
我可以在一个节点集群中运行 ElasticSearch,因为即使是数百万个表情包的文本总和,对于 ElasticSearch 的常规规模来说也不算大。这将是成本效益的,但当然是以可靠性为代价的。 -
由于我一直在使用 Postgres 来存储关于 meme(例如上下文、来源等)的其余结构化数据,因此将 meme 文本存储在 ElasticSearch 中让我感到担忧,因为这可能会模糊“单一真相来源”的范式。在过去的经验中,确保两个真相来源一致可能会成为极端复杂和头痛的来源。 -
在进行了一些搜索后,我发现可以利用 PGSync 自动同步选定的 Postgres 列到 ElasticSearch。这似乎是一个很好的权衡,可以保持单一的真实来源(Postgres)并在单节点配置下有效地运行 ElasticSearch。如果发生数据丢失,我可以删除 ElasticSearch,而 PGSync 将允许我轻松重建文本搜索索引。 -
ElasticSearch 具有大量可配置性,适用于文本搜索,并提供完整的 REST API,使我能够轻松将其集成到我的服务中。
我的最终设计看起来像下面这样:
极端随意的最终实施基础设施图。
测试生成的数据集显示,其扩展性非常好,即使在相对简陋的硬件上也能在不到一秒内搜索数百万个梗图。在撰写本文时,我能够在只有 6 个核心和 16GB 内存的共享 Linode 实例上索引和搜索大约 1700 万个梗图的文本。这使成本相对较低,这对于打算长时间运行的副项目来说很重要。
视频梗图、ffmpeg 和 OCR
实际上,梗图并不仅限于图片。现在许多梗图都是视频,甚至包含音频轨道。这无疑是由于移动网络技术的改进,使得大文件的快速传输成为可能。在某些情况下,例如 GIF,视频甚至更好,因为它们具有更好的压缩,因此可以更小。
为了索引此类梗,视频必须被切割成一系列截图,然后像常规梗一样进行 OCR 处理。为了解决这个问题,我编写了一个小型微服务,它执行以下操作:
-
接受一个输入视频文件。 -
使用 ffmpeg(通过库),从视频中提取出十个均匀分布的截图。 -
发送截图文件到 iPhone OCR 服务。 -
返回从视频文件中 OCR 每个截图后的结果集。
升级 iPhone OCR 服务为 OCR 集群
不出所料,这显著增加了 OCR 服务的负载。对于每个视频梗,进行 OCR 的工作量实际上是原来的 10 倍。尽管 OCR 应用服务器的速度很快,但这仍然成为了一个主要瓶颈,我最终选择将 iOS OCR 服务升级为一个集群:
别担心,有风扇在保持它们凉爽。
这个配置看起来相当昂贵,因为使用了这么多部 iPhone。然而,也有一些事情对我有利,使得这个价格比你预期的便宜得多:
-
由于这些是为通过 iOS Vision API 进行 OCR 而专门设计的,我可以使用较旧(且更便宜)的 iPhone 型号,例如 iPhone SE(第二代)。 -
我有不关心屏幕裂缝、划痕和其他外观问题的优势,这进一步降低了成本。 -
更好的是,我甚至不想把它们用作手机,所以即使是被禁用 IMEI 或锁定在不受欢迎网络上的 iPhone,对我来说也是完全可行的。
综合考虑这些因素,我能够以相当便宜的价格找到 iPhone。例如,这里有一份符合我要求的清单,价格相当实惠:
这部手机可能只卖 40 美元,因为它被锁定在一个不受欢迎的美国运营商(Cricket)上,因此大多数人都不想被它困住。
这些成本与云 OCR 服务相比如何?GCP 的 Cloud Vision API 每千张图片 OCR 收费 1.50 美元。这意味着使用这种自制解决方案,我们将在大约 27K 张图片后超过 iPhone 的成本。当然,也许 GCP 的 OCR 服务在质量上要好得多,但在我测试中,对于这个用例,结果似乎非常相似。尝试在数千万 OCR 请求的规模上使用云 API,成本对这个项目来说将是难以承受的。
密切关注 eBay 拍卖,我买下了任何以极低价格出售的 iPhone。利用家里闲置的旧 Raspberry Pi,我将其配置为 Nginx 负载均衡器,以均匀分配请求到 iPhone 上。添加了一些网络设备和便宜的散热风扇,我拥有了一个可以轻松处理更大需求的 OCR 集群。
最终(复杂)架构
最终架构看起来与上图类似。虽然由于我为成本优化而增加了一些复杂性,但更便宜的设施将使我能够运行这个副项目更长的时间。
总体来说,这是一个有趣的项目,而且还有大量个人实用性的额外奖励。我在各种主题上学到了很多,包括 ElasticSearch 配置、iOS 应用开发,以及一点关于机器学习的内容。在未来的文章中,我希望详细说明我为它构建的一些其他功能,包括以下内容:
-
“通过图片搜索”/“图片相似度”搜索数百万个表情包的规模。 -
自动检测和标记不适宜工作场所的恶搞图片。 -
构建索引所有梗的抓取基础设施。
如果你想试试,请访问 https://findthatmeme.com,并告诉我你的想法!