基于DeepFace深度学习模型的离线版人脸识别API接口实现(类似百度在线API接口)
一 背景
人脸识别技术经过数年的发展,在技术算法、识别性能、应用场景以及隐私保护和数据安全等方面都取得了显著的进步和成熟。
应用场景
- 门禁系统:
- 在门禁系统中,离线人脸识别可用于身份验证,用户只需站在摄像头前,系统即可自动识别身份,无需刷卡或输入密码,提高通行效率并增强安全性。
- 适用于小区、写字楼、学校、图书馆等需要控制人员进出的场所。
- 考勤系统:
- 在考勤系统中,离线人脸识别可用于记录员工的上下班时间,员工只需在摄像头前刷脸即可完成考勤,无需手动打卡,减少人为错误并提高工作效率。
- 适用于企业、工厂、政府机构等需要严格考勤管理的场所。
- 社区安防:
- 在社区安防领域,离线人脸识别可用于实时监测和报警,系统能够识别陌生人脸并及时发出警报,增强社区的安全防范能力。
- 适用于小区、别墅区、商业广场等需要强化安全监控的场所。
- 酒店入住:
- 在酒店入住过程中,离线人脸识别可用于身份验证,顾客只需通过刷脸即可完成入住手续,简化流程并提高服务效率。
- 适用于各类酒店、民宿等住宿场所。
- 智慧家居:
- 在智能家居领域,离线人脸识别可用于个性化服务,如智能音箱通过识别用户的脸来提供个性化的音乐推荐等。
- 适用于注重生活品质和便捷性的家庭用户。
- 人证核验:
- 在政务办理、通关核验、考试等场景中,离线人脸识别可用于身份核验,通过读取证件芯片照并与现场采集的人脸照进行比对来完成身份验证。
- 提高了核验效率和准确性,减少了人工审核的繁琐和错误。
离线版人脸识别优势
- 成本节约:离线版人脸识别系统通常不需要额外的网络设备和复杂的网络配置,因此在设备成本上更为经济。
- 无需网络:在无网络或网络不稳定的环境中,离线版人脸识别系统依然能够稳定运行,保证了系统的连续性和可靠性。
- 快速识别:由于没有网络数据传输转换等延迟,所以可以实现较快的识别速度,提升了使用效率。
- 数据本地化:人脸数据存储在本地设备中,减少了数据在网络传输过程中被窃取的风险,增强了用户隐私保护。
DeepFace 深度学习模型
DeepFace是由Facebook(现更名为Meta)于2014年开发的一种深度学习模型,专门用于人脸识别和验证。它是当时最先进的人脸识别系统之一,展示了深度学习在计算机视觉任务中的巨大潜力。技术特点如下:
深度卷积神经网络(CNN):
- DeepFace使用了一个包含多个卷积层和全连接层的深度卷积神经网络来提取人脸特征。
- 输入图像经过预处理后,输入到CNN中,通过多层卷积和池化操作提取出高维的特征表示,最终生成一个高维向量(称为“特征嵌入”或“特征嵌入”),用于人脸的相似性比较。
人脸对齐:
- 在人脸识别之前,DeepFace使用了一种基于3D人脸模型的对齐方法,通过3D仿射变换将输入图像对齐到一个标准的面部参考框架。这种对齐方法能够处理不同角度和表情的人脸图像,提高识别准确性。
高精度与鲁棒性:
- DeepFace在各种基准测试中表现出色,准确率非常高。
- 它对不同角度、光照和表情变化的人脸图像有较好的鲁棒性。
官方链接
GitHub - serengil/deepface: A Lightweight Face Recognition and Facial Attribute Analysis (Age, Gender, Emotion and Race) Library for Python
二 人脸识别系统 - API接口集
DeepFace 特征模型
模型测评:本系统使用的是 Facenet 模型。
人脸库 - 层级结构
人脸库、用户组、用户、用户下的人脸层级关系如下所示:
人脸库管理 API 组
创建人脸库 API
# 范例 curl -X POST -H "Content-Type: application/json" -d "{""group_id"": ""example_group"", ""parent_id"": ""parent_group""}" http://localhost/v1/group/add
# Windows 安装curl命令注意事项 https://cloud.tencent.com/developer/article/1907043
# curl下载地址 https://curl.se/windows/
@app.route('/v1/group/add', methods=['GET', 'POST'])
def add_group():data = request.get_json()if not data or 'group_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: group_id'}), 400group_id = data['group_id']parent_id = data.get('parent_id')# 检查组ID是否已经存在existing_group = session.query(Group).filter_by(group_id=group_id).first()if existing_group:return jsonify({"error_code": 409, 'error_msg': f'Group {group_id} already exists'}), 409# 如果提供了parent_id,则检查其是否存在if parent_id:parent_group = session.query(Group).filter_by(group_id=parent_id).first()if not parent_group:return jsonify({"error_code": 404, 'error_msg': f'Parent group {parent_id} does not exist'}), 404new_group = Group(group_id=group_id, parent_id=parent_id)session.add(new_group)session.commit()return jsonify({"error_code": 0, 'error_msg': 'Group added successfully'}), 201
删除人脸库 API
@app.route('/v1/group/delete', methods=['DELETE', 'POST'])
def delete_group():data = request.get_json()if not data or 'group_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: group_id'}), 400group_id = data['group_id']# 查找并删除组group_to_delete = session.query(Group).filter_by(group_id=group_id).first()if not group_to_delete:return jsonify({"error_code": 404, 'error_msg': f'Group {group_id} does not exist'}), 404else:# 如果这个组id是父id,则不允许删除parent_id = session.query(Group).filter_by(parent_id=group_id).first()if parent_id:return jsonify({"error_code": 404, 'error_msg': f'Group {group_id} is parent_id, cannot be deleted'}), 404session.delete(group_to_delete)session.commit()return jsonify({"error_code": 0, 'error_msg': f'Group {group_id} deleted successfully'}), 200
获取人脸库信息 API
@app.route('/v1/group/list', methods=['GET', 'POST'])
def list_groups():# 获取分页参数page = int(request.args.get('page', 1))per_page = int(request.args.get('per_page', 100))# 计算偏移量offset = (page - 1) * per_page# 查询所有组的group_id和parent_idquery = session.query(Group.group_id, Group.parent_id)total_count = query.count()all_groups = query.offset(offset).limit(per_page).all()# 将结果转换为列表groups = [{'group_id': group[0], 'parent_id': group[1]}for group in all_groups]return jsonify({"error_code": 0,'groups': groups,'total_count': total_count,'page': page,'per_page': per_page}), 200
获取人脸库列表及信息 API
# 根据group_id获取所有用户,支持分页
# 范例 curl -X GET "http://localhost/v1/users/by_group?group_id=example_group&page=1&per_page=100"@app.route('/v1/users/by_group', methods=['GET', 'POST'])
def get_users_by_group():group_id = request.args.get('group_id')if not group_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: group_id'}), 400page = int(request.args.get('page', 1)) # 当前页码,默认为1per_page = int(request.args.get('per_page', 100)) # 每页数量,默认为100offset = (page - 1) * per_page # 计算偏移量total_count = session.query(User).filter_by(group_id=group_id).count() # 总用户数量users = session.query(User).filter_by(group_id=group_id).offset(offset).limit(per_page).all()users_list = [{"user_id": user.user_id, "user_name": user.user_name}for user in users]return jsonify({"error_code": 0,'users': users_list,"group_id": group_id,'total_count': total_count,'page': page,'per_page': per_page}), 200
人脸库 - 用户管理 API 组
添加用户 API
# 添加一个新用户
# 范例 curl -X POST -H "Content-Type: application/json" -d "{\"user_id\": \"user1\", \"user_name\": \"John Doe\", \"group_id\": \"example_group\"}" http://localhost/v1/user/add
@app.route('/v1/user/add', methods=['POST'])
def add_user():data = request.get_json()if not data or 'user_id' not in data or 'user_name' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameters'}), 400user_id = data['user_id']user_name = data['user_name']group_id = data.get('group_id')# 检查用户ID是否已经存在existing_user = session.query(User).filter_by(user_id=user_id).first()if existing_user:return jsonify({"error_code": 409, 'error_msg': f'User {user_id} already exists'}), 409# group_id必须存在existing_group = session.query(Group).filter_by(group_id=group_id).first()if not existing_group:return jsonify({"error_code": 400, 'error_msg': f'Group {group_id} not exists'}), 400new_user = User(user_id=user_id, user_name=user_name, group_id=group_id)session.add(new_user)session.commit()return jsonify({"error_code": 0, 'error_msg': 'User added successfully'}), 201
删除用户 API
# 根据用户ID删除一个用户
# 范例 curl -X DELETE -H "Content-Type: application/json" http://localhost/v1/user/delete?user_id=user1
@app.route('/v1/user/delete', methods=['DELETE', 'POST'])
def delete_user():user_id = request.args.get('user_id')if not user_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: user_id'}), 400user_to_delete = session.query(User).filter_by(user_id=user_id).first()if not user_to_delete:return jsonify({"error_code": 404, 'error_msg': f'User {user_id} does not exist'}), 404session.delete(user_to_delete)session.commit()return jsonify({"error_code": 0, 'error_msg': f'User {user_id} deleted successfully'}), 200
更新用户 API
# 根据用户ID更新用户信息
# 范例 curl -X PUT -H "Content-Type: application/json" -d "{\"user_id\": \"user1\", \"user_name\": \"John Doe Updated\"}" http://localhost/v1/user/update
# 范例 curl -X PUT -H "Content-Type: application/json" -d "{\"user_id\": \"user1\", \"user_name\": \"John Doe Updated\", \"group_id\": \"parent_group\"}" http://localhost/v1/user/update
@app.route('/v1/user/update', methods=['PUT', 'POST'])
def update_user():data = request.get_json()if not data or 'user_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: user_id'}), 400user_id = data['user_id']user_to_update = session.query(User).filter_by(user_id=user_id).first()if not user_to_update:return jsonify({"error_code": 404, 'error_msg': f'User {user_id} does not exist'}), 404# 未输入group_id,则不更改组;有输入group_id,则group_id必须存在if 'group_id' in data:group_id = data['group_id']# group_id必须存在existing_group = session.query(Group).filter_by(group_id=group_id).first()if not existing_group:return jsonify({"error_code": 400, 'error_msg': f'Group {group_id} not exists'}), 400else:user_to_update.group_id = data.get('group_id', user_to_update.group_id)user_to_update.user_name = data.get('user_name', user_to_update.user_name)session.commit()return jsonify({"error_code": 0, 'error_msg': f'User {user_id} updated successfully'}), 200
获取用户信息 API
# 根据用户ID获取用户信息
# 范例 curl -X GET http://localhost/v1/user/get?user_id=user1
@app.route('/v1/user/get', methods=['GET', 'POST'])
def get_user():user_id = request.args.get('user_id')if not user_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: user_id'}), 400user = session.query(User).filter_by(user_id=user_id).first()if not user:return jsonify({"error_code": 404, 'error_msg': f'User {user_id} does not exist'}), 404return jsonify({"error_code": 0,'user': {"user_id": user.user_id, "user_name": user.user_name, "group_id": user.group_id}}), 200
获取全部用户 API
# 获取所有用户列表,支持分页
# 范例 curl -X GET "http://localhost/v1/user/list?page=1&per_page=100"
@app.route('/v1/user/list', methods=['GET', 'POST'])
def list_users():page = int(request.args.get('page', 1)) # 当前页码,默认为1per_page = int(request.args.get('per_page', 100)) # 每页数量,默认为100total_count = session.query(User).count() # 总用户数量offset = (page - 1) * per_page # 计算偏移量users = session.query(User).offset(offset).limit(per_page).all()users_list = [{"user_id": user.user_id, "user_name": user.user_name,"group_id": user.group_id} for user in users]return jsonify({"error_code": 0,'users': users_list,'total_count': total_count,'page': page,'per_page': per_page}), 200
获取指定组的用户 API
# 根据group_id获取所有用户,支持分页
# 范例 curl -X GET "http://localhost/v1/users/by_group?group_id=example_group&page=1&per_page=100"@app.route('/v1/users/by_group', methods=['GET', 'POST'])
def get_users_by_group():group_id = request.args.get('group_id')if not group_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: group_id'}), 400page = int(request.args.get('page', 1)) # 当前页码,默认为1per_page = int(request.args.get('per_page', 100)) # 每页数量,默认为100offset = (page - 1) * per_page # 计算偏移量total_count = session.query(User).filter_by(group_id=group_id).count() # 总用户数量users = session.query(User).filter_by(group_id=group_id).offset(offset).limit(per_page).all()users_list = [{"user_id": user.user_id, "user_name": user.user_name}for user in users]return jsonify({"error_code": 0,'users': users_list,"group_id": group_id,'total_count': total_count,'page': page,'per_page': per_page}), 200
人脸库 - 人脸管理 API组
添加人脸 API
# 添加一个新Face,并保存上传的图片文件
# 范例一 curl -X POST -H "Content-Type: multipart/form-data" -F "face_id=face1" -F "user_id=user1" -F "file=@./face1.jpg" http://localhost/v1/face/add
# 范例二 curl -X POST -H "Content-Type: multipart/form-data" -F "face_id=face2" -F "user_id=user2" -F "file=@./face2.jpg" http://localhost/v1/face/add
@app.route('/v1/face/add', methods=['POST'])
def add_face():data = request.form.to_dict()file = request.files.get('file')if not data or 'user_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter user_id'}), 400user_id = data['user_id']user = session.query(User).filter_by(user_id=user_id).first()if not user:return jsonify({"error_code": 404, 'error_msg': f'User {user_id} does not exist'}), 404if not file or not allowed_file(file.filename):return jsonify({"error_code": 400, 'error_msg': 'invalid image file type'}), 400if not data or 'face_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter face_id'}), 400face_id = data['face_id']face = session.query(Face).filter_by(face_id=face_id).first()if face:return jsonify({"error_code": 409, 'error_msg': f'Face {face_id} already exist'}), 409filename = secure_filename(file.filename)# 添加时间戳以防止文件重名timestamp = datetime.now().strftime('%Y%m%d%H%M%S')new_filename = f"{timestamp}-{user_id}-{filename}"image_path = convert_path(os.path.join(UPLOAD_FOLDER, new_filename))# 创建存储路径os.makedirs(os.path.dirname(image_path), exist_ok=True)file.save(image_path)embedding = dr.get_embedding_dump(image_path)if embedding == None:try:os.remove(image_path)except Exception as e:passreturn jsonify({"error_code": 412, 'error_msg': f'Face {face_id} get feature value failed'}), 412new_face = Face(face_id=request.form['face_id'], user_id=user_id,image_path=image_path, embedding_face=embedding)session.add(new_face)session.commit()return jsonify({"error_code": 0, 'error_msg': 'Face added successfully', 'image_path': image_path}), 201
删除人脸 API
# 根据face_id删除一个Face
# 范例 curl -X DELETE -H "Content-Type: application/json" http://localhost/v1/face/delete?face_id=face1
@app.route('/v1/face/delete', methods=['DELETE', 'GET', 'POST'])
def delete_face():face_id = request.args.get('face_id')if not face_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: face_id'}), 400face_to_delete = session.query(Face).filter_by(face_id=face_id).first()if not face_to_delete:return jsonify({"error_code": 404, 'error_msg': f'Face {face_id} does not exist'}), 404image_path = face_to_delete.image_pathif os.path.exists(image_path):os.remove(image_path)session.delete(face_to_delete)session.commit()return jsonify({"error_code": 0, 'error_msg': f'Face {face_id} deleted successfully'}), 200
获取全部人脸信息API
# 根据user_id获取所有Face信息
# 范例 curl -X GET "http://localhost/v1/face/get_by_user?user_id=user1"
@app.route('/v1/face/get_by_user', methods=['GET', 'POST'])
def get_faces_by_user():user_id = request.args.get('user_id')if not user_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: user_id'}), 400faces = session.query(Face).filter_by(user_id=user_id).all()faces_list = [{"face_id": face.face_id, "user_id": face.user_id,"image_path": face.image_path} for face in faces]return jsonify({"error_code": 0, 'faces': faces_list}), 200
删除人脸信息API
# 根据user_id删除所有Face
# 范例 curl -X DELETE "http://localhost/v1/face/delete_by_user?user_id=user1"@app.route('/v1/face/delete_by_user', methods=['DELETE', 'POST'])
def delete_faces_by_user():user_id = request.args.get('user_id')if not user_id:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: user_id'}), 400faces_to_delete = session.query(Face).filter_by(user_id=user_id).all()for face in faces_to_delete:image_path = face.image_pathif os.path.exists(image_path):os.remove(image_path)session.delete(face)session.commit()return jsonify({"error_code": 0, 'message': f'All faces for user {user_id} deleted successfully'}), 200
人脸搜索 API
人脸搜索 | 搜索与目标人脸最相似的一张。如需返回相似的多张人脸,需要微调代码,返回多个值。 |
人脸搜索结果参考 | 用于参考搜索结果分数的阈值。如果分数低于阈值,则不建议认为是同一个人;如果分数超过阈值,则是同一个人的概率非常高。 |
# 人脸识别
# 范例 curl -X POST -H "Content-Type: multipart/form-data" -F "group_id=parent_group" -F "file=@./face1.jpg" http://localhost/v1/face/recognize
@app.route('/v1/face/recognize', methods=['POST'])
def recognize_face():data = request.form.to_dict()if not data or 'group_id' not in data:return jsonify({"error_code": 400, 'error_msg': 'Missing required parameter: group_id'}), 400group_id = data['group_id']# 检查组ID是否已经存在existing_group = session.query(Group).filter_by(group_id=group_id).first()if existing_group:passelse:return jsonify({"error_code": 409, 'error_msg': f'Group {group_id} not exists'}), 409file = request.files.get('file')if not file or not allowed_file(file.filename):return jsonify({"error_code": 400, 'error_msg': 'invalid image file type'}), 400# 读取文件内容image_data = file.read()image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)embedding = dr.get_embedding(image)if embedding == None:try:# os.remove(image_path)passexcept Exception as e:passreturn jsonify({"error_code": 412, 'error_msg': f'get feature value failed'}), 412faces = get_faces_by_group_id(group_id)face_id = ''user_id = ''min_distance = -1.0for face in faces:distance = np.linalg.norm(np.array(face["embedding_face"]) - np.array(embedding))if min_distance < 0:min_distance = distanceface_id = face["face_id"]user_id = face["user_id"]else:if distance < min_distance:min_distance = distanceface_id = face["face_id"]user_id = face["user_id"]try:# os.remove(image_path)passexcept Exception as e:pass# 返回值distance建议设置在0~0.5内,值越小,图像对比越严格。大于1的值一般都是未识别到。return jsonify({"error_code": 0, 'error_msg': 'Recognized face successfully', 'face_id': face_id, 'user_id': user_id, 'distance': min_distance}), 200
三 人脸识别系统 - 数据库存储
人脸库类
class Group(Base):__tablename__ = 'groups'group_id = Column(String, primary_key=True)parent_id = Column(String)
用户类
class User(Base):__tablename__ = 'users'user_id = Column(String, primary_key=True)group_id = Column(String)user_name = Column(String)
人脸类
class Face(Base):__tablename__ = 'faces'face_id = Column(String, primary_key=True)user_id = Column(String)image_path = Column(String)#脸部特征值embedding_face = Column(LargeBinary)
四 特征模型
系统运行时,会自动下载模型,也可以手动下载。
模型下载地址
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/age_model_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/arcface_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/deepid_keras_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/facenet512_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/facenet_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/facial_expression_model_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/gender_model_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/openface_weights.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/race_model_single_batch.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/retinaface.h5
wget -c https://github.com/serengil/deepface_models/releases/download/v1.0/vgg_face_weights.h5
wget -c https://github.com/serengil/deepface_models/archive/refs/tags/v1.0.zip
wget -c https://github.com/serengil/deepface_models/archive/refs/tags/v1.0.tar.gz
本次关于人脸识别系统的介绍就完成了,欢迎业务交流6550523
源码链接
基于deepface的人脸识别库.zip资源-CSDN文库
人脸识别应用
【访客管理系统】基于低代码+golang+人脸识别技术实现的访客管理系统-CSDN博客