代码:https://download.csdn.net/download/qq_38735017/87425916
前言
本教程是教程是介绍如何使用 Tensorflow 实现的 MTCNN 和 MobileFaceNet 实现的人脸识别,并不介绍如何训练模型。关于如何训练 MTCNN 和 MobileFaceNet,这两个模型都是比较轻量的模型,所以就算这两个模型在 CPU 环境下也有比较好的预测速度,众所周知,笔者比较喜欢轻量级的模型,如何让我从准确率和预测速度上选择,我会更倾向于速度,因本人主要是研究深度学习在移动设备等嵌入式设备上的的部署。好了,下面就来介绍如何实现这两个模型实现三种人脸识别,使用路径进行人脸注册和人脸识别,使用摄像头实现人脸注册和人脸识别,通过 HTTP 实现人脸注册和人脸识别。
本地人脸图像识别
本地人脸图像识别就是要通过路径读取本地的图像进行人脸注册或者人脸识别,对应的代码为 path_infer.py。首先要加载好人脸识别的两个模型,一个是人脸检测和关键点检测模型 MTCNN 和人脸识别模型 MobileFaceNet,加载这两个模型已经封装在一个工具中了,方便加载。 然后 add_faces() 这个函数是从 temp 路径中读取手动添加的图片的人脸库中,具体来说,例如你有 100 张已经用人脸中对应人名字来命名图片文件名,但是你不能直接添加到人脸库 face_db 中,因为人脸库中是存放经过 MTCNN 模型处理过的图片,所以大规模添加人脸图片需要通过暂存在 temp 文件夹中的方式来然程序自动添加。最后是读取人脸库中图像,通过 MobileFaceNet 预测获取每张人脸的特征值存放在到一个列表中,等着之后的人脸对比识别。
# 检测人脸检测模型mtcnn_detector=load_mtcnn()# 加载人脸识别模型face_sess,inputs_placeholder,embeddings=load_mobilefacenet()# 添加人脸add_faces(mtcnn_detector)# 加载已经注册的人脸faces_db=load_faces(face_sess,inputs_placeholder,embeddings)
人脸注册是通过图像路径读取人脸图像,然后使用 MTCNN 检测图像中的人脸,并通过人脸关键点进行人脸对齐,最后裁剪并缩放成 112*112 的图片,并以注册名命名文件存储在人脸库中。
defface_register(img_path,name):image=cv2.imdecode(np.fromfile(img_path,dtype=np.uint8),1)faces,landmarks=mtcnn_detector.detect(image)iffaces.shape[0]isnot0:faces_sum=0bbox=[]points=[]fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:bbox=faces[i,0:4]points=landmarks[i,:].reshape((5,2))faces_sum+=1iffaces_sum==1:nimg=face_preprocess.preprocess(image,bbox,points,image_size='112,112')cv2.imencode('.png',nimg)[1].tofile('face_db/%s.png'%name)print("注册成功!")else:print('注册图片有错,图片中有且只有一个人脸')else:print('注册图片有错,图片中有且只有一个人脸')
人脸识别是通过图像路径读取将要识别的人脸,通过经过 MTCNN 的检测人脸和对其,在使用 MobileFaceNet 预测人脸的特征,最终得到特征和人脸库中的特征值比较相似度,最终得到阈值超过 0.6 的最高相似度结果,对应的名称就是该人脸识别的结果。最后把结果在图像中画框和标记上名称并显示出来。
defface_recognition(img_path):image=cv2.imdecode(np.fromfile(img_path,dtype=np.uint8),1)faces,landmarks=mtcnn_detector.detect(image)iffaces.shape[0]isnot0:faces_sum=0fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:faces_sum+=1iffaces_sum>0:# 人脸信息info_location=np.zeros(faces_sum)info_location[0]=1info_name=[]probs=[]# 提取图像中的人脸input_images=np.zeros((faces.shape[0],112,112,3))fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:bbox=faces[i,0:4]points=landmarks[i,:].reshape((5,2))nimg=face_preprocess.preprocess(image,bbox,points,image_size='112,112')nimg=nimg-127.5nimg=nimg*0.0078125input_images[i,:]=nimg# 进行人脸识别feed_dict={inputs_placeholder:input_images}emb_arrays=face_sess.run(embeddings,feed_dict=feed_dict)emb_arrays=sklearn.preprocessing.normalize(emb_arrays)fori,embeddinginenumerate(emb_arrays):embedding=embedding.flatten()temp_dict={}# 比较已经存在的人脸数据库forcom_faceinfaces_db:ret,sim=feature_compare(embedding,com_face["feature"],0.70)temp_dict[com_face["name"]]=simdict=sorted(temp_dict.items(),key=lambdad:d[1],reverse=True)ifdict[0][1]>VERIFICATION_THRESHOLD:name=dict[0][0]probs.append(dict[0][1])info_name.append(name)else:probs.append(dict[0][1])info_name.append("unknown")forkinrange(faces_sum):# 写上人脸信息x1,y1,x2,y2=faces[k][0],faces[k][1],faces[k][2],faces[k][3]x1=max(int(x1),0)y1=max(int(y1),0)x2=min(int(x2),image.shape[1])y2=min(int(y2),image.shape[0])prob='%.2f'%probs[k]label="{}, {}".format(info_name[k],prob)cv2img=cv2.cvtColor(image,cv2.COLOR_BGR2RGB)pilimg=Image.fromarray(cv2img)draw=ImageDraw.Draw(pilimg)font=ImageFont.truetype('font/simfang.ttf',18,encoding="utf-8")draw.text((x1,y1-18),label,(255,0,0),font=font)image=cv2.cvtColor(np.array(pilimg),cv2.COLOR_RGB2BGR)cv2.rectangle(image,(x1,y1),(x2,y2),(255,0,0),2)cv2.imshow('image',image)cv2.waitKey(0)cv2.destroyAllWindows()
最后的动时选择是人脸注册还是人脸识别。
if__name__=='__main__':i=int(input("请选择功能,1为注册人脸,2为识别人脸:"))image_path=input("请输入图片路径:")ifi==1:user_name=input("请输入注册名:")face_register(image_path,user_name)elifi==2:face_recognition(image_path)else:print("功能选择错误")
日志输出如下:
loaded face: 张伟.png loaded face: 迪丽热巴.png 请选择功能,1为注册人脸,2为识别人脸:1 请输入图片路径:test.png 请输入注册名:夜雨飘零 注册成功!
识别效果图:
相机人脸识别
在 camera_infer.py 实现使用相机的人脸识别,通过调用相机获取图像,进行人脸注册和人脸识别,在使用人脸注册或者人脸识别之前,同样先加载人脸检测模型 MTCNN 和 MobileFaceNet,并将临时 temp 文件夹中的人脸经过 MTCNN 处理添加到人脸库中,最后把人脸库中的人脸使用 MobileFaceNet 预测得到特征值,并报特征值和对应的人脸名称存放在列表中。
# 检测人脸检测模型mtcnn_detector=load_mtcnn()# 加载人脸识别模型face_sess,inputs_placeholder,embeddings=load_mobilefacenet()# 添加人脸add_faces(mtcnn_detector)# 加载已经注册的人脸faces_db=load_faces(face_sess,inputs_placeholder,embeddings)
通过使用摄像头实时获取图像,在人脸注册这里当摄像头拍摄到人脸之后,可以点击 y 键拍照,拍照获得到图片之后,需要经过 MTCNN 检测判断是否存在人脸,检测成功之后,会对人脸进行裁剪并以注册名直接存储在人脸库中 face_db。
defface_register():print("点击y确认拍照!")cap=cv2.VideoCapture(0)whileTrue:ret,frame=cap.read()ifret:cv2.imshow('image',frame)ifcv2.waitKey(1)&0xFF==ord('y'):faces,landmarks=mtcnn_detector.detect(frame)iffaces.shape[0]isnot0:faces_sum=0bbox=[]points=[]fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:bbox=faces[i,0:4]points=landmarks[i,:].reshape((5,2))faces_sum+=1iffaces_sum==1:nimg=face_preprocess.preprocess(frame,bbox,points,image_size='112,112')user_name=input("请输入注册名:")cv2.imencode('.png',nimg)[1].tofile('face_db/%s.png'%user_name)print("注册成功!")else:print('注册图片有错,图片中有且只有一个人脸')else:print('注册图片有错,图片中有且只有一个人脸')break
在人脸识别中,通过调用摄像头实时获取图像,通过使用 MTCNN 检测人脸的位置,并使用 MobileFaceNet 进行识别,最终在图像上画框并写上识别的名字,结果会跟着摄像头获取的图像实时识别的。
defface_recognition():cap=cv2.VideoCapture(0)whileTrue:ret,frame=cap.read()ifret:faces,landmarks=mtcnn_detector.detect(frame)iffaces.shape[0]isnot0:faces_sum=0fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:faces_sum+=1iffaces_sum==0:continue# 人脸信息info_location=np.zeros(faces_sum)info_location[0]=1info_name=[]probs=[]# 提取图像中的人脸input_images=np.zeros((faces.shape[0],112,112,3))fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:bbox=faces[i,0:4]points=landmarks[i,:].reshape((5,2))nimg=face_preprocess.preprocess(frame,bbox,points,image_size='112,112')nimg=nimg-127.5nimg=nimg*0.0078125input_images[i,:]=nimg# 进行人脸识别feed_dict={inputs_placeholder:input_images}emb_arrays=face_sess.run(embeddings,feed_dict=feed_dict)emb_arrays=sklearn.preprocessing.normalize(emb_arrays)fori,embeddinginenumerate(emb_arrays):embedding=embedding.flatten()temp_dict={}# 比较已经存在的人脸数据库forcom_faceinfaces_db:ret,sim=feature_compare(embedding,com_face["feature"],0.70)temp_dict[com_face["name"]]=simdict=sorted(temp_dict.items(),key=lambdad:d[1],reverse=True)ifdict[0][1]>VERIFICATION_THRESHOLD:name=dict[0][0]probs.append(dict[0][1])info_name.append(name)else:probs.append(dict[0][1])info_name.append("unknown")forkinrange(faces_sum):# 写上人脸信息x1,y1,x2,y2=faces[k][0],faces[k][1],faces[k][2],faces[k][3]x1=max(int(x1),0)y1=max(int(y1),0)x2=min(int(x2),frame.shape[1])y2=min(int(y2),frame.shape[0])prob='%.2f'%probs[k]label="{}, {}".format(info_name[k],prob)cv2img=cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)pilimg=Image.fromarray(cv2img)draw=ImageDraw.Draw(pilimg)font=ImageFont.truetype('font/simfang.ttf',18,encoding="utf-8")draw.text((x1,y1-18),label,(255,0,0),font=font)frame=cv2.cvtColor(np.array(pilimg),cv2.COLOR_RGB2BGR)cv2.rectangle(frame,(x1,y1),(x2,y2),(255,0,0),2)cv2.imshow('image',frame)ifcv2.waitKey(1)&0xFF==ord('q'):break
在启动程序中,通过选择功能,选择注册人脸或者是识别人脸。
if__name__=='__main__':i=int(input("请选择功能,1为注册人脸,2为识别人脸:"))ifi==1:face_register()elifi==2:face_recognition()else:print("功能选择错误")
日志输出如下:
loaded face: 张伟.png loaded face: 迪丽热巴.png 请选择功能,1为注册人脸,2为识别人脸:1 点击y确认拍照! 请输入注册名:夜雨飘零 注册成功!
识别效果图:
通过服务接口识别
程序在 server_main.py 中实现,通过使用 Flask 提供网络服务接口,如果要允许跨域访问需要设置 CORS(app),本程序虽然是默认开启跨域访问,但是为了可以在浏览器上调用摄像头,启动的 host 设置为 localhost。另外还要加载 MTCNN 模型和 MobileFaceNet 模型,并报人脸库的图像加载到程序中。
app=Flask(__name__)# 允许跨越访问CORS(app)# 人脸识别阈值VERIFICATION_THRESHOLD=config.VERIFICATION_THRESHOLD# 检测人脸检测模型mtcnn_detector=load_mtcnn()# 加载人脸识别模型face_sess,inputs_placeholder,embeddings=load_mobilefacenet()# 加载已经注册的人脸faces_db=load_faces(face_sess,inputs_placeholder,embeddings)
提供一个 /register 的人脸注册接口,通过表单上传的图像和注册名,经过 MTCNN 检测,是否包含人脸,如果注册成功,将会把图像裁剪并储存在人脸库中 face_db。并更新已经加载的人脸库,注意这是全部重新读取更新。
@app.route("/register",methods=['POST'])defregister():globalfaces_dbupload_file=request.files['image']user_name=request.values.get("name")ifupload_file:try:image=cv2.imdecode(np.frombuffer(upload_file.read(),np.uint8),cv2.IMREAD_UNCHANGED)faces,landmarks=mtcnn_detector.detect(image)iffaces.shape[0]isnot0:faces_sum=0bbox=[]points=[]fori,faceinenumerate(faces):ifround(faces[i,4],6)>0.95:bbox=faces[i,0:4]points=landmarks[i,:].reshape((5,2))faces_sum+=1iffaces_sum==1:nimg=face_preprocess.preprocess(image,bbox,points,image_size='112,112')cv2.imencode('.png',nimg)[1].tofile('face_db/%s.png'%user_name)# 更新人脸库faces_db=load_faces(face_sess,inputs_placeholder,embeddings)returnstr({"code":0,"msg":"success"})returnstr({"code":3,"msg":"image not or much face"})except:returnstr({"code":2,"msg":"this file is not image or not face"})else:returnstr({"code":1,"msg":"file is None"})
提供 /recognition 人脸识别接口,通过上传图片进行人脸识别,把识别的结果返回给用户,返回的结果不仅包括的识别的名字,还包括人脸框和关键点。因为也提供了一个 is_chrome_camera 参数,这个是方便在浏览器上调用摄像头获取图像进行预测,因为如果直接把浏览器拍摄到的图像直接预测会出现错误,所以如果是浏览器拍照识别,需要先存储起来,然后重新读取。
@app.route("/recognition",methods=['POST'])defrecognition():start_time1=time.time()upload_file=request.files['image']is_chrome_camera=request.values.get("is_chrome_camera")ifupload_file:try:img=cv2.imdecode(np.frombuffer(upload_file.read(),np.uint8),cv2.IMREAD_UNCHANGED)# 兼容浏览器摄像头拍照识别ifis_chrome_camera=="True":cv2.imwrite('test.png',img)img=cv2.imdecode(np.fromfile('test.png',dtype=np.uint8),1)except:returnstr({"error":2,"msg":"this file is not image"})try:info_name,probs,info_bbox,info_landmarks=recognition_face(img)ifinfo_nameisNone:returnstr({"error":3,"msg":"image not have face"})except:returnstr({"error":3,"msg":"image not have face"})# 封装识别结果data_faces=[]foriinrange(len(info_name)):data_faces.append({"name":info_name[i],"probability":probs[i],"bbox":list_to_json(np.around(info_bbox[i],decimals=2).tolist()),"landmarks":list_to_json(np.around(info_landmarks[i],decimals=2).tolist())})data=str({"code":0,"msg":"success","data":data_faces}).replace("'",'"')print('duration:[%.0fms]'%((time.time()-start_time1)*1000),data)returndataelse:returnstr({"error":1,"msg":"file is None"})
在 templates 目录下创建 index.html 文件,主要是以下两个表单和一个拍摄实时显示的 video,拍照的图像在 canvas 显示,最后上传。
<formaction="/register"enctype="multipart/form-data"method="post"> 注册人脸:<inputtype="file"requiredaccept="image/*"name="image"><br> 注册名称:<inputtype="text"name="name"><br><inputtype="submit"value="上传"></form><br/><br/><br/><formaction="/recognition"enctype="multipart/form-data"method="post"> 预测人脸:<inputtype="file"requiredaccept="image/*"name="image"><br><inputtype="submit"value="上传"></form><br/><br/><br/><videoid="video"width="640"height="480"autoplay></video><buttonid="snap">拍照</button><br/><br/><canvasid="canvas"width="640"height="480"></canvas><buttonid="upload">上传</button>
通过下面启动整个服务。
@app.route('/')defhome():returnrender_template("index.html")if__name__=='__main__':app.run(host=config.HOST,port=config.POST)
日志输出如下:
loaded face: 张伟.png loaded face: 迪丽热巴.png * Serving Flask app "server_main" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://localhost:5000/ (Press CTRL+C to quit)
页面图:
识别返回结果: