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