お久しぶりになります、iOSエンジニアのしだです。
開発部ブログも更新する人が多くなって、前回からだいぶ日が空いたように感じます。
今回は、あるまとまった写真から顔を切り出して、FaceNetから特徴量を抽出しクラスタリングを試しました。
以下の FaceNet のトレーニング済みのモデルがあるので使用しています。
FaceNet は、Triplet Loss と呼ばれる基準となる Anchor と Positive の距離を近くに、Negative との距離を遠くにマッピングされるように学習されます。
顔画像から特徴ベクトルとして利用可能なのでクラスタリングなどのタスクに利用できます。
準備
- Python 3.6.6
- FaceNet リポジトリを clone しておく
$ mkdir facenet-example $ cd facenet-example $ git clone https://github.com/davidsandberg/facenet.git
- FaceNet の学習済みモデル から学習済みモデルをダウンロード
- 顔画像: 112枚(81枚の家族写真素材 から顔検出して切り出したもの)
特徴ベクトルの抽出
FaceNet の facenet/src/compare.py に実際のサンプルコードがあるので、そちらを見てもらうほうが理解しやすいかもしれません。 FaceNet のモデルを利用するためのサンプルコードです。グラフと tf.Session を再利用できるように変えています。
from facenet.src import facenet class FaceEmbedding(object): def __init__(self, model_path): # モデルを読み込んでグラフに展開 facenet.load_model(model_path) self.input_image_size = 160 self.sess = tf.Session() self.images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0") self.embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0") self.phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0") self.embedding_size = self.embeddings.get_shape()[1] def __del__(self): self.sess.close() def load_image(self, image_path, width, height, mode): image = Image.open(image_path) image = image.resize([width, height], Image.BILINEAR) return np.array(image.convert(mode)) def face_embeddings(self, image_path): image = self.load_image(image_path, self.input_image_size, self.input_image_size, 'RGB') prewhitened = facenet.prewhiten(image) prewhitened = prewhitened.reshape(-1, prewhitened.shape[0], prewhitened.shape[1], prewhitened.shape[2]) feed_dict = { self.images_placeholder: prewhitened, self.phase_train_placeholder: False } embeddings = self.sess.run(self.embeddings, feed_dict=feed_dict) return embeddings
使い方
# ダウンロードした学習済みのモデルを解凍してパスを指定します FACE_MEDEL_PATH = './models/20180402-114759/20180402-114759.pb' face_embedding = FaceEmbedding(FACE_MEDEL_PATH) # 顔画像のファイルリスト faces_image_paths = glob.glob('./images/faces/*.jpg') # 顔画像から特徴ベクトルを抽出 features = np.array([face_embedding.face_embeddings(f)[0] for f in faces_image_paths]) print(features.shape) # ==> (112, 128) 顔画像112枚が128次元表現になります
クラスタリング
PCA を使って次元削減
128次元の特徴ベクトルを2次元座標にプロットするために、128次元から2次元にPCAを使って次元削減します。 scikit-learn を用いれば容易にPCA を利用できます。
from sklearn.decomposition import PCA pca = PCA(n_components=2) pca.fit(features) reduced = pca.fit_transform(features) print(reduced.shape) # ==> (112, 2) 128次元を2次元表現にされます print(pca.explained_variance_ratio_) # ==> array([0.21991092, 0.13256498], dtype=float32)
kMeans によるクラスタリング
次元削減した2次元ベクトルを kMeans を使って、4つにクラスタリングしてみます。
4つにしたのは、使用した家族写真素材に4人写っていることを目視で確認して今回はクラスター数4に指定しています。
from sklearn.cluster import KMeans K = 4 kmeans = KMeans(n_clusters=K).fit(reduced) pred_label = kmeans.predict(reduced) print(pred_label) # ==> [2 0 2 1 1 1 1 2 1 ...] # クラスタリングした結果をプロット x = reduced[:, 0] y = reduced[:, 1] plt.scatter(x, y, c=pred_label) plt.colorbar() plt.show()
あとは、実際にクラスタリングされた各クラスターが、同じような顔画像の集まりになっているか確認します。
少し間違っている画像もありますが、おおよそ合っていそうです。
子どもの顔画像(赤点、緑点)は割と近い分布になっており、お父さん・お母さんぽい顔画像は離れて分布されているため、大人の顔画像は判別しやすい印象があります。
さいごに
学習済みの FaceNet が簡単に利用できるので、お試しで顔認識するときはとても重宝してます。 顔認識は弊社でも利用用途が多いのでこの当たりの技術はチェックしていきたいと思います。