What's?

Home / Lineに画像送ってホスティングしつつ解析した | AWS Lambda S3 Rekognition

Lineに画像送ってホスティングしつつ解析した | AWS Lambda S3 Rekognition
March 07, 2020

DEVELOPMENT

web
aws
s3
lambda
rekognition

Line Messaging API を AWS Lambda + API Gatewayで実装し、S3へのアップロード、Rekognitionでの画像解析をする紹介です。

最初に

皆さんLINEはメッセージツールとして大体使っていると思います。

うちで運用しているwebサイトは、このブログのように、hugo や hexo、Gatsbyなどの静的サイトジェネレーターを使って、マークダウン方式で記事を作成し、

サイトの更新をしています。

その際、エンジニアであれば、ターミナルやエディタで作業可能ですが、

非エンジニアの場合にこの作業をしてもらう場合、どうすればいいか考えると

  1. netlify でhostingするように変える
  2. 自前で投稿ページを作り、AWS codeBuildか何かでジェネレーターを実行するよう作る
  3. マークダウン方式だけ覚えてもらい、webのマークダウンエディタを使い作成してもらい、エンジニアがUPする

wordpressは今更絶対嫌なんで、このくらいでしょうか?

将来的には、みんなターミナルが触れるようになるといいと、願っているので、 今回は勉強のためにも、

3 の マークダウン方式だけ覚えてもらい、webのマークダウンエディタを使い作成してもらう

を採用しようと思いました。

画像問題

その場合、画像はどうすればいいか。画像もエンジニアに渡して埋め込んでもうとなると、とってもエンジニがが手間です。

DropBoxやGoogle photo flicker等も試しましたが、サイズや作業的にイマイチだったので、

ホスティング先を自分で用意し、

誰もが使い慣れているLineで画像がUPできて、ホスティング先を返して上げれば楽だな。と思い作ることにしました。


ついでに解析もしちゃう

流れとしては、

  1. Lineに画像送信
  2. Webhookより、API GatewayからLambdaを呼ぶ
  3. 送られてきた画像をS3にUP
  4. Hosting先URLをLineで返却

ですが、せっかくS3に画像上げたんなら、前から試したかったRekognitionで、画像を解析し、ラベルにより、画像のキーワードを抽出するようにしました。

そして最後に、抽出されたキーワードをTranslateで日本語にし、返してあげるという流れになります。


構成

server


投稿側

  1. LINEに画像送信
  2. LINE Messaging APIのwebhookにて、APIGatewayを通じて、Lambdaへ
  3. 送られてきた画像を取得し、S3へUPしそのURLを取得
  4. S3にアップされた画像をRekognitionで画像解析
  5. 英語でラベルを取得
  6. Rekognitionで取得したラベルを Translatteへ
  7. ラベルを日本語で取得

閲覧側

こっちはよくあるサーバーレスの構成ですね。

  1. UPされた画像URLへアクセス
  2. Route53からCloudfrontへ
  3. CloudfrontからS3へ画像を取得
  4. 返却
  5. 返却
  6. 画像表示

この組み方は、

サーバレス AWS s3 + cloudfront + route53 + ACM で静的website ホスティング のメモ

に詳しく記載してます。


準備

まずは、下記のページより、

  • Line Developer登録し、Messaging APIを使える状況に整える
  • AWS S3やLambda、API Gatewayの準備

を進めます。

Line Messaging API で Line Bot を作ってみた | AWS lambda


ちょっと手こずった部分

LINEから送られてきた画像取得がサクッとできなかった

const getLineImage = ("メッセージIDです") => {
  return new Promise((resolve, reject) => {
    client.getMessageContent(id)
      .then((stream) => {
        var data = []
        stream.on('data', (chunk) => {
          data.push(new Buffer.from(chunk))
        })
        stream.on('error', (error) => {
          debug("Error: line image. " + error)
          reject(error)
        })
        stream.on('end', () => {
          debug("Success: get line image.")
          resolve(data)
        })
      })
  })
}

コール時は、awaitで呼びますね。

