ハードウェア技術者のスキルアップ日誌

某家電メーカーの技術者がスキルアップのために勉強したことを記録するブログです

Tensorflowで作成したグラフのノード名、型を表示する方法

Tensorflowの学習済みモデル、重みデータのcheckpointファイルをProtocolbuf形式(pbファイル)に変換する際にグラフのノード名を指定する必要があったのですが、ノード名を知る方法がわからなかったので調べてみました。

結論から記載すると、以下の記述ですべてのネットワーク構成を可視化できます。

graph = tf.Graph()
with graph.as_default():

    for op in graph.get_operations():
        print(op.type)  # type
        print(op.name) # name
        print(op.op_def) # protocol buf

関数graph.get_operations()でグラフのOperations一覧を取得します。
各ノードごとに型と名前とprotocol bufを表示していきます。

ネットワークの層数が多いと見ていくのが大変なので、typeがReLUのみのノード名を表示するなど、if文で条件を絞ると確認がしやすくなります。

 

参考サイト

http://docs.fabo.io/tensorflow/building_graph/tensorflow_graph_part1.html

 

Tensorflow, CUDA, CuDNN バージョン確認方法

Tensorflow GPU環境を作る際に何度も調べなおしているので備忘録です。

対応表

Tensorflow-GPU, CUDA, CuDNNのバージョンは正しい組み合わせでないと正常に動かないため、以下のリンク先で確認します。

https://www.tensorflow.org/install/source#tested_build_configurations

GPUで対応するCUDAのバージョンが決まると思うので、それに合わせてTensorflow-GPUをインストールします。

 

Tensorflow-GPUのバージョン確認方法

$ pip list

 anacondaを使用している場合はconda listでOKです。

 

CUDAのバージョン確認方法

$ nvcc -V

 

CuDNNのバージョン確認方法

$ cat /usr/include/cudnn.h | grep CUDNN_MAJOR -A 2

 

Tensorflow-GPUが動作しない場合、以上3つのコマンドでバージョンを確認し、対応している組み合わせかを確認してみてください。

シェルスクリプト - フォルダ内の全ファイルに対して処理

シェルスクリプトを作るときにフォルダ内の各ファイルに対して同じ処理を繰り返す場合が多いのですが、よく書き方が分からなくなるので、備忘録として記録しておきます。

シェルスクリプトのサンプル

以下はフォルダ内の各mp4ファイルを引数にしてsample.pyを実行するものです。
シェルスクリプトの引数に対象フォルダのパスを指定して実行します。

#!/bin/bash
[ "x$1" = "x" ] && exit 1
#引数にファイルパスを代入
DIR=$1

for file in `\find $DIR -name '*.mp4'`; do
  #echo $file
 python sample.py $file
done

各行の解説

[ "x$1" = "x" ] && exit 1

"x$1"と"x"が等しい、即ち$1(スクリプトの第一引数)が指定されてないときに
エラーで終了する。

DIR=$1

$1(スクリプトの第一引数)を変数DIRに代入する。

for file in `\find $DIR -name '*.mp4'`; do

` `内の条件を満たすものを変数fileに代入し、条件に満足するものがなくなるまで
do - done内の処理を繰り返す。
` `は内側の処理を行った結果で置換する記述で$()でも同じ。
findコマンドでDIRで指定したディレクトリ内のファイル名がmp4で終わるものを検索。
この検索結果をひとつずつ変数findに代入する。
ディレクトリの階層が深くてもすべて抽出して実行可能。
逆にサブディレクトリは処理したくない場合は-maxdepthオプションで1を指定する。

#echo $file

対象ファイルが正しく取れているかの確認のため、ファイル名を表示。
デバッグ用のため、コメントアウト

python sample.py $file

抽出したファイル名を引数にしてsample.pyを実行する。

応用

find文の検索条件を変えたり、forループの中を変えることでいろいろな処理に応用できると思います。自動でたくさんのファイルを処理したい場合に活用したいと思います。

フォルダ内のファイルを連番でリネーム

