💍

結婚式の余興を技術で彩る:AWS × LINE Botによる表情判定システムの開発で学んだこと

2024/12/19に公開

株式会社Sun Asterisk でバックエンドエンジニアをしている三浦です。
昨年に続き、2024年もアドベントカレンダーに参加させていただきました!

🖊️昨年のアドベントカレンダーの記事はこちら🖊️
https://zenn.dev/sun_asterisk/articles/b383de63847298

概要

LINE公式アカウントから送った写真を Amazon Rekognition で表情分析をして幸せ度を数値化するというシステムを作り、その幸せ度を競うコンテストを結婚式の余興として行いました💒

本記事では、開発する際に工夫した点や苦労した点、学んだ点などをまとめています。
Rekognitionを使ったパブリックなアプリケーションを動かすために利用したLambdaやS3といったAWSリソースについての内容がメインとなりますが、具体的な実装については参考記事通りなので割愛しています。

おまけとして開発にかかった総費用などの小話も載せていますので、ぜひ最後まで読んでいただけますと幸いです!


ゲストの卓上に用意した案内。ゲストの皆様に使ってもらうためにLINEアプリをインターフェースにして案内もわかりやすく、余興に参加しやすいようにしました!

背景

ちょうど先月の11月に自身の結婚式があったのですが、せっかくなのでエンジニアっぽい余興をしたいと思い作るに至りました。
参考にさせていただいた記事はこちらです。ありがとうございます☺️

https://qiita.com/ymd65536/items/f1d50596cdd1d76d1c7b

以下、個人的に特に良いと感じたポイントです。

  • インターフェースとしてLINEを使う
    • 参加者が気軽に参加できる(写真を送るだけで参加できる)
    • 画面の開発などの手間が省ける
  • サーバーレスサービスを使う
    • 短期間で簡単に構築できる
  • AWS LambdaやAmazon Rekognitionなど、触ったことがないAWSのサービスが含まれていた(個人的興味)

使用したサービス

参考記事と同じで、特に変更した点はありません。
S3、CloudWatch以外は初めて触るサービスばかりでした。


構成図

Amazon Rekognitionとは

Amazon Rekognition(以下、Rekognition)は機械学習を利用した画像や動画の分析を簡単に実施できるAWSのサービスです。
今回はその中でも顔検出APIとして提供されているDetectFacesを利用しました。

https://docs.aws.amazon.com/rekognition/latest/APIReference/API_DetectFaces.html

工夫したこと

まずは開発面で工夫したことからのご紹介です。
ざっくり以下の3点について工夫をしました。

  • ① Lambdaのバージョン管理機能の利用
  • ② 特定のワードを送ることで現在のランキングを取得できる機能を追加
  • ③ Rekognitionでの分析時に重み付けのようなことができなかったため、結果取得後に点数の調整ロジックを追加

① Lambdaのバージョン管理機能

Lambdaは使ったことがなかったのですが、とりあえずコードをデプロイし動作検証をしていたところ、途中でバージョン管理が必要だと感じました。

Lambdaの開発も一般的な開発と同じくGitHubなどを利用するのかなとなんとなく思いつつ調べていたところ、Lambdaにはバージョン管理機能があるということを知りました。

よくよく考えるとこのサービスは短期的にしか使用しないものであり、一度安定版が完成したらそれ以降は変更することはありません。
そのためわざわざGitHubで管理するほどでもないと考え、Lambdaのバージョン管理機能を使ってバージョン管理をすることにしました。

Lambdaのバージョン管理機能はGitHubとは異なり、バージョニングは基本的には直線の形で更新されていきます。
過去のバージョンについてはエイリアスという機能を使うことで特定のバージョンに別名を与えることができるため、安定版にエイリアスをつけることで、開発中のバージョンと安定版を簡単に切り替えることができます。

バージョン管理とエイリアスについてはこちらの記事が参考になりました。

https://zenn.dev/amarelo_n24/articles/158a166d832487

② ランキング集計機能

無事参考元の記事通りに実装ができたのですが、テストをしていた段階でランキング集計機能が欲しいと感じました。

別途リアルタイムのランキングが見れる画面を作るか悩みましたが、LINE上で全て完結させたかったため、「結果発表」という特定のワードを送ることで現在のランキングを取得できるような処理を追加しました。

ランキング取得の際の注意点としては、ゲストの方1人につき複数写真を送る可能性があるため、

  • ユーザーでグルーピング
  • その中で最も高いスコアを取得

という処理内容にしました。

次の点数の調整部分でも触れますが、同率順位が発生する可能性もあると考え、5位までのランキングを取得するようにもしました。
(テストした結果、5位くらいまでを取得すれば重複する可能性が低そうだと判断。それでも被ってしまった場合はジャンケンをしてもらうことを想定)

実装自体は上記の通り実現するだけで特に難しいことはしていないため省略します。

③ 点数の調整

