使用 TensorFlow 和 Express.js 实现AI图像识别

语言: CN / TW / HK

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 4 天,点击查看活动详情

人工智能是一个相当广泛的领域,以其惊人的能力和复杂的性质吸引着人们的兴趣。自 1940 年代首次提出使用电子设备重建大脑的可能性以来,这项技术已经取得了长足的进步。尽管人工智能和机器学习在其生命周期的大部分时间里都是相当独特的领域,但事情开始发生变化。

在其历史的大部分时间里,机器学习只有那些接受过广泛的计算机科学教育并可以使用强大硬件的人才能使用。但是现在,随着 TensorFlow 和 Keras 等开源平台的出现,任何具有编码基础知识和计算机的人都可以训练 ML 模型和/或利用大量现有模型。

人工智能的这种民主化对于行业的未来乃至整个人类的未来都至关重要。不能让行业巨头拥有世界历史上最强大的工具的唯一访问权,它需要是你或我都有机会访问和利用的东西。

综上所述,可能认为图像识别和对象检测是一项艰巨的任务,如果从头开始使用 C++ 工作,这可能是真的。然而,作为一个前端开发工程师或者一个单纯的 JavaScript 开发人员,也可以在 Express 服务器中实现对象检测。

文档源码:https://github.com/QuintionTang/img-recognition-server

开始

编码

那么,到底需要建造什么?好吧,幸运的是,这并不复杂。将有一个带有端点的 Express 服务器,该端点接收图像上传并使用来自 TensorFlow 的 COCO-SSD 模型对上传图像中存在的对象进行预测。然后它将以 JSON 格式返回这些预测的数组以供客户端使用。

这基本上是一个 TensorFlow 教程的服务器实现,该教程逐步构建了一个客户端应用程序,该应用程序在网络摄像头输入流上运行对象检测。之后,它以可见的方式向最终用户显示预测。

客户端与服务器端

强烈推荐前面提到的客户端实现教程,它可以有自己的好处。在客户端计算机上运行预训练模型时,可以增加他们的隐私、模型的速度并降低成本,不必为服务器设置付费。这些肯定是要考虑的,但服务器端也有它的好处。

服务器端实现允许完全访问将用于运行模型的硬件,从而允许针对更大、更好和更高内存的模型进行优化。Node.js 服务器几乎可以在包括 Raspberry Pi 在内的任何计算机上运行,这意味着执行模型操作的集中式服务器可以成为 IoT 集成中的一项资产。

话虽如此,这一切都取决于用例和最终用户。花时间根据实际情况寻找最佳实施总是一个好主意,这将导致最具可扩展性、可维护性和灵活性的产品。

构建服务器

首先,需要确保环境设置正确,需要安装 Node.js 来进行包管理和运行代码。虽然不一定需要特定版本,但在本教程中使用了 Node v14.17.0Npm v6.14.13

正确安装 TensorFlow 包需要 Python 2.73+ 不行。如果安装了其他版本,请不要担心。只需转到下载页面,安装正确的版本(2.7.16 引入了一个错误修复,所以我使用它),然后使用 py -2.7 命令切换到它(可以使用 py -0 命令检查所有当前安装的版本)。

新项目

满足要求后,为其创建一个新文件夹并导航到该文件夹。

mkdir ./img-recognition-server && cd ./img-recognition-server

初始化一个新的空 npm 项目。

npm init -y

执行完后的 package.json 代码如下:

{ "name": "img-recognition-server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

依赖库

接下来,将安装所需的依赖项,前两个是 TensorFlow 包,@tensorflow/tfjs-node@tensorflow-models/coco-ssd,第一个是针对 Node 优化的主包,第二个是进行预测的模型。对于 Express 服务器,需要服务器的 express,处理环境变量的 dotenv 和处理图像上传的 busboy

npm install @tensorflow-models/coco-ssd @tensorflow/tfjs-node express dotenv busboy --save

执行后 package.json 应如下所示。

{ "name": "img-recognition-server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@tensorflow-models/coco-ssd": "^2.2.2", "@tensorflow/tfjs-node": "^3.18.0", "busboy": "^1.6.0", "dotenv": "^16.0.1", "express": "^4.18.1" } }

一旦正确配置了 npm 项目,就可以开始编写代码了。

现在,将创建一个新的 index.js 文件,作为程序的入口点。在整个教程中,将提供各种代码片段,可以将每个代码片段添加到文件末尾以最终创建一个完整的程序。输入文件,将开始导入正确的模块。

``` // * TensorFlow import * as tf from "@tensorflow/tfjs-node"; import coco_ssd from "@tensorflow-models/coco-ssd";

// * Server import express from "express"; import busboy from "busboy"; import { config } from "dotenv"; config(); ```

在这里,正在导入正确的模块并初始化 dotenv 包。现在,如果在 index.js 旁边的目录根目录中放置一个 .env 文件,可以编写环境变量并使用 process.env.[variable_name] 将它们带入代码中。创建一个 .env 文件并编写以下内容。