仕事で作業している中で手こずった内容を備忘録としてメモしておきたいと思います。
今回はLinuxOSでフォルダ内のファイルを連番でリネームする方法です。

 

コマンドの一例はこちら。

$ ls *.png | sort -t - -k 2 -n | awk '{ printf "mv %s %04d.png\n", $0, NR }' | sh

 pngファイルをソート後、若い順に0001から連番で番号を振ります。

 

今後応用できるように各コマンドの意味を残しておきます。

①ls *.png
拡張子pngファイルのみを表示します。jpgだけ抽出したかったらls *.jpg

②sort -t - -k 2 -n
区切り文字'-'で区切り、2番目の項目で数値として昇順に並べ替え

awk '{ printf "mv %s %04d.png\n", $0, NR}'
awkコマンドはawk 'パターン {アクション}' ファイル名」で、テキストファイルを
1行ずつ読み、パターンに合致した行に対して、アクションで指定された内容を実行します。
このコマンドは全行に対し、{ printf "mv %s %04d.png\n", $0, NR}を実行します。
$0は②の出力、NRは行番号を示すので、
 「mv (元のファイル名) (0パディングした4桁の数字.png)」
を表示します。NRは1から始まるので100番から始めたい場合はNR+99とします。

④sh
③の出力をシェルに渡します。

 

いろいろなケースにこれを応用して活用していきたいと思います。

FaceNet(顔認証)を使って自動撮影カメラを作ってみた

前回、GITHUBで公開されているFaceNetを動かしてみました。
今回はこれを使って登録した人の顔を自動で撮影するおもちゃを作ってみたいと思います。

masaeng.hatenablog.com

 

ソースコードの修正

FaceNetのcompare.pyを修正して、USBカメラで撮影した映像に対して、
FaceNetで顔認証を行うスクリプトを作成しました。


①ライブラリインポートとMain関数、引数取得用の関数

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from scipy import misc
import tensorflow as tf
import numpy as np
import sys
import os
import cv2
import copy
import glob
import argparse
import facenet
import align.detect_face
from timeit import default_timer as timer

minsize = 20  # minimum size of face
fd_threshold = [ 0.6, 0.7, 0.7 ]  # three steps's threshold
factor = 0.709  # scale factor
input_image_size = 160
fr_threshold = 1.2

def main(args):
  margin = args.margin
  with tf.Graph().as_default():
    #gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=args.gpu_memory_fraction)
    gpu_options = tf.GPUOptions(allow_growth=True) # GPUのメモリ割り当て方法を変更
    sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options,
