Blog

Blog

PHODAL

Serverless 打造智能微信小程序:图像识别分析文本

结束了五一国庆的八天假期后,便开始着手为微信小程序《代码协作》制作一个图片识别代码的功能。这个需求的主要来源是,在有的公司、组织、团队上,代码是不能直接拷贝出来的。但是,拍照是允许的。为了协作方便,一般会拍照在微信群里讨论。对于我而言,因此这样一个真实需求的存在,我便想试试能不能做这样的一个功能。

对于图片识别功能来说,只需要两步:

  1. 上传图片到 AWS S3 上
  2. 找到一个合适的图片识别服务,来识别这张图片

然而,第一步是最难的一步。

Serverless 上传图片

按理来说,我们只需要在 Serverless 应用里,接受这个数据,然后再存储到 AWS S3 就可以了。

微信小程序上传图片时,采用的 ContentType 是 multipart/form-data,这就意味着上传的图片不是 base64 形式的。在 AWS Gateway 里,接受到的数据变成了:

{ fieldname: 'file',
  originalname: 'tmp_41852593e221427e029327f987f3d588.png',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 e8 00 00 02 80 08 06 00 00 00 0f 73 52 67 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 40 00 ... >,
  size: 870857 }

最开始的时候,我以为是微信小程序上传了。于是,我开始用 curl 来测试上传图片:

curl -v -F file=@test.jpg https://code.wdsm.io/upload

然后对比了本地 readFileSync 的 Buffer 数据,发现图片的编码变了。

然后,我测试了文本上传:

curl -v -F file=@hello.txt https://code.wdsm.io/upload

发现文本的编码结果是不变的。在 Google 了一番之后,发现是因为 AWS Gateway 是能识别文本编码的。

在我尝试了 N 次之后,并在本地构建了个类似的环境。发现了 AWS Gateway 做了一个神奇的转换:Buffer.from(imageData, 'utf8').toString('utf8')。理论上,经过这样的转换,数据应该是变回原来的格式。然而,这个图片并不是 utf8 编码,所以这个数据无法转换回去。

在继续 Google 了一番之后,发现了一个 Serverless Framework 的插件:serverless-apigw-binary。它可以将图片转换为二进制形式的,而 AWS Gateway 不会对二进制的内容进行特殊编码。其配置如下所示:

custom:
  apigwBinary:
    types:
      - 'image/jpeg'
      - 'image/png'
      - 'text/html'
      - 'multipart/form-data'

上面配置中,最重要的其实是 'multipart/form-data',它会将我们的请求转换为二进制形式的。

正在我以为要大功告成的时候,我发现直接用 AWS Lambda 解析出来还是有问题。在尝试了多次之后,结合了一下 express 里的 multer 来解析这个数据,然后它工作了:

const express = require("express");
const multer = require('multer');

const multerupload = multer();
const app = express();

app.post('/upload', multerupload.any(), function (req, res) {
  let file = req.files[0];
  ...
});

果然,编码是要靠猜的。

Serverless 图片识别

在开始这部分之前,需要先了解一下 AWS Rekognition。

Amazon Rekognition 让您能够轻松地为应用程序添加图像和视频分析功能。您只需向 Rekognition API 提供图像或视频,然后此服务就能识别对象、人员、文字、场景和活动,以及检测任何不适宜的内容。

由于,我们的《代码协作》应用整个是基于 AWS 的,因此我们也就继续使用 AWS 的服务来识别文本。先将图片放到 AWS S3 上,然后才是识别:

s3.putObject({
  Body: file.buffer,
  Bucket: bucketName,
  Key: file.originalname,
  ContentType: file.mimetype
}, function (err, data) {
  const s3Config = {
    bucket: bucketName,
    imageName: file.originalname,
  };

  return ImageAnalyser
    .getImageText(s3Config)
    .then((text) => {
      console.log(JSON.stringify(text, null, '\t'));
      res.status(200).send(text)
    })
    ...
})

识别部分代码如下:

const rek = new AWS.Rekognition();

