Mirrativ tech blog

株式会社ミラティブの開発者(サーバサイドエンジニア,iOSエンジニア,Androidエンジニア,機械学習エンジニア,インフラエンジニア, etc...)によるブログです

Mirrativにおけるプッシュ通知ぼかしへの挑戦

こんにちは Mirrativ CTOの夏です

今回は先日iOSでリリースした通知ぼかし機能について、裏でどういうことをしているのかについて軽く紹介したいと思います。

f:id:hottestseason:20190517204251p:plain

MirrativではOSから提供されるAPI( ReplayKitMediaProjection )を用いて、スマホ画面を直接収録・配信することで、配信用のSDKを各ゲームやアプリに埋め込んでもらう必要がなく、ユーザさんが色々なアプリを気軽に切り替えながら友達と雑談を楽しんだり、ゲームを通じて新しい友達を作ることができます。

その反面、配信者が適切な設定を行っていない場合、意図しないプッシュ通知が視聴者に表示されてしまう場合もあります。

Mirrativでは配信準備画面に注意文言と啓蒙動画を載せていますが、どうしても設定が漏れたりする場合があるため、配信者が注意しなくても、プッシュ通知が視聴者に見えない仕組みを以前から検討していました。

f:id:hottestseason:20190517174205:plain:w300

Android側は NotificationListenerService を利用することで、通知を受信したタイミングで配信を一時的に停止する設定を入れています。

f:id:hottestseason:20190517171341p:plain:w300

iOSに関しては他の配信アプリなども参考に、画面に表示される通知を自動で検知し、通知領域にのみモザイクをかける方針を検討し、 副業の方と整理し、以下のような要件で実装してもらうことになりました。 (本人からの希望で名前はお出ししていませんが、以下の実装詳細の説明などに関しても本人にまとめて頂いたものを流用しています)

  • 配信中に配信者の端末に届いたプッシュ通知 (Remote/Local問わず) にモザイクをかけて配信したい
  • ただしMirrativアプリの通知はぼかさなくて良い
    • 配信へのコメントがぼかされてしまうと、視聴者が「配信者がコメントを読めない」という誤解を招く場合がある
  • 対象は画面上部からスライドインしてくるバナー通知のみ。ひとまず通知センターに表示される通知等はモザイクをかけないで良い
    • 能動的なアクションが必要となる通知は、配信者の注意によって防止できる
  • 同様の理由で、配信者が通知をタップした場合等はモザイクが外れてしまっても良い

つまりこいつです。

f:id:hottestseason:20190510154316p:plain
iOS プッシュ通知

副業の方に実装をお願いする以前、iOS11から利用できるVisionフレームワークの短形検知を利用したプロトタイプを試していたのですが、ゲームなどが動いているバックグラウンドで、OSに一番殺されやすいプロセスの中、さらに配信しながら行う処理であることを考えると、CPU使用率やメモリ使用量などが懸念点となり、汎用的な手法を取るより、プッシュ通知という特定の領域に絞って自前で検知アルゴリズムを実装することになりました。

基本方針の検討

iOSには現状、他アプリからのプッシュ通知イベントをフックする手段が無いため、配信用にサンプルされた画像から通知の存在有無を判定し、その領域を検出する画像処理が必要となります。 通知領域が検出できれば、あとは単純にその領域に対してモザイクをかければよいでしょう。

考えうる方針としては以下があると思われます。

  1. 機械学習的なアプローチ
  2. 非・機械学習的なアプローチ
    1. 既存の汎用的なアルゴリズム
    2. 専用特化したアルゴリズム

1.の機械学習的なアプローチは、背景画像や端末解像度の違い等に対して頑健に作れる、細かな閾値調整等が (2-bと比較して) あまり必要ない、等のメリットはあるものの、一方で学習のために一定量の教師データ (特に正例) が必要となる、デバッグが困難である等のデメリットがあります。

2-a. 既存アルゴリズムは、例えば古典的なCanny法やHough変換などを用いることは可能であり、うまくすれば将来的なUIの変更等に対して非常に頑健に作れる可能性があるものの、基本的に計算量が大きくなるというデメリットがあります。通知ぼかしはゲームアプリ等のプレイ中にも常時動かす処理であるため、計算量は限界まで削減したい所です。

また、これら既存アルゴリズムの対象が主に風景などの自然写真であるのに対して、今回の対象は固定的な通知領域UIであるため、その削減余地は非常に大きいと言えます(エッジの角度を一切気にしないで良い、出現位置を事前に調べることが可能、etc)。