log_device_placement=False)) with sess.as_default():
# 顔検出のネットワーク作成 MTCNN pnet, rnet, onet = align.detect_face.create_mtcnn(sess, None) image_paths = glob.glob(args.reg_paths) # 登録済み画像のフォルダ nrof_images = len(image_paths) #登録済み画像の数(only one person) # 登録済み画像から顔のみを抽出したリストを作成 images = load_and_align_data(image_paths, nrof_images, pnet, rnet, onet, args) nrof_images = len(images) #登録に成功した顔の数(only one person) # Load the model facenet.load_model(args.model) # Get input and output tensors images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0") embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0") phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0") embedding_size = embeddings.get_shape()[1] # Run forward pass to calculate embeddings feed_dict = { images_placeholder: images, phase_train_placeholder:False } emb_reg = sess.run(embeddings, feed_dict=feed_dict) # 登録済み画像の特徴ベクトル抽出 # カメラ映像/動画ファイルの取得 video_capture = cv2.VideoCapture(0) # camera input print('Start Recognition') #fps計算 初期化 frame_num = 1 accum_time =0 curr_fps = 0 prev_time = timer() fps = "FPS: ??" while True: ret, frame = video_capture.read() if ret == False: break if frame.ndim == 2: frame = facenet.to_rgb(frame) frame = frame[:, :, 0:3] #frame = cv2.resize(frame, (640, 352)) # 入力画像をリサイズ bounding_boxes, _ = align.detect_face.detect_face(frame, minsize,
pnet, rnet, onet, fd_threshold, factor) nrof_faces = bounding_boxes.shape[0] print('Detected_FaceNum: %d' % nrof_faces, end='') if nrof_faces > 0: #顔を検出した場合 det = bounding_boxes[:, 0:4] frame_size = np.asarray(frame.shape)[0:2] cropped = [] scaled = [] scaled_reshape = [] v_bb = np.zeros((nrof_faces,4), dtype=np.int32) for i in range(nrof_faces): emb_array = np.zeros((1, embedding_size)) v_bb[i][0] = np.maximum(det[i][0]-margin/2, 0) # 左上 x(横) v_bb[i][1] = np.maximum(det[i][1]-margin/2, 0) # 左上 y(縦) v_bb[i][2] = np.minimum(det[i][2]+margin/2, frame_size[1]) # 右下 x(横) v_bb[i][3] = np.minimum(det[i][3]+margin/2, frame_size[0]) # 右下 y(縦) cropped.append(frame[v_bb[i][1]:v_bb[i][3], v_bb[i][0]:v_bb[i][2], :]) cropped[i] = facenet.flip(cropped[i], False) scaled.append(misc.imresize(cropped[i],
(input_image_size, input_image_size), interp='bilinear')) scaled[i] = cv2.resize(scaled[i], (input_image_size,input_image_size), interpolation=cv2.INTER_CUBIC) scaled[i] = facenet.prewhiten(scaled[i]) scaled_reshape.append(scaled[i].reshape(-1,input_image_size,input_image_size,3)) cv2.rectangle(frame, (v_bb[i][0], v_bb[i][1]), (v_bb[i][2], v_bb[i][3]), (0, 255, 0), 2) feed_dict = {images_placeholder: scaled_reshape[i],
phase_train_placeholder: False} emb_array[0, :] = sess.run(embeddings, feed_dict=feed_dict) # 特徴ベクトルの抽出 # 識別(登録済み画像の特徴ベクトルとのユークリッド距離を計算) dist_ave = cal_distance(emb_reg, emb_array, nrof_images) print(' %1.4f ' % dist_ave, end='') if dist_ave < fr_threshold: # 認識のしきい値 #plot result idx under box text_x = v_bb[i][0] text_y = v_bb[i][3] + 20 print('Find registered person', end='') cv2.rectangle(frame, (v_bb[i][0], v_bb[i][1]),
(v_bb[i][2], v_bb[i][3]), (0, 0, 255), 2) else: print('', end='') else: #顔非検出の場合 print(' Alignment Failure', end='') print('') #frame_num表示 cv2.putText(frame, str(frame_num), (3,30), cv2.FONT_HERSHEY_SIMPLEX,
0.50, (255, 0, 0), thickness=2) frame_num += 1 #fps計算 curr_time = timer() exec_time = curr_time - prev_time prev_time = curr_time accum_time = accum_time + exec_time curr_fps = curr_fps + 1 if accum_time > 1: accum_time = accum_time - 1 fps = "FPS: " + str(curr_fps) curr_fps = 0 cv2.putText(frame, fps, (3,15), cv2.FONT_HERSHEY_SIMPLEX, 0.50, (255, 0, 0), thickness=2) cv2.imshow('Video', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break video_capture.release() cv2.destroyAllWindows() def parse_arguments(argv): parser = argparse.ArgumentParser() parser.add_argument('model', type=str, help='Could be either a directory containing the meta_file and ckpt_file or a model protobuf (.pb) file') # parser.add_argument('image_files', type=str, nargs='+', help='Images to compare') parser.add_argument('reg_paths', type=str, help='The path of registered human faces') parser.add_argument('--image_size', type=int, help='Image size (height, width) in pixels.', default=160) parser.add_argument('--margin', type=int, help='Margin for the crop around the bounding box (height, width) in pixels.', default=44) parser.add_argument('--gpu_memory_fraction', type=float, help='Upper bound on the amount of GPU memory that will be used by the process.', default=1.0) return parser.parse_args(argv)

 

②MTCNNで顔検出し、顔画像のリストを作成

