目画像から瞼の間の領域を求める2値クラス分類モデル

Deep Learningに関し、Toyレベルのサンプルはいろいろオープンにされていて勉強できる。また、Academic Paperに、記録狙いの巨大なNeuralNetworkが書いてある。しかし、それらの間にある、ある程度、実用レベルを狙っているが、さほど複雑でない、NeuralNetworkの情報は、あまり公開されていません。

そこで、自分がやったことに関し、似たようなことをやろうとしている方に役立つかもしれないので、残しておきます。

以下、目周辺の画像のPixelに関し、まぶたの間の領域とそれ以外を２値分類する（セグメントする）、２値クラス分類モデルです。
ここの画像は、テスト入力画像と、Sigmoid出力[0..1]を、gray scaleの[0..255]に変換したMaskとを、bitwise_orしたサンプルです。
![1_1_1_20170109194545320_left_crop_6_8_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/7224cc8c-3ac6-592e-09ad-23dc1fa46c3c.jpeg)![1_0_2_20161219195639772_right_light29_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/66f8d3c7-c836-d9f6-47c1-4bcc5c86db48.jpeg)![39_1_4_20170105001146812_left_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/52bcb961-6b3d-de1d-3e3b-f22d4bfd8dce.jpeg)![img00284_left_crop_5_8_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/b5fc49b9-b707-87cd-9dd0-ae7c0f04f19b.jpeg)![img00551_right_flip_dark29_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/0c04f576-bf80-34d0-e425-739d4ff14e5a.jpeg)![img08133_right_flip_crop_4_8_maskedImage.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/210659/c1bc5885-789d-5f3d-dec0-afda06f08ca6.jpeg)



PyTorchを使っています。データローダーと、Training/testコードは、[PyTorchのサイトのTutorial](https://pytorch.org/tutorials/)のもろもろのサンプルコードの微改造なので、省略します。

入力データは、
・縦48x横78pixel画像が、84K個。
・正解領域を１としそれ以外を０とするような、画像と同じサイズのマスク。
・90:10でTrainig:Test区分けしています。

データソースは、商用利用を許諾された画像を集め、Flip、明度変更、Crop、Rotationなどで、Data Argumentionしています。

正解Tagは、DlibのLandmark Tagをもとにして、輪郭をとって内側を塗りつぶし、多少のモルホロジー変換を施し作りましたが、Dlibは白人以外に弱い、眼鏡などがあると間違えやすい、などの問題があり、手修正が大変でした。

ネットワークモデルは、Sonyの小林さんという方がYouTubeに投稿している[SegmentationのTutorial動画](https://www.youtube.com/watch?v=Eu7EKQ--Rvk)を参考にしました。Convolutionを４層重ね、Sigmoidするだけですが、Dilationという仕組みでグローバルなコンテキストを認識するようにした。

Training結果のConfusion Matrixは数値化していませんが、どのテスト結果を見ても目領域を外していません。つまり、目領域ではないのに目領域であると判定された、False-Positiveはほとんどない。

たったこれだけのコードで、ほぼ完ぺきに目領域をセグメントできる。

```Python
class EyeMaskNet(nn.Module):
    def __init__(self):
        super(EyeMaskNet, self).__init__()

        self.conv1 = nn.Conv2d(1, 16, (3,3), dilation=(1,1), padding=(1,1))
        nn.init.kaiming_normal_(self.conv1.weight)
        self.bn1 = nn.BatchNorm2d(16)
        self.conv2 = nn.Conv2d(16, 16, (3,3), dilation=(2,2), padding=(2,2)) #dilate=2 -> kernel=5x5
        nn.init.kaiming_normal_(self.conv2.weight)
        self.bn2 = nn.BatchNorm2d(16)
        self.conv3 = nn.Conv2d(16, 16, (3,3), dilation=(4,4), padding=(4,4)) #dilate=4 -> kernel=9x9
        nn.init.kaiming_normal_(self.conv3.weight)
        self.bn3 = nn.BatchNorm2d(16)
        self.conv4 = nn.Conv2d(16, 16, (3,3), dilation=(8,8), padding=(8,8)) #dilate=8 -> kernel=17x17
        nn.init.kaiming_normal_(self.conv4.weight)
        self.bn4 = nn.BatchNorm2d(16)

        self.convf = nn.Conv2d(16, 1, (3,3), dilation=(1,1), padding=(1,1))
        nn.init.kaiming_normal_(self.convf.weight)

    def forward(self, x):
        x = x.float()
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = F.relu(self.bn4(self.conv4(x)))
        x = F.sigmoid(self.convf(x))
        return x
```