# -*- coding: utf-8 -*-
##------------------------------------------
## Face recognition Program step1 Ver 0.02
## platform: linux / windows
##
## 2022.06.29 Masahiro Izutsu
##------------------------------------------
## face_rec1.py
## ver 0.02 2022.07.14 ソース修正
# 定数定義
title = 'Face recognition (step1) Ver 0.02'
IMAGE_EXT = {'.bmp', '.png', '.jpeg', '.jpg', '.tif'}
FACE_FOLDER = 'face_img' # 登録画像フォルダパス
FACE_LIST = 'facelist.txt' # 登録リストファイル
ATTEND_LOG = 'attend.csv' # 記録ファイル名
ATTEND_COUNT = 10 # 同一認識フレーム数
ATTEND_DSEC = 10 # 記録制限秒数
from os.path import expanduser
WINDOW_WIDTH = 640 # 表示ウインドウの幅 (静止画)
INPUT_IMAGE_DEF = 'cam' # 入力イメージ・パラメータ初期値
#INPUT_IMAGE_DEF = expanduser('./face_test/face_test1.mp4')
#INPUT_IMAGE_DEF = expanduser('./face_test/face_test.png')
# import処理
import cv2
import numpy as np
import face_recognition
import os
import argparse
import my_logging
from my_print import printG, printR, printY, printC, printB, printN
import my_puttext
import my_csv
import my_file
import my_winstat
import my_fps
import my_datetime
import my_dialog
# 入力パラメータの定義
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', metavar = 'INPUT_IMAGE', type=str, default = INPUT_IMAGE_DEF,
help = 'Absolute path to image file or cam for camera stream.'
'Default value is \'cam\'')
parser.add_argument('--tol', metavar = 'TOLERANCE', default = '0.6',
help = 'Strict the comparison parameter (0<<1) Default value is \'0.6\'')
parser.add_argument('--log', metavar = 'LOG', default = '3',
help = 'Log level(-1/0/1/2/3/4/5) Default value is \'3\'')
parser.add_argument('-t', '--title', metavar = 'TITLE', default = 'y',
help = 'Program title flag.(y/n) Default value is \'y\'')
parser.add_argument('-s', '--speed', metavar = 'SPEED', default = 'y',
help = 'Speed display flag.(y/n) Default calue is \'y\'')
parser.add_argument('-o', '--out', metavar = 'IMAGE_OUT', default = 'non',
help = 'Processed image file path. Default value is \'non\'')
return parser
# 入力パラメータの表示
def display_info(input_str, toler, log, titleflg, speedflg, outpath):
printG(f' - Program title : {title}')
printG(f' - OpenCV version : {cv2.__version__}')
printY(f' - Input image : {input_str}')
printY(f' - tolerance : {toler}')
printY(f' - Log level : {log}')
printY(f' - Program Title : {titleflg}')
printY(f' - Speed flag : {speedflg}')
printY(f' - Processed out : {outpath}')
# 128次元の顔エンコーディングのリスト
def find_encodings(images):
encode_list = []
for img in images:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
encode = face_recognition.face_encodings(img)[0]
encode_list.append(encode)
return encode_list
# 顔検出記録
def mark_attendance(attend_logfile, name):
result = False
encoding = 'utf_8_sig'
key = 'Name'
attime ='Time'
my_csv_treatment = my_csv.CSVtreatment(attend_logfile, encoding)
s = my_csv_treatment.read_csv()
if len(s) == 0: # 新規ファイル作成
data = [key, attime]
my_csv_treatment.write_csv(data)
time_now = my_datetime.now_strftime('%Y/%m/%d %H:%M:%S')
time = my_csv_treatment.read_csv_row_value(key, name, attime)
if time is None: # 記録なし
result = True
else: # 記録あり
result = my_datetime.time_check(time, time_now, ATTEND_DSEC)
if result:
data = [name, time_now]
my_csv_treatment.append_csv(data)
return result
# ** main関数 **
def main():
# 日本語フォント指定
fontFace = my_puttext.get_font()
# 入力パラメータの処理
ARGS = parse_args().parse_args()
input_str = ARGS.input
toler = ARGS.tol
log = ARGS.log
logsel = int(log)
titleflg = ARGS.title
speedflg = ARGS.speed
outpath = ARGS.out
# ファイル処理クラスのインスタンス
my_file_treatment = my_file.FileTreatment(logsel)
if input_str == '0':
input_str = my_dialog.select_image_movie_file()
if len(input_str) <= 0:
printR('Program canceled.')
return 0
input_stream = input_str
if input_str.lower() == "cam" or input_str.lower() == "camera":
input_stream = 0
isstream = True
else:
filetype = my_file_treatment.is_pict(input_stream)
isstream = filetype == 'None'
if (filetype == 'NotFound'):
printR(f"'{input_stream}': Input file Not found.")
return 0
if isstream and outpath != 'non' and os.path.splitext(outpath)[1] != '.mp4':
printR(f"'{outpath}': Output file type is '.mp4'")
return 0
# 記録ファイル名
s = input_str if input_stream == 0 else os.path.basename(input_str)
s = s.replace('.','_')
attend_logfile = f'{s}_{ATTEND_LOG}'
# アプリケーション・ログ設定
module = os.path.basename(__file__)
module_name = os.path.splitext(module)[0]
logger = my_logging.get_module_logger_sel(module_name, logsel)
logger.info('Starting..')
# 情報表示
display_info(input_str, toler, log, titleflg, speedflg, outpath)
# 登録画像のリストアップ
face_images = [] # 登録画像のイメージリスト
face_names = [] # 登録画像の名前リスト(日本語名)
facelist = my_file_treatment.get_file_list_sel(f'{FACE_FOLDER}/*', IMAGE_EXT)
my_csv_treatment = my_csv.CSVtreatment(f'{FACE_FOLDER}/{FACE_LIST}', 'utf_8_sig', logsel)
fname, jname = my_csv_treatment.read_list2_csv()
for reg_face in facelist:
current_img = cv2.imread(reg_face)
face_images.append(current_img)
logger.debug(f"'{reg_face}' read <{facelist is not None}>")
f = os.path.basename(reg_face)
s = os.path.splitext(f)[0]
# 日本語の登録名を設定する
n = len(fname)
if n > 0:
for i in range(n):
if f == fname[i]:
s = jname[i]
break
face_names.append(s)
logger.debug(face_names)
encode_list_known = find_encodings(face_images)
logger.debug(f'Find {len(encode_list_known)} face registration images !!')
window_name = title + " (hit 'q' or 'esc' key to exit)"
match_name = '' # 一致した名前
match_count = 0 # 連続で一致した回数
# 入力準備
if (isstream):
# カメラ
cap = cv2.VideoCapture(input_stream)
success, frame = cap.read()
loopflg = cap.isOpened()
img_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
img_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
else:
# 画像ファイル読み込み
frame = cv2.imread(input_stream)
if frame is None:
logger.error(f'unable to read the input.')
return 0
# アスペクト比を固定してリサイズ
img_h, img_w = frame.shape[:2]
if (img_w > WINDOW_WIDTH):
height = round(img_h * (WINDOW_WIDTH / img_w))
frame = cv2.resize(frame, dsize = (WINDOW_WIDTH, height))
loopflg = True # 1回ループ
img_width = frame.shape[1]
img_height = frame.shape[0]
# ウインドウ初期設定
cv2.namedWindow(window_name, flags=cv2.WINDOW_AUTOSIZE | cv2.WINDOW_GUI_NORMAL)
cv2.moveWindow(window_name, 20, 0)
# 処理結果の記録 step1
if (outpath != 'non'):
if (isstream):
fps = int(cap.get(cv2.CAP_PROP_FPS))
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
outvideo = cv2.VideoWriter(outpath, fourcc, fps, (img_width, img_height))
# 計測値初期化
fpsWithTick = my_fps.fpsWithTick()
frame_count = 0
fps_total = 0
fpsWithTick.get() # fps計測開始
# メインループ
while (loopflg):
if frame is None:
logger.error(f'nable to read the input.')
return 0
img_resize = cv2.resize(frame, (0, 0), None, 0.25, 0.25)
img_resize = cv2.cvtColor(img_resize, cv2.COLOR_BGR2RGB)
face_frame = face_recognition.face_locations(img_resize)
encode_frame = face_recognition.face_encodings(img_resize, face_frame)
# カメラフレーム内の顔検出
n = len(face_frame)
c = ( 0,102, 51) if n > 0 else (71, 99, 255)
s = f'face:{n:2}'
cv2.rectangle(frame, (10, 42), (68, 59), c, -1)
cv2.putText(frame, s, (15, 54), cv2.FONT_HERSHEY_DUPLEX, fontScale=0.4, color=(255, 255, 255), lineType=cv2.LINE_AA)
for encode_face, face_loc in zip(encode_frame, face_frame):
# フレーム内に顔が検出された場合
matches = face_recognition.compare_faces(encode_list_known, encode_face, tolerance=float(toler))
face_dis = face_recognition.face_distance(encode_list_known, encode_face)
logger.debug(f'count: {match_count:03} distance: {face_dis}')
match_idx = np.argmin(face_dis)
if matches[match_idx]:
name = face_names[match_idx].upper()
color = (102, 102, 0)
if match_name == '':
match_name = name
if match_count < 500:
match_count = match_count + 1
if match_count == ATTEND_COUNT:
result = mark_attendance(attend_logfile, name) # 名前登録
logger.debug(f'Mark attendancs !! {result}')
else:
name = '未登録'
color = (71, 99, 255)
match_name = ''
match_count = 0
y1, x2, y2, x1 = face_loc
y1, x2, y2, x1 = y1*4, x2*4, y2*4, x1*4
cv2.rectangle(frame, (x1, y1-30), (x2, y1), color, cv2.FILLED)
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
my_puttext.cv2_putText(frame, name, (x1+6, y1-6), fontFace, 16, (255, 255, 255), 0)
if len(face_frame) <= 0:
# フレーム内に顔が検出できない場合
logger.debug("Face can't be detected !!")
match_name = ''
match_count = 0
# FPSを計算する
fps = fpsWithTick.get()
st_fps = 'fps: {:>6.2f}'.format(fps)
if (speedflg == 'y'):
cv2.rectangle(frame, (80, 42), (165, 59), (90, 90, 90), -1)
cv2.putText(frame, st_fps, (85, 54), cv2.FONT_HERSHEY_DUPLEX, fontScale=0.4, color=(255, 255, 255), lineType=cv2.LINE_AA)
# タイトル描画
if (titleflg == 'y'):
cv2.putText(frame, title, (12, 32), cv2.FONT_HERSHEY_DUPLEX, fontScale=0.8, color=(0, 0, 0), lineType=cv2.LINE_AA)
cv2.putText(frame, title, (10, 30), cv2.FONT_HERSHEY_DUPLEX, fontScale=0.8, color=(200, 200, 0), lineType=cv2.LINE_AA)
# 画像表示
cv2.imshow(window_name, frame)
# 処理結果の記録 step2
if (outpath != 'non'):
if (isstream):
outvideo.write(frame)
else:
cv2.imwrite(outpath, frame)
# 何らかのキーが押されたら終了
breakflg = False
while(True):
key = cv2.waitKey(1)
if key == 27 or key == 113: # 'esc' or 'q'
breakflg = True
break
if not my_winstat._is_visible(window_name): # 'Close' button
breakflg = True
break
if (isstream):
break
if ((breakflg == False) and isstream):
# 次のフレームを読み出す
success, frame = cap.read()
if success == False:
break
loopflg = cap.isOpened()
else:
loopflg = False
# 終了処理
if (isstream):
cap.release()
# 処理結果の記録 step3
if (outpath != 'non'):
if (isstream):
outvideo.release()
cv2.destroyAllWindows()
printC('FPS average: {:>10.2f}'.format(fpsWithTick.get_average()))
logger.info('Finished.\n')
# main関数エントリーポイント(実行開始)
if __name__ == "__main__":
main()