Cheese!

https://github.com/ITF-Death-March-Circle

C++

Go

Nuxt.js

Azure

オンラインでも皆で集合写真!

notch_man

推しアイデア

オタクの夏休みは24時間引き籠もり生活! このままでは夏休みの想い出に華がない! そこで!私たちはオンラインでも外で集合写真が撮れるサービスを作っちゃいました! さあ、皆で揃ってハイチーズ! (撮る相手が居ない悲しいオタクは居ないと信じて)

作った背景

いつも通りのフロント・バックエンドの実装だけでは面白くないので少し凝った実装をやりたかったお気持ちがありました。ということで、貧弱サーバーと相性の悪そうな画像処理を取り入れたアイデアを実装したく今回の提案に至りました。

推し技術

OpenCVをPythonで扱っている姿を観測したnotch_manは腹がたったので強い意思を持ってC++で実装した。このためにC++が動くDockerfileを作ったり色々頑張った! 画像処理は重たい処理ですがあれこれ頑張って高速化しました。

プロジェクト詳細

High Cheese

コンセプト

上で書いた通りですが、オンラインでも外に出た気分で記念写真を撮ることが出来ます!

遊び方

  1. https://cheese-itf.azurewebsites.net/ にアクセス!
  2. 写真を撮影する
  3. 結果が表示される!

おまけ要素として、背景を投票するボタンを作りました。 皆さんの撮りたい情景に合わせてお好きな物を選んで下さい! ちなみに、特定の条件を満たすとスペシャルな背景になるかも???

※セキュリティ対策として本番環境にtokenを埋め込んで居ます。これは外部に漏らさないようにお願いします。

主な技術

このプロダクトで使用した主な技術は以下の通りです。

  • Go
  • Nuxt.js
  • OpenCV(C++)
  • Docker
  • Azure

Goは基幹APIの実装、Nuxtはフロントで用いています。画像処理はC++なOpenCVで行っています。 APIとフロントはDocker化しており、AzureAppServiceを用いてデプロイしています。 OpenCVのDockerファイルはOpenCVのビルドから行う必要があるので通常だと3時間近くビルドに時間がかかります。 そこで全部で4つのステージにビルドを分け、内3つのビルドを完了したイメージを事前にDockerHubにpushすることで大幅なビルド時間の短縮に成功しました。

フロントエンド

フロントエンドの技術概要は以下の通りです。

  • Nuxt.js
  • SSR
  • TypeScript
  • TailwindCSS
  • fortawesome
  • CompositionAPI
  • Express
  • axios
  • moment

フロントエンドフレームワークとしてNuxt.js(TypeScript環境)を利用しました。これは担当のニッシー☆がフロントエンドへの理解を深めたいという理由で使っています。 スタイリングのためにTailwindCSSを用いています。 本番環境でSSRを実現させるためにWebサーバーとしてExpressを使用し、その中でNuxtアプリを読み込んで実行させています。 そのためのNodeランタイムを用意するために先述のAzureAppServiceにデプロイしています。 カメラの利用をブラウザ上で許可させるために本番環境はSSL化を意識して構築しています。

バックエンド

バックエンドの技術概要は以下の通りです。

  • Gin
  • Go
  • Redis
  • HTTP通信
  • WebSocket通信

Go言語製バックエンドフレームワークのGinを使用してアプリ用のAPIを開発しました。 HTTP通信とWebSocket通信の2つの通信技術を利用しています。HTTP通信は画像のアップロードとダウンロード、WebSocket通信は背景画像の投票機能にて用いています。 こちらもインフラはAzureAppServiceのコンテナ基盤を用いています。

インフラ

インフラ環境の構成図は以下の通りです。NodeランタイムやDocker基盤を搭載したAzureAppServiceをそれぞれ立てました。 CI/CDパイプラインをGitHubActionsで構成しており、mainブランチにpushするだけでデプロイが完了します。 投票機能で用いた値等はAzureCacheForRedisで永続化させています。

Cheeseインフラ構成図

画像処理

顔写真の合成はOpenCVで行っています。 顔抽出処理のステップは以下の通りになっています。

  1. 秘伝の調整技術で会場の照明条件に合わせて画像を補正
  2. カスケード分類器を使って顔のおおよその位置をクリッピング

さらに当初の実装では以下の事を行おうと試みました。

  1. HSV空間で肌色領域の抽出
  2. 黒色領域(髪の毛を想定)
  3. 3,4のステップ和集合から顔のみを抽出
  4. その画像を合成

ただ、これについてはステップ5の2画像の合成でOpenCVで使われるcv::Matの仕様の問題で2つのcv::Matの和集合を構成することが非常に困難でありここで挫折しました。これを自由にやるためには自炊画像処理ライブラリを実装するのが手っ取り早いですが、処理速度ではOpenCVに負けてしまうので実装放棄しました。cv::Matは〇ソです。 そのため2までのステップで背景が多少入っている画像から良い感じに味付けをして合成を行うアプローチを取りました。

下の写真のように3~6のステップを色々頑張ったら顔領域をほぼ抽出できたのですが、その合成だけ上手くいかなかったのでそこが悔やまれる点です。

aaaaa.PNG

続いて、アップロードされた画像から顔領域を抜き出し、背景テンプレートにはめていきます。この処理手順は以下の通りになっています。

  1. 背景画像のサイズに応じて顔写真の配置マップを作成
  2. 乱数を生成して顔の配置場所を算出
  3. 配置エリアに収まるよう画像をリサイズ
  4. 背景のフレームを描画
  5. 顔写真を描画

最初は横並びで画像を作っていましたが、ランダム配置の方が面白みがあったのでこのようなアルゴリズムを実装しました。ちなみにcv::warpAffineを使った画像の合成を行うことで、画像領域外への書き込みが発生した場合は無視されるのでUnsafeな実装にならずに済むメリットがあります。

このように生成された画像を出力して顔写真の合成は完了します。

notch_man

@notch_man8600