バスキュール技術ブログ

バスキュールが得意とするインタラクティブエンジニアリングを、あますことなくお届け!

Detectronでおしゃ文字をつくる

エンジニアの丸山(@maruware)です。

1年ほど前にデイリーポータルZで「現実化したフォント」がおしゃれなのでマスターしたい :: デイリーポータルZ という記事が掲載されていました。

oshamoji

こういうの(記事内より引用)

おしゃれですね。記事内ではこれを"おしゃ文字"と名付けていました。

一方でDeep Learning的な研究の進歩が著しい昨今、セマンティックセグメンテーション(タグ付きのマスクを検出するようなもの)の分野も盛んに研究されていて、Mask R-CNNなどすごい精度が上がってきています。

(Detectronより引用)

(matterport/Mask_RCNN より引用)

これがあればおしゃ文字が自動でつくれるんじゃないか、ということで

先日、NIHONBASHI SAKURA FESTIVALというイベントの中のPhoto Sessionという枠で「写真家が現地で記念撮影をしてくれて、それをおしゃ文字化する」という形で実施してみたので紹介します。

Detectronを使う

DetectronはfacebookがOSSとして公開しているセマンティックセグメンテーション用のライブラリです。あまりライブラリとしてのAPIやドキュメントは整備されていないためコードを追っていく必要があります。

(記事の最後におしゃ文字部分とまとめてjupyter notebookのサンプルのリンクを記載しました)

検出処理を実行するとバウンディングボックス、セグメント、クラス(物体のカテゴリ)が得られます。

モデルの一覧はMODEL_ZOO.mdにあり、学習済みのモデルデータも提供されています。今回はいくつか試した結果、End-to-End Faster & Mask R-CNN BaselinesのR-101-FPN 2x を使用しました。

検出処理の時間はGTX 1070やK80を積んだマシンで0.2〜0.3秒くらいでした。初回実行時のみ2〜3秒ほどかかります。

おしゃ文字をつくる

今回はイベントの趣旨から、主に記念撮影の被写体である人間を前景としてマスクのターゲットにします。

ざっくりした流れとしては

  1. Detectronで検出
  2. 検出結果のリストから人間クラスのマスクのみ抽出
  3. 元画像からマスクで抜いた前景画像を作成
  4. 文字を描画したテキスト画像を作成
  5. 元画像、テキスト画像、前景画像と重ねて合成

というものになります。

描画まわりはpythonのImageMagick bindingライブラリのwandを使用しました。最初はPillowを使っていましたが、カーニングを調整することができないようなのでwandに移行しました。

アプリケーションとして実装する上で得られたTipsを以下で紹介していきます。

Tips 1. 後ろの方にいる被写体ではない人を除外したい

f:id:bascule-dev:20190405202101j:plain 例えばこちらのような開けた場所では後ろの方に小さく人が写ります。文字の上に重なるとレイヤー感がおかしく見え、不具合のように見えます。

この問題を解決するために、

  1. 面積で閾値処理: 1番大きく検出されたバウンディングボックスの面積との比率で判定
  2. Y座標で閾値処理: バウンディングボックスの下端を比較して判定
  3. 深度推定をして閾値処理: 別途、2Dによる深度推定を行って判定

といった手法を検討しました。

1は子供連れの方で誤る可能性、3は試したもののまだ現実的ではなさそう、ということで2を採用しました。

ただし、幼児を抱いた状態で幼児が除外される、などが起きる懸念があります。

Tips 2. 被写体が持つ手荷物もいっしょに抜きたい

f:id:bascule-dev:20190405202123j:plain いい例がなかったのでフリー素材からですが(つまりレアケースとも言えそうですが)、被写体がかばんを持っていてその上に文字が重なると不具合のように見えます。

Detectron(正しくはデータセットのcoco)ではハンドバッグも対象クラスになっているので、「被写体と見なした人のセグメントと交差するハンドバッグのセグメントはマスクに含む」と判定をすることで対応しました。

f:id:bascule-dev:20190405202149j:plain

