├── README.md └── code ├── README.md ├── affineTrans.py ├── face_detection.py ├── face_recognition.py ├── featureExtraction.py ├── images ├── 0001.png ├── 0001_01.png ├── 0003.png ├── 0003_01.png ├── README.md ├── affine.jpg ├── keliamoniz1.jpg ├── keliamoniz2.jpg ├── libs │ ├── 0001.fea │ ├── 0001.png │ ├── 0002.fea │ ├── 0002.png │ ├── 0003.fea │ ├── 0003.png │ ├── 0011.fea │ ├── 0011.png │ ├── 0012.fea │ ├── 0012.png │ ├── 0013.fea │ ├── 0013.png │ ├── keliamoniz1.fea │ └── keliamoniz1.png └── person7_dect.jpg ├── models └── README.md ├── mtcnn ├── mtcnn.py ├── test_tfrecords.py └── tools.py └── window.py /README.md: -------------------------------------------------------------------------------- 1 | # FR-system 2 | 基于深度学习的人脸识别系统,包括人脸检测,人脸对齐,特征提取,人脸匹配四个过程。 3 | 1. 人脸检测用的MTCNN,人脸识别用的Arcface,基于TensorFlow。 4 | 2. 基于python Tkinter开发界面 5 | 6 | 介绍:https://coder67.cn/posts/f5c8e179.html 7 | 8 | 演示:https://www.bilibili.com/video/BV1Zi4y1P7iq/ 9 | 10 | **开发环境**: 11 | 1. python3.6.0+tensorflow 1.12.0 12 | 13 | 参考: 14 | 15 | 1. https://github.com/wangbm/MTCNN-Tensorflow 16 | 2. https://github.com/deepinsight/insightface 17 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | ## 使用说明 2 | 3 | 运行window.py,显示GUI 4 | 5 | 整个系统使用window.py中的window类和face_recogniton.py中的face_recogniton类。 6 | 7 | 其中face_recogniton是整合了face_detection.py,affineTrans.py,featureExtraction.py; 8 | 9 | 因此你可以通过这几个文件了解流程。 10 | 11 | ``` 12 | # 流程 13 | # 人脸检测-》人脸对齐-》特征向量提取-》相似度对比 14 | # 利用face_detection提取人脸,获取特征点,在affineTrans进行仿射变换对齐为112*112图片 15 | # 接着在featureExtraction中提取特征向量,最后进行相似度对比。 16 | ``` 17 | 18 | ### 注意 19 | 20 | 如果cudn内存不够或者系统没有GPU,请**取消注释**face_recognition中的一下代码 21 | 22 | ```python 23 | # 取消注释表示设置利用cpu 24 | # import os 25 | # 26 | # os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" 27 | # os.environ["CUDA_VISIBLE_DEVICES"] = "-1" 28 | ``` 29 | 30 | ### 运行环境 31 | 32 | python1.6.0+tensorflow 1.12.0 33 | 34 | ## 函数说明 35 | 36 | ### face_detection.py 37 | 38 | ```python 39 | # 获取5个特征点和人脸检测框 40 | def get_landmarkAndrect(image_path='./images/0001.png') 41 | ``` 42 | 43 | 返回人脸5个特征点数组列表和对应人脸检测框列表。 44 | 45 | 可以直接运行本文件得到如下结果 46 | 47 | ![person7_dect](images/person7_dect.jpg) 48 | 49 | ### affineTrans.py 50 | 51 | ```python 52 | def get_cropImage(face_landmarks, img_path="images/face_test.jpg", mode=1) 53 | ``` 54 | 55 | 返回仿射变换后的对齐图像(mode=1表示112\*112,mode=2表示112\*96) 56 | 57 | 可以直接运行本文件得到如下结果 58 | 59 | ![affine](images/affine.jpg) 60 | 61 | ### featureExtraction.py 62 | 63 | ```python 64 | # 获取112*112图片的特征向量 65 | def get_512features(img_path='images/112_112.jpg'): 66 | ``` 67 | 68 | 返回对齐图像的特征向量值 69 | 70 | 可以直接运行本文得到如下结果 71 | 72 | ``` 73 | -7.06420047e-03 -4.02247421e-02 5.08635081e-02 -4.72537568e-03 74 | 7.56369787e-04 -4.51981463e-03 1.13019533e-02 -5.53472899e-02 75 | -4.00648527e-02 -4.12669219e-02 -2.16021296e-02 1.97736938e-02 76 | -1.26779191e-02 1.98411848e-02 -7.07795396e-02 6.14322238e-02 77 | -3.71924862e-02 5.16385324e-02 -6.80582300e-02 4.65788767e-02 78 | ... 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /code/affineTrans.py: -------------------------------------------------------------------------------- 1 | import cv2, numpy 2 | import face_detection 3 | 4 | imgSize = [112, 112] 5 | 6 | normalizationPoint = [[0.31556875000000000, 0.4615741071428571], 7 | [0.68262291666666670, 0.4615741071428571], 8 | [0.50026249999999990, 0.6405053571428571], 9 | [0.34947187500000004, 0.8246919642857142], 10 | [0.65343645833333330, 0.8246919642857142]] 11 | 12 | coord5point = numpy.array(normalizationPoint) * 112 13 | 14 | # 最终的人脸对齐图像尺寸分为两种:112x96和112x112,并分别对应结果图像中的两组仿射变换目标点,如下所示 15 | imgSize1 = [112, 96] 16 | imgSize2 = [112, 112] 17 | 18 | coord5point1 = [[30.2946, 51.6963], # 112x96图像的目标点 19 | [65.5318, 51.6963], 20 | [48.0252, 71.7366], 21 | [33.5493, 92.3655], 22 | [62.7299, 92.3655]] 23 | 24 | coord5point2 = [[30.2946 + 8.0000, 51.6963], # 112x112图像的目标点,在112*96标准点基础上所有x坐标向右平移8px 25 | [65.5318 + 8.0000, 51.6963], 26 | [48.0252 + 8.0000, 71.7366], 27 | [33.5493 + 8.0000, 92.3655], 28 | [62.7299 + 8.0000, 92.3655]] 29 | 30 | 31 | def transformation_from_points(points1, points2): 32 | # 变量类型转换 33 | points1 = points1.astype(numpy.float64) 34 | points2 = points2.astype(numpy.float64) 35 | # mean()函数功能:求取均值 36 | # 经常操作的参数为axis,以m * n矩阵举例: 37 | # 38 | # axis 不设置值,对 m*n 个数求均值,返回一个实数 39 | # axis = 0:压缩行,对各列求均值,返回 1* n 矩阵 40 | # axis =1 :压缩列,对各行求均值,返回 m *1 矩阵 41 | c1 = numpy.mean(points1, axis=0) 42 | c2 = numpy.mean(points2, axis=0) 43 | points1 -= c1 44 | # print(c1) 45 | points2 -= c2 46 | # 计算标准差 47 | # axis=0计算每一列的标准差 48 | s1 = numpy.std(points1) 49 | s2 = numpy.std(points2) 50 | points1 /= s1 51 | points2 /= s2 52 | # numpy.linalg模块包含线性代数的函数,可以计算逆矩阵、求特征值、解线性方程组以及求解行列式等 53 | # svd为奇异值分解 54 | U, S, Vt = numpy.linalg.svd(points1.T * points2) 55 | R = (U * Vt).T 56 | return numpy.vstack([numpy.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), numpy.matrix([0., 0., 1.])]) 57 | 58 | 59 | def warp_im(img_im, orgi_landmarks, tar_landmarks): 60 | pts1 = numpy.float64(numpy.mat([[point[0], point[1]] for point in orgi_landmarks])) 61 | # print(pts1.shape) 62 | pts2 = numpy.float64(numpy.mat([[point[0], point[1]] for point in tar_landmarks])) 63 | # 求仿射变换矩阵(2行3列) 64 | M = transformation_from_points(pts1, pts2) 65 | # 第三个参数为变换后的图像大小(采用二元元祖(宽,高)),第二个参数为变换矩阵 66 | dst = cv2.warpAffine(img_im, M[:2], (img_im.shape[1], img_im.shape[0])) 67 | # print(M) 68 | # print(M[:2]) 69 | return dst 70 | 71 | 72 | def get_cropImage(face_landmarks, img_path="images/face_test.jpg", mode=1): 73 | pic_path = img_path 74 | img_im = cv2.imread(pic_path) 75 | coord5 = coord5point 76 | img_size = imgSize 77 | # 获取112*96图片 78 | if mode == 2: 79 | coord5 = coord5point1 80 | img_size = imgSize1 81 | # 仿射变换 82 | dst = warp_im(img_im, face_landmarks, coord5) 83 | # 截取112*112 84 | crop_im = dst[0:img_size[0], 0:img_size[1]] 85 | return crop_im 86 | 87 | 88 | if __name__ == '__main__': 89 | path = './images/person7.jpg' 90 | img = cv2.imread(path) 91 | features_points, rects = face_detection.get_landmarkAndrect(path) 92 | crop_img = get_cropImage(features_points[3], path) 93 | cv2.imshow("img1", crop_img) 94 | cv2.imshow('1', img) 95 | # cv2.imwrite('./images/affine.jpg', crop_img) 96 | cv2.waitKey(0) 97 | -------------------------------------------------------------------------------- /code/face_detection.py: -------------------------------------------------------------------------------- 1 | import time 2 | import tensorflow as tf 3 | import cv2 4 | import numpy as np 5 | from mtcnn.mtcnn import PNet, RNet, ONet 6 | from mtcnn.tools import detect_face, get_model_filenames 7 | 8 | 9 | # 获取5个特征点和人脸检测框 10 | def get_landmarkAndrect(image_path='./images/0001.png'): 11 | model_dir = './models/all_in_one/' 12 | minsize = 20 13 | threshold = [0.8, 0.8, 0.8] 14 | factor = 0.7 15 | 16 | img = cv2.imread(image_path) 17 | # cv2.imshow("1", img) 18 | # cv2.waitKey(0) 19 | # tools.py 20 | file_paths = get_model_filenames(model_dir) 21 | with tf.device('/gpu:0'): 22 | with tf.Graph().as_default(): 23 | gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.7) 24 | config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options) 25 | # config = tf.ConfigProto(allow_soft_placement=True) 26 | with tf.Session(config=config) as sess: 27 | if len(file_paths) == 3: 28 | image_pnet = tf.placeholder( 29 | tf.float32, [None, None, None, 3]) 30 | pnet = PNet({'data': image_pnet}, mode='test') 31 | out_tensor_pnet = pnet.get_all_output() 32 | 33 | image_rnet = tf.placeholder(tf.float32, [None, 24, 24, 3]) 34 | rnet = RNet({'data': image_rnet}, mode='test') 35 | out_tensor_rnet = rnet.get_all_output() 36 | 37 | image_onet = tf.placeholder(tf.float32, [None, 48, 48, 3]) 38 | onet = ONet({'data': image_onet}, mode='test') 39 | out_tensor_onet = onet.get_all_output() 40 | 41 | saver_pnet = tf.train.Saver( 42 | [v for v in tf.global_variables() 43 | if v.name[0:5] == "pnet/"]) 44 | saver_rnet = tf.train.Saver( 45 | [v for v in tf.global_variables() 46 | if v.name[0:5] == "rnet/"]) 47 | saver_onet = tf.train.Saver( 48 | [v for v in tf.global_variables() 49 | if v.name[0:5] == "onet/"]) 50 | 51 | saver_pnet.restore(sess, file_paths[0]) 52 | 53 | def pnet_fun(img): 54 | return sess.run( 55 | out_tensor_pnet, feed_dict={image_pnet: img}) 56 | 57 | saver_rnet.restore(sess, file_paths[1]) 58 | 59 | def rnet_fun(img): 60 | return sess.run( 61 | out_tensor_rnet, feed_dict={image_rnet: img}) 62 | 63 | saver_onet.restore(sess, file_paths[2]) 64 | 65 | def onet_fun(img): 66 | return sess.run( 67 | out_tensor_onet, feed_dict={image_onet: img}) 68 | 69 | else: 70 | saver = tf.train.import_meta_graph(file_paths[0]) 71 | saver.restore(sess, file_paths[1]) 72 | 73 | def pnet_fun(img): 74 | return sess.run( 75 | ('softmax/Reshape_1:0', 76 | 'pnet/conv4-2/BiasAdd:0'), 77 | feed_dict={ 78 | 'Placeholder:0': img}) 79 | 80 | def rnet_fun(img): 81 | return sess.run( 82 | ('softmax_1/softmax:0', 83 | 'rnet/conv5-2/rnet/conv5-2:0'), 84 | feed_dict={ 85 | 'Placeholder_1:0': img}) 86 | 87 | def onet_fun(img): 88 | return sess.run( 89 | ('softmax_2/softmax:0', 90 | 'onet/conv6-2/onet/conv6-2:0', 91 | 'onet/conv6-3/onet/conv6-3:0'), 92 | feed_dict={ 93 | 'Placeholder_2:0': img}) 94 | 95 | start_time = time.time() 96 | rectangles, points = detect_face(img, minsize, 97 | pnet_fun, rnet_fun, onet_fun, 98 | threshold, factor) 99 | duration = time.time() - start_time 100 | 101 | # print("检测时间:", duration) 102 | # print(type(rectangles)) 103 | points = np.transpose(points) 104 | # print("边界框:", rectangles[0]) 105 | # for i in range(len(rectangles)): 106 | # print("边界框:", rectangles[i]) 107 | # print("特征点", type(points), points.reshape(5, 2))#待修改 108 | # 如果有多个人的特征点 109 | num = points.shape[0] 110 | if num == 0: 111 | return [], [] 112 | 113 | split_points = np.vsplit(points, num) 114 | k = 0 115 | for i in split_points: 116 | split_points[k] = i.reshape(5, 2) 117 | k += 1 118 | return split_points, rectangles 119 | 120 | 121 | if __name__ == '__main__': 122 | path = './images/person7.jpg' 123 | img = cv2.imread(path) 124 | img_shape = img.shape 125 | fea_points, rectangles = get_landmarkAndrect(path) 126 | res_size = len(fea_points) 127 | for i in range(res_size): 128 | rect = rectangles[i] 129 | points = fea_points[i] 130 | top_left = (max(0, int(rect[0])), max(0, int(rect[1]))) 131 | right_bottom = (min(img_shape[1], int(rect[2])), min(img_shape[0], int(rect[3]))) 132 | cv2.rectangle(img, top_left, right_bottom, (0, 0, 255), 1) 133 | for i in range(5): 134 | cv2.circle(img, (points[i][0], points[i][1]), 1, (0, 0, 255), 4) 135 | cv2.imshow('1', img) 136 | cv2.waitKey(0) 137 | -------------------------------------------------------------------------------- /code/face_recognition.py: -------------------------------------------------------------------------------- 1 | import time 2 | import tensorflow as tf 3 | import cv2 4 | import numpy as np 5 | from mtcnn.mtcnn import PNet, RNet, ONet 6 | from mtcnn.tools import detect_face, get_model_filenames 7 | 8 | # 设置利用cpu 9 | # import os 10 | # 11 | # os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" 12 | # os.environ["CUDA_VISIBLE_DEVICES"] = "-1" 13 | 14 | 15 | class face_recognition: 16 | # 静态属性 17 | model_path = './models/all_in_one/' 18 | is_first = True 19 | p1 = 'null' 20 | p2 = 'null' 21 | p3 = 'null' 22 | 23 | images = 'null' 24 | dropout_rate = 'null' 25 | output_tensor = 'null' 26 | sess_recog = 'null' 27 | 28 | def __init__(self): 29 | self.imgSize = [112, 112] 30 | self.normalizationPoint = [[0.31556875000000000, 0.4615741071428571], 31 | [0.68262291666666670, 0.4615741071428571], 32 | [0.50026249999999990, 0.6405053571428571], 33 | [0.34947187500000004, 0.8246919642857142], 34 | [0.65343645833333330, 0.8246919642857142]] 35 | self.coord5point = np.array(self.normalizationPoint) * 112 36 | 37 | # 初始化 38 | face_recognition.init_session() 39 | 40 | # 获取5个特征点和人脸检测框 41 | def get_landmarkAndrect(self, image_path): 42 | minsize = 20 43 | threshold = [0.8, 0.8, 0.8] 44 | factor = 0.7 45 | img = cv2.imread(image_path) 46 | start_time = time.time() 47 | rectangles, points = detect_face(img, minsize, 48 | face_recognition.p1, face_recognition.p2, face_recognition.p3, 49 | threshold, factor) 50 | duration = time.time() - start_time 51 | print("检测时间:", duration) 52 | points = np.transpose(points) 53 | num = points.shape[0] 54 | if num == 0: 55 | return [], [] 56 | 57 | split_points = np.vsplit(points, num) 58 | k = 0 59 | for i in split_points: 60 | split_points[k] = i.reshape(5, 2) 61 | k += 1 62 | return split_points, rectangles 63 | 64 | def transformation_from_points(self, points1, points2): 65 | # 变量类型转换 66 | points1 = points1.astype(np.float64) 67 | points2 = points2.astype(np.float64) 68 | # mean()函数功能:求取均值 69 | # 经常操作的参数为axis,以m * n矩阵举例: 70 | # 71 | # axis 不设置值,对 m*n 个数求均值,返回一个实数 72 | # axis = 0:压缩行,对各列求均值,返回 1* n 矩阵 73 | # axis =1 :压缩列,对各行求均值,返回 m *1 矩阵 74 | c1 = np.mean(points1, axis=0) 75 | c2 = np.mean(points2, axis=0) 76 | points1 -= c1 77 | # print(c1) 78 | points2 -= c2 79 | # 计算标准差 80 | # axis=0计算每一列的标准差 81 | s1 = np.std(points1) 82 | s2 = np.std(points2) 83 | points1 /= s1 84 | points2 /= s2 85 | # numpy.linalg模块包含线性代数的函数,可以计算逆矩阵、求特征值、解线性方程组以及求解行列式等 86 | # svd为奇异值分解 87 | U, S, Vt = np.linalg.svd(points1.T * points2) 88 | R = (U * Vt).T 89 | return np.vstack([np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), np.matrix([0., 0., 1.])]) 90 | 91 | def warp_im(self, img_im, orgi_landmarks, tar_landmarks): 92 | pts1 = np.float64(np.mat([[point[0], point[1]] for point in orgi_landmarks])) 93 | # print(pts1.shape) 94 | pts2 = np.float64(np.mat([[point[0], point[1]] for point in tar_landmarks])) 95 | # 求仿射变换矩阵(2行3列) 96 | M = self.transformation_from_points(pts1, pts2) 97 | # 第三个参数为变换后的图像大小(采用二元元祖(宽,高)),第二个参数为变换矩阵 98 | dst = cv2.warpAffine(img_im, M[:2], (img_im.shape[1], img_im.shape[0])) 99 | # print(M) 100 | # print(M[:2]) 101 | return dst 102 | 103 | # 获取仿射变换后的图片 104 | def get_cropImage(self, face_landmarks, img_path, mode=1): 105 | pic_path = img_path 106 | img_im = cv2.imread(pic_path) 107 | # 仿射变换 108 | dst = self.warp_im(img_im, face_landmarks, self.coord5point) 109 | # 截取112*112 110 | crop_im = dst[0:self.imgSize[0], 0:self.imgSize[1]] 111 | return crop_im 112 | 113 | # 获取size为112*112的512维特征向量 114 | def get_512features(self, img_path): 115 | img = img_path 116 | if type(img_path) == str: 117 | img = cv2.imread(img_path, cv2.COLOR_BGR2RGB) 118 | img = np.array(img, dtype=np.float32) 119 | # 图片尺寸更改为112 * 112 120 | if img.shape[0] != 112 or img.shape[1] != 112: 121 | print("需要更改图片尺寸") 122 | img = cv2.resize(img, (112, 112)) 123 | else: 124 | if img.shape == (112, 96, 3): 125 | z = np.zeros((112, 8, 3), dtype=np.float32) 126 | img = np.append(img, z, axis=1) 127 | img = np.append(z, img, axis=1) 128 | # img = np.array(img, dtype=np.uint8) 129 | # cv.imshow("img",img) 130 | # cv.waitKey(0) 131 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 132 | img = np.array(img, dtype=np.float32) 133 | img = img - 127.5 134 | img = img * 0.0078125 135 | img = np.expand_dims(img, axis=0) 136 | 137 | prediction0 = face_recognition.sess_recog.run(face_recognition.output_tensor, feed_dict={face_recognition.images: img, face_recognition.dropout_rate: 1}) 138 | return prediction0 139 | 140 | # 获取任意图片大小的特征向量 141 | def get_single_feature_vector(self, img_path): 142 | features_points, _ = self.get_landmarkAndrect(img_path) 143 | crop_img = self.get_cropImage(features_points[0], img_path) 144 | flip_img = cv2.flip(crop_img, 1, dst=None) # 水平镜像 145 | vector = self.get_512features(crop_img) # 提取的512维特征向量 146 | vector_flip = self.get_512features(flip_img) 147 | # 向量求和,利用求和后的向量做相似度对比 148 | sum_vector = [] 149 | for i in range(512): 150 | sum_vector.append(vector[0][i] + vector_flip[0][i]) 151 | return sum_vector 152 | 153 | def cos_sim(self, vector_a, vector_b): 154 | """ 155 | 计算两个向量之间的余弦相似度 156 | :param vector_a: 向量 a 157 | :param vector_b: 向量 b 158 | :return: sim 159 | """ 160 | if type(vector_a) == list: 161 | vector_a = np.mat(vector_a) 162 | if type(vector_b) == list: 163 | vector_b = np.mat(vector_b) 164 | num = np.dot(vector_a, vector_b.T) 165 | denom = np.linalg.norm(vector_a) * np.linalg.norm(vector_b) 166 | sim = num / denom 167 | return sim 168 | 169 | @staticmethod 170 | def init_session(): 171 | if face_recognition.is_first: 172 | face_recognition.init_recog_session() 173 | face_recognition.is_first = False 174 | model_dir = face_recognition.model_path 175 | gragh = tf.get_default_graph() 176 | gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.7) 177 | config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options) 178 | sess = tf.Session(config=config) 179 | file_paths = get_model_filenames(model_dir) 180 | if len(file_paths) == 3: 181 | print(1) 182 | image_pnet = tf.placeholder( 183 | tf.float32, [None, None, None, 3]) 184 | pnet = PNet({'data': image_pnet}, mode='test') 185 | out_tensor_pnet = pnet.get_all_output() 186 | 187 | image_rnet = tf.placeholder(tf.float32, [None, 24, 24, 3]) 188 | rnet = RNet({'data': image_rnet}, mode='test') 189 | out_tensor_rnet = rnet.get_all_output() 190 | 191 | image_onet = tf.placeholder(tf.float32, [None, 48, 48, 3]) 192 | onet = ONet({'data': image_onet}, mode='test') 193 | out_tensor_onet = onet.get_all_output() 194 | 195 | saver_pnet = tf.train.Saver( 196 | [v for v in tf.global_variables() 197 | if v.name[0:5] == "pnet/"]) 198 | saver_rnet = tf.train.Saver( 199 | [v for v in tf.global_variables() 200 | if v.name[0:5] == "rnet/"]) 201 | saver_onet = tf.train.Saver( 202 | [v for v in tf.global_variables() 203 | if v.name[0:5] == "onet/"]) 204 | 205 | saver_pnet.restore(sess, file_paths[0]) 206 | 207 | def pnet_fun(img): 208 | return sess.run( 209 | out_tensor_pnet, feed_dict={image_pnet: img}) 210 | 211 | saver_rnet.restore(sess, file_paths[1]) 212 | 213 | def rnet_fun(img): 214 | return sess.run( 215 | out_tensor_rnet, feed_dict={image_rnet: img}) 216 | 217 | saver_onet.restore(sess, file_paths[2]) 218 | 219 | def onet_fun(img): 220 | return sess.run( 221 | out_tensor_onet, feed_dict={image_onet: img}) 222 | 223 | else: 224 | saver = tf.train.import_meta_graph(file_paths[0]) 225 | saver.restore(sess, file_paths[1]) 226 | 227 | def pnet_fun(img): 228 | return sess.run( 229 | ('softmax/Reshape_1:0', 230 | 'pnet/conv4-2/BiasAdd:0'), 231 | feed_dict={ 232 | 'Placeholder:0': img}) 233 | 234 | def rnet_fun(img): 235 | return sess.run( 236 | ('softmax_1/softmax:0', 237 | 'rnet/conv5-2/rnet/conv5-2:0'), 238 | feed_dict={ 239 | 'Placeholder_1:0': img}) 240 | 241 | def onet_fun(img): 242 | return sess.run( 243 | ('softmax_2/softmax:0', 244 | 'onet/conv6-2/onet/conv6-2:0', 245 | 'onet/conv6-3/onet/conv6-3:0'), 246 | feed_dict={ 247 | 'Placeholder_2:0': img}) 248 | 249 | face_recognition.p1 = pnet_fun 250 | face_recognition.p2 = rnet_fun 251 | face_recognition.p3 = onet_fun 252 | 253 | @staticmethod 254 | def init_recog_session(): 255 | if face_recognition.is_first: 256 | model_path = "models/ckpt_model_d" 257 | # saver = tf.train.import_meta_graph(model_path + '/InsightFace_iter_best_710000.ckpt.meta') # 加载图结构 258 | # 恢复tensorflow图,也就是读取神经网络的结构,从而无需再次构建网络 259 | saver = tf.train.import_meta_graph(model_path + '/InsightFace_iter_best_710000.ckpt.meta') 260 | # 获取当前图,为了后续训练时恢复变量 261 | gragh = tf.get_default_graph() 262 | images = gragh.get_tensor_by_name('img_inputs:0') 263 | dropout_rate = gragh.get_tensor_by_name('dropout_rate:0') 264 | output_tensor = gragh.get_tensor_by_name('resnet_v1_50/E_BN2/Identity:0') 265 | output_tensor1 = gragh.get_tensor_by_name('arcface_loss/norm_embedding:0') 266 | sess = tf.Session() 267 | # 如果没有checkpoint文件 268 | saver.restore(sess, model_path + '/InsightFace_iter_best_710000.ckpt') # 重点,将地址写到.ckpt 269 | 270 | face_recognition.sess_recog = sess 271 | face_recognition.images = images 272 | face_recognition.dropout_rate = dropout_rate 273 | face_recognition.output_tensor = output_tensor1 274 | 275 | 276 | -------------------------------------------------------------------------------- /code/featureExtraction.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import cv2 as cv 3 | import numpy as np 4 | 5 | model_path = "models/ckpt_model_d" 6 | # saver = tf.train.import_meta_graph(model_path + '/InsightFace_iter_best_710000.ckpt.meta') # 加载图结构 7 | # 恢复tensorflow图,也就是读取神经网络的结构,从而无需再次构建网络 8 | saver = tf.train.import_meta_graph(model_path + '/InsightFace_iter_best_710000.ckpt.meta') 9 | gragh = tf.get_default_graph() # 获取当前图,为了后续训练时恢复变量 10 | images = gragh.get_tensor_by_name('img_inputs:0') 11 | dropout_rate = gragh.get_tensor_by_name('dropout_rate:0') 12 | output_tensor = gragh.get_tensor_by_name('resnet_v1_50/E_BN2/Identity:0') 13 | output_tensor1 = gragh.get_tensor_by_name('arcface_loss/norm_embedding:0') 14 | sess = tf.Session() 15 | # 如果没有checkpoint文件 16 | saver.restore(sess, model_path + '/InsightFace_iter_best_710000.ckpt') # 重点,将地址写到.ckpt 17 | 18 | 19 | # 如果有checkpoint文件 20 | # saver.restore(sess, tf.train.latest_checkpoint(model_path))# 加载变量值 21 | 22 | 23 | def get_512features(img_path='images/112_112.jpg'): 24 | img = img_path 25 | if type(img_path) == str: 26 | img = cv.imread(img_path, cv.COLOR_BGR2RGB) 27 | img = np.array(img, dtype=np.float32) 28 | # 图片尺寸更改为112 * 112 29 | if img.shape[0] != 112 or img.shape[1] != 112: 30 | print("需要更改图片尺寸") 31 | img = cv.resize(img, (112, 112)) 32 | else: 33 | if img.shape == (112, 96, 3): 34 | z = np.zeros((112, 8, 3), dtype=np.float32) 35 | img = np.append(img, z, axis=1) 36 | img = np.append(z, img, axis=1) 37 | # img = np.array(img, dtype=np.uint8) 38 | # cv.imshow("img",img) 39 | # cv.waitKey(0) 40 | img = cv.cvtColor(img, cv.COLOR_BGR2RGB) 41 | img = np.array(img, dtype=np.float32) 42 | img = img - 127.5 43 | img = img * 0.0078125 44 | img = np.expand_dims(img, axis=0) 45 | 46 | prediction0 = sess.run(output_tensor1, feed_dict={images: img, dropout_rate: 1}) 47 | return prediction0 48 | 49 | 50 | if __name__ == '__main__': 51 | fea = get_512features('images/affine.jpg') 52 | print(fea) 53 | -------------------------------------------------------------------------------- /code/images/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/0001.png -------------------------------------------------------------------------------- /code/images/0001_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/0001_01.png -------------------------------------------------------------------------------- /code/images/0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/0003.png -------------------------------------------------------------------------------- /code/images/0003_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/0003_01.png -------------------------------------------------------------------------------- /code/images/README.md: -------------------------------------------------------------------------------- 1 | ## 使用说明 2 | 3 | 运行window.py,显示GUI 4 | 5 | 整个系统使用window.py中的window类和face_recogniton.py中的face_recogniton类。 6 | 7 | 其中face_recogniton是整合了face_detection.py,affineTrans.py,featureExtraction.py; 8 | 9 | 因此你可以通过这几个文件了解流程。 10 | 11 | ``` 12 | # 流程 13 | # 人脸检测-》人脸对齐-》特征向量提取-》相似度对比 14 | # 利用face_detection提取人脸,获取特征点,在affineTrans进行仿射变换对齐为112*112图片 15 | # 接着在featureExtraction中提取特征向量,最后进行相似度对比。 16 | ``` 17 | 18 | ### 注意 19 | 20 | 如果cudn内存不够或者系统没有GPU,请**取消注释**face_recognition中的一下代码 21 | 22 | ```python 23 | # 取消注释表示设置利用cpu 24 | # import os 25 | # 26 | # os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" 27 | # os.environ["CUDA_VISIBLE_DEVICES"] = "-1" 28 | ``` 29 | 30 | ### 运行环境 31 | 32 | python1.6.0+tensorflow 1.12.0 33 | 34 | ## 函数说明 35 | 36 | ### face_detection.py 37 | 38 | ```python 39 | # 获取5个特征点和人脸检测框 40 | def get_landmarkAndrect(image_path='./images/0001.png') 41 | ``` 42 | 43 | 返回人脸5个特征点数组列表和对应人脸检测框列表。 44 | 45 | 可以直接运行本文件得到如下结果 46 | 47 | ![person7_dect](../images/person7_dect.jpg) 48 | 49 | ### affineTrans.py 50 | 51 | ```python 52 | def get_cropImage(face_landmarks, img_path="images/face_test.jpg", mode=1) 53 | ``` 54 | 55 | 返回仿射变换后的对齐图像(mode=1表示112\*112,mode=2表示112\*96) 56 | 57 | 可以直接运行本文件得到如下结果 58 | 59 | ![affine](../images/affine.jpg) 60 | 61 | ### featureExtraction.py 62 | 63 | ```python 64 | # 获取112*112图片的特征向量 65 | def get_512features(img_path='images/112_112.jpg'): 66 | ``` 67 | 68 | 返回对齐图像的特征向量值 69 | 70 | 可以直接运行本文得到如下结果 71 | 72 | ``` 73 | -7.06420047e-03 -4.02247421e-02 5.08635081e-02 -4.72537568e-03 74 | 7.56369787e-04 -4.51981463e-03 1.13019533e-02 -5.53472899e-02 75 | -4.00648527e-02 -4.12669219e-02 -2.16021296e-02 1.97736938e-02 76 | -1.26779191e-02 1.98411848e-02 -7.07795396e-02 6.14322238e-02 77 | -3.71924862e-02 5.16385324e-02 -6.80582300e-02 4.65788767e-02 78 | ... 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /code/images/affine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/affine.jpg -------------------------------------------------------------------------------- /code/images/keliamoniz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/keliamoniz1.jpg -------------------------------------------------------------------------------- /code/images/keliamoniz2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/keliamoniz2.jpg -------------------------------------------------------------------------------- /code/images/libs/0001.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0001.fea -------------------------------------------------------------------------------- /code/images/libs/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0001.png -------------------------------------------------------------------------------- /code/images/libs/0002.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0002.fea -------------------------------------------------------------------------------- /code/images/libs/0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0002.png -------------------------------------------------------------------------------- /code/images/libs/0003.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0003.fea -------------------------------------------------------------------------------- /code/images/libs/0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0003.png -------------------------------------------------------------------------------- /code/images/libs/0011.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0011.fea -------------------------------------------------------------------------------- /code/images/libs/0011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0011.png -------------------------------------------------------------------------------- /code/images/libs/0012.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0012.fea -------------------------------------------------------------------------------- /code/images/libs/0012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0012.png -------------------------------------------------------------------------------- /code/images/libs/0013.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0013.fea -------------------------------------------------------------------------------- /code/images/libs/0013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/0013.png -------------------------------------------------------------------------------- /code/images/libs/keliamoniz1.fea: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/keliamoniz1.fea -------------------------------------------------------------------------------- /code/images/libs/keliamoniz1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/libs/keliamoniz1.png -------------------------------------------------------------------------------- /code/images/person7_dect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luqicoder/FR-system/1a9d269b1e015540d6207f4e469a347fb84aeb38/code/images/person7_dect.jpg -------------------------------------------------------------------------------- /code/models/README.md: -------------------------------------------------------------------------------- 1 | all_in_one文件夹:人脸检测模型 2 | 3 | ckpt_model_d文件夹:人脸识别模型 4 | 5 | 百度云盘地址: 6 | 链接: https://pan.baidu.com/s/1eZ_hGjKMsbeg39__aYfK9w?pwd=h9g6 提取码: h9g6 复制这段内容后打开百度网盘手机App,操作更方便哦 7 | -------------------------------------------------------------------------------- /code/mtcnn/mtcnn.py: -------------------------------------------------------------------------------- 1 | """Main script. Contain model definition and training code.""" 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2017 Baoming Wang 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | import os 26 | 27 | import tensorflow as tf 28 | import numpy as np 29 | 30 | 31 | def layer(op): 32 | 33 | def layer_decorated(self, *args, **kwargs): 34 | 35 | name = kwargs.setdefault('name', self.get_unique_name(op.__name__)) 36 | if len(self.terminals) == 0: 37 | raise RuntimeError('No input variables found for layer %s.' % name) 38 | elif len(self.terminals) == 1: 39 | layer_input = self.terminals[0] 40 | else: 41 | layer_input = list(self.terminals) 42 | layer_output = op(self, layer_input, *args, **kwargs) 43 | tf.add_to_collection('feature_map', layer_output) 44 | self.layers[name] = layer_output 45 | self.feed(layer_output) 46 | return self 47 | 48 | return layer_decorated 49 | 50 | 51 | class NetWork(object): 52 | 53 | def __init__(self, inputs, trainable=True, 54 | weight_decay_coeff=4e-3, mode='train'): 55 | 56 | self.inputs = inputs 57 | self.terminals = [] 58 | self.layers = dict(inputs) 59 | self.trainable = trainable 60 | self.mode = mode 61 | self.out_put = [] 62 | self.weight_decay_coeff = weight_decay_coeff 63 | 64 | if self.mode == 'train': 65 | self.tasks = [inp[0] for inp in inputs] 66 | self.weight_decay = {} 67 | self.setup_training_graph() 68 | else: 69 | self.setup() 70 | 71 | def setup_training_graph(self): 72 | 73 | for index, task in enumerate(self.tasks): 74 | self.weight_decay[task] = [] 75 | reuse_bool = False 76 | if index is not 0: 77 | reuse_bool = True 78 | self.setup(task=task, reuse=reuse_bool) 79 | 80 | def setup(self, task='data'): 81 | 82 | raise NotImplementedError('Must be implemented by the subclass.') 83 | 84 | def load(self, data_path, session, prefix, ignore_missing=False): 85 | 86 | data_dict = np.load(data_path, encoding='latin1').item() 87 | for op_name in data_dict: 88 | with tf.variable_scope(prefix + op_name, reuse=True): 89 | for param_name, data in data_dict[op_name].items(): 90 | try: 91 | var = tf.get_variable(param_name) 92 | session.run(var.assign(data)) 93 | except ValueError: 94 | if not ignore_missing: 95 | raise 96 | 97 | def feed(self, *args): 98 | 99 | assert len(args) != 0 100 | self.terminals = [] 101 | for fed_layer in args: 102 | if isinstance(fed_layer, str): 103 | try: 104 | fed_layer = self.layers[fed_layer] 105 | except KeyError: 106 | raise KeyError('Unknown layer name fed: %s' % fed_layer) 107 | self.terminals.append(fed_layer) 108 | return self 109 | 110 | def get_output(self): 111 | 112 | return self.terminals[-1] 113 | 114 | def get_all_output(self): 115 | 116 | return self.out_put 117 | 118 | def get_weight_decay(self): 119 | 120 | assert self.mode == 'train' 121 | return self.weight_decay 122 | 123 | def get_unique_name(self, prefix): 124 | 125 | ident = sum(t.startswith(prefix) for t, _ in self.layers.items()) + 1 126 | return '%s_%d' % (prefix, ident) 127 | 128 | def make_var(self, name, shape): 129 | 130 | return tf.get_variable( 131 | name, 132 | shape, 133 | trainable=self.trainable, 134 | initializer=tf.truncated_normal_initializer( 135 | stddev=1e-4)) 136 | 137 | def validate_padding(self, padding): 138 | 139 | assert padding in ('SAME', 'VALID') 140 | 141 | @layer 142 | def conv(self, inp, k_h, k_w, c_o, s_h, s_w, name, 143 | task=None, relu=True, padding='SAME', 144 | group=1, biased=True, wd=None): 145 | 146 | self.validate_padding(padding) 147 | c_i = int(inp.get_shape()[-1]) 148 | assert c_i % group == 0 149 | assert c_o % group == 0 150 | 151 | def convolve(i, k): return tf.nn.conv2d( 152 | i, k, [1, s_h, s_w, 1], padding=padding) 153 | with tf.variable_scope(name) as scope: 154 | kernel = self.make_var( 155 | 'weights', shape=[ 156 | k_h, k_w, c_i / group, c_o]) 157 | if group == 1: 158 | output = convolve(inp, kernel) 159 | else: 160 | input_groups = tf.split(inp, group, 3) 161 | kernel_groups = tf.split(kernel, group, 3) 162 | output_groups = [convolve(i, k) for i, k in 163 | zip(input_groups, kernel_groups)] 164 | output = tf.concat(output_groups, 3) 165 | if (wd is not None) and (self.mode == 'train'): 166 | self.weight_decay[task].append( 167 | tf.multiply(tf.nn.l2_loss(kernel), wd)) 168 | if biased: 169 | biases = self.make_var('biases', [c_o]) 170 | output = tf.nn.bias_add(output, biases) 171 | if relu: 172 | output = tf.nn.relu(output, name=scope.name) 173 | return output 174 | 175 | @layer 176 | def prelu(self, inp, name): 177 | 178 | with tf.variable_scope(name): 179 | i = int(inp.get_shape()[-1]) 180 | alpha = self.make_var('alpha', shape=(i,)) 181 | return tf.nn.relu(inp) + tf.multiply(alpha, -tf.nn.relu(-inp)) 182 | 183 | @layer 184 | def max_pool(self, input, k_h, k_w, s_h, s_w, name, 185 | padding='SAME'): 186 | 187 | self.validate_padding(padding) 188 | return tf.nn.max_pool(input, 189 | ksize=[1, k_h, k_w, 1], 190 | strides=[1, s_h, s_w, 1], 191 | padding=padding, 192 | name=name) 193 | 194 | @layer 195 | def fc(self, inp, num_out, name, task=None, relu=True, wd=None): 196 | 197 | with tf.variable_scope(name): 198 | input_shape = inp.get_shape() 199 | if input_shape.ndims == 4: 200 | dim = 1 201 | for d in input_shape[1:].as_list(): 202 | dim *= int(d) 203 | feed_in = tf.reshape(inp, [-1, dim]) 204 | else: 205 | feed_in, dim = (inp, input_shape[-1].value) 206 | weights = self.make_var('weights', shape=[dim, num_out]) 207 | if (wd is not None) and (self.mode == 'train'): 208 | self.weight_decay[task] \ 209 | .append(tf.multiply(tf.nn.l2_loss(weights), wd)) 210 | biases = self.make_var('biases', [num_out]) 211 | op = tf.nn.relu_layer if relu else tf.nn.xw_plus_b 212 | return op(feed_in, weights, biases, name=name) 213 | 214 | @layer 215 | def softmax(self, target, name=None): 216 | 217 | with tf.variable_scope(name): 218 | return tf.nn.softmax(target, name=name) 219 | 220 | 221 | class PNet(NetWork): 222 | 223 | def setup(self, task='data', reuse=False): 224 | 225 | with tf.variable_scope('pnet', reuse=reuse): 226 | ( 227 | self.feed(task) .conv( 228 | 3, 229 | 3, 230 | 10, 231 | 1, 232 | 1, 233 | padding='VALID', 234 | relu=False, 235 | name='conv1') .prelu( 236 | name='PReLU1') .max_pool( 237 | 2, 238 | 2, 239 | 2, 240 | 2, 241 | name='pool1') .conv( 242 | 3, 243 | 3, 244 | 16, 245 | 1, 246 | 1, 247 | padding='VALID', 248 | relu=False, 249 | name='conv2') .prelu( 250 | name='PReLU2') .conv( 251 | 3, 252 | 3, 253 | 32, 254 | 1, 255 | 1, 256 | task=task, 257 | padding='VALID', 258 | relu=False, 259 | name='conv3', 260 | wd=self.weight_decay_coeff) .prelu( 261 | name='PReLU3')) 262 | 263 | if self.mode == 'train': 264 | if task == 'cls': 265 | (self.feed('PReLU3') 266 | .conv(1, 1, 2, 1, 1, task=task, relu=False, 267 | name='pnet/conv4-1', wd=self.weight_decay_coeff)) 268 | elif task == 'bbx': 269 | (self.feed('PReLU3') 270 | .conv(1, 1, 4, 1, 1, task=task, relu=False, 271 | name='pnet/conv4-2', wd=self.weight_decay_coeff)) 272 | elif task == 'pts': 273 | (self.feed('PReLU3') 274 | .conv(1, 1, 10, 1, 1, task=task, relu=False, 275 | name='pnet/conv4-3', wd=self.weight_decay_coeff)) 276 | self.out_put.append(self.get_output()) 277 | else: 278 | (self.feed('PReLU3') 279 | .conv(1, 1, 2, 1, 1, relu=False, name='pnet/conv4-1') 280 | .softmax(name='softmax')) 281 | self.out_put.append(self.get_output()) 282 | (self.feed('PReLU3') 283 | .conv(1, 1, 4, 1, 1, relu=False, name='pnet/conv4-2')) 284 | self.out_put.append(self.get_output()) 285 | 286 | 287 | class RNet(NetWork): 288 | 289 | def setup(self, task='data', reuse=False): 290 | 291 | with tf.variable_scope('rnet', reuse=reuse): 292 | ( 293 | self.feed(task) .conv( 294 | 3, 295 | 3, 296 | 28, 297 | 1, 298 | 1, 299 | padding='VALID', 300 | relu=False, 301 | name='conv1') .prelu( 302 | name='prelu1') .max_pool( 303 | 3, 304 | 3, 305 | 2, 306 | 2, 307 | name='pool1') .conv( 308 | 3, 309 | 3, 310 | 48, 311 | 1, 312 | 1, 313 | padding='VALID', 314 | relu=False, 315 | name='conv2') .prelu( 316 | name='prelu2') .max_pool( 317 | 3, 318 | 3, 319 | 2, 320 | 2, 321 | padding='VALID', 322 | name='pool2') .conv( 323 | 2, 324 | 2, 325 | 64, 326 | 1, 327 | 1, 328 | padding='VALID', 329 | task=task, 330 | relu=False, 331 | name='conv3', 332 | wd=self.weight_decay_coeff) .prelu( 333 | name='prelu3') .fc( 334 | 128, 335 | task=task, 336 | relu=False, 337 | name='conv4', 338 | wd=self.weight_decay_coeff) .prelu( 339 | name='prelu4')) 340 | 341 | if self.mode == 'train': 342 | if task == 'cls': 343 | (self.feed('prelu4') 344 | .fc(2, task=task, relu=False, 345 | name='rnet/conv5-1', wd=self.weight_decay_coeff)) 346 | elif task == 'bbx': 347 | (self.feed('prelu4') 348 | .fc(4, task=task, relu=False, 349 | name='rnet/conv5-2', wd=self.weight_decay_coeff)) 350 | elif task == 'pts': 351 | (self.feed('prelu4') 352 | .fc(10, task=task, relu=False, 353 | name='rnet/conv5-3', wd=self.weight_decay_coeff)) 354 | self.out_put.append(self.get_output()) 355 | else: 356 | (self.feed('prelu4') 357 | .fc(2, relu=False, name='rnet/conv5-1') 358 | .softmax(name='softmax')) 359 | self.out_put.append(self.get_output()) 360 | (self.feed('prelu4') 361 | .fc(4, relu=False, name='rnet/conv5-2')) 362 | self.out_put.append(self.get_output()) 363 | 364 | 365 | class ONet(NetWork): 366 | 367 | def setup(self, task='data', reuse=False): 368 | 369 | with tf.variable_scope('onet', reuse=reuse): 370 | ( 371 | self.feed(task) .conv( 372 | 3, 373 | 3, 374 | 32, 375 | 1, 376 | 1, 377 | padding='VALID', 378 | relu=False, 379 | name='conv1') .prelu( 380 | name='prelu1') .max_pool( 381 | 3, 382 | 3, 383 | 2, 384 | 2, 385 | name='pool1') .conv( 386 | 3, 387 | 3, 388 | 64, 389 | 1, 390 | 1, 391 | padding='VALID', 392 | relu=False, 393 | name='conv2') .prelu( 394 | name='prelu2') .max_pool( 395 | 3, 396 | 3, 397 | 2, 398 | 2, 399 | padding='VALID', 400 | name='pool2') .conv( 401 | 3, 402 | 3, 403 | 64, 404 | 1, 405 | 1, 406 | padding='VALID', 407 | relu=False, 408 | name='conv3') .prelu( 409 | name='prelu3') .max_pool( 410 | 2, 411 | 2, 412 | 2, 413 | 2, 414 | name='pool3') .conv( 415 | 2, 416 | 2, 417 | 128, 418 | 1, 419 | 1, 420 | padding='VALID', 421 | relu=False, 422 | name='conv4') .prelu( 423 | name='prelu4') .fc( 424 | 256, 425 | relu=False, 426 | name='conv5') .prelu( 427 | name='prelu5')) 428 | 429 | if self.mode == 'train': 430 | if task == 'cls': 431 | (self.feed('prelu5') 432 | .fc(2, task=task, relu=False, 433 | name='onet/conv6-1', wd=self.weight_decay_coeff)) 434 | elif task == 'bbx': 435 | (self.feed('prelu5') 436 | .fc(4, task=task, relu=False, 437 | name='onet/conv6-2', wd=self.weight_decay_coeff)) 438 | elif task == 'pts': 439 | (self.feed('prelu5') 440 | .fc(10, task=task, relu=False, 441 | name='onet/conv6-3', wd=self.weight_decay_coeff)) 442 | self.out_put.append(self.get_output()) 443 | else: 444 | (self.feed('prelu5') 445 | .fc(2, relu=False, name='onet/conv6-1') 446 | .softmax(name='softmax')) 447 | self.out_put.append(self.get_output()) 448 | (self.feed('prelu5') 449 | .fc(4, relu=False, name='onet/conv6-2')) 450 | self.out_put.append(self.get_output()) 451 | (self.feed('prelu5') 452 | .fc(10, relu=False, name='onet/conv6-3')) 453 | self.out_put.append(self.get_output()) 454 | 455 | 456 | def read_and_decode(filename_queue, label_type, shape): 457 | 458 | reader = tf.TFRecordReader() 459 | _, serialized_example = reader.read(filename_queue) 460 | features = tf.parse_single_example( 461 | serialized_example, 462 | features={ 463 | 'image_raw': tf.FixedLenFeature([], tf.string), 464 | 'label_raw': tf.FixedLenFeature([], tf.string), 465 | }) 466 | image = tf.decode_raw(features['image_raw'], tf.uint8) 467 | image = tf.cast(image, tf.float32) 468 | 469 | image = (image - 127.5) * (1. / 128.0) 470 | image.set_shape([shape * shape * 3]) 471 | image = tf.reshape(image, [shape, shape, 3]) 472 | label = tf.decode_raw(features['label_raw'], tf.float32) 473 | 474 | if label_type == 'cls': 475 | image = tf.image.random_flip_left_right(image) 476 | image = tf.image.random_flip_up_down(image) 477 | label.set_shape([2]) 478 | elif label_type == 'bbx': 479 | label.set_shape([4]) 480 | elif label_type == 'pts': 481 | label.set_shape([10]) 482 | 483 | return image, label 484 | 485 | 486 | def inputs(filename, batch_size, num_epochs, label_type, shape): 487 | 488 | with tf.device('/cpu:0'): 489 | if not num_epochs: 490 | num_epochs = None 491 | 492 | with tf.name_scope('input'): 493 | filename_queue = tf.train.string_input_producer( 494 | filename, num_epochs=num_epochs) 495 | 496 | image, label = read_and_decode(filename_queue, label_type, shape) 497 | 498 | images, sparse_labels = tf.train.shuffle_batch( 499 | [image, label], batch_size=batch_size, num_threads=2, 500 | capacity=1000 + 3 * batch_size, 501 | min_after_dequeue=1000) 502 | 503 | return images, sparse_labels 504 | 505 | 506 | def train_net(Net, training_data, base_lr, loss_weight, 507 | train_mode, num_epochs=[1, None, None], 508 | batch_size=64, weight_decay=4e-3, 509 | load_model=False, load_filename=None, 510 | save_model=False, save_filename=None, 511 | num_iter_to_save=10000, 512 | gpu_memory_fraction=1): 513 | 514 | images = [] 515 | labels = [] 516 | tasks = ['cls', 'bbx', 'pts'] 517 | shape = 12 518 | if Net.__name__ == 'RNet': 519 | shape = 24 520 | elif Net.__name__ == 'ONet': 521 | shape = 48 522 | for index in range(train_mode): 523 | image, label = inputs(filename=[training_data[index]], 524 | batch_size=batch_size, 525 | num_epochs=num_epochs[index], 526 | label_type=tasks[index], 527 | shape=shape) 528 | images.append(image) 529 | labels.append(label) 530 | while len(images) is not 3: 531 | images.append(tf.placeholder(tf.float32, [None, shape, shape, 3])) 532 | labels.append(tf.placeholder(tf.float32)) 533 | net = Net((('cls', images[0]), ('bbx', images[1]), ('pts', images[2])), 534 | weight_decay_coeff=weight_decay) 535 | 536 | print('all trainable variables:') 537 | all_vars = tf.get_collection(key=tf.GraphKeys.TRAINABLE_VARIABLES) 538 | for var in all_vars: 539 | print(var) 540 | 541 | print('all local variable:') 542 | local_variables = tf.local_variables() 543 | for l_v in local_variables: 544 | print(l_v.name) 545 | 546 | prefix = str(all_vars[0].name[0:5]) 547 | out_put = net.get_all_output() 548 | cls_output = tf.reshape(out_put[0], [-1, 2]) 549 | bbx_output = tf.reshape(out_put[1], [-1, 4]) 550 | pts_output = tf.reshape(out_put[2], [-1, 10]) 551 | 552 | # cls loss 553 | softmax_loss = loss_weight[0] * \ 554 | tf.reduce_mean( 555 | tf.nn.softmax_cross_entropy_with_logits(labels=labels[0], 556 | logits=cls_output)) 557 | weight_losses_cls = net.get_weight_decay()['cls'] 558 | losses_cls = softmax_loss + tf.add_n(weight_losses_cls) 559 | 560 | # bbx loss 561 | square_bbx_loss = loss_weight[1] * \ 562 | tf.reduce_mean(tf.squared_difference(bbx_output, labels[1])) 563 | weight_losses_bbx = net.get_weight_decay()['bbx'] 564 | losses_bbx = square_bbx_loss + tf.add_n(weight_losses_bbx) 565 | 566 | # pts loss 567 | square_pts_loss = loss_weight[2] * \ 568 | tf.reduce_mean(tf.squared_difference(pts_output, labels[2])) 569 | weight_losses_pts = net.get_weight_decay()['pts'] 570 | losses_pts = square_pts_loss + tf.add_n(weight_losses_pts) 571 | 572 | global_step_cls = tf.Variable(1, name='global_step_cls', trainable=False) 573 | global_step_bbx = tf.Variable(1, name='global_step_bbx', trainable=False) 574 | global_step_pts = tf.Variable(1, name='global_step_pts', trainable=False) 575 | 576 | train_cls = tf.train.AdamOptimizer(learning_rate=base_lr) \ 577 | .minimize(losses_cls, global_step=global_step_cls) 578 | train_bbx = tf.train.AdamOptimizer(learning_rate=base_lr) \ 579 | .minimize(losses_bbx, global_step=global_step_bbx) 580 | train_pts = tf.train.AdamOptimizer(learning_rate=base_lr) \ 581 | .minimize(losses_pts, global_step=global_step_pts) 582 | 583 | init_op = tf.group(tf.global_variables_initializer(), 584 | tf.local_variables_initializer()) 585 | 586 | config = tf.ConfigProto() 587 | config.allow_soft_placement = True 588 | config.gpu_options.per_process_gpu_memory_fraction = gpu_memory_fraction 589 | config.gpu_options.allow_growth = True 590 | 591 | loss_agg_cls = [0] 592 | loss_agg_bbx = [0] 593 | loss_agg_pts = [0] 594 | step_value = [1, 1, 1] 595 | 596 | with tf.Session(config=config) as sess: 597 | sess.run(init_op) 598 | saver = tf.train.Saver(max_to_keep=200000) 599 | if load_model: 600 | saver.restore(sess, load_filename) 601 | else: 602 | net.load(load_filename, sess, prefix) 603 | if save_model: 604 | save_dir = os.path.split(save_filename)[0] 605 | if not os.path.exists(save_dir): 606 | os.makedirs(save_dir) 607 | coord = tf.train.Coordinator() 608 | threads = tf.train.start_queue_runners(sess=sess, coord=coord) 609 | try: 610 | while not coord.should_stop(): 611 | choic = np.random.randint(0, train_mode) 612 | if choic == 0: 613 | _, loss_value_cls, step_value[0] = sess.run( 614 | [train_cls, softmax_loss, global_step_cls]) 615 | loss_agg_cls.append(loss_value_cls) 616 | elif choic == 1: 617 | _, loss_value_bbx, step_value[1] = sess.run( 618 | [train_bbx, square_bbx_loss, global_step_bbx]) 619 | loss_agg_bbx.append(loss_value_bbx) 620 | else: 621 | _, loss_value_pts, step_value[2] = sess.run( 622 | [train_pts, square_pts_loss, global_step_pts]) 623 | loss_agg_pts.append(loss_value_pts) 624 | 625 | if sum(step_value) % (100 * train_mode) == 0: 626 | agg_cls = sum(loss_agg_cls) / len(loss_agg_cls) 627 | agg_bbx = sum(loss_agg_bbx) / len(loss_agg_bbx) 628 | agg_pts = sum(loss_agg_pts) / len(loss_agg_pts) 629 | print( 630 | 'Step %d for cls: loss = %.5f' % 631 | (step_value[0], agg_cls), end='. ') 632 | print( 633 | 'Step %d for bbx: loss = %.5f' % 634 | (step_value[1], agg_bbx), end='. ') 635 | print( 636 | 'Step %d for pts: loss = %.5f' % 637 | (step_value[2], agg_pts)) 638 | loss_agg_cls = [0] 639 | loss_agg_bbx = [0] 640 | loss_agg_pts = [0] 641 | 642 | if save_model and (step_value[0] % num_iter_to_save == 0): 643 | saver.save(sess, save_filename, global_step=step_value[0]) 644 | 645 | except tf.errors.OutOfRangeError: 646 | print( 647 | 'Done training for %d epochs, %d steps.' % 648 | (num_epochs[0], step_value[0])) 649 | finally: 650 | coord.request_stop() 651 | 652 | coord.join(threads) 653 | -------------------------------------------------------------------------------- /code/mtcnn/test_tfrecords.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Jan 1 16:09:32 2015 4 | 5 | @author: crw 6 | """ 7 | # 参数含义: 8 | # CropFace(image, eye_left, eye_right, offset_pct, dest_sz) 9 | # eye_left is the position of the left eye 10 | # eye_right is the position of the right eye 11 | # 比例的含义为:要保留的图像靠近眼镜的百分比, 12 | # offset_pct is the percent of the image you want to keep next to the eyes (horizontal, vertical direction) 13 | # 最后保留的图像的大小。 14 | # dest_sz is the size of the output image 15 | # 16 | import sys, math 17 | from PIL import Image 18 | 19 | 20 | # 计算两个坐标的距离 21 | def Distance(p1, p2): 22 | dx = p2[0] - p1[0] 23 | dy = p2[1] - p1[1] 24 | return math.sqrt(dx * dx + dy * dy) 25 | 26 | # 根据参数,求仿射变换矩阵和变换后的图像。 27 | 28 | 29 | def ScaleRotateTranslate(image, angle, center=None, new_center=None, scale=None, resample=Image.BICUBIC): 30 | if (scale is None) and (center is None): 31 | return image.rotate(angle=angle, resample=resample) 32 | nx, ny = x, y = center 33 | sx = sy = 1.0 34 | if new_center: 35 | (nx, ny) = new_center 36 | if scale: 37 | (sx, sy) = (scale, scale) 38 | cosine = math.cos(angle) 39 | sine = math.sin(angle) 40 | a = cosine / sx 41 | b = sine / sx 42 | c = x - nx * a - ny * b 43 | d = -sine / sy 44 | e = cosine / sy 45 | f = y - nx * d - ny * e 46 | return image.transform(image.size, Image.AFFINE, (a, b, c, d, e, f), resample=resample) 47 | # 根据所给的人脸图像,眼睛坐标位置,偏移比例,输出的大小,来进行裁剪。 48 | 49 | 50 | def CropFace(image, eye_left=(0, 0), eye_right=(0, 0), offset_pct=(0.3, 0.3), dest_sz=(96, 112)): 51 | # calculate offsets in original image 计算在原始图像上的偏移。 52 | offset_h = math.floor(float(offset_pct[0]) * dest_sz[0]) 53 | offset_v = math.floor(float(offset_pct[1]) * dest_sz[1]) 54 | # get the direction 计算眼睛的方向。 55 | eye_direction = (eye_right[0] - eye_left[0], eye_right[1] - eye_left[1]) 56 | # calc rotation angle in radians 计算旋转的方向弧度。 57 | rotation = -math.atan2(float(eye_direction[1]), float(eye_direction[0])) 58 | # distance between them # 计算两眼之间的距离。 59 | dist = Distance(eye_left, eye_right) 60 | # calculate the reference eye-width 计算最后输出的图像两只眼睛之间的距离。 61 | reference = dest_sz[0] - 2.0 * offset_h 62 | # scale factor # 计算尺度因子。 63 | scale = float(dist) / float(reference) 64 | # rotate original around the left eye # 原图像绕着左眼的坐标旋转。 65 | image = ScaleRotateTranslate(image, center=eye_left, angle=rotation) 66 | # crop the rotated image # 剪切 67 | crop_xy = (eye_left[0] - scale * offset_h, eye_left[1] - 1.4*scale * offset_v) # 起点 68 | crop_size = (dest_sz[0] * scale, dest_sz[1] * scale) # 大小 69 | image = image.crop( 70 | (int(crop_xy[0]), int(crop_xy[1]), int(crop_xy[0] + crop_size[0]), int(crop_xy[1] + crop_size[1]))) 71 | # resize it 重置大小 72 | image = image.resize(dest_sz, Image.ANTIALIAS) 73 | return image 74 | 75 | 76 | def creat_face(points, imagee): 77 | image = Image.open(imagee) 78 | leftx = points[0][0] 79 | lefty = points[0][1] 80 | rightx = points[0][2] 81 | righty = points[0][3] 82 | 83 | CropFace(image, eye_left=(leftx, lefty), eye_right=(rightx, righty), offset_pct=(0.3, 0.3), 84 | dest_sz=(96, 112)).save(imagee) -------------------------------------------------------------------------------- /code/mtcnn/tools.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2017 Baoming Wang 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import os 24 | import sys 25 | import re 26 | 27 | import numpy as np 28 | import tensorflow as tf 29 | import cv2 30 | 31 | 32 | def view_bar(num, total): 33 | 34 | rate = float(num) / total 35 | rate_num = int(rate * 100) + 1 36 | r = '\r[%s%s]%d%%' % ("#" * rate_num, " " * (100 - rate_num), rate_num, ) 37 | sys.stdout.write(r) 38 | sys.stdout.flush() 39 | 40 | 41 | def int64_feature(value): 42 | 43 | return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) 44 | 45 | 46 | def bytes_feature(value): 47 | 48 | return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) 49 | 50 | 51 | def get_model_filenames(model_dir): 52 | 53 | files = os.listdir(model_dir) 54 | pnet = [s for s in files if 'pnet' in s and 55 | os.path.isdir(os.path.join(model_dir, s))] 56 | rnet = [s for s in files if 'rnet' in s and 57 | os.path.isdir(os.path.join(model_dir, s))] 58 | onet = [s for s in files if 'onet' in s and 59 | os.path.isdir(os.path.join(model_dir, s))] 60 | if pnet and rnet and onet: 61 | if len(pnet) == 1 and len(rnet) == 1 and len(onet) == 1: 62 | _, pnet_data = get_meta_data(os.path.join(model_dir, pnet[0])) 63 | _, rnet_data = get_meta_data(os.path.join(model_dir, rnet[0])) 64 | _, onet_data = get_meta_data(os.path.join(model_dir, onet[0])) 65 | return (pnet_data, rnet_data, onet_data) 66 | else: 67 | raise ValueError('There should not be more ' 68 | 'than one dir for each model') 69 | else: 70 | return get_meta_data(model_dir) 71 | 72 | 73 | def get_meta_data(model_dir): 74 | 75 | files = os.listdir(model_dir) 76 | meta_files = [s for s in files if s.endswith('.meta')] 77 | if len(meta_files) == 0: 78 | raise ValueError('No meta file found in the model ' 79 | 'directory (%s)' % model_dir) 80 | elif len(meta_files) > 1: 81 | raise ValueError('There should not be more than ' 82 | 'one meta file in the model directory (%s)' 83 | % model_dir) 84 | meta_file = meta_files[0] 85 | max_step = -1 86 | for f in files: 87 | step_str = re.match(r'(^[A-Za-z]+-(\d+))', f) 88 | if step_str is not None and len(step_str.groups()) >= 2: 89 | step = int(step_str.groups()[1]) 90 | if step > max_step: 91 | max_step = step 92 | data_file = step_str.groups()[0] 93 | return (os.path.join(model_dir, meta_file), 94 | os.path.join(model_dir, data_file)) 95 | 96 | 97 | def detect_face(img, minsize, pnet, rnet, onet, threshold, factor): 98 | 99 | factor_count = 0 100 | total_boxes = np.empty((0, 9)) 101 | points = [] 102 | h = img.shape[0] 103 | w = img.shape[1] 104 | minl = np.amin([h, w]) 105 | m = 12.0 / minsize 106 | minl = minl * m 107 | # creat scale pyramid 108 | scales = [] 109 | while minl >= 12: 110 | scales += [m * np.power(factor, factor_count)] 111 | minl = minl * factor 112 | factor_count += 1 113 | 114 | # first stage 115 | for j in range(len(scales)): 116 | scale = scales[j] 117 | hs = int(np.ceil(h * scale)) 118 | ws = int(np.ceil(w * scale)) 119 | im_data = imresample(img, (hs, ws)) 120 | im_data = (im_data - 127.5) * (1. / 128.0) 121 | img_x = np.expand_dims(im_data, 0) 122 | out = pnet(img_x) 123 | out0 = out[0] 124 | out1 = out[1] 125 | boxes, _ = generateBoundingBox(out0[0, :, :, 1].copy(), 126 | out1[0, :, :, :].copy(), 127 | scale, 128 | threshold[0]) 129 | 130 | # inter-scale nms 131 | pick = nms(boxes.copy(), 0.5, 'Union') 132 | if boxes.size > 0 and pick.size > 0: 133 | boxes = boxes[pick, :] 134 | total_boxes = np.append(total_boxes, boxes, axis=0) 135 | 136 | numbox = total_boxes.shape[0] 137 | if numbox > 0: 138 | pick = nms(total_boxes.copy(), 0.7, 'Union') 139 | total_boxes = total_boxes[pick, :] 140 | regw = total_boxes[:, 2] - total_boxes[:, 0] 141 | regh = total_boxes[:, 3] - total_boxes[:, 1] 142 | qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw 143 | qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh 144 | qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw 145 | qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh 146 | total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, 147 | total_boxes[:, 4]])) 148 | total_boxes = rerec(total_boxes.copy()) 149 | total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32) 150 | dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad( 151 | total_boxes.copy(), w, h) 152 | 153 | numbox = total_boxes.shape[0] 154 | if numbox > 0: 155 | # second stage 156 | tempimg = np.zeros((24, 24, 3, numbox)) 157 | for k in range(0, numbox): 158 | tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) 159 | tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], 160 | :] = img[y[k] - 1:ey[k], x[k] - 1:ex[k], :] 161 | if (tmp.shape[0] > 0 and tmp.shape[1] > 0 or 162 | tmp.shape[0] == 0 and tmp.shape[1] == 0): 163 | tempimg[:, :, :, k] = imresample(tmp, (24, 24)) 164 | else: 165 | return np.empty() 166 | tempimg = (tempimg - 127.5) * 0.0078125 167 | tempimg1 = np.transpose(tempimg, (3, 0, 1, 2)) 168 | out = rnet(tempimg1) 169 | out0 = np.transpose(out[0]) 170 | out1 = np.transpose(out[1]) 171 | score = out0[1, :] 172 | ipass = np.where(score > threshold[1]) 173 | total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), 174 | np.expand_dims(score[ipass].copy(), 1)]) 175 | mv = out1[:, ipass[0]] 176 | if total_boxes.shape[0] > 0: 177 | pick = nms(total_boxes, 0.7, 'Union') 178 | total_boxes = total_boxes[pick, :] 179 | total_boxes = bbreg(total_boxes.copy(), np.transpose(mv[:, pick])) 180 | total_boxes = rerec(total_boxes.copy()) 181 | 182 | numbox = total_boxes.shape[0] 183 | if numbox > 0: 184 | # third stage 185 | total_boxes = np.fix(total_boxes).astype(np.int32) 186 | dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad( 187 | total_boxes.copy(), w, h) 188 | tempimg = np.zeros((48, 48, 3, numbox)) 189 | for k in range(0, numbox): 190 | tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) 191 | tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], 192 | :] = img[y[k] - 1:ey[k], x[k] - 1:ex[k], :] 193 | if (tmp.shape[0] > 0 and tmp.shape[1] > 0 or 194 | tmp.shape[0] == 0 and tmp.shape[1] == 0): 195 | tempimg[:, :, :, k] = imresample(tmp, (48, 48)) 196 | else: 197 | return np.empty() 198 | tempimg = (tempimg - 127.5) * 0.0078125 199 | tempimg1 = np.transpose(tempimg, (3, 0, 1, 2)) 200 | out = onet(tempimg1) 201 | out0 = np.transpose(out[0]) 202 | out1 = np.transpose(out[1]) 203 | out2 = np.transpose(out[2]) 204 | score = out0[1, :] 205 | points = out2 206 | ipass = np.where(score > threshold[2]) 207 | points = points[:, ipass[0]] 208 | total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), 209 | np.expand_dims(score[ipass].copy(), 1)]) 210 | mv = out1[:, ipass[0]] 211 | 212 | w = total_boxes[:, 2] - total_boxes[:, 0] + 1 213 | h = total_boxes[:, 3] - total_boxes[:, 1] + 1 214 | points[0:10:2, :] = np.tile(w, (5, 1)) * \ 215 | (points[0:10:2, :] + 1) / 2 + \ 216 | np.tile(total_boxes[:, 0], (5, 1)) - 1 217 | points[1:11:2, :] = np.tile(h, (5, 1)) * \ 218 | (points[1:11:2, :] + 1) / 2 + \ 219 | np.tile(total_boxes[:, 1], (5, 1)) - 1 220 | if total_boxes.shape[0] > 0: 221 | total_boxes = bbreg(total_boxes.copy(), np.transpose(mv)) 222 | pick = nms(total_boxes.copy(), 0.7, 'Min') 223 | total_boxes = total_boxes[pick, :] 224 | points = points[:, pick] 225 | 226 | return total_boxes, points 227 | 228 | 229 | def detect_face_12net(img, minsize, pnet, threshold, factor): 230 | 231 | factor_count = 0 232 | total_boxes = np.empty((0, 9)) 233 | h = img.shape[0] 234 | w = img.shape[1] 235 | minl = np.amin([h, w]) 236 | m = 12.0 / minsize 237 | minl = minl * m 238 | # creat scale pyramid 239 | scales = [] 240 | while minl >= 12: 241 | scales += [m * np.power(factor, factor_count)] 242 | minl = minl * factor 243 | factor_count += 1 244 | 245 | # first stage 246 | for j in range(len(scales)): 247 | scale = scales[j] 248 | hs = int(np.ceil(h * scale)) 249 | ws = int(np.ceil(w * scale)) 250 | im_data = imresample(img, (hs, ws)) 251 | im_data = (im_data - 127.5) * (1. / 128.0) 252 | img_x = np.expand_dims(im_data, 0) 253 | out = pnet(img_x) 254 | out0 = out[0] 255 | out1 = out[1] 256 | boxes, _ = generateBoundingBox(out0[0, :, :, 1].copy(), 257 | out1[0, :, :, :].copy(), 258 | scale, 259 | threshold) 260 | 261 | # inter-scale nms 262 | pick = nms(boxes.copy(), 0.5, 'Union') 263 | if boxes.size > 0 and pick.size > 0: 264 | boxes = boxes[pick, :] 265 | total_boxes = np.append(total_boxes, boxes, axis=0) 266 | 267 | numbox = total_boxes.shape[0] 268 | if numbox > 0: 269 | pick = nms(total_boxes.copy(), 0.7, 'Union') 270 | total_boxes = total_boxes[pick, :] 271 | regw = total_boxes[:, 2] - total_boxes[:, 0] 272 | regh = total_boxes[:, 3] - total_boxes[:, 1] 273 | qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw 274 | qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh 275 | qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw 276 | qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh 277 | total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, 278 | total_boxes[:, 4]])) 279 | total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32) 280 | return total_boxes 281 | 282 | 283 | def detect_face_24net(img, minsize, pnet, rnet, threshold, factor): 284 | 285 | factor_count = 0 286 | total_boxes = np.empty((0, 9)) 287 | h = img.shape[0] 288 | w = img.shape[1] 289 | minl = np.amin([h, w]) 290 | m = 12.0 / minsize 291 | minl = minl * m 292 | # creat scale pyramid 293 | scales = [] 294 | while minl >= 12: 295 | scales += [m * np.power(factor, factor_count)] 296 | minl = minl * factor 297 | factor_count += 1 298 | 299 | # first stage 300 | for j in range(len(scales)): 301 | scale = scales[j] 302 | hs = int(np.ceil(h * scale)) 303 | ws = int(np.ceil(w * scale)) 304 | im_data = imresample(img, (hs, ws)) 305 | im_data = (im_data - 127.5) * 0.0078125 306 | img_x = np.expand_dims(im_data, 0) 307 | out = pnet(img_x) 308 | out0 = out[0] 309 | out1 = out[1] 310 | boxes, _ = generateBoundingBox(out0[0, :, :, 1].copy(), 311 | out1[0, :, :, :].copy(), 312 | scale, 313 | threshold[0]) 314 | 315 | # inter-scale nms 316 | pick = nms(boxes.copy(), 0.5, 'Union') 317 | if boxes.size > 0 and pick.size > 0: 318 | boxes = boxes[pick, :] 319 | total_boxes = np.append(total_boxes, boxes, axis=0) 320 | 321 | numbox = total_boxes.shape[0] 322 | if numbox > 0: 323 | pick = nms(total_boxes.copy(), 0.7, 'Union') 324 | total_boxes = total_boxes[pick, :] 325 | regw = total_boxes[:, 2] - total_boxes[:, 0] 326 | regh = total_boxes[:, 3] - total_boxes[:, 1] 327 | qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw 328 | qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh 329 | qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw 330 | qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh 331 | total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, 332 | total_boxes[:, 4]])) 333 | total_boxes = rerec(total_boxes.copy()) 334 | total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32) 335 | dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph = pad( 336 | total_boxes.copy(), w, h) 337 | 338 | numbox = total_boxes.shape[0] 339 | if numbox > 0: 340 | # second stage 341 | tempimg = np.zeros((24, 24, 3, numbox)) 342 | for k in range(0, numbox): 343 | tmp = np.zeros((int(tmph[k]), int(tmpw[k]), 3)) 344 | tmp[dy[k] - 1:edy[k], dx[k] - 1:edx[k], 345 | :] = img[y[k] - 1:ey[k], x[k] - 1:ex[k], :] 346 | if (tmp.shape[0] > 0 and tmp.shape[1] > 0 or 347 | tmp.shape[0] == 0 and tmp.shape[1] == 0): 348 | tempimg[:, :, :, k] = imresample(tmp, (24, 24)) 349 | else: 350 | return np.empty() 351 | tempimg = (tempimg - 127.5) * 0.0078125 352 | tempimg1 = np.transpose(tempimg, (3, 0, 1, 2)) 353 | out = rnet(tempimg1) 354 | out0 = np.transpose(out[0]) 355 | out1 = np.transpose(out[1]) 356 | score = out0[1, :] 357 | ipass = np.where(score > threshold[1]) 358 | total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), 359 | np.expand_dims(score[ipass].copy(), 1)]) 360 | mv = out1[:, ipass[0]] 361 | if total_boxes.shape[0] > 0: 362 | pick = nms(total_boxes, 0.5, 'Union') 363 | total_boxes = total_boxes[pick, :] 364 | total_boxes = bbreg(total_boxes.copy(), np.transpose(mv[:, pick])) 365 | return total_boxes 366 | 367 | 368 | def nms(boxes, threshold, method): 369 | 370 | if boxes.size == 0: 371 | return np.empty((0, 3)) 372 | x1 = boxes[:, 0] 373 | y1 = boxes[:, 1] 374 | x2 = boxes[:, 2] 375 | y2 = boxes[:, 3] 376 | s = boxes[:, 4] 377 | area = (x2 - x1 + 1) * (y2 - y1 + 1) 378 | s_sort = np.argsort(s) 379 | pick = np.zeros_like(s, dtype=np.int16) 380 | counter = 0 381 | while s_sort.size > 0: 382 | i = s_sort[-1] 383 | pick[counter] = i 384 | counter += 1 385 | idx = s_sort[0:-1] 386 | xx1 = np.maximum(x1[i], x1[idx]) 387 | yy1 = np.maximum(y1[i], y1[idx]) 388 | xx2 = np.minimum(x2[i], x2[idx]) 389 | yy2 = np.minimum(y2[i], y2[idx]) 390 | w = np.maximum(0.0, xx2 - xx1 + 1) 391 | h = np.maximum(0.0, yy2 - yy1 + 1) 392 | inter = w * h 393 | if method is 'Min': 394 | o = inter / np.minimum(area[i], area[idx]) 395 | else: 396 | o = inter / (area[i] + area[idx] - inter) 397 | s_sort = s_sort[np.where(o <= threshold)] 398 | pick = pick[0:counter] 399 | return pick 400 | 401 | 402 | def bbreg(boundingbox, reg): 403 | 404 | if reg.shape[1] == 1: 405 | reg = np.reshape(reg, (reg.shape[2], reg.shape[3])) 406 | 407 | w = boundingbox[:, 2] - boundingbox[:, 0] + 1 408 | h = boundingbox[:, 3] - boundingbox[:, 1] + 1 409 | b1 = boundingbox[:, 0] + reg[:, 0] * w 410 | b2 = boundingbox[:, 1] + reg[:, 1] * h 411 | b3 = boundingbox[:, 2] + reg[:, 2] * w 412 | b4 = boundingbox[:, 3] + reg[:, 3] * h 413 | boundingbox[:, 0:4] = np.transpose(np.vstack([b1, b2, b3, b4])) 414 | return boundingbox 415 | 416 | 417 | def generateBoundingBox(imap, reg, scale, t): 418 | 419 | stride = 2 420 | cellsize = 12 421 | 422 | imap = np.transpose(imap) 423 | dx1 = np.transpose(reg[:, :, 0]) 424 | dy1 = np.transpose(reg[:, :, 1]) 425 | dx2 = np.transpose(reg[:, :, 2]) 426 | dy2 = np.transpose(reg[:, :, 3]) 427 | y, x = np.where(imap >= t) 428 | if y.shape[0] == 1: 429 | dx1 = np.flipud(dx1) 430 | dy1 = np.flipud(dy1) 431 | dx2 = np.flipud(dx2) 432 | dy2 = np.flipud(dy2) 433 | score = imap[(y, x)] 434 | reg = np.transpose(np.vstack([dx1[(y, x)], dy1[(y, x)], 435 | dx2[(y, x)], dy2[(y, x)]])) 436 | if reg.size == 0: 437 | reg = np.empty((0, 3)) 438 | bb = np.transpose(np.vstack([y, x])) 439 | q1 = np.fix((stride * bb + 1) / scale) 440 | q2 = np.fix((stride * bb + cellsize - 1 + 1) / scale) 441 | boundingbox = np.hstack([q1, q2, np.expand_dims(score, 1), reg]) 442 | return boundingbox, reg 443 | 444 | 445 | def pad(total_boxes, w, h): 446 | 447 | tmpw = (total_boxes[:, 2] - total_boxes[:, 0] + 1).astype(np.int32) 448 | tmph = (total_boxes[:, 3] - total_boxes[:, 1] + 1).astype(np.int32) 449 | numbox = total_boxes.shape[0] 450 | 451 | dx = np.ones((numbox), dtype=np.int32) 452 | dy = np.ones((numbox), dtype=np.int32) 453 | edx = tmpw.copy().astype(np.int32) 454 | edy = tmph.copy().astype(np.int32) 455 | 456 | x = total_boxes[:, 0].copy().astype(np.int32) 457 | y = total_boxes[:, 1].copy().astype(np.int32) 458 | ex = total_boxes[:, 2].copy().astype(np.int32) 459 | ey = total_boxes[:, 3].copy().astype(np.int32) 460 | 461 | tmp = np.where(ex > w) 462 | edx.flat[tmp] = np.expand_dims(-ex[tmp] + w + tmpw[tmp], 1) 463 | ex[tmp] = w 464 | 465 | tmp = np.where(ey > h) 466 | edy.flat[tmp] = np.expand_dims(-ey[tmp] + h + tmph[tmp], 1) 467 | ey[tmp] = h 468 | 469 | tmp = np.where(x < 1) 470 | dx.flat[tmp] = np.expand_dims(2 - x[tmp], 1) 471 | x[tmp] = 1 472 | 473 | tmp = np.where(y < 1) 474 | dy.flat[tmp] = np.expand_dims(2 - y[tmp], 1) 475 | y[tmp] = 1 476 | 477 | return dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph 478 | 479 | 480 | def rerec(bboxA): 481 | 482 | h = bboxA[:, 3] - bboxA[:, 1] 483 | w = bboxA[:, 2] - bboxA[:, 0] 484 | size = np.maximum(w, h) 485 | bboxA[:, 0] = bboxA[:, 0] + w * 0.5 - size * 0.5 486 | bboxA[:, 1] = bboxA[:, 1] + h * 0.5 - size * 0.5 487 | bboxA[:, 2:4] = bboxA[:, 0:2] + np.transpose(np.tile(size, (2, 1))) 488 | return bboxA 489 | 490 | 491 | def imresample(img, sz): 492 | 493 | im_data = cv2.resize(img, (sz[1], sz[0]), interpolation=cv2.INTER_AREA) 494 | return im_data 495 | 496 | 497 | def IoU(box, boxes): 498 | 499 | box_area = (box[2] - box[0] + 1) * (box[3] - box[1] + 1) 500 | area = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1) 501 | xx1 = np.maximum(box[0], boxes[:, 0]) 502 | yy1 = np.maximum(box[1], boxes[:, 1]) 503 | xx2 = np.minimum(box[2], boxes[:, 2]) 504 | yy2 = np.minimum(box[3], boxes[:, 3]) 505 | 506 | # compute the width and height of the bounding box 507 | w = np.maximum(0, xx2 - xx1 + 1) 508 | h = np.maximum(0, yy2 - yy1 + 1) 509 | 510 | inter = w * h 511 | ovr = inter / (box_area + area - inter) 512 | return ovr 513 | 514 | 515 | def convert_to_square(bbox): 516 | 517 | square_bbox = bbox.copy() 518 | 519 | h = bbox[:, 3] - bbox[:, 1] + 1 520 | w = bbox[:, 2] - bbox[:, 0] + 1 521 | max_side = np.maximum(h, w) 522 | square_bbox[:, 0] = bbox[:, 0] + w * 0.5 - max_side * 0.5 523 | square_bbox[:, 1] = bbox[:, 1] + h * 0.5 - max_side * 0.5 524 | square_bbox[:, 2] = square_bbox[:, 0] + max_side - 1 525 | square_bbox[:, 3] = square_bbox[:, 1] + max_side - 1 526 | return square_bbox 527 | -------------------------------------------------------------------------------- /code/window.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import tkinter as tk 4 | from tkinter import ttk, filedialog 5 | from tkinter import Menu 6 | 7 | import cv2 8 | from PIL import Image, ImageTk 9 | from face_recognition import face_recognition 10 | 11 | 12 | class MainWindows(tk.Tk): 13 | def __init__(self): 14 | super().__init__() # 初始化基类 15 | 16 | self.title("人脸识别系统") 17 | self.resizable(width=False, height=False) 18 | self.minsize(640, 320) 19 | 20 | self.tabControl = ttk.Notebook(self) # Create Tab Control 21 | self.tab1 = ttk.Frame(self.tabControl) # Create a tab 22 | self.tab2 = ttk.Frame(self.tabControl) # Add a second tab 23 | self.tab3 = ttk.Frame(self.tabControl) # Add a second tab 24 | 25 | self.menu_bar = Menu(self) # Creating a Menu Bar 26 | 27 | self.init_ui() 28 | 29 | self.selected_files = [] # 被选中的文件,获取识别结果被使用 30 | self.photo_libs = [] # 本地图片库 31 | self.feature_libs = [] # 本地特征向量库 32 | self.lib_path = './images/libs' # 本地库文件路径 33 | 34 | self.face_recog = face_recognition() 35 | 36 | self.update_treeview() 37 | 38 | def init_ui(self): 39 | # self.btn = tk.Button(self, text='点我吧') 40 | # self.btn.pack(padx=200, pady=30) 41 | # self.btn.config(command=self.tell_you) 42 | self.tabControl.add(self.tab1, text='人脸验证(1:1)') # Add the tab 43 | self.tabControl.add(self.tab2, text='人脸辨别(1:N)') # Make second tab visible 44 | self.tabControl.add(self.tab3, text='人脸数据库管理') # Make second tab visible 45 | self.tabControl.pack(expand=1, fill="both") # Pack to make visible 46 | 47 | self.init_tab1() 48 | self.init_tab2() 49 | self.init_tab3() 50 | 51 | self.config(menu=self.menu_bar) 52 | self.init_menu() 53 | 54 | def init_tab1(self): 55 | mighty = ttk.LabelFrame(self.tab1, text='') 56 | mighty.pack() 57 | self.label1 = tk.Label(mighty, text='检测图片1', bg="Silver", padx=15, pady=15) 58 | self.label2 = tk.Label(mighty, text='检测图片2', bg="Silver", padx=15, pady=15) 59 | self.label1.grid(column=0, row=0, sticky='W') 60 | self.label2.grid(column=1, row=0, sticky='W') 61 | btn1 = ttk.Button(mighty, text="选择文件", command=self.select_btn_tab1) 62 | btn2 = ttk.Button(mighty, text="获取结果", command=self.get_result1) 63 | btn1.grid(column=0, row=1, sticky='W') 64 | btn2.grid(column=0, row=2, sticky='W') 65 | label3 = tk.Label(mighty, text='相似度(一般70%以上可以表示为同一个人):') 66 | label3.grid(column=0, row=3, sticky='W') 67 | self.name = tk.StringVar() 68 | name_entered = ttk.Entry(mighty, width=12, textvariable=self.name) 69 | name_entered.grid(column=1, row=3, sticky='W') # align left/West 70 | 71 | def init_tab2(self): 72 | mighty2 = ttk.LabelFrame(self.tab2, text='') 73 | mighty2.pack() 74 | 75 | self.label_rec = tk.Label(mighty2, text='待识别图片', bg="Silver", padx=15, pady=15) 76 | self.label_rec.grid(column=0, row=0, sticky='W') 77 | btn_sel = ttk.Button(mighty2, text="选择文件", command=self.select_btn_tab2) 78 | btn2_res = ttk.Button(mighty2, text="获取结果", command=self.get_result2) 79 | btn_sel.grid(column=0, row=1, sticky='W') 80 | btn2_res.grid(column=0, row=2, sticky='W') 81 | label_res = tk.Label(mighty2, text='识别对象名:') 82 | label_res.grid(column=0, row=3, sticky='W') 83 | self.name2 = tk.StringVar() 84 | name_entered2 = ttk.Entry(mighty2, width=12, textvariable=self.name2) 85 | name_entered2.grid(column=1, row=3, sticky='W') # align left/West 86 | 87 | def init_tab3(self): 88 | mighty3 = ttk.LabelFrame(self.tab3, text='本地数据') 89 | mighty3.pack() 90 | self.tree = ttk.Treeview(mighty3, height=4, columns=('姓名')) # 表格 91 | self.tree.grid(row=0, column=0, sticky='nsew') 92 | # Setup column heading 93 | self.tree.heading('0', text='姓名', anchor='center') 94 | self.tree.column('姓名', anchor='center', width=100) 95 | s = ttk.Style() 96 | s.configure('Treeview', rowheight=110) 97 | s.configure("mystyle.Treeview", highlightthickness=0, bd=0, font=('Calibri', 11)) # Modify the font of the body 98 | s.configure("mystyle.Treeview.Heading", font=('Calibri', 13, 'bold')) # Modify the font of the headings 99 | s.layout("mystyle.Treeview", [('mystyle.Treeview.treearea', {'sticky': 'nswe'})]) # Remove the borders 100 | newb1 = ttk.Button(mighty3, text='点击添加', width=20, command=self.add_data) 101 | newb1.grid(row=1, column=0, sticky='nsew') 102 | newb2 = ttk.Button(mighty3, text='选中删除', width=20, command=self.delete_cur_treeview) 103 | newb2.grid(row=2, column=0, sticky='nsew') 104 | newb3 = ttk.Button(mighty3, text='刷新界面', width=20, command=self.update_treeview) 105 | newb3.grid(row=3, column=0, sticky='nsew') 106 | 107 | def init_menu(self): 108 | # Add menu items 109 | file_menu = Menu(self.menu_bar, tearoff=0) 110 | file_menu.add_command(label="New") 111 | file_menu.add_separator() 112 | file_menu.add_command(label="Exit", command=self._quit) 113 | self.menu_bar.add_cascade(label="File", menu=file_menu) 114 | 115 | # Add another Menu to the Menu Bar and an item 116 | help_menu = Menu(self.menu_bar, tearoff=0) 117 | help_menu.add_command(label="About") 118 | self.menu_bar.add_cascade(label="Help", menu=help_menu) 119 | 120 | def show_img(self, labels, filename, length=1): 121 | if len(filename) < 1: 122 | return 123 | for i in range(length): 124 | img = Image.open(filename[i]) 125 | half_size = (256, 256) 126 | img.thumbnail(half_size, Image.ANTIALIAS) 127 | photo = ImageTk.PhotoImage(img) 128 | labels[i].configure(image=photo) 129 | labels[i].image = photo 130 | 131 | def select_file(self): 132 | self.selected_files = [] 133 | ftypes = [('Image Files', '*.tif *.jpg *.png')] 134 | dlg = filedialog.Open(filetypes=ftypes, multiple=True) 135 | filename = dlg.show() 136 | self.selected_files = filename 137 | return filename 138 | 139 | def select_btn_tab1(self): 140 | self.show_img([self.label1, self.label2], self.select_file(), 2) 141 | 142 | def get_result1(self): 143 | if len(self.selected_files) < 2: 144 | return 145 | 146 | img_path1 = self.selected_files[0] 147 | img_path2 = self.selected_files[1] 148 | # 计算两张图片的余弦相似度 149 | v1 = self.face_recog.get_single_feature_vector(img_path1) 150 | v2 = self.face_recog.get_single_feature_vector(img_path2) 151 | res = self.face_recog.cos_sim(v1, v2).tolist()[0][0] 152 | res = format(res * 100, '.2f') 153 | # name.configure(text=str(res[0][0])) 154 | self.name.set(res + '%') 155 | 156 | def select_btn_tab2(self): 157 | self.show_img([self.label_rec], self.select_file()) 158 | 159 | def get_result2(self): 160 | if len(self.selected_files) >= 1: 161 | cur_fea = self.face_recog.get_single_feature_vector(self.selected_files[0]) 162 | max_value = 0 163 | name = '' 164 | features_len = len(self.feature_libs) 165 | for i in range(features_len): 166 | cur_cos = self.face_recog.cos_sim(self.feature_libs[i][1], cur_fea) 167 | if cur_cos > max_value: 168 | max_value = cur_cos 169 | name = self.feature_libs[i][0] 170 | if max_value < 0.7: 171 | name = '未识别' 172 | self.name2.set(name.split('\\')[-1]) 173 | 174 | def get_lib(self, suffix='png'): 175 | ret = [] 176 | for root, dirs, files in os.walk(self.lib_path): 177 | for file in files: 178 | # 获取文件名, 文件路径 179 | suf = file.split('.')[-1] 180 | if suf == suffix: 181 | if suffix == 'png': 182 | ret.append([file.split('.')[0], os.path.join(root, file)]) 183 | elif suffix == 'fea': 184 | ret.append(os.path.join(root, file)) 185 | 186 | return ret 187 | 188 | def add_data(self): 189 | files = self.select_file() 190 | for img_path in files: 191 | fea = self.face_recog.get_single_feature_vector(img_path) 192 | save_name = img_path.split('/')[-1].split('.')[0] 193 | save_name_fea = '.\\images\\libs\\' + save_name + '.fea' 194 | save_name_img = '.\\images\\libs\\' + save_name + '.png' 195 | 196 | features_points, _ = self.face_recog.get_landmarkAndrect(img_path) 197 | crop_img = self.face_recog.get_cropImage(features_points[0], img_path) 198 | cv2.imwrite(save_name_img, crop_img) 199 | # cv.imshow("1", crop_img) 200 | # cv.waitKey(0) 201 | # 参考:https://blog.csdn.net/reyyy/article/details/108223977 202 | v_size = len(fea) # 获取列表的长度 203 | fmt = str(v_size) + 'd' 204 | with open(save_name_fea, 'wb') as binfile: 205 | data = struct.pack(fmt, *fea) 206 | binfile.write(data) 207 | 208 | self.update_treeview() 209 | 210 | # 删除treeview视图中的项,本地数据未修改(待添加) 211 | def delete_cur_treeview(self): 212 | item = self.tree.focus() 213 | self.tree.delete(item) 214 | 215 | def delete_all_treeview(self): 216 | for row in self.tree.get_children(): 217 | self.tree.delete(row) 218 | 219 | def update_treeview(self): 220 | self.photo_libs = [] 221 | self.delete_all_treeview() 222 | 223 | libs_img = self.get_lib() 224 | libs_len = len(libs_img) 225 | count = 0 226 | 227 | for i in range(libs_len): 228 | cur_pair = libs_img[i] 229 | # print(cur_pair) 230 | img = Image.open(cur_pair[1]) 231 | half_size = (100, 100) 232 | img.thumbnail(half_size, Image.ANTIALIAS) 233 | photo = ImageTk.PhotoImage(img) 234 | self.photo_libs.append(photo) 235 | self.tree.insert('', count, text="", image=self.photo_libs[-1], value=(cur_pair[0])) 236 | count += 1 237 | 238 | # 更新特征向量库 239 | self.get_features_vec_lib() 240 | 241 | def get_features_vec_lib(self): 242 | self.feature_libs = [] 243 | files = self.get_lib('fea') 244 | for file in files: 245 | # 读取本地数据 246 | read_result = [] 247 | fmt = str(512) + 'd' 248 | with open(file, 'rb') as binfile: 249 | data = binfile.read() 250 | a = struct.unpack(fmt, data) 251 | # print(a) 252 | read_result.append(a) 253 | self.feature_libs.append([file.split('/')[-1].split('.')[0], read_result]) 254 | 255 | def _quit(self): 256 | self.quit() 257 | self.destroy() 258 | exit() 259 | 260 | 261 | if __name__ == '__main__': 262 | app = MainWindows() 263 | app.mainloop() 264 | --------------------------------------------------------------------------------