以上から、今回は通知領域検出に特化したルールベースのアプローチを取ります。整理すると、メリットとデメリットは以下となります。

  • メリット
    • 教師データの収集が必要ない (vs 1)
    • 実行時パフォーマンスが高い (vs 2-a)
  • デメリット
    • 閾値等を手動で微調整する必要がある
      • OSアップデートによって通知領域の形状が変更されるなどした場合、閾値を再調整する必要が出てくるかもしれない
    • 端末解像度の違いなどに対して感度が高い
      • 新しい解像度の端末が発売されるために、データを追加する必要がある
      • ただしこの運用コストを多少緩和するアプリを作った。詳細は後述

今回は、メリットに上げた点を優先するためにこれらのデメリットを犠牲にしました。なお、この実装によって正例を自動収集することが可能になるので、それを用いて将来的に機械学習的アプローチに乗り換えていくという方法も考えられますが、収集の仕組みの実装・機能再実装・再QA等のコスト、そして結局UI変更に対しては脆弱であり運用コストをゼロには出来ない点等を考えると、(微妙な所ですが) 現行アプローチの継続に軍配が上がると思われます。

通知領域の検出

通知領域の定義

検出したい通知領域は、例えば以下のような角丸矩形ですが、

f:id:hottestseason:20190510154316p:plain

プッシュ通知は画面上部からスライドインしてくるため、上のような完全な角丸矩形でしか検出できない場合、スライドイン中の通知内容が見えてしまうことになります。よって、以下のような角丸矩形の下部を検出し、その領域を画面最上端まで拡張した領域を検出することにします。

f:id:hottestseason:20190510155649p:plain

ここで言う「領域」(Filled Rectangle) とは、以下2点によって定義できるでしょう。

  1. 外縁がエッジで囲まれている
  2. 内部が塗りつぶされている

これらは機械処理しやすいよう、以下のように換言できます。

  1. 外縁に大きな輝度変化が存在する
  2. 内部に大きな輝度変化が存在しない

f:id:hottestseason:20190510155854p:plain

外縁をまたぐ赤矢印の部分に大きな輝度変化が存在し、内部の青矢印の部分に存在しなければ、領域とみなすことができそうです。矢印の間隔を小さくしていくことで精度は向上しますが、パフォーマンスの関係上、適度に間引くことにします。また、内外どちらも背景色の影響を受ける (特に内部は透かし/ブラーがある) ため、適当な閾値を探す必要があります。

輝度変化の指標

輝度変化を捉える手法はいくつかありますが、ここでは一次微分の分散を用いることにします。具体的には、まず下図中の黄点のように矢印線上のいくつかの点をサンプリングします。

f:id:hottestseason:20190510155954p:plain

その上でRGBそれぞれの一次微分(つまり画素値の差分)を計算し、それらの分散(dv)を下式によって計算します。

image

ここでpiはi番目の画素値、Dは画素間の距離です。dvをRGBそれぞれについて計算し、最大のものを特徴量として使用します。最初にグレースケール化してから計算すると特定ケースにおいて精度が落ちたので、今回はRGBごとに計算するようにしています。

このdv値を、事前に調べておいた閾値と比較し、大きければエッジがあり、小さければエッジが無いと判定することが可能となります。閾値は、外縁・内部ともに2.0が一番精度が高そうでしたが、ここは調整の余地がありますし、将来UIに変更が入った場合も調整する必要が出てくる可能性があります。

検出ロジック

上記のアルゴリズムによって、矢印の両端点が与えられた場合に、それらを繋ぐ直線状にエッジが存在するか否かを判定することが可能となりました。これを用いて、与えられた画像の全領域内に通知領域が存在するか否かを判定していきます。基本的な流れとしては、以下になります。

  1. 矢印の端点群を生成する
  2. 端点群を順に走査し、それぞれエッジ存在判定をする
    • 存在する場合はスコアを加算する
    • bridge(外縁)の場合、スコアは下線部・横線部・角丸部ごとに異なった値を用いる (角丸は通知領域を特徴づける有力な特性であるため強めに評価する。また横線部はそもそもの端点が少ないためこちらも強めに評価する)
    • inner(内部)の場合、スコアは常に1で良い
  3. それぞれのスコアの平均値が基準を満たしているかを、閾値によって判定する
    • 直感的な解釈としては、innerについてはエッジが存在すると判定された矢印の個数の割合。bridgeについてはそれ重み付けを行ったもの
    • つまり、bridgeのスコア平均値が閾値を上回っており、かつinnerのそれが閾値を下回っている場合に、通知領域が存在すると判定する
    • 色々試した結果、bridgeは0.8, innerは0.2で今の所よく検出できている模様

ノックアウト

