顔画像生成のためのデータセットを厳選する

memo.sugyan.com

の記事を書いてから10ヶ月も経っていた…。

  • Twitterからアイドルの自撮り画像をひたすら収集
  • dlib で顔検出し、各部位座標を利用して正規化し切り抜き
  • Webアプリで管理、選別作業
  • 選別作業自体を分類モデルに学習させて半自動化

というところの仕組みまでは出来て、あとは淡々と作業していくだけ、だったが まぁ色々あって時間かかり、、ここ最近でようやく選別後の画像が 16,500件ほど集まった。

十分とは言えないがそれなりの量にはなったので生成モデルを使って画像生成を試したい、と思ったが、改めて選別した画像を見返してみると「これは OK ラベルを付けたが やっぱり NG かな…」というものが幾つか混ざっていることに気付いた。

annotationの基準が変わるのはよくあることなので、初期は OK と思っていたものでも より良質な画像を多く見た後だと NG に分類すべきという判断になったりする。

とはいえ 16,500件の画像を一つ一つまたチェックするのも大変なので、自動的にある程度フィルタリングできるようにしていった。

重複検出

まずは重複している画像を排除。画像収集はTwitterからだけど、アイドルさんも同じ自撮り画像(もしくは僅かに加工をしただけのもの)を複数回使い回したりすることがある。選別時点では OKNG かだけでしか判断していないので、後から見返してみると幾つもある同じ画像に OK ラベルをつけていることに気付いたりする。

わずかに加工されていたりしているとまったく同一の画像にはならないので、ここでは Perceptual Hash で類似しているものを抽出するようにした。 PythonImageHash moduleで計算できる。

import imagehash
from PIL import Image

image = Image.open(image_path)
phash = imagehash.phash(image)

全候補画像に対しこれを計算し、値が同一もしくは限りなく近い値になっているものは重複として検出することができる。

顔検出を再度

収集した画像はすべて前記事の手法で dlib を使ってface detectionとlandmark detectionに成功して抽出されているもののはずだが、100%正確に検出できているとは限らない。元画像からの回転・切り抜き・拡大縮小などの影響で detection の結果が大きく変わっていたりするかもしれない。ので改めて前候補画像に対し検出をかけて face landmark の座標を算出し直した。

単一の顔だけが写っているはずなのに 実際に dlib.get_frontal_face_detector() にかけてみると一つも検出されなかったり複数検出されてしまったりする。このdetectorは run methodで幾つかのパラメータを指定して検出精度を調整できるらしいので、少しずつ調整して1つだけ検出されるようにした。

http://dlib.net/python/index.html#dlib.fhog_object_detector.run

import dlib

def detect_single_face(detector, image):
    for adjust_threshold in [x / 20.0 for x in range(0, -10, -1)]:
        for upsample_num_times in range(0, 3):
            detections, scores, indices = detector.run(
                image, upsample_num_times, adjust_threshold
            )
            if len(detections) == 1:
                return detections, scores, indices

正しく検出され正規化されている顔画像であれば、目の位置は平行になっていて顔の部分は中心で同程度の幅・高さを持っているはず。 それらの角度や座標の値が全候補画像の中で外れ値になっている場合、正しく顔やlandmarkを検出できておらず、学習データとして相応しくない可能性があるので除外することにした。

偏差を不偏標準偏差で割った検定統計量  \frac{x - \mu}{\sigma} が大きく平均値からブレているものを抽出。

ja.wikipedia.org

実際いくつかチェックしてみると大きく傾いてしまっているものやlandmarkが全然正しくない位置で検出されて正規化が失敗しているものなどが発見された。

エッジ強度でぼやけ抽出

次に、「正しく顔は検出できているが そもそも顔がハッキリ写っていないもの」を除外。 ピントが合っていないものやブレているものや暗くて画質が低いもの、など色んなパターンがあるので一概には決められないが、ともかく顔部分がハッキリ写っていないのは良くないので、候補から外していく。

