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

目次

line


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 ホスティング のメモ

今更ですが、サーバーを使用せず、node環境でコンパイルした静的ファイルを、AWS上で公開する方法のメモです。 静的コンテンツの配信性能を高めるために、cloudfront経由でs3にアクセスする正しい方法と、route53でドメイン管理、ACMでhttps対応をするなど、サーバーレスでのAWS上のwebサイトの公開方法をメモしておきます。 .....

に詳しく記載してます。


準備

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

を進めます。

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

Twitterをやっていて、いろんなエンジニアを見て触発されたので、自分の事業にためになるものを!と思い、ほとんどの人が使っているLineをつかって何かできないかと考えていたところ、焼肉店でどの部位を食べればいいかわからない時に、提案してくれるBotを作成してみました。 .....


ちょっと手こずった部分


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 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でどうぞ!

U-chan ( Nobuyuki Ukai )

学生時代は建築やデザインを専攻していたが、Yahoo!Japanにエンジニアとして運良く入社し、2年半で波情報を配信する波伝説に転職。3年後、Yahoo!時代の先輩の立ち上げたベンチャーに転職。数年後、伊豆下田に移住し、ゲストハウスを開業しながらリモートでエンジニアを続けたが、焼肉店の開業とともに株式会社UKAIを立ち上げ、法人成り。その後、カフェとゲストハウスをもう一軒開業し、現在は焼肉店、カフェ、ゲストハウス2件目を運営。今季は自社Webサイトの立ち上げ予定!

comments powered by Disqus