ある1つの矢印のdv値が既定の閾値を下回った/上回った場合、即座に通知領域が存在しないと判定しbail outします。それぞれ1.0と10.0が効果的でした。これによって、例えば真っ白な画面を判定する際などに、全ての端点ペアを走査することなく即座に検出処理を中止することが可能となり、多少パフォーマンスと検出精度が向上します。

端末ごとの事前情報の調査

上述のアルゴリズムを用いる際、各端末の解像度ごとに通知領域の出現位置を事前に調べておくことが、精度やパフォーマンスの観点から非常に重要となります。例えばiPhone7とiPad miniでは通知領域のx座標が異なりますが、これを事前情報として持っておかないと一定範囲のxに対して検出メソッドの呼び出しをループしなければならなくなり、計算量が飛躍的に増大してしまいます。逆に端末ごとに固定値を調べておくことができれば、x座標を決め打ちすることが可能となります。

この事前情報を調査するため、単にローカル通知を表示するためだけのアプリを実装し、各シミュレータで動かして、通知が表示されたスクショを撮影し、それらに対して非常に単純なロジックによって通知領域を検出して、以下のような端末解像度毎の出現位置を割り出しました。

{
  "1080x1920": {
    "width": 1080,
    "height": 1920,
    "scale": 2.61,
    "notification_region": {
      "x": 21,
      "y": 21,
      "width": 1038,
      "height": 325
    }
  },
  "1125x2436": {
    "width": 1125,
    "height": 2436,
    "scale": 3,
    "notification_region": {
      "x": 24,
      "y": 120,
      "width": 1077,
      "height": 374
    }
  },
  // ...
}

時系列を加味した最適化

上述した手法を用いれば一応通知領域は検出できますが、まだパフォーマンスに改善余地があります。通知領域が存在する可能性のある全領域を30fpsで検索していると流石に重く、手元のiPhone7ではCPU使用率が30-50%程度となりました。よって、どうにかして探索範囲を狭める必要があります。また、対象範囲を狭めることができれば結果的に誤検知が減り精度の向上も期待できます。

ここでは、通知領域の位置の時間的変位が利用できます。噛み砕いて言うと、以下のような想定を置くことが可能です。

  1. 前フレームにおいて、通知領域が検出されなかった場合、探索領域は最上端の一定部分のみでよい (必ず上からスライドインしてくるため)
  2. 前フレームにおいて、ある位置に通知領域が検出された場合、その周辺を優先して探索すればよい

余談ですが、通知領域のスライドアニメーションは一般的なease-in/outなので、これを利用してシグモイド関数等を使って線形モデルよりも精度の高い出現位置予測を出来そうですが、試してみたところそこまで精度が上がらなかったのでやめました。

ただし、前回の検出位置という状態変数を持つことにより、いくつか問題が出てきます。具体的には以下のようなものをケアする必要があります。

  • 通知表示中に配信開始した場合、全領域を探索しないと正しく検出できない
  • 端末回転中は通知検出ができないので、その間に通知が来た場合、全領域を探索しないと正しく検出できない
  • 何かのミスで一瞬通知領域が無いと判定されてしまった場合、次回以降の一定期間も全探索したい (fail-safe的に)

Mirrativからの通知だけモザイクをかけない

通知領域を検出した場合、その通知領域内のアイコン部分を調べ、もしMirrativのアプリアイコンと一致していれば通知領域なしと判定します。 こちらに関しては、SADを用いた単純なテンプレートマッチングを行っています。アルゴリズム詳細はこのへんのサイトを参照して下さい。 Mirrativのアプリアイコンと一致していなければ、対応する領域にCore ImageのCIFilterのうちCIPixellateを適用してモザイクをかけています。 (当初はCIGaussianBlurで実装していたのですが、配信が異常に落ちる事件があった結果、CIPixellateに変更しました)

課題と更なる挑戦

すでにiOSの全ユーザに公開済みの機能とはいえ、まだ特定の端末(新型iPad Proなど)に対応していなかったり、特定のタイミングでMirrativの通知がぼかされたり、 Mirrativ以外の通知がぼかされていなかったりと一部不具合も残っているため、現在CSチームと協力して不具合が起きている箇所の動画を収集しており、さらなる改善に繋げようとしております。

適用領域がニッチとは言え、他にはあまり類をみないソフトウェアだと思うので、さらなる改善後にはOSS化してみようと思っていますので、乞うご期待を!!

ミラティブは多種多様な技術(ライブストリーミング・Unityによる3Dアバター・機械学習などなど)を扱っており、我こそはという方がいらっしゃれば副業などでも構わないので、ぜひご応募ください!!

www.mirrativ.co.jp

追伸

f:id:hottestseason:20190517202752p:plain

追伸の追伸

今年度ミラティブ初めての新卒エンジニアの安西くんがブログを始めました!!

mirrativ-stream.hatenablog.com