PORT=8008

可以使用任何你喜欢的端口,只需更改号码,以此作为配置文件。稍后,将在代码中使用它。然而,首先,将在代码中初始化模型。

// * 初始化 MODEL let model = undefined; (async () => { model = await coco_ssd.load({ base: "mobilenet_v1", }); })();

现在,使用异步立即调用函数表达式 (IIFE),可以将模型加载到变量中。确保在模型初始化之前不会尝试使用它。现在已经初始化了这个模型,设置 Express 服务器。

const app = express(); const PORT = process.env.PORT || 8008; app.post("/predict", (req, res) => {}); app.listen(PORT, () => { console.log("Listening on port " + PORT); });

回到 PORT 环境变量,将使用它来初始化应用程序实例,还创建了一个 HTTP POST 路由来处理对象识别。为了能够解释它,把它留空,但现在编写里面的实际代码:

app.post("/predict", (req, res) => { if (!model) { res.status(500).send("TensorFlow MODEL 未加载"); } // 创建一个BusBoy实例 const bb = busboy({ headers: req.headers }); bb.on("file", (fieldname, file, filename, encoding, mimetype) => { const buffer = []; file.on("data", (data) => { buffer.push(data); }); file.on("end", async () => { // 运行目标检测 const image = tf.node.decodeImage(Buffer.concat(buffer)); const predictions = await model.detect(image, 3, 0.25); res.json(predictions); }); }); req.pipe(bb); });

所以,这就是它变得复杂的地方。首先,检查 TensorFlow 模型是否已初始化,如果没有,抛出异常让用户知道。

在检查之后,使用 busboy 包来拦截来自 POST 请求的 Content-Type: multipart/form-data,这是必要的,因为 Express 无法正确解析表单数据,因此使用类似 busboy 的扩展来创建数据流/管道以通过。然后,当上传图像时,它会将图像输入缓冲区,然后可以通过 tf.node.decodeImage 方法读取。

TensorFlow 将解码任何格式为 BMP、GIF、JPEG 或 PNG 的图像。此方法返回所谓的张量,它基本上是一个多维数组,coco_ssd 模型更容易理解。话虽如此,coco_ssd 模型还支持 ImageData、HTML Image 元素、HTML Canvas 元素或 HTML Video 元素,并会做出相应的预测。

model.detect 还接受另外两个可选参数,如上所示,在图像之后,可以定义要发回的最大边界框数,以及要包含的预测的最小分数。将最大框数设置为 3,并将分数设置为 0.25

一旦所有这些都运行完毕,并且模型完成了预测,然后将从 model.detect 方法返回的数组作为 JSON 发送回最终用户。既然已经这样做了,就完成了。已经成功创建了一个服务器,该服务器可以上传图像并基于此返回预测,可以使用以下命令运行服务器。

node index.js

如果某些东西不起作用并且想仔细检查,将在下面留下完整的代码:

``` // TensorFlow import * as tf from "@tensorflow/tfjs-node"; import coco_ssd from "@tensorflow-models/coco-ssd";

// Server import express from "express"; import busboy from "busboy"; import { config } from "dotenv"; config();

// 初始化 TensorFlow MODEL let model = undefined; (async () => { model = await coco_ssd.load({ base: "mobilenet_v1", }); })();

const app = express(); const PORT = process.env.PORT || 8008; app.post("/predict", (req, res) => { if (!model) { res.status(500).send("TensorFlow MODEL 未加载"); } // 创建一个BusBoy实例 const bb = busboy({ headers: req.headers }); bb.on("file", (fieldname, file, filename, encoding, mimetype) => { const buffer = []; file.on("data", (data) => { buffer.push(data); }); file.on("end", async () => { // 运行目标检测 const image = tf.node.decodeImage(Buffer.concat(buffer)); const predictions = await model.detect(image, 3, 0.25); res.json(predictions); }); }); req.pipe(bb); }); app.listen(PORT, () => { console.log("Listening on port " + PORT); }); ```

测试

测试是编码必不可少的过程,这里将使用 Postman 来模拟API上传图片。设置参数 image ,类型为 File,这样就可以选择一张图片。设置如下图:

未标题-1.jpg

执行后的结果如下:

[ { "bbox": [ 258.85168075561523, 106.97694540023804, 1633.216495513916, 769.5937013626099 ], "class": "dog", "score": 0.9752472043037415 } ]

预测的结果是图片为 dog ,下面是测试的图片:

dog.jpg

文档源码:https://github.com/QuintionTang/img-recognition-server

总结

本文简单展示使用 Express.js 构建一个 TensorFlow 的图像识别服务,这个服务可以将图像上传到并用于运行图像检测。如果需要构建自己的模型,需要学习更多的支持,去探索使用模型,将AI添加到应用程序。随着人工智能的发展,作为开发人员也是有必要去尝试。