今回の場合は事前に手荷物を預かるようなオペレーションもできましたが、一応かばんを持っていても抜けるように対応しました。

Tips 3. 雨が降って傘を持っている可能性がある。傘も抜きたい。

今回は屋外で撮影が行われるため、当日に雨が降り傘をさしている可能性があります。傘もかばんと同様に人と同じレイヤーに当たるので抜き出します。

幸い、傘もDetectronの対象クラスなので検出されます。

ただ、

  • 傘は人をくり抜いたような形のセグメントになり、ときどき交差しないことがある
  • 後ろの人の傘が被写体の人と交差することがある

ということでかばんと同様の交差判定では対応できませんでした。

f:id:bascule-dev:20190405202254p:plain

そこで、基本的には傘は頭を覆うように写るため、「バウンディングボックス内に被写体の上端を含む傘」を対象とするようにしました。

ヒューリスティックであまりイケてないですが、雨天はイレギュラーということでお茶を濁すことにしました。

その他

文字組みについて

デザイナーと相談ながら調整した結果

  • 文字の30%が人に重なっているくらいがちょうどよい
  • 文字のサイズは基本は画像の横幅いっぱいに大きく表示するのが良い
  • 1〜2文字の場合は画面幅合わせだと大きすぎるので調整
  • 画として美しい文字数は3 or 4文字。6文字までがきれいに見える限界。

という結論になりました。

結果

マスクを人力で用意するのはしんどいので定量的な評価はできておらず、目視での評価になりますが、概ね違和感なくおしゃ文字することができました。

ただ、いくつか課題が出ました。

2列に並んで撮影するとTips 1で行った処理で後列が除外される

f:id:bascule-dev:20190405202327j:plain

f:id:bascule-dev:20190405202548j:plain

足まで写っていないときにセグメントが上半身のみになってしまい、除外されてしまいました。2列に並ぶのは記念写真ではあるあるだと思うのでもうちょっと工夫が必要そうです。

幼児を抱いているとTips 1で行った処理で幼児が除外される

予想通り、幼児はTips 1の処理で除外されてしまいました。 予想外に幼児を連れた方が多く参加されてて申し訳ない気持ちになりました。

f:id:bascule-dev:20190405202609p:plain f:id:bascule-dev:20190405202630p:plain

また、これは想定外だったのですが、幼児を前で抱えると誤検出が増えることがわかりました。

f:id:bascule-dev:20190405202651p:plain

大人の足が幼児と同一人物として検出されています。他にもこういった例が見られました。cocoの学習データにこういったサンプルが少なさそうなのでこういった例を拾うのはむずかしそうです。

幼児対策として

  • 幼児は横に抱いてもらう
  • 大人は上半身のみで撮影する

とカットで逃げることで対処してもらいました。

f:id:bascule-dev:20190408161515p:plain

写真集

以下、写真集です。

f:id:bascule-dev:20190405202732j:plain

f:id:bascule-dev:20190405202753j:plain

f:id:bascule-dev:20190405203104j:plain

暗くても抜ける(すごい)

f:id:bascule-dev:20190405203125j:plain

おわりに

テクノロジーの無駄遣いという感もありますが、AI的なものをエンターテイメント活用できて面白かったです。記念撮影という形で試せたことも、

  • 写真のコントロールがこちらでできる
  • 枚数も少ない(GPUインスタンスいっぱい立てるとお金がかかる)
  • 1組に複数回の撮影ができるため、失敗してる写真があってもおもしろくらいでごまかせる

といった点でよかったです。

まだ人力でPhotoshopなどで抜くのに及ばない課題はいろいろありますが、撮ったら待たずにすぐに見れるのは体験として良いし連写に対応できるのも長所かなと思います。

そのうちプリクラに載るかもしれないですね。

今回ご協力いただいた写真家の五十嵐絢也さんにはむずかしい要求にも快く応えていただき良いおしゃ文字写真をつくることができました。

jupyter notebook にコードのサンプルもアップしたのでよかったら見てみてください。