class ImageAnalyser {
  static getImageText(s3Config) {
    ...

    return new Promise((resolve, reject) => {
      rek.detectText(params, (err, data) => {
        if (err) {
          console.log(params, err);
          return reject(new Error(err));
        }
        return resolve(data);
      });
    });
  }
}

对应的返回结果:

{"TextDetections":[{"DetectedText":"test","Type":"LINE","Id":0,"Confidence":94.28083038330078,"Geometry":{"BoundingBox":{"Width":1.0391244888305664,"Height":1.0596693754196167,"Left":-0.023785971105098724,"Top":0.0029582083225250244},"Polygon":[{"X":-0.023785971105098724,"Y":0.0029582083225250244},{"X":1.0153385400772095,"Y":-0.012681752443313599},{"X":1.018007755279541,"Y":1.046987533569336},{"X":-0.021116789430379868,"Y":1.0626275539398193}]}},{"DetectedText":"test","Type":"WORD","Id":1,"ParentId":0,"Confidence":94.28083038330078,"Geometry":{"BoundingBox":{"Width":1.0354670286178589,"Height":1.058911919593811,"Left":-0.02123582363128662,"Top":-0.0029199719429016113},"Polygon":[{"X":-0.023785971105098724,"Y":0.0029582083225250244},{"X":1.0153385400772095,"Y":-0.012681752443313599},{"X":1.018007755279541,"Y":1.046987533569336},{"X":-0.021116789430379868,"Y":1.0626275539398193}]}}]}

微信小程序展示

于是在小程序端,我们只需要处理对应的结果即可:

wx.uploadFile({
  url: 'https://code.wdsm.io/upload',
  filePath: path,
  name: 'file',
  success: function (res) {
    let textDetections = JSON.parse(res.data)['TextDetections'];
    let code = '';

    for (let i = 0; i < textDetections.length; i++) {
      let textDetection = textDetections[i];
      if (textDetection.Type === 'LINE') {
        code = code + textDetection.DetectedText + '\n';
      }
    }

    that.setData({
      code: code,
      isUploading: false
    });
  },
  ...
})

然后,在页面上显示即可:

<view class="ai">
  <button type="default" class="btn" bindtap="chooseImage">上传图片</button>
  <view wx:if="{{code}}" class="weui-cells__title" size="mini">(点击复制文本)</view>
  <view wx:if="{{code}}" bindtap="copyCode">
     {{code}}
  </view>
</view>

<loading hidden="{{!isUploading}}">
  上传中...
</loading>

不过,这里有一个问题,这个 loading 好像有问题。

代码:https://github.com/phodal/code

关于我

Github: @phodal     微博:@phodal     知乎:@phodal    

微信公众号(Phodal)

围观我的Github Idea墙, 也许,你会遇到心仪的项目

QQ技术交流群: 321689806

新书《全栈应用开发:精益实践》

这不是一本深入前端、后台、运维、设计、分析等各个领域的书籍。本书以实践的方式,将这一系列的领域及理论知识结合到一起,来帮助读者构建全栈Web 开发的知识体系,并辅以精益及敏捷的思想,来一步步开发Web 应用:从创建一个UI 原型到编写出静态的前端页面;从静态的前端页面到带后台的应用,并部署应用;从Web 后台开发API 到开发移动Web 应用。在这个过程中,我们还将介绍一些相辅相成的步骤:使用构建系统来加速Web 应用的开发;为应用添加数据分析工具来改进产品;使用分析工具来改善应用的性能;通过自动化部署来加快上线流程;从而帮助读者开发出一个真正可用的全栈 Web 应用。同时,我们也将帮助读者把这些步骤应用到现有的系统上,改进现有系统的开发流程。

comment

Feeds

RSS / Atom

最近文章

关于作者

Phodal Huang

Developer, Consultant, Writer, Designer

ThoughtWorks 高级咨询师

工程师 / 咨询师 / 作家 / 设计学徒

开源深度爱好者

出版有《自己动手设计物联网》、《全栈应用开发:精益实践》

联系我: h@phodal.com

微信公众号: 与我沟通

标签