0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

エラー率0.4%のFace Landmark回帰モデル

Last updated at Posted at 2020-04-19

Face Landmarkを求める問題です。FaceBoxを入力し、目(瞼輪郭)の中心2点、鼻筋2点、鼻の下3点の座標を、求める。

family (59)_light22.jpgfamily (46)_flip_crop_13_7.jpgfamily (32)_light10.jpgfamily (88)_rotate_2.jpg

Landmark問題は、問題を複数に分け、モジュールをカスケードさせて、解くのがトレンドらしいです->参考
そこで、第一弾目の課題として、両目と鼻筋の概略位置を捉えました(鼻の下3点は、おまけです。使うかどうかわからない)。

入力データは、インターネット上でオープンにされているデータセットのうち、商用を許諾されたものを利用しています。それを、OpenCVでFaceBoxでCropし、128x128にリスケールしたものです。Flipと、ランダムに回転、明度変更、Cropして、218K個まで増やしています。

正解座標Tagは、Dlibの68点だっけ、あれをベースに利用しましたが、いろいろ加工したり、手修正を加えて、作っています(なぜDlibのをそのまま使えないか、最後に書いておきます。)

PyTorchを使っています。データローダーと、Training/testコードは、PyTorchのサイトのTutorialのもろもろのサンプルコードの微改造なので、省略します。そこに、以下のモデルを投入。

AveragePoolingをはさみながらConvolutionを4層。その後、Affine層を3層。2D回帰なので、ロス関数はMSEを使っています。

エラーは、7個のMarkのどれか一つでも正解座標からx/y方向のいずれでも4pixel以上外れていたら間違いと判定しています。その結果、8エポック目で、エラー率は0.0037、およそ0.4%です。ほぼ間違えない。

class FaceMark7Net(nn.Module):
    def __init__(self):
        super(FaceMark7Net, self).__init__()

        self.conv1 = nn.Conv2d(1, 16, 3, padding=1)
        nn.init.kaiming_normal_(self.conv1.weight)
        self.bn1 = nn.BatchNorm2d(16)
        self.conv2 = nn.Conv2d(16, 32, 3, padding=1)
        nn.init.kaiming_normal_(self.conv2.weight)
        self.bn2 = nn.BatchNorm2d(32)
        self.conv3 = nn.Conv2d(32, 64, 3, padding=1)
        nn.init.kaiming_normal_(self.conv3.weight)
        self.bn3 = nn.BatchNorm2d(64)
        self.conv4 = nn.Conv2d(64, 128, 3, padding=1)
        nn.init.kaiming_normal_(self.conv4.weight)
        self.bn4 = nn.BatchNorm2d(128)

        self.fc1 = nn.Linear(32768, 1024)
        nn.init.kaiming_normal_(self.fc1.weight)
        self.bnf1 = nn.BatchNorm1d(1024)
        self.fc2 = nn.Linear(1024, 128)
        nn.init.kaiming_normal_(self.fc2.weight)
        self.bnf2 = nn.BatchNorm1d(128)
        self.fc3 = nn.Linear(128, 14)
        nn.init.kaiming_normal_(self.fc3.weight)

    def forward(self, x):

        x = x.float()
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.avg_pool2d(x, 2)
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.avg_pool2d(x, 2)
        x = F.relu(self.bn3(self.conv3(x)))
        x = F.avg_pool2d(x, 2)
        x = F.relu(self.bn4(self.conv4(x)))

        x = torch.flatten(x, 1)
        x = F.relu(self.bnf1(self.fc1(x)))
        x = F.relu(self.bnf2(self.fc2(x)))
        x = self.fc3(x)
        return x

image.png

Attention機構をはさむのも実験しましたが、なしでも十分な性能出ました。

モデルがでかいので、これからまたいろいろ手を加えると思います。

注:DlibのLandmarkの問題
0.西洋人の顔以外の精度が低い。眼鏡や細目に弱い。
1.Landmarkが一体どういう定義(作業基準)でつけられたものなのか、探ったが、結局、昔、イギリスのどこかの大学だかでLandmark付けのコンテストをやっていた、その時の正解データがまずありき、のようです。で、定義なしで作られたデータでTrainingされた、Dlibの検出器の結果には、揺れがある。例えば、鼻の頂と思われるMarkは、人が判断して鼻が一番高くなったところかというとそうでもなく、わしっぱなの先っぽだったり、東洋人の低い鼻のまあ適当な位置だったりする。目回りも、瞼の内側だったり、外側だったり、揺れている。鼻の上のつけねらしいMarkの位置も、どういう基準なのか、適当。
2.顔を側面に向けると、片目は、かなり隠れます。顔を下向きにすると、鼻の下の部分は隠れてしまいます。Dlibのつけるマークは、隠れている3D座標を2Dに写像したものでなく、カメラから見て鼻部分、目部分の境界にあり、セグメンテーション向きのものです。応用によってはそれでいいのですが、セグメンテーションが最終目的ではない場合、不都合です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?