# -*- coding: utf-8 -*-
##------------------------------------------
## Face recognition Program step3 Ver 0.04
## platform: linux / windows
##
## 2022.09.15 Masahiro Izutsu
##------------------------------------------
## face_rec4.py
## ver 0.02 2022.07.14 ソース修正
## ver 0.02 2022.07.16 大文字の拡張子(.JPG)にも対応
## ver 0.03 2022.08.19 自動ドア制御追加
## ver 0.04 2022.09.15 認証結果処理を別ファイルに
# import処理
import cv2
import numpy as np
import face_recognition
import cvui
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_movedlg
import my_dialog
import my_process
import face_rec_yaml
import door_ctrl
import rec_result
# 定数定義
title = 'Face recognition (step4) Ver 0.04'
IMAGE_EXT = {'.bmp', '.png', '.jpeg', '.jpg', '.JPG', '.tif'}
FACE_FOLDER = 'face_img' # 登録画像フォルダパス
FACE_LIST = 'facelist.txt' # 登録リストファイル
ATTEND_LOG = 'attend.csv' # 記録ファイル名
ATTEND_COUNT = 10 # 同一認識フレーム数
ATTEND_DSEC = 20 # 記録制限秒数
STATUS_H = 40 # ステータスラインの高さ
STATUS_LV = 200 # ステータスラインの背景レベル
INPUT_IMAGE_DEF = 'cam' # 入力イメージ・パラメータ初期値
# 入力パラメータの定義
# -o 出力ファイルパス(出力ファイル指定) 'xxxx/xxxxxx.xxx' フォルダは存在していること
# 静止画ファイルの拡張子 '.jpg 動画ファイルは '.mp4'
#
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('-m', '--mode', metavar = 'ACTION_MODE', type=str, default = '0',
help = 'Action mode (0/1/2) Default value is \'0\'')
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, facefol, action_mode, toler, log, titleflg, speedflg, outpath):
printG(f' - Program title : {title}')
printG(f' - OpenCV version : {cv2.__version__}')
printY(f' - Input image : {input_str}')
printY(f' - Face Folder : {facefol}')
printY(f' - Action mode : {action_mode}')
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
# 顔の領域取得
# in: 画像イメージ (OpenCV)
# out: 顔の領域リスト (1/4サイズ座標)
#
def get_face_locations(img):
img0 = cv2.resize(img, (0, 0), None, 0.25, 0.25)
img0 = cv2.cvtColor(img0, cv2.COLOR_BGR2RGB)
face_frame = face_recognition.face_locations(img0)
return face_frame
# 顔検出記録
# in: attend_logfile 記録ファイル名
# name 検出した顔の名前
# logger ログ出力オブジェクト
# out: 結果 (True/False)
#
def mark_attendance(attend_logfile, name, logger):
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)
rec_result.recognition_result(name, time_now, logger)
return result
# 顔登録
# in: facefol 顔登録フォルダ
# match_name 一致した名前
# img 登録する顔のイメージ
# my_csv_treatment CSV リストファイルのオブジェクト
# logger ログ出力オブジェクト
# out: 登録フラグ
#
def face_registration(facefol, match_name, img, my_csv_treatment, logger):
befor, after = my_csv_treatment.read_list2_csv()
file_path = my_datetime.now_strftime('%Y%m%d_%H%M%S') + '.jpg'
retf = True
savef = False
ttle = f"'{file_path}'の登録"
msg = '登録する名前を入力してください。'
msg_r = ''
while(retf):
file_name = my_movedlg.input_dialog(ttle, msg, init = match_name)
if file_name == None or len(file_name) == 0: # [X] ボタン キャンセル
msg_r = 'キャンセル'
retf = False # リトライ・ループ終了
break
# 登録名が入力された場合
retf = False # リトライ・ループ終了
row_data = [file_path, file_name] # 登録データ
if len(befor) <= 0:
msg_r = '初回登録'
my_csv_treatment.write_csv(row_data)
savef = True
else:
regf = False
for f in after:
if f == file_name:
regf = True
break
if regf:
if my_movedlg.yesno_dialog('TEST', '登録済みです。入れ替えますか?'):
msg_r = '入れ替え'
for n in range(len(after)):
if after[n] == file_name:
os.remove(f'{facefol}/{befor[n]}')
befor[n] = file_path
my_csv_treatment.write_list2_csv(befor, after)
savef = True
else:
msg_r = '再設定'
retf = True
else:
msg_r = '追加登録'
befor.insert(0, file_path)
after.insert(0, file_name)
my_csv_treatment.write_list2_csv(befor, after)
savef = True
if savef:
cv2.imwrite(f'{facefol}/{file_path}', img)
logger.debug(f'{msg_r}: {file_path} → {file_name}')
return savef
# 登録画像のリストアップ
# in: facefol 顔登録フォルダ
# my_file_treatment ファイル処理のオブジェクト
# my_csv_treatment CSV リストファイルのオブジェクト
# logger ログ出力オブジェクト
# out: face_names 登録された顔の名前(日本語)
# encode_list_known 登録された顔のエンコード・リスト
#
def get_encode_list(facefol, my_file_treatment, my_csv_treatment, logger):
face_images = [] # 登録画像のイメージリスト
face_names = [] # 登録画像の名前リスト(日本語名)
facelist = my_file_treatment.get_file_list_sel(f'{facefol}/*', IMAGE_EXT)
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(f'face list: {face_names}')
encode_list_known = find_encodings(face_images)
logger.debug(f'Find {len(encode_list_known)} face registration images !!')
return face_names, encode_list_known
# ** main関数 **
def main():
# 日本語フォント指定
fontFace = my_puttext.get_font()
# 入力パラメータの処理
ARGS = parse_args().parse_args()
input_str = ARGS.input
action_mode = int(ARGS.mode)
outpath = ARGS.out
# アプリケーション設定値
facefol = face_rec_yaml.get_facefolder() # 登録画像フォルダパス
toler = face_rec_yaml.get_tolerance() # 認証しきい値 (0.0〜1.0)文字列
titleflg = face_rec_yaml.get_titleflg() # タイトル表示 (y/n
speedflg = face_rec_yaml.get_speedflg() # F.P.S.表示 (y/n)
uart_enbl = face_rec_yaml.get_uart_enable() # UART 許可 (自動ドア制御)
uart_port = face_rec_yaml.get_uart_port() # UART ポート
logsel = face_rec_yaml.get_loglevel() # ログ表示レベル (-1/0/1/2/3/4/5)
# ファイル処理クラスのインスタンス
my_file_treatment = my_file.FileTreatment(logsel)
my_csv_treatment = my_csv.CSVtreatment(f'{facefol}/{FACE_LIST}', 'utf_8_sig', 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, facefol, action_mode, toler, logsel, titleflg, speedflg, outpath)
# 自動ドア制御オブジェクト生成
if uart_enbl == 'Yes':
door = door_ctrl.Door()
ser = door.set_port(uart_port)
if ser is None:
printR(f"'{uart_port}' port not found.")
else:
printG(f"'{uart_port}' port ready.")
# 登録画像のリストアップ
face_names, encode_list_known = get_encode_list(facefol, my_file_treatment, my_csv_treatment, logger)
# 関数内パラメータ
window_name = title + " (hit 'q' or 'esc' key to exit)"
window_cap = 'Capture window'
inface_count = 0 # フレーム内に検出した顔の数
match_name = '' # 一致した名前
match_count = 0 # 連続で一致した回数
facerec_flg = False # 顔検出フラグ
save_flg = False # 登録フラグ
frame_cap_still = None # 静止画フレーム
# 入力準備
if (isstream):
# カメラ
cap = cv2.VideoCapture(input_stream)
success, img_frame = cap.read()
if success == False:
logger.error('camera read error !!')
loopflg = cap.isOpened()
img_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
img_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
else:
# 画像ファイル読み込み
img_frame = cv2.imread(input_stream)
if img_frame is None:
logger.error('unable to read the input.')
return 0
# アスペクト比を固定してリサイズ(スクリーンに収まるサイズ)
h, w = my_movedlg.get_display_size()
mx = h - 10 if h > w else w - 10
img_frame = my_process.frame_resize(img_frame, mx)
loopflg = True # 1回ループ
img_width = img_frame.shape[1]
img_height = img_frame.shape[0]
# 画面ステータス領域 (画面下部 STATUS_H pixel)の確保
y = STATUS_H if action_mode > 0 else 0
frame = np.zeros((img_height + y, img_width, 3), np.uint8)
frame[:,:,:] = STATUS_LV # 背景色 (200, 200, 200)
btn_x, btn_y = 40, img_height + 6 # 'Save'ボタン位置
btn_w, btn_h = 70, 32 # ボタン 幅/高さ
# 顔撮影位置(action_mode = 2)
x = int(img_width/8)
y = int(img_height/8)
face_x0, face_y0, face_x1, face_y1 = x*3, y*2, x*5, y*5
# ウインドウ初期設定
cv2.namedWindow(window_name, flags=cv2.WINDOW_AUTOSIZE | cv2.WINDOW_GUI_NORMAL)
cv2.moveWindow(window_name, 20, 0)
cvui.init(window_name)
# 処理結果の記録 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 img_frame is None:
logger.error('unable to read the input.')
return 0
# フレーム(img_frame)内の顔検出
frame_cap = img_frame.copy() # 現在のフレームを保存
img_resize = cv2.resize(img_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)
# 画面上部に画像を配置
frame[0:img_height,:] = img_frame
frame[img_height:,:,:] = STATUS_LV
# 検出した顔の領域表示
inface_count = len(face_frame)
c = ( 0,102, 51) if inface_count > 0 else (71, 99, 255)
s = f'face:{inface_count: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) # 名前登録
logger.debug(f'Mark attendancs !! {result}')
if result and uart_enbl == 'Yes':
door.open_close() # 自動ドア開閉 !!!
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 inface_count == 1:
# フレーム内に顔が一つの場合
if action_mode == 2:
facerec_flg = True if x1 < face_x0 and y1 < face_y0 and x2 > face_x1 and y2 > face_y1 else False
else:
facerec_flg = True
elif inface_count <= 0:
# フレーム内に顔が検出できない場合
logger.debug("Face can't be detected !!")
match_name = ''
match_count = 0
facerec_flg = False
elif inface_count > 1:
# フレーム内に顔が2つ以上ある場合
facerec_flg = False
# 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)
# 顔撮影位置表示
if action_mode == 2:
cv2.rectangle(frame, (face_x0, face_y0), (face_x1, face_y1), (0, 0, 255), 1)
# -----------------------------------------
# ステータスライン・イベント処理
if (isstream and action_mode > 0):
# 'Save' ボタン・イベント処理
if facerec_flg:
if cvui.button(frame, btn_x, btn_y, "&Save"):
logger.debug("'Save' button pushed !!")
frame_cap_still = frame_cap.copy() # 静止画フレーム
face_fr = get_face_locations(frame_cap_still)
if len(face_fr) > 0:
save_flg = True
else:
x = int(img_width/2) - 100
y = int(img_height/2) - 50
print(x, y)
cv2.rectangle(frame_cap_still, (x, y), (x+180, y+ 40), (0, 0, 255), -1)
my_puttext.cv2_putText(frame_cap_still, '顔が検出できません !!', (x + 12, y + 30), fontFace, 16, (255, 255, 255), 0)
cv2.namedWindow(window_cap, flags=cv2.WINDOW_AUTOSIZE | cv2.WINDOW_GUI_NORMAL)
cv2.moveWindow(window_cap, img_width + 100, 0)
cv2.imshow(window_cap, frame_cap_still)
# 'Name' ボタン・イベント処理
if save_flg and my_winstat._is_visible(window_cap):
if cvui.button(frame, btn_x + btn_w + 10, btn_y, "&Name"):
logger.debug("'Name' button pushed !!")
# 顔登録
if input_str == 'cam' and face_registration(facefol, match_name, frame_cap_still, my_csv_treatment, logger):
# 登録画像の再リストアップ
face_names, encode_list_known = get_encode_list(facefol, my_file_treatment, my_csv_treatment, logger)
cv2.destroyWindow(window_cap)
save_flg = False
# -----------------------------------------
# ウインドウの更新/画像表示
cvui.update()
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, img_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()
if uart_enbl == 'Yes':
door.finish()
printC('FPS average: {:>10.2f}'.format(fpsWithTick.get_average()))
logger.info('Finished.\n')
# main関数エントリーポイント(実行開始)
if __name__ == "__main__":
main()
・「