途中経過で処理が必要なので、Promiseをでさらにラッピングしました。

リファレンス ▶ Line Developer

参考 ▶ LINE BOTに送った画像をNode.jsで受信して保存するサンプル | Qiita


LINEで取得した画像をS3へUP

const AWS = require('aws-sdk')
AWS.config.region = 'リージョン'

const s3 = new AWS.S3({ apiVersion: 'xxxxxxxxxxxxx' })

// put image for s3 bucket.
// put image for s3 bucket.
const putS3 = async (data, id) => {
  debug(data)
  var params = {
    Bucket: process.env.S3_BUCKET,
    Key: "ファイル名",
    ContentType: 'ファイルフォーマット', // ここないと、webでアクセスしても表示できず、ダンロードされちゃう
    Body: Buffer.concat(data)
  }

  return await s3.putObject(params).promise()
}

await で呼び出せばOKです。

参考 ▶ JavaScript Promises の使用 | AWS ドキュメント


Rekognitionへの処理

// get label of images from rekognition
const getImageLabelFromRekognition = (id) => {
  return new Promise((resolve, reject) => {
    const params = {
      Image: {
        S3Object: {
          Bucket: "バケット名",
          Name: "UPした画像のファイル名",
        },
      },
      MaxLabels: 30 // 最大取得件数
    }
    rekognition.detectLabels(params).promise()
      .then((data) => {
        var list = []
        data.Labels.forEach(label => {
          list.push(label.Name)
          debug(`Label:      ${label.Name}`)
          debug(`Confidence: ${label.Confidence}`)
          debug("Instances:")
          label.Instances.forEach(instance => {
            let box = instance.BoundingBox
            debug("  Bounding box:")
            debug(`    Top:        ${box.Top}`)
            debug(`    Left:       ${box.Left}`)
            debug(`    Width:      ${box.Width}`)
            debug(`    Height:     ${box.Height}`)
            debug(`  Confidence: ${instance.Confidence}`)
          })
          debug("Parents:")
          label.Parents.forEach(parent => {
            debug(`  ${parent.Name}`)
          })
          debug("------------")
        })
        debug("success: rekognition")
        resolve(list)  // キーワードを配列で返した
      }).catch((error) => {
        debug("error: rekognition. " + error)
        reject(error)
      })
  })
}

S3から画像を取得して、渡せば可能です。ほぼドキュメント通りですね。

イメージ内のラベルの検出 | AWS ドキュメント


Translateの処理

先程の配列のデータを連結し、文字列にし、一気に渡してます。 ソッチのほうがレスポンス早そうな気がするので。

const getTranslate = async (text) => {
  let params = {
    Text: text,
    SourceLanguageCode: 'en',
    TargetLanguageCode: 'ja',
  }
  return await translate.translateText(params).promise()
}

まとめ

promise連打なので、promiseやawait async部分についてしっかり理解しておいたほうが良さそうですね。 Lineの画像の取得部分が少し手こずりましたが

なんとかなりました。

このあと、サービス化に向けて、LINE PAYの導入方法とか調べてみようと思います。

何かあればTwitterでどうぞ!

うかい / 株式会社UKAI
うかい@代表取締役兼エンジニア株式会社UKAI
Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。詳細はこちらから

🙏よかったらシェアお願いします🙏
WRITTEN BY
うかい / 株式会社UKAI
うかい@代表取締役兼エンジニア株式会社UKAI

Nobuyuki Ukai

株式会社UKAI 代表取締役CEO。建築を専攻していたが、新卒でYahoo!Japanにエンジニアとして入社。その後、転職、脱サラ、フリーランスエンジニアを経て、田舎へ移住し、ゲストハウスの開業、法人成り。ゲストハウス2軒、焼肉店、カフェ、食品製造業を運営しています。詳細はこちらから

CONACT
入力して下さい
Slack からもどうぞ

※お気軽にどうぞ!(6月20日まで有効!お早めに)

COPYRIGHT © 2020 UKAI CO., LTD. ALL RIGHTS RESERVED.