はじめに
主成分分析を使った顔認証プログラムを試してみた。プログラムはpythonで主成分分析のライブラリはscikit-learnを使用。顔画像はこの論文だとおっさんの画像で面白くないので橋本環奈の顔画像を使用。
主成分分析とは
詳細な説明のあるサイトや書籍が多数あるので割愛。簡単に言うと多次元の特徴空間を低次元の部分空間に次元削減を行うことで、そのデータの持つ特徴を抽出するデータ分析手法の一種。対象が画像の場合はN×N(ピクセル)の画素値を要素とした2次元の行列だが、$N^2$次元の特徴ベクトルとして表すことに注意する。
顔認証などの画像マッチングに応用する場合は、テンプレートの主成分との相関値(類似度)を算出することにより、最も相関が高いものを正解として出力する。
顔認証の実装
理論
この論文をベースとした。
まず、学習用の顔データをn個そろえる。各画像はN×Nにリサイズされているものとする。
X = \{\vec{x}_1,\vec{x}_2,\cdots,\vec{x}_i,\cdots,\vec{x}_n\}
ここで、$\vec{x_i}$は、N×N画像を$N^2$次元の特徴ベクトルとして表したものとする。
そこから、平均値
\vec{\mu} = \frac{1}{n}\sum_{i=1}^{n}\vec{x}_i
を求め、共分散行列
S = \sum_{i=1}^{n}\sum_{j=1}^{n}(\vec{\mu}-\vec{x}_i)(\vec{\mu}-\vec{x}_j)^T
を得る。そこから固有値問題
S\vec{v}=\lambda\vec{v}
を解くことで、固有値$\lambda_j$、固有ベクトル$\vec{v}_j$を得る。固有ベクトルを、対応する固有値の大きい順に並べることで、その固有ベクトルが第一主成分、第二主成分・・・となる。
顔認証は、対象画像と、この学習済み主成分との相関値を計算することで行われる。相関値は、固有ベクトル(主成分ベクトル)を横に並べた射影行列
V = \{\vec{v}_1,\vec{v}_2,\cdots,\vec{v}_d\}
を用いて、対象画像の特徴ベクトル$X_{obs}$との内積により得られる。ここでは、$d=1$として、第1主成分により相関値を求める。すなわち、相関値$R$は、
R = \vec{v}_1\cdot X_{obs}^T
と計算することができる。
## 実装
まず、学習用の橋本環奈の顔画像用意。本来はいろいろな画像を登録するが、顔認証の場合はシビアで様々な角度から撮影した画像が多数ないと厳しい。とりあえず、今回はあくまで練習なので下記の画像を5個コピーして登録した。
次に、練習用にいろんな画像を用意。
手書き文字画像(3)、橋本環奈(オリジナル(org)と、ちょっと似てる別の画像(kanna_2)の2つ)、出川哲郎(degawa)、モナ・リザ(MN)の計5つ
コード
import os
from glob import glob
import numpy as np
import sys
from sklearn.decomposition import PCA
import cv2
SIZE = 64
# GRAYSCALE
def Image_PCA_Analysis(d, X):
# 主成分分析
pca = PCA(n_components=d)
pca.fit(X)
print("主成分")
print(pca.components_)
print("平均")
print(pca.mean_)
print("共分散行列")
print(pca.get_covariance())
print("共分散行列の固有値")
print(pca.explained_variance_)
print("共分散行列の固有ベクトル")
v = pca.components_
print(v)
#主成分分析結果
print("主成分の分散説明率")
print(pca.explained_variance_ratio_)
print("累積寄与率")
c_contribute_ratio = pca.explained_variance_ratio_.sum()
print(c_contribute_ratio)
# 次元削減と復元
X_trans = pca.transform(X)
X_inv = pca.inverse_transform(X_trans)
print('X.shape =', X.shape)
print('X_trans.shape =', X_trans.shape)
print('X_inv.shape =', X_inv.shape)
for i in range(X_inv.shape[0]):
cv2.imshow("gray", X_inv[i].reshape(SIZE,SIZE))
cv2.waitKey(0)
cv2.destroyAllWindows()
return v,c_contribute_ratio
def img_read(path):
x = []
files = glob(path)
for file in files:
img = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
img2 = cv2.resize(img, (SIZE, SIZE)) # N*Nにリサイズ
x.append(img2)
X = np.array(x)
X = X.reshape(X.shape[0],SIZE*SIZE) # 特徴ベクトル×nにする
X = X / 255.0 # 0-1にしないといけないのでnormalize
print(X.shape)
return X
def main():
# 主成分分析次元数
d = 5
path = './kanna/*.png'
X = img_read(path)
# PCA
v, c_contribute_ratio = Image_PCA_Analysis(d, X)
# マッチング
path = './kanna2/*.png'
files = glob(path)
for file in files:
X2 = img_read(file)
X2 = X2 / np.linalg.norm(X2)
# 相関値(d次元の固有ベクトルを用いて作った射影行列と特徴ベクトルの積)
eta = np.dot(v[0],X2.T)
print("相関値:", file, np.linalg.norm(eta * 255))
return
対象のテスト画像(5つ)は平均輝度がばらばらなので特徴ベクトルのノルムを1に規格化している。
結果
期待通り、橋本環奈の相関値は高く、モナ・リザ、出川の相関値は低かった。出川は手書き文字「3」より低く、手書き文字「3」のほうが出川より橋本環奈の顔に近いという結果が出ました。
相関値: ./kanna2\3.png 21.292788187030233
相関値: ./kanna2\degawa.png 14.11580341763399
相関値: ./kanna2\kanna_2.png 32.536060418259474
相関値: ./kanna2\kanna_org.png 39.014994579329326
相関値: ./kanna2\MN.png 26.90538714456287```
同じ画像を多数登録しているので当たり前だが、第1主成分のみでほぼ説明できており、累積寄与率は100%
主成分の分散説明率
[1.00000000e+00 3.15539405e-32 0.00000000e+00 0.00000000e+00
0.00000000e+00]
累積寄与率
1.0
なお、相関値の定性的理解としては以下の図の通り、まず、学習画像のデータは、主成分軸に投影されているとみなす(次元圧縮)。主成分ベクトルと対象画像(の特徴ベクトル)の内積をとることで、主成分ベクトルとの方向に近い画像が、内積の値が大きいことになる。