def load_and_align_data(image_paths, nrof_images,pnet, rnet, onet, args):
  img_list = []
  for image in image_paths:
    img = misc.imread(os.path.expanduser(image), mode='RGB') # 画像読み込み RGB形式

    img_size = np.asarray(img.shape)[0:2]
    bounding_boxes, _ = align.detect_face.detect_face(img, minsize,
                     pnet, rnet, onet, fd_threshold, factor) # 顔検出 if len(bounding_boxes) < 1: # 顔が検出されなかった場合 print("can't detect face", image) continue det = np.squeeze(bounding_boxes[0,0:4]) #顔の検出ポイント cropped = cropped_face(det, img, img_size, args) img_list.append(cropped) if nrof_images > 1 : images = np.stack(img_list) # 登録済み画像から顔のみ抽出したリスト else : images = img_list return images

 

③顔領域のクロッピング

def cropped_face(det, img, img_size, args):
  margin = args.margin
  bb = np.zeros(4, dtype=np.int32)
  bb[0] = np.maximum(det[0]-margin/2, 0)   # 左上 x(横)
  bb[1] = np.maximum(det[1]-margin/2, 0)   # 左上 y(縦)
  bb[2] = np.minimum(det[2]+margin/2, img_size[1])   # 右下 x(横)
  bb[3] = np.minimum(det[3]+margin/2, img_size[0])   # 右下 y(縦)

  cropped = img[bb[1]:bb[3],bb[0]:bb[2],:] # bounding boxの場所指定
  aligned = misc.imresize(cropped, 
    (input_image_size, input_image_size), interp='bilinear') # クロッピングしてリサイズ aligned = facenet.prewhiten(aligned) return aligned

 

④各顔のユークリッド距離を計算

def cal_distance(emb_reg, emb_video, nrof_images):
  dist = np.zeros(nrof_images, dtype=np.float64)
  dist_ave = 0.
  cnt = 1
  for j in range(nrof_images):
    dist[j] = np.sqrt(np.sum(np.square(np.subtract(emb_reg[j,:],
                        emb_video[0, :])))) #ユークリッド距離計算 dist.sort() #距離が短い順に並び替え for x in range(3): # kNN, k=3 dist_ave += dist[x] cnt += 1 if cnt > len(dist): break dist_ave = dist_ave / float(cnt-1) # 登録済み画像とのユークリッド距離(最近点3個) return dist_ave

 

実行時には以下のように引数を指定します。

$ python [重みファイルのパス] [登録する顔画像のパス] \
$ --image_size 160 --margin 32 --gpu_memory_fraction 0

