Compare commits
3 Commits
master
...
2021_11_09
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dd0f2b606 | |||
| 54e2d07dde | |||
| b519dfc0bb |
23
README.rst
23
README.rst
@ -8,8 +8,12 @@ Detect and recognize single/multi-faces from camera;
|
||||
|
||||
调用摄像头进行人脸识别, 支持多张人脸同时识别;
|
||||
|
||||
#. Tkinter 人脸录入界面, 支持录入时设置姓名 / Face register GUI with Tkinter, support setting name when registering
|
||||
|
||||
#. 摄像头人脸录入 / Face register
|
||||
.. image:: introduction/face_register_tkinter_GUI.png
|
||||
:align: center
|
||||
|
||||
#. 简单的 OpenCV 摄像头人脸录入界面 / Simple face register GUI with OpenCV
|
||||
|
||||
.. image:: introduction/face_register.png
|
||||
:align: center
|
||||
@ -84,7 +88,13 @@ Steps
|
||||
|
||||
git clone https://github.com/coneypo/Dlib_face_recognition_from_camera
|
||||
|
||||
#. 进行人脸信息采集录入 / Register faces
|
||||
#. 进行人脸信息采集录入, Tkinter GUI / Register faces with Tkinter GUI
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 get_faces_from_camera_tkinter.py
|
||||
|
||||
#. 进行人脸信息采集录入, OpenCV GUI / Register faces with OpenCV GUI
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@ -122,11 +132,12 @@ Repo 的 tree / 树状图:
|
||||
::
|
||||
|
||||
.
|
||||
├── get_faces_from_camera.py # Step 1. Face register
|
||||
├── get_faces_from_camera.py # Step 1. Face register GUI with OpenCV
|
||||
├── get_faces_from_camera_tkinter.py # Step 1. Face register GUI with Tkinter
|
||||
├── features_extraction_to_csv.py # Step 2. Feature extraction
|
||||
├── face_reco_from_camera.py # Step 3. Face recognizer
|
||||
├── face_reco_from_camera_single_face.py # Step 3. Face recognizer for single person
|
||||
├── face_reco_from_camera_ot.py # Step 3. Face recognizer with OT
|
||||
├── face_reco_from_camera_single_face.py # Step 3. Face recognizer for single person
|
||||
├── face_reco_from_camera_ot.py # Step 3. Face recognizer with OT
|
||||
├── face_descriptor_from_camera.py # Face descriptor computation
|
||||
├── how_to_use_camera.py # Use the default camera by opencv
|
||||
├── data
|
||||
@ -193,7 +204,7 @@ Python 源码介绍如下:
|
||||
从上一步存下来的图像文件中, 提取人脸数据存入CSV;
|
||||
|
||||
* 会生成一个存储所有特征人脸数据的 "features_all.csv";
|
||||
* size: n*128 , n means n people you registered and 128 means 128D features of the face
|
||||
* size: n*129 , n means nx faces you registered and 129 means face name + 128D features of this face
|
||||
|
||||
|
||||
#. face_reco_from_camera.py:
|
||||
|
||||
@ -54,13 +54,13 @@ class Face_Recognizer:
|
||||
csv_rd = pd.read_csv(path_features_known_csv, header=None)
|
||||
for i in range(csv_rd.shape[0]):
|
||||
features_someone_arr = []
|
||||
for j in range(0, 128):
|
||||
self.face_name_known_list.append(csv_rd.iloc[i][0])
|
||||
for j in range(1, 129):
|
||||
if csv_rd.iloc[i][j] == '':
|
||||
features_someone_arr.append('0')
|
||||
else:
|
||||
features_someone_arr.append(csv_rd.iloc[i][j])
|
||||
self.face_feature_known_list.append(features_someone_arr)
|
||||
self.face_name_known_list.append("Person_"+str(i+1))
|
||||
logging.info("Faces in Database:%d", len(self.face_feature_known_list))
|
||||
return 1
|
||||
else:
|
||||
@ -187,7 +187,7 @@ class Face_Recognizer:
|
||||
self.current_frame_face_cnt = len(faces)
|
||||
|
||||
# 7. 在这里更改显示的人名 / Modify name if needed
|
||||
self.show_chinese_name()
|
||||
# self.show_chinese_name()
|
||||
|
||||
# 8. 写名字 / Draw name
|
||||
img_with_name = self.draw_name(img_rd)
|
||||
@ -206,8 +206,8 @@ class Face_Recognizer:
|
||||
# OpenCV 调用摄像头并进行 process
|
||||
def run(self):
|
||||
# cap = cv2.VideoCapture("video.mp4") # Get video stream from video file
|
||||
cap = cv2.VideoCapture(0) # Get video stream from camera
|
||||
cap.set(3, 480) # 640x480
|
||||
cap = cv2.VideoCapture("0") # Get video stream from camera
|
||||
cap.set(3, 480) # 640x480
|
||||
self.process(cap)
|
||||
|
||||
cap.release()
|
||||
|
||||
@ -82,13 +82,13 @@ class Face_Recognizer:
|
||||
csv_rd = pd.read_csv(path_features_known_csv, header=None)
|
||||
for i in range(csv_rd.shape[0]):
|
||||
features_someone_arr = []
|
||||
for j in range(0, 128):
|
||||
self.face_name_known_list.append(csv_rd.iloc[i][0])
|
||||
for j in range(1, 129):
|
||||
if csv_rd.iloc[i][j] == '':
|
||||
features_someone_arr.append('0')
|
||||
else:
|
||||
features_someone_arr.append(csv_rd.iloc[i][j])
|
||||
self.face_features_known_list.append(features_someone_arr)
|
||||
self.face_name_known_list.append("Person_" + str(i + 1))
|
||||
logging.info("Faces in Database: %d", len(self.face_features_known_list))
|
||||
return 1
|
||||
else:
|
||||
|
||||
@ -80,13 +80,13 @@ class Face_Recognizer:
|
||||
csv_rd = pd.read_csv(path_features_known_csv, header=None)
|
||||
for i in range(csv_rd.shape[0]):
|
||||
features_someone_arr = []
|
||||
for j in range(0, 128):
|
||||
self.face_name_known_list.append(csv_rd.iloc[i][0])
|
||||
for j in range(1, 129):
|
||||
if csv_rd.iloc[i][j] == '':
|
||||
features_someone_arr.append('0')
|
||||
else:
|
||||
features_someone_arr.append(csv_rd.iloc[i][j])
|
||||
self.features_known_list.append(features_someone_arr)
|
||||
self.face_name_known_list.append("Person_" + str(i + 1))
|
||||
logging.info("Faces in Database: %d", len(self.features_known_list))
|
||||
return 1
|
||||
else:
|
||||
|
||||
@ -70,7 +70,7 @@ def return_features_mean_personX(path_face_personX):
|
||||
# 计算 128D 特征的均值 / Compute the mean
|
||||
# personX 的 N 张图像 x 128D -> 1 x 128D
|
||||
if features_list_personX:
|
||||
features_mean_personX = np.array(features_list_personX).mean(axis=0)
|
||||
features_mean_personX = np.array(features_list_personX, dtype=object).mean(axis=0)
|
||||
else:
|
||||
features_mean_personX = np.zeros(128, dtype=int, order='C')
|
||||
return features_mean_personX
|
||||
@ -80,17 +80,23 @@ def main():
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# 获取已录入的最后一个人脸序号 / Get the order of latest person
|
||||
person_list = os.listdir("data/data_faces_from_camera/")
|
||||
person_num_list = []
|
||||
for person in person_list:
|
||||
person_num_list.append(int(person.split('_')[-1]))
|
||||
person_cnt = max(person_num_list)
|
||||
person_list.sort()
|
||||
|
||||
with open("data/features_all.csv", "w", newline="") as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
for person in range(person_cnt):
|
||||
for person in person_list:
|
||||
# Get the mean/average features of face/personX, it will be a list with a length of 128D
|
||||
logging.info("%sperson_%s", path_images_from_camera, str(person + 1))
|
||||
features_mean_personX = return_features_mean_personX(path_images_from_camera + "person_" + str(person + 1))
|
||||
logging.info("%sperson_%s", path_images_from_camera, person)
|
||||
features_mean_personX = return_features_mean_personX(path_images_from_camera + person)
|
||||
|
||||
if len(person.split('_', 2)) == 2:
|
||||
# "person_x"
|
||||
person_name = person
|
||||
else:
|
||||
# "person_x_tom"
|
||||
person_name = person.split('_', 2)[-1]
|
||||
features_mean_personX = np.insert(features_mean_personX, 0, person_name, axis=0)
|
||||
# features_mean_personX will be 129D, person name + 128 features
|
||||
writer.writerow(features_mean_personX)
|
||||
logging.info('\n')
|
||||
logging.info("所有录入人脸数据存入 / Save all the features of faces registered into: data/features_all.csv")
|
||||
|
||||
291
get_faces_from_camera_tkinter.py
Normal file
291
get_faces_from_camera_tkinter.py
Normal file
@ -0,0 +1,291 @@
|
||||
from tkinter import *
|
||||
from tkinter import font as tkFont
|
||||
from PIL import Image, ImageTk
|
||||
import dlib
|
||||
import numpy as np
|
||||
import cv2
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import logging
|
||||
|
||||
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
|
||||
detector = dlib.get_frontal_face_detector()
|
||||
|
||||
|
||||
class Face_Register:
|
||||
def __init__(self):
|
||||
|
||||
self.existing_faces_cnt = 0 # 已录入的人脸计数器 / cnt for counting saved faces
|
||||
self.ss_cnt = 0 # 录入 person_n 人脸时图片计数器 / cnt for screen shots
|
||||
self.current_frame_faces_cnt = 0 # 当前帧中人脸计数器 / cnt for counting faces in current frame
|
||||
|
||||
# Tkinter GUI
|
||||
self.win = Tk()
|
||||
self.win.title("Face Register @coneypo")
|
||||
self.win.geometry("1300x550")
|
||||
|
||||
# GUI left part
|
||||
self.frame_left_camera = Frame(self.win)
|
||||
self.label = Label(self.win)
|
||||
self.label.pack(side=LEFT)
|
||||
self.frame_left_camera.pack()
|
||||
|
||||
# GUI right part
|
||||
self.frame_right_info = Frame(self.win)
|
||||
self.label_cnt_face_in_database = Label(self.frame_right_info, text=str(self.existing_faces_cnt))
|
||||
self.label_fps_info = Label(self.frame_right_info, text="")
|
||||
self.input_name = Entry(self.frame_right_info)
|
||||
self.input_name_char = ""
|
||||
self.label_warning = Label(self.frame_right_info)
|
||||
self.label_face_cnt = Label(self.frame_right_info, text="Faces in current frame: ")
|
||||
self.log_all = Label(self.frame_right_info)
|
||||
|
||||
self.font_title = tkFont.Font(family='Helvetica', size=20, weight='bold')
|
||||
self.font_step_title = tkFont.Font(family='Helvetica', size=15, weight='bold')
|
||||
self.font_warning = tkFont.Font(family='Helvetica', size=15, weight='bold')
|
||||
|
||||
self.path_photos_from_camera = "data/data_faces_from_camera/"
|
||||
self.current_face_dir = ""
|
||||
self.font = cv2.FONT_ITALIC
|
||||
|
||||
# Current frame and face ROI position
|
||||
self.current_frame = np.ndarray
|
||||
self.face_ROI_image = np.ndarray
|
||||
self.face_ROI_width_start = 0
|
||||
self.face_ROI_height_start = 0
|
||||
self.face_ROI_width = 0
|
||||
self.face_ROI_height = 0
|
||||
self.ww = 0
|
||||
self.hh = 0
|
||||
|
||||
self.out_of_range_flag = FALSE
|
||||
self.face_folder_created_flag = FALSE
|
||||
|
||||
# FPS
|
||||
self.frame_time = 0
|
||||
self.frame_start_time = 0
|
||||
self.fps = 0
|
||||
self.fps_show = 0
|
||||
self.start_time = time.time()
|
||||
|
||||
self.cap = cv2.VideoCapture(0) # Get video stream from camera
|
||||
# self.cap = cv2.VideoCapture("test.mp4") # Input local video
|
||||
|
||||
# 删除之前存的人脸数据文件夹 / Delete old face folders
|
||||
def GUI_clear_data(self):
|
||||
# 删除之前存的人脸数据文件夹, 删除 "/data_faces_from_camera/person_x/"...
|
||||
folders_rd = os.listdir(self.path_photos_from_camera)
|
||||
for i in range(len(folders_rd)):
|
||||
shutil.rmtree(self.path_photos_from_camera + folders_rd[i])
|
||||
if os.path.isfile("data/features_all.csv"):
|
||||
os.remove("data/features_all.csv")
|
||||
self.label_cnt_face_in_database['text'] = "0"
|
||||
self.existing_faces_cnt = 0
|
||||
self.log_all["text"] = "Face images and `features_all.csv` removed!"
|
||||
|
||||
def GUI_get_input_name(self):
|
||||
self.input_name_char = self.input_name.get()
|
||||
self.create_face_folder()
|
||||
self.label_cnt_face_in_database['text'] = str(self.existing_faces_cnt)
|
||||
|
||||
def GUI_info(self):
|
||||
Label(self.frame_right_info,
|
||||
text="Face register",
|
||||
font=self.font_title).grid(row=0, column=0, columnspan=3, sticky=W, padx=2, pady=20)
|
||||
|
||||
Label(self.frame_right_info,
|
||||
text="FPS: ").grid(row=1, column=0, columnspan=2, sticky=W, padx=5, pady=2)
|
||||
self.label_fps_info.grid(row=1, column=2, sticky=W, padx=5, pady=2)
|
||||
|
||||
Label(self.frame_right_info,
|
||||
text="Faces in database: ").grid(row=2, column=0, columnspan=2, sticky=W, padx=5, pady=2)
|
||||
self.label_cnt_face_in_database.grid(row=2, column=2, columnspan=3, sticky=W, padx=5, pady=2)
|
||||
|
||||
Label(self.frame_right_info,
|
||||
text="Faces in current frame: ").grid(row=3, column=0, columnspan=2, sticky=W, padx=5, pady=2)
|
||||
self.label_face_cnt.grid(row=3, column=2, columnspan=3, sticky=W, padx=5, pady=2)
|
||||
|
||||
self.label_warning.grid(row=4, column=0, columnspan=3, sticky=W, padx=5, pady=2)
|
||||
|
||||
# Step 1: Clear old data
|
||||
Label(self.frame_right_info,
|
||||
font=self.font_step_title,
|
||||
text="Step 1: Clear face photos").grid(row=5, column=0, columnspan=2, sticky=W, padx=5, pady=20)
|
||||
Button(self.frame_right_info,
|
||||
text='Clear',
|
||||
command=self.GUI_clear_data).grid(row=6, column=0, columnspan=3, sticky=W, padx=5, pady=2)
|
||||
|
||||
# Step 2: Input name and create folders for face
|
||||
Label(self.frame_right_info,
|
||||
font=self.font_step_title,
|
||||
text="Step 2: Input name").grid(row=7, column=0, columnspan=2, sticky=W, padx=5, pady=20)
|
||||
|
||||
Label(self.frame_right_info, text="Name: ").grid(row=8, column=0, sticky=W, padx=5, pady=0)
|
||||
self.input_name.grid(row=8, column=1, sticky=W, padx=0, pady=2)
|
||||
|
||||
Button(self.frame_right_info,
|
||||
text='Input',
|
||||
command=self.GUI_get_input_name).grid(row=8, column=2, padx=5)
|
||||
|
||||
# Step 3: Save current face in frame
|
||||
Label(self.frame_right_info,
|
||||
font=self.font_step_title,
|
||||
text="Step 3: Save face image").grid(row=9, column=0, columnspan=2, sticky=W, padx=5, pady=20)
|
||||
|
||||
Button(self.frame_right_info,
|
||||
text='Save current face',
|
||||
command=self.save_current_face).grid(row=10, column=0, columnspan=3, sticky=W)
|
||||
|
||||
# Log
|
||||
self.log_all.grid(row=11, column=0, columnspan=20, sticky=W, padx=5, pady=20)
|
||||
|
||||
self.frame_right_info.pack()
|
||||
|
||||
# 新建保存人脸图像文件和数据 CSV 文件夹 / Mkdir for saving photos and csv
|
||||
def pre_work_mkdir(self):
|
||||
# 新建文件夹 / Create folders to save face images and csv
|
||||
if os.path.isdir(self.path_photos_from_camera):
|
||||
pass
|
||||
else:
|
||||
os.mkdir(self.path_photos_from_camera)
|
||||
|
||||
# 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1
|
||||
def check_existing_faces_cnt(self):
|
||||
if os.listdir("data/data_faces_from_camera/"):
|
||||
# 获取已录入的最后一个人脸序号 / Get the order of latest person
|
||||
person_list = os.listdir("data/data_faces_from_camera/")
|
||||
person_num_list = []
|
||||
for person in person_list:
|
||||
person_order = person.split('_')[1].split('_')[0]
|
||||
person_num_list.append(int(person_order))
|
||||
self.existing_faces_cnt = max(person_num_list)
|
||||
|
||||
# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
|
||||
else:
|
||||
self.existing_faces_cnt = 0
|
||||
|
||||
# 更新 FPS / Update FPS of Video stream
|
||||
def update_fps(self):
|
||||
now = time.time()
|
||||
# 每秒刷新 fps / Refresh fps per second
|
||||
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
|
||||
self.fps_show = self.fps
|
||||
self.start_time = now
|
||||
self.frame_time = now - self.frame_start_time
|
||||
self.fps = 1.0 / self.frame_time
|
||||
self.frame_start_time = now
|
||||
|
||||
self.label_fps_info["text"] = str(self.fps.__round__(2))
|
||||
|
||||
def create_face_folder(self):
|
||||
# # 4. 新建存储人脸的文件夹 / Create the folders for saving faces
|
||||
self.existing_faces_cnt += 1
|
||||
if self.input_name_char:
|
||||
self.current_face_dir = self.path_photos_from_camera + \
|
||||
"person_" + str(self.existing_faces_cnt) + "_" + \
|
||||
self.input_name_char
|
||||
else:
|
||||
self.current_face_dir = self.path_photos_from_camera + \
|
||||
"person_" + str(self.existing_faces_cnt)
|
||||
os.makedirs(self.current_face_dir)
|
||||
self.log_all["text"] = "\"" + self.current_face_dir + "/\" created!"
|
||||
logging.info("\n%-40s %s", "新建的人脸文件夹 / Create folders:", self.current_face_dir)
|
||||
|
||||
self.ss_cnt = 0 # 将人脸计数器清零 / Clear the cnt of screen shots
|
||||
self.face_folder_created_flag = 1 # 已经按下 'n' / Pressed 'n' already
|
||||
|
||||
def save_current_face(self):
|
||||
if self.face_folder_created_flag:
|
||||
if self.current_frame_faces_cnt == 1:
|
||||
if not self.out_of_range_flag:
|
||||
self.ss_cnt += 1
|
||||
# 根据人脸大小生成空的图像 / Create blank image according to the size of face detected
|
||||
self.face_ROI_image = np.zeros((int(self.face_ROI_height * 2), self.face_ROI_width * 2, 3),
|
||||
np.uint8)
|
||||
for ii in range(self.face_ROI_height * 2):
|
||||
for jj in range(self.face_ROI_width * 2):
|
||||
self.face_ROI_image[ii][jj] = self.current_frame[self.face_ROI_height_start - self.hh + ii][
|
||||
self.face_ROI_width_start - self.ww + jj]
|
||||
self.log_all["text"] = "\"" + self.current_face_dir + "/img_face_" + str(
|
||||
self.ss_cnt) + ".jpg\"" + " saved!"
|
||||
self.face_ROI_image = cv2.cvtColor(self.face_ROI_image, cv2.COLOR_BGR2RGB)
|
||||
|
||||
cv2.imwrite(self.current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg", self.face_ROI_image)
|
||||
logging.info("%-40s %s/img_face_%s.jpg", "写入本地 / Save into:",
|
||||
str(self.current_face_dir), str(self.ss_cnt) + ".jpg")
|
||||
else:
|
||||
self.log_all["text"] = "Please do not out of range!"
|
||||
else:
|
||||
self.log_all["text"] = "No face in current frame!"
|
||||
else:
|
||||
self.log_all["text"] = "Please run step 2!"
|
||||
|
||||
def get_frame(self):
|
||||
try:
|
||||
if self.cap.isOpened():
|
||||
ret, frame = self.cap.read()
|
||||
return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
except:
|
||||
print("Error: No video input!!!")
|
||||
|
||||
# 获取人脸 / Main process of face detection and saving
|
||||
def process(self):
|
||||
ret, self.current_frame = self.get_frame()
|
||||
faces = detector(self.current_frame, 0)
|
||||
# Get frame
|
||||
if ret:
|
||||
self.update_fps()
|
||||
self.label_face_cnt["text"] = str(len(faces))
|
||||
# 检测到人脸 / Face detected
|
||||
if len(faces) != 0:
|
||||
# 矩形框 / Show the ROI of faces
|
||||
for k, d in enumerate(faces):
|
||||
self.face_ROI_width_start = d.left()
|
||||
self.face_ROI_height_start = d.top()
|
||||
# 计算矩形框大小 / Compute the size of rectangle box
|
||||
self.face_ROI_height = (d.bottom() - d.top())
|
||||
self.face_ROI_width = (d.right() - d.left())
|
||||
self.hh = int(self.face_ROI_height / 2)
|
||||
self.ww = int(self.face_ROI_width / 2)
|
||||
|
||||
# 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
|
||||
if (d.right() + self.ww) > 640 or (d.bottom() + self.hh > 480) or (d.left() - self.ww < 0) or (
|
||||
d.top() - self.hh < 0):
|
||||
self.label_warning["text"] = "OUT OF RANGE"
|
||||
self.label_warning['fg'] = 'red'
|
||||
self.out_of_range_flag = TRUE
|
||||
color_rectangle = (255, 0, 0)
|
||||
else:
|
||||
self.out_of_range_flag = FALSE
|
||||
self.label_warning["text"] = ""
|
||||
color_rectangle = (255, 255, 255)
|
||||
self.current_frame = cv2.rectangle(self.current_frame,
|
||||
tuple([d.left() - self.ww, d.top() - self.hh]),
|
||||
tuple([d.right() + self.ww, d.bottom() + self.hh]),
|
||||
color_rectangle, 2)
|
||||
self.current_frame_faces_cnt = len(faces)
|
||||
img = Image.fromarray(self.current_frame)
|
||||
# Convert image to PhotoImage
|
||||
imgtk = ImageTk.PhotoImage(image=img)
|
||||
self.label.imgtk = imgtk
|
||||
self.label.configure(image=imgtk)
|
||||
|
||||
self.win.after(20, self.process)
|
||||
|
||||
def run(self):
|
||||
self.pre_work_mkdir()
|
||||
self.check_existing_faces_cnt()
|
||||
self.GUI_info()
|
||||
self.process()
|
||||
self.win.mainloop()
|
||||
|
||||
|
||||
def main():
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
Face_Register_con = Face_Register()
|
||||
Face_Register_con.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
introduction/face_register_tkinter_GUI.png
Normal file
BIN
introduction/face_register_tkinter_GUI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
0
test_tkinter.py
Normal file
0
test_tkinter.py
Normal file
Reference in New Issue
Block a user