はじめに
本稿では昨年度末に提案されたInstance Segmentationに関する新しいアプローチ
SOLO: Segmenting Objects by Locations (https://arxiv.org/abs/1912.04488) を紹介します。
物体検出でお馴染みのYOLOをもじったようなタイトルですが、こちらはInstance Segmentationです。しかし、YOLOに負けず劣らずシンプルな面白いアプローチで、精度等のパフォーマンスも高い(らしい)ので、是非トライください。
なお、先日(2020年3月)、SOLOv2も発表されました。
SOLOv2: Dynamic, Faster and Stronger (https://arxiv.org/abs/2003.10152)
こちらも完全にYOLOv2を彷彿とさせるタイトルですね。
Instance Segmentationとは?
物体の領域をオブジェクトごとにピクセルレベルで検出および分類する手法です。
矩形で囲うObject Detectionのように物体の数量を判断でき、尚且つSemantic Segmentationのようにピクセルレベルの厳密な領域分割ができる、というものです。
Instance Segmentationの従来手法
Instance Segmentation分野では、Mask R-CNNが最も有名でデファクトスタンダードにあたると思います。下図のように、矩形を見つけてから色塗りするような手法です。
Mask R-CNNについては以下の良記事をご参考ください。
https://qiita.com/shtamura/items/4283c851bc3d9721ed96
SOLO [Segmenting Objects by Locations]
SOLOってどんな手法?
下図がわかりやすいです。
CNNのEncoder & Decoderを経て、Category BranchとMask Branchに分岐します。Category Branchではクラス分類をおこない、Mask Branchではセグメンテーションをおこないます。
(1) Category Branch
入力画像をグリッド状に分けて、グリッドの個数分だけクラス分類をおこないます。各グリッドセルに物体の中心が存在する場合はそのクラスを、存在しない場合は「背景」を返す形です。
(2) Mask Branch
各グリッドセルに物体が存在するとしてセグメンテーションしてしまいます。したがって、グリッドの個数分だけ画像全体をセグメンテーションすることになります。例えば12x12でグリッド分割した場合、マスクの最終出力は144枚(144ch)のセグメンテーション画像ということになります。ここがユニークなポイントですね。
全体のネットワークをイメージで書くと下図のような形になります。
Mask Branchの出力は、下図のようなイメージになります。オブジェクトの中心が存在するセルには、マスクが出力されていることが見てわかります。近傍のセルにも似たようなマスクがはられることになりますが、最終出力はNMS(Non Maximum Suppression)をおこなうことで、重複したマスクを除外することができます。
従来手法とココが違う!
「矩形検出⇒セグメンテーション色塗り」のような、二段階のアプローチではなく、シングルステージでダイレクト一発検出なのがアツいところです。
コーディングも容易で、損失関数もマスクのDice Lossと分類のFocal Lossだけなので、お気持ちはほぼU-Netです。Bounding Boxの回帰予測の類がないのがらくちんです。
SOLOのここが面白い①: Coord Convの活用
SOLOを特徴づけるのはグリッド状のマスク出力ですが、グリッドにマスクを割り振るところが難しく、工夫無しではあまりうまくいきません。通常、CNNは座標情報を得ることができないので、畳み込んで得られたマスクを各グリッドセルに分類しにくいためです。
それに対して、SOLOではCoord Convを用いることでこれを解決しています。Coord Convは、height方向とwidth方向の座標値を-1~+1で割り振った値をチャンネル方向に追加(concatenate)するだけです。
(https://arxiv.org/abs/1807.03247)
論文によると、Coord Convだけで大幅に精度が改善するとのことです。
SOLOのここが面白い②: Mask Kernel Branch
さらに、SOLO(v2)ではマスクの割り振りをより明示的におこなう手法が提案されています。
上述したMask Branchをさらに分岐させて、最終的なマスクの割り振りを位置情報ベースの重みで与えるような方法です。グリッドの場所毎にchannel-wiseなattentionを与えるといっていいと思います。
下図の一番左が通常のSOLO、右側がMask Kernel Branch付きのSOLOです。
SOLOのここが面白い③: Matrix NMS
Non Maximum Suppressionは、重複するマスクを除去するための必須の後処理ですが、繰り返し演算で処理時間を無駄に要するのがネックです。
NMSの詳細は別の記事をご参考いただきたいと思います。ざっくり説明すると、NMSではスコアの高いマスクから順番に選びとっていき、下位スコアのマスクは、上位スコアのマスクと重複(IoU)が大きい場合は除去します。思わずfor文を書きたくなるような処理です。
一方、Matrix NMSでは上述の重複(IoU)が大きい下位マスクを除去する代わりに、相対的にスコアを落とすようなペナルティを与えます。
以下のコードが論文中に記述されており、decayの処理でスコアの低減がおこなわれています。IoUは0~1の間の値を撮りますが、これが大きくなるほどdecayが小さくなり、スコアが低減されます。しかし、上位のマスクとのIoUが大きい場合(ious.maxが大きい場合)には、そのマスクの影響は除外されます(decayは小さくなりません)。
これを用いると、for文要らずで処理が通常のNMS(Hard NMS)よりも9倍速く、精度も少し向上したとのことです(下表)。少し雑な説明をしておりますので、厳密な説明はSOLOv2論文をご参考ください。
パフォーマンス
そんなシンプルなSOLOですが、速度・精度とも結構いいパフォーマンスが出るそうです。COCOのAverage Precisionが論文中にありますが、SOLO v2は多くの従来手法を凌駕しています。
所感
論文中で書かれているものもありますが、個人的に感じる欠点としては以下です。
1. 物品が多いとグリッドを細かくする必要があり、出力サイズが大きくなる (メモリ圧迫)
2. 検出物品のサイズによらず、画像全域のマスク画像を出力するので、各出力に無駄(背景)が多い。
3. 物品が検出されない場合であってもグリッドセルの数だけ出力がある。背景が多いと損した気分になる。
4. グリッドの割り当てが中心点でおこなわれるので、中心が被ると検出漏れしやすい。
モデルを大幅に変更しない限り、物品のサイズやバラつきっぷりを元にグリッドサイズ等を決めて、無駄なく構成するしかありません。
上述4については、面積比などでグリッドに割り振るのも手だと思います。下図の犬と猫は同一中心ですが、各グリッド毎の面積比をとれば、犬のセルも猫のセルもあります。少し試しましたが、こちらの方が有効なケースもありそうです。
さいごに
SOLOを用いるとシングルステージで簡単にInstance Segmentationができます。
Mask R-CNNに飽きてきた皆さんも、Instance Segmentationを敬遠していた皆さんも、気が向いたら是非トライください。
まだまだ勉強不足のため、不備や誤りがあるかと存じます。何かございましたらご指摘・コメントください。