割と頭を悩ませたのがこちらの部分です。
Rekognitionでの表情分析はパラメータなどによって点数を調整するといったことができず、はっきりとした笑顔の写真であれば割と簡単に満点(Confidentialが100)が出てしまうため、点数の調整が必要でした。

同率が出過ぎるとジャンケンの参加者が多くなってしまい時間がかかりそうだったため、なるべく同率が出ないように点数調整のロジックを工夫しました。

以下のことを意識しつつ、何度もテストを行い調整をしました。

  • HAPPYをベースにしつつ、ANGRYSADなど、相反する感情のスコアについてはHAPPYから差し引く
  • ただしお祝いの場なのでマイナスの点数にはならないように最低点数の考慮

ちなみに、Rekognitionで検出できる感情のタイプは下記の8つです。

HAPPY:幸せ
SURPRISED:驚き
CONFUSED:困惑
ANGRY:怒り
DISGUSTED:うんざり
CALM:穏やか
FEAR:恐れ
SAD:悲しみ

それぞれの感情のタイプごとに信頼度(Confidential)が返されます(参考)。
HAPPYだけ抽出しますが、このような感じです。

"Emotions": [
  {
    "Type": "HAPPY",
    "Confidence": 2.2830512523651123
  }
],

詰まったポイント・学んだこと

次に、開発中に詰まったポイントや学んだことについてご紹介します。

AWSを使って実際に動くものを作った経験がほとんどないため、いくつか詰まるポイントがありました。その過程で学んだことを以下3点に絞ってご紹介します。

  • ① Lambdaのレイヤー追加
  • ② LambdaとS3バケットのリージョン
  • ③ アクセス管理

① Lambdaのレイヤー追加

手順通りに実装を進めていたところ、LINEのWebhookの設定で疎通確認をした際に502エラーが返ってきました。
CloudWatchのログには以下のエラーが出ていました。

[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'requests'

解決方法:Lambdaのレイヤーを利用して'requests'ライブラリを追加

Pythonの標準ライブラリであるrequestであればimport requestするだけで良いようですが、今回はrequestsという外部モジュールを使っているためライブラリの追加が必要でした。
requestsはLambdaでAPIリクエストするために使用)

その際、Lambdaのレイヤーという仕組みを使ってrequestsを追加しました。
レイヤーを使えば、複数のLambda関数でライブラリを共有することができます。

どのような時にレイヤーを使うべきかについてはAWSのドキュメントで以下のように説明されています。
今回のケースでは、主に3点目の依存関係の共有の目的でレイヤーを採用しました。

レイヤーの使用を検討する理由は複数あります。

  • デプロイパッケージのサイズを小さくするため。 関数コードとともにすべての関数依存関係をデプロイパッケージに含める代わりに、レイヤーに配置します。これにより、デプロイパッケージは小さく整理された状態に保たれます。
  • コア関数ロジックを依存関係から分離するため。 レイヤーを使用すると、関数コードと独立して関数の依存関係を更新でき、その逆も可能となります。これにより、関心事の分離が促進され、関数ロジックに集中することができます。
  • 複数の関数間で依存関係を共有するため。 レイヤーを作成したら、それをアカウント内の任意の数の関数に適用できます。レイヤーがない場合、個々のデプロイパッケージに同じ依存関係を含める必要があります。
  • Lambda コンソールのコードエディターを使用するため。 コードエディターは、関数コードの軽微な更新をすばやくテストするのに便利なツールです。ただし、デプロイパッケージのサイズが大きすぎる場合は、エディターを使用できません。レイヤーを使用すると、パッケージのサイズが小さくなり、コードエディターの使用制限を解除できます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/chapter-layers.html

実際のレイヤーの追加手順については、こちらの記事を参考に行いました。
https://qiita.com/rapirapi/items/faf18994fcc69a1136bf

より詳しく解説している記事もありますので必要に応じて参考にしてみてください。

ちなみにレイヤーはほいほいといくつも追加できるわけではなく、最大5つまでという制約があるため注意が必要そうです。

② LambdaとS3バケットのリージョン

いざLambdaでトリガーを追加しようという時にわかったのですが、LambdaとS3のバケットのリージョンが違うとトリガーの追加ができないという問題に直面しました。

バケットを作り直さないといけないのと、IAMポリシーなどを修正しないといけないので少し手間でした。
(てっきりバケットもap-northeast-1で作成していたと思っていたのですが、誤って違うリージョンで作成していたようです...)


実際に出たエラーの内容

ちなみに直接トリガーすることはできませんが、調べたところSNSとSQSを組み合わせることで異なるAWSリージョンでも処理ができる方法がありました👌
いつかこのような要件に遭遇する可能性もありますので、頭の片隅に置いておこうと思います。

こちらが参考記事です。
https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/subscribe-a-lambda-function-to-event-notifications-from-s3-buckets-in-different-aws-regions.html