登録したい顔画像をフォルダに入れて、引数でそのパスを指定します。
簡単のため、登録できる人数は一人だけとしていますが、
画像は複数枚入れてもOKです。
パス指定は data/registered/*のようにワイルドカードで複数枚の指定が可能です。

その他の引数は元のcompare.pyと同様です。

 

処理内容を図にすると以下のような感じです。
登録顔画像と未知の顔の距離を計算し、閾値比較をします。
顔画像を4枚以上登録した場合は距離が近い方から3点の距離の平均値を
閾値比較に使用しています。

f:id:masashi_k:20190804003948p:plain

閾値は fr_thresholdという変数で定義しています。
上記ソースコードでは閾値は1.2ですが、カメラの撮影条件によって
適切な値に設定してください。

 

ラズパイ上で動作させる

このスクリプトをさらに応用して、登録した顔がカメラに写ったら
その画像をJPGで保存するプログラムを作ります。
さらに、これをラズパイ上で動作させ、撮影したことが分かるように
撮影したらLEDを1秒間点灯させるようにしたいと思います。
(ラズパイで動かす意味はあまりありませんが・・・)

以前勉強した、ラズパイでLEDを点灯させる処理を上記のソースコードに追加します。

masaeng.hatenablog.com

 

追加したソースコード

import RPi.GPIO as GPIO
import time
import subprocess

LED_PIN = 26          # 36pin

def main():
    num = 1
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(LED_PIN ,GPIO.OUT)

    if dist_ave < fr_threshold: # 認識のしきい値
      #plot result idx under box
      text_x = v_bb[i][0]
      text_y = v_bb[i][3] + 20
      print('Find registered person', end='')
      cv2.rectangle(frame, (v_bb[i][0], v_bb[i][1]), 
                                 (v_bb[i][2], v_bb[i][3]), (0, 0, 255), 2)

      GPIO.output(LED_PIN , GPIO.HIGH)
      cv2.imwrite("picture{0:03d}.jpg".format(num), frame)  #保存先を指定
      time.sleep(1)
      GPIO.output(LED_PIN , GPIO.LOW)
      num = num + 1

 

回路図は前回同様こちらです。38,40ピンは今回使いません。

f:id:masashi_k:20190706235403p:plain

 

作成したスクリプト実行させてみるとこのように登録顔を見つけたときにLEDが点灯します。リード線で接続している緑色のLEDがGPIO16 36ピンにつながっています。

f:id:masashi_k:20190809233831j:plain

息子の写真1枚登録し、1~2時間ほどスクリプトを走らせてみた結果、このように写真が撮れています。(リモートからラズパイにアクセスしています。)

f:id:masashi_k:20190809232558p:plain

全部で47枚の写真が撮れており、正しく息子を判別して写真撮影できたのはわずか7枚(正解率約15%)でした。そもそも顔でないものを顔と誤判別していたり、親子である私と息子の識別ができていないなど、性能はまだまだでした。登録顔が1枚だけだったので、いろいろな角度の顔画像を登録すればもう少し性能が上がるのではと思います。 

まとめ

 GITHUBで公開されているFaceNetの顔認識を使って、登録した人の写真を自動で撮影する機器をラズベリーパイで作ってみました。登録顔が1枚だけだったので、精度が今一つでしたが、FaceNetの概要を理解でき、また、所望の機能をプログラムで実現することができました。今後もディープラーニングを使った機器のアイデアを考えて、実際に作っていけたらと考えています。

 

 

【読書記録】- THE TEAM 5つの法則

 今回は読書メモ。リンクアンドモチベーション取締役 麻野耕司さんのTHE TEAMという本を読んでみたので、学んだことを記録します。

今の仕事は個人作業の要素が強いのですが、一方で、メンバーを信頼し、協力しながら困難に立ち向かうといったチームで進める仕事に憧れがあります。
将来そんなチームを作れるようにチームの成功の法則を理解しておきたいと思います。

THE TEAM 5つの法則 (NewsPicks Book)

THE TEAM 5つの法則 (NewsPicks Book)

 

 

5つの法則 

チームの法則は以下の5つが紹介されています。
頭文字をとるとABCDEで覚えやすいです。

f:id:masashi_k:20190731221759p:plain

 

Aim - 目標設定

目標には行動レベル、成果レベル、意義レベルの3つのレベルがある。
行動レベルの目標は取るべきアクションがわかりやすい、意義レベルの目標は何をするかの自由度が高く、メンバーが自ら何をするかを考える必要がある。
メンバーの能力に応じて目標のレベルを変える。
ブレイクスルーは意義レベルの目標を立てないと起こせない。

 

Boarding - 人員選定

ビジネスの環境変化が激しいケースでは人員の流動性、メンバーの多様性が重要。
いろいろなスキルを持った人をメンバーに入れる必要あり。

 

Communication - 意思疎通

ルールを定めることで時間的なコミュニケーションコストを減らせる。
しかし、細かすぎるルールはNG。 環境変化が大きいとすぐ使えなくなる。
誰が言うか、どのように言うかで伝わり方が変わる。
メンバーの経験、感覚、思考、能力を考慮した声掛けが必要。
心理的に安全な場を作る。(何を言っても大丈夫、発言を責めないなど)

 

Decision - 意思決定

合議をする場合:
 選択肢を選ぶ基準とその優先順位を決める
 その上で選択肢を考える

リーダーが決める場合:
 どの案もメリット、デメリットがある 五分五分のことも多いので早く決める
 決めた後はメンバーが着実に実行する 決定を正解にするのはメンバー

 

Engagement - 共感創造

理念・方針/活動・成長/人材・風土/待遇・特権
これらいずれかでメンバーのモチベーションを高める

 Engagement = 報酬の魅力(WILL) x 達成可能性(CAN) x 危機感(MUST)

近年感情報酬(やりがい、仲間とのつながり、貢献など)を重視する傾向が強い。

 

その他

当事者意識をメンバーに持たせるには・・・
 ・人員の余剰を減らす
 ・責任範囲を明確化する
 ・参画感を持たせる 

FaceNet(顔認識)を動かしてみた

YOLOやSSDなどディープラーニングのネットワークをいくつか試してきましたが、
今回は顔認識のニューラルネットワークであるFaceNetを動かしてみましたので
手順を記録しておきます。

FaceNetの概要

FaceNetは2015年にGoogleが発表した顔認証用のニューラルネットワークです。

FaceNetの論文はこちらから参照できます。
https://arxiv.org/pdf/1503.03832.pdf

 

Siamese Network

FaceNetはSiamese(シャム) Networkをベースにしており、
入力画像がどのクラスに属するかではなく、
画像同士が似ているか似てないかを表す距離を学習します。

   f:id:masashi_k:20190725220116p:plain

クラスに分類する場合、候補となる人のどれに当たるかを分類することになるため、
各人の画像を大量に収集して学習する必要があります。
さらに、候補者が増えた場合は、再学習が必要です。
一方、Siamese Networkのように画像間の距離を算出する場合は、
その距離により2つの顔画像が同一人物かどうかを判断できるので
少ない画像で判別が可能となります。
画像は1枚でも判定が可能でそのような学習タスクをOne Shot Learningと呼びます。

 

Triplet Loss

Siamese Networkでは2つの画像のペアで学習を行いますが、
FaceNetでは3つの画像の組み合わせで学習を行います。
その時に使用する損失関数がTriplet Lossと呼ばれるもので、
基準となるAnchorに対して、同一ラベルのものを近くに、異なるラベルのものを
遠くに置くように学習します。

f:id:masashi_k:20190725222629p:plain

 

FaceNetのネットワーク構成

ネットワーク構成は以下のようになっています。
顔のみを切り出し、正規化した画像を入力します。
DEEP ARCHITECTUREの部分が特徴量を抽出するCNNで、
論文上ではZeiler&Fergus、Inceptionを使用しています。
他のCNNを使用することも可能と思われます。
その出力CNNのに対してL2ノルムを取り、Triplet Lossを計算します。

f:id:masashi_k:20190725222937p:plain

 

OpenFace

オープンソースの顔認識技術としてOpenFaceというものもあります。http://cmusatyalab.github.io/openface/

こちらはFaceNet論文を元にしたオープンソースの実装です。
DNN(Deep Neural Network)を用い、顔から128次元の特徴ベクトルを抽出します。

f:id:masashi_k:20190725223903j:plain

         OpenFaceのサイト(上記URL)から引用

 

FaceNetを動かしてみた

GITHUBで公開されているこちらのレポジトリを試してみました。

github.com

FaceNetは顔認識のニューラルネットワークですが、実際に認識をさせるためには
写真から顔の部分を切り出す必要があります。
このため、facenetではMTCNNという顔検知のニューラルネットワークを前段で
使用しており、その結果をfacenetに入力する形になっています。

MTCNNの論文
https://kpzhang93.github.io/MTCNN_face_detection_alignment/paper/spl.pdf

 

ネットワークの動かし方はこちらのサイトがとても参考になりました。

appliedmachinelearning.blog


Face_IDという別のレポジトリに関するものですが、
facenet(小文字表記はレポジトリ名)をベースに修正されているようで、
共通している部分が多いです。

上記サイトでは2つの命題について記載されています。
①Face verification:登録された顔が画像の中にあるかを判別
②Face identification:あらかじめ登録された顔のデータベースから
          どれに当てはまるかを識別
今回は①を試してみました 

環境

 OS:Windows 10 Home (64bit)
 Python 3.5
 Anaconda 4.2.0
 Tensorflow 1.12.0

 

動かし方

GITHUBからレポジトリをCloneして任意の場所に解凍します。
https://github.com/davidsandberg/facenet

②以下のURLから学習済みの重みをダウンロードし、facenetの下に保存します。
 データセットCASIA-WebFaceとVGGFace2の2種類あります。
https://drive.google.com/file/d/1R77HmFADxe87GmoLwzfgMu_HY0IhcyBz/view
https://drive.google.com/file/d/1EXPBSXwTaqrSC0OhUdXNmKSh9qJUQ55-/view

③Anacondaプロンプトを起動し、プロジェクトディレクトリに移動します。
Requirements.txtに記載されているライブラリをインストールします。
必要なライブラリは以下の通りです。

  Tensorflow
  scipy
  scikit-learn
  opencv-python
  h5py 
  matplotlib
  Pillow
  requests
  psutil

※以下のコマンドで自動で必要なライブラリをインストールできますが、
Tensorflowのバージョンが1.7.0になってしまうので、ダウングレードが不要であれば、
Requirements.txtを修正のうえ、実行してください。
私の環境では1.12.0でも問題なく動作しました。

$ pip install -r requirements.txt

 

④サンプルスクリプトcompare.pyを実行

$ python [重みファイルのパス] [比較したい画像ファイルパス(2つ以上も可)] \
$ --image_size 160 --margin 32 --gpu_memory_fraction 0

重みファイルのパス:②で取得したファイルのパスを指定
比較したい画像パス:顔の類似度を計算したい画像ファイルを指定
 手元に画像がない場合はサンプルとしてfacenet/data/imagesの中に画像があります。
--image_size:画像サイズ デフォルトは160pixel
--margin:顔検知した際、検知した場所から何pixel外側をクロップするか
     デフォルト 44pixel
--gpu_memory_fraction:GPUメモリ使用量の設定

具体的にはこのようになります。

$ python src/compare.py src/20180402-114759 \
$ data/images/Anthony_Hopkins_0001.jpg \
$ data/images/Anthony_Hopkins_0002.jpg \
$ --image_size 160 --margin 32 --gpu_memory_fraction 0

結果は以下の通りとなります。

Images:
0: data\images\Anthony_Hopkins_0001.jpg
1: data\images\Anthony_Hopkins_0002.jpg

Distance matrix
     0            1
0   0.0000   0.8516
1   0.8516   0.0000

Anthony_Hopkinsの画像を二枚入力し、0と1の画像の距離が0.856となっています。
距離が短いほど2つの顔が類似しているということになります。

同一人物だけだと面白くないので別の顔画像で試した結果も示します。
Face_IDのレポジトリにはもう少しサンプル画像が入っていますので、
それを使って試しました。

結果はこちら。

Images:
0: facenet/dataset/test-images/mark1.jpeg
1: facenet/dataset/test-images/mark.jpeg
2: facenet/dataset/test-images/bradley.jpeg
3: facenet/dataset/test-images/hritik.jpeg

 

Distance matrix
        0         1         2         3

0    0.0000    0.6942    1.4113    1.4643
1    0.6942    0.0000    1.4722    1.4246
2    1.4113    1.4722    0.0000    1.1274
3    1.4643    1.4246    1.1274    0.0000

0,1がマークザッカーバーグで同一人物、2,3は別の人物です。
結果を見ると0-1の距離が約0.7と短く、それ以外の距離はすべて1以上です。
FaceNetの論文では1.1が閾値としてよいとされているので、
確かに同一人物では画像間の距離が1.1以下となっていることを確認できました。

まとめ

GITHUBに公開されているFaceNetのレポジトリを実際に動かしてみて、
顔同士の距離が算出され、それが人の類似度になっていることを確認しました。
次は、このサンプルコードを使って、ラズパイでなにか作ってみたいと思います。

参考サイト

https://qiita.com/koshian2/items/554b3cbef6aeb1aab10d

https://www.slideshare.net/kaorunasuno/20150611-nasuno

https://blog.imind.jp/entry/2019/05/01/142232

https://blog.imind.jp/entry/2019/05/06/192603