ポートレートなどでは人物はハッキリ写っていて背景がぼやけていたりするので、まずは画像から顔領域だけを切り取って、その領域の画像に対して cv2.Laplacian を算出し、それらの値の分散を調べる。これが低いものは全体的に勾配が小さくハッキリと写っていないことが多い、と判断できる。

import cv2

img = cv2.imread(image_path, cv2.IMREAD_COLOR)
face = img[face_top:face_bottom, face_left:face_right, :]
gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
variance = cv2.Laplacian(gray, cv2.CV_64F).var()

もちろん例外もあるだろうし、「この値より低かったらぼやけている」という閾値も決めづらいところではある。 今回は全体の中から下位1%を除外することにした。

口の有無

また、モノを食べていたりマスクを着けていたりして口がハッキリ写っていない画像が幾つかあったのが気になった。 そういったものが含まれても良いとは思うけど、出来れば今回のデータセットでは口がちゃんと写っている画像で揃えたい。

検出したlandmarkの座標を使えば、口の部分だけを切り取ることはできる。その領域の画像内に、「赤っぽい箇所」が発見できなかったら口が隠れていると判定することにした。 単純なRGBでのredの強さだけでは白色も赤とみなされてしまう。こういうときは HSV色空間で判定するのが良いらしい。Hueで赤色周辺の値で絞り、Saturation, Valueはそれなりの閾値を設けておく。その範囲内であれば「赤っぽい」といった判定ができるようだ。

import cv2
import numpy as np

mouth = img[ymin:ymax, xmin:xmax, :]
hsv = cv2.cvtColor(mouth, cv2.COLOR_BGR2HSV)
mask = sum(
    [
        cv2.inRange(hsv, np.array([0, 96, 128]), np.array([30, 255, 255])),
        cv2.inRange(hsv, np.array([150, 96, 128]), np.array([180, 255, 255])),
    ]
)
red = mask.sum()

赤は cv2HSVでは 0 - 180 の値で表されるらしい。ので、 0 - 30, 150 - 180 の範囲を赤とすることにした。S, V の範囲は適当に。 こうして得られた mask がすべて 0 だったなら、「口のあるはずの領域に赤い要素がまったく含まれていない」とみなすことができる。 実際幾つか調べてみると 食べ物などで口が隠れていたりするものが検出できた。それ以外にも普通に写っているはずのものもあったが、それらは画像全体が極端に明るかったり淡い色に加工されていたりして口の赤色が閾値に収まっていなかったようだ。 それはそれで学習データとしては微妙なので除外することにした。

全体の画質

顔がぼやけた画像などは極力排除したが、それ以外にも画像全体が画質が低いものもあったようだった。これも出来れば除外したい。 blind or No-reference (NR) image quality assessment (IQA) の手法としてBRISQUEというのがあるらしい。

image-quality moduleで計算できる。

from imquality import brisque
from PIL import Image

image = Image.open(image_path)
score = brisque.score(image)

これもどれくらいで閾値を設けるかは難しいところで、ともかく値が極端に高いものは画質が低いとみなすことが出来るようだったので、ある程度フィルタリングした中から 最終的にデータセットに選ぶときにはこの値が低い順に抽出するようにした。

終結

こうして、 OK ラベルをつけた 約16,500件の画像から 「重複なく、正しく顔検出された結果 大きな傾きや偏りなく顔領域が存在しており、ぼやけておらず、口も写っており、画質も低くない」ものを厳選して 16000枚の画像データセットを作成した。

そのデータを使って実際に StyleGAN2 ADA の生成モデルに学習させてみているところ それなりのアイドル顔画像が生成できてきているようだ。

f:id:sugyan:20201120235412p:plain

この先についてはまた別記事に書こう

Repository

https://github.com/sugyan/image-dataset/tree/master/python/assessment