③ アクセス管理

IAMはJSON形式で明示的に許可を設定していくため理解がしやすかったのですが、
少しややこしく感じたのが、S3のアクセスコントロールです。

S3についてはIAMだけでなくブロックパブリックアクセスやバケットポリシー、ACLが登場するため、それぞれどう設定するのが適切なのかと悩んだ部分だったので整理しました。

ブロックパブリックアクセス

ブロックパブリックアクセスは、バケット単位でのパブリックアクセスをブロックするための設定です(パブリックアクセス = AWS認証情報がないアクセス)。

基本的にはセキュリティを考慮しバケットのブロックパブリックアクセス設定は無効にするのが推奨されていますが、静的ウェブサイトを公開する場合などは有効にする必要があります。

今回は表情分析の結果がある一定の点数を超えた際に以下ように新郎新婦の写真を返すという仕組みを用意していたのですが、
その画像をLINE上で表示できるようにするためS3に置いた画像に対しパブリックアクセスを許可する必要がありました。

通常の結果 一定以上の点数の結果
S3に配置した画像を表示

また、これは本記事の執筆時に知ったのですが、以下の参考記事のようにCloudFrontを経由してS3のパブリックアクセスのブロックを有効にしたまま公開する方法もあるようでした。
今回のケースにおいてはそこまでケアする必要はなかったですが、静的ウェブサイトを公開する際などには参考になるかもしれません。

https://dev.classmethod.jp/articles/cloudfront-s3web/

バケットポリシー

バケットポリシーはリソースベースのポリシー(=リソース側で一元管理ができる)で、バケット単位でのアクセス制御ができます。
IAMポリシーとの使い分けとしては、IPアドレスによる制限などより細かい制御をしたい場合にバケットポリシーを使うと良いようです。

両者を併用する場合、IAMポリシーとバケットポリシーの評価論理には注意が必要となります。
IAMポリシーもしくはバケットポリシーのどちらかが許可しているとアクセスに成功しますが、
どちらかの許可があっても明示的な拒否があるとアクセスは失敗するためです。

https://dev.classmethod.jp/articles/s3-iam-policy-kanzennirikaishita/

ACL

ACLはバケットとオブジェクトへのアクセスを管理するものですが、基本的にはACLは無効にしておくことが推奨されています(そのためデフォルトで無効化が選択されている)。
中にはACLを有効化にするケースもあるようですが、特別な場合なので基本的には推奨されている無効化の設定のままで良いでしょう。

おまけ:余興の結果と開発総費用

さて、最後に余興の結果と開発総費用についてご紹介します。

LINE公式アカウント自体は元々結婚式のアナウンスで使用するという目的もあったため、ゲストの方々には事前に案内して1ヶ月くらい前から登録をお願いしていました。

コンテストの案内自体は当日行い、会場の卓上にコンテストの案内を置くなどして写真を送ってもらうように促しました。

余興の結果

当日集まった写真の枚数:48枚

自分たちを含めて全体の参加者が60名ほどだったので、無事コンテストができるくらいの枚数が集まり一安心でした😮‍💨
(正直あまり集まらなかったらどうしようという不安がありました)

当日は特に使いづらいといった声もなく、LINEをインターフェースとして使ったことにより気軽に参加していただけたのかなと思います。
後日、飲み会などでも使えるのでクローズしないでというリクエストもいただけたので、UX的にも合格点だったのではないでしょうか!

また、上記で紹介した工夫した点は以下のような結果となりました。

  • ランキング集計機能
    →特に問題はありませんでした。期待通りに動作しました。

  • 点数の調整(重み付け)
    →同率が3名出てしまいましたが、もともと100%は防げないとは考えていたため想定通りジャンケンをしてもらいました✊

開発総費用

AWSについては無料枠があるので費用の負担はほとんどないようなものでした。
一番高かったのはLINEの費用でしたが、アナウンスなどと兼用していたため許容範囲内でした。

項目 費用
AWS:開発費用(9月〜10月) 211.5円
AWS:結婚式当月の費用(11月) 121円
LINE公式アカウント 11,000円(5,500円/月)

以下11月の請求書ですが、無料枠のおかげもありほぼほぼCloudWatchのコストが占める形となりました。

11月の請求書


さいごに

まずは余興として無事に進行でき、ゲストの皆様には楽しんでいただけて何よりでした。
笑顔の写真を沢山送って下さったので、笑顔溢れる楽しいイベントとなり余興としては成功だったと思います!

大半が初めて触るサービスばかりで不安もありましたが、楽しく開発しつつ学ぶことができたので良かったです。

エンジニアのスキルを活かして結婚式を彩ることができ大満足の結果となりました🧑‍💻

明日はAsuto Masudaさんの『SESのバウンスレートを下げた話』です、お楽しみに!
それでは、最後までお読みいただきありがとうございました!!

Sun* Developers

Discussion