推しアイデア
オタクの夏休みは24時間引き籠もり生活! このままでは夏休みの想い出に華がない! そこで!私たちはオンラインでも外で集合写真が撮れるサービスを作っちゃいました! さあ、皆で揃ってハイチーズ! (撮る相手が居ない悲しいオタクは居ないと信じて)
オタクの夏休みは24時間引き籠もり生活! このままでは夏休みの想い出に華がない! そこで!私たちはオンラインでも外で集合写真が撮れるサービスを作っちゃいました! さあ、皆で揃ってハイチーズ! (撮る相手が居ない悲しいオタクは居ないと信じて)
いつも通りのフロント・バックエンドの実装だけでは面白くないので少し凝った実装をやりたかったお気持ちがありました。ということで、貧弱サーバーと相性の悪そうな画像処理を取り入れたアイデアを実装したく今回の提案に至りました。
OpenCVをPythonで扱っている姿を観測したnotch_manは腹がたったので強い意思を持ってC++で実装した。このためにC++が動くDockerfileを作ったり色々頑張った! 画像処理は重たい処理ですがあれこれ頑張って高速化しました。
上で書いた通りですが、オンラインでも外に出た気分で記念写真を撮ることが出来ます!
おまけ要素として、背景を投票するボタンを作りました。 皆さんの撮りたい情景に合わせてお好きな物を選んで下さい! ちなみに、特定の条件を満たすとスペシャルな背景になるかも???
※セキュリティ対策として本番環境にtokenを埋め込んで居ます。これは外部に漏らさないようにお願いします。
このプロダクトで使用した主な技術は以下の通りです。
Goは基幹APIの実装、Nuxtはフロントで用いています。画像処理はC++なOpenCVで行っています。 APIとフロントはDocker化しており、AzureAppServiceを用いてデプロイしています。 OpenCVのDockerファイルはOpenCVのビルドから行う必要があるので通常だと3時間近くビルドに時間がかかります。 そこで全部で4つのステージにビルドを分け、内3つのビルドを完了したイメージを事前にDockerHubにpushすることで大幅なビルド時間の短縮に成功しました。
フロントエンドの技術概要は以下の通りです。
フロントエンドフレームワークとしてNuxt.js(TypeScript環境)を利用しました。これは担当のニッシー☆がフロントエンドへの理解を深めたいという理由で使っています。 スタイリングのためにTailwindCSSを用いています。 本番環境でSSRを実現させるためにWebサーバーとしてExpressを使用し、その中でNuxtアプリを読み込んで実行させています。 そのためのNodeランタイムを用意するために先述のAzureAppServiceにデプロイしています。 カメラの利用をブラウザ上で許可させるために本番環境はSSL化を意識して構築しています。
バックエンドの技術概要は以下の通りです。
Go言語製バックエンドフレームワークのGinを使用してアプリ用のAPIを開発しました。 HTTP通信とWebSocket通信の2つの通信技術を利用しています。HTTP通信は画像のアップロードとダウンロード、WebSocket通信は背景画像の投票機能にて用いています。 こちらもインフラはAzureAppServiceのコンテナ基盤を用いています。
インフラ環境の構成図は以下の通りです。NodeランタイムやDocker基盤を搭載したAzureAppServiceをそれぞれ立てました。 CI/CDパイプラインをGitHubActionsで構成しており、mainブランチにpushするだけでデプロイが完了します。 投票機能で用いた値等はAzureCacheForRedisで永続化させています。
顔写真の合成はOpenCVで行っています。 顔抽出処理のステップは以下の通りになっています。
さらに当初の実装では以下の事を行おうと試みました。
ただ、これについてはステップ5の2画像の合成でOpenCVで使われるcv::Matの仕様の問題で2つのcv::Matの和集合を構成することが非常に困難でありここで挫折しました。これを自由にやるためには自炊画像処理ライブラリを実装するのが手っ取り早いですが、処理速度ではOpenCVに負けてしまうので実装放棄しました。cv::Matは〇ソです。 そのため2までのステップで背景が多少入っている画像から良い感じに味付けをして合成を行うアプローチを取りました。
下の写真のように3~6のステップを色々頑張ったら顔領域をほぼ抽出できたのですが、その合成だけ上手くいかなかったのでそこが悔やまれる点です。
続いて、アップロードされた画像から顔領域を抜き出し、背景テンプレートにはめていきます。この処理手順は以下の通りになっています。
最初は横並びで画像を作っていましたが、ランダム配置の方が面白みがあったのでこのようなアルゴリズムを実装しました。ちなみにcv::warpAffineを使った画像の合成を行うことで、画像領域外への書き込みが発生した場合は無視されるのでUnsafeな実装にならずに済むメリットがあります。
このように生成された画像を出力して顔写真の合成は完了します。