app.py
# coding=UTF-8
from flask import Flask, request, jsonify
import facedata
from flask_cors import CORS
app = Flask(__name__)
@app.route("/insert_data/", methods=['GET', 'POST'])
def insert_data():
file_id = request.form.get('file_id')
user_id = request.form.get('user_id')
file_path = request.form.get('file_path')
name = request.form.get('name')
facedata.create_data_table(name)
if file_path:
data = facedata.insert_one_data(file_path, int(user_id), int(file_id),name)
return jsonify(data)
return jsonify({'code': 0, 'msg': '图片未找到'})
@app.route("/select_data/", methods=['GET', 'POST'])
def select_data():
file_id = request.form.get('file_id')
user_id = request.form.get('user_id')
file_path = request.form.get('file_path')
name = request.form.get('name')
facedata.create_data_table(name)
if file_path:
data = facedata.select_data(file_path, user_id,name)
return jsonify(data)
return jsonify({'code': 0, 'msg': '图片未找到'})
@app.route("/delete_data/", methods=['GET', 'POST'])
def delete_data():
file_id = request.form.get('file_id')
name = request.form.get('name')
facedata.create_data_table(name)
if file_id:
expr = "face_id in [{}]".format(file_id)
facedata.delete_data(expr,name)
return jsonify({'code': 1, 'msg': '删除成功'})
return jsonify({'code': 0, 'msg': '图片未找到'})
@app.errorhandler(404)
def handle_404_error(error):
print('error')
"""自定义错误的方法"""
# 前端用户最终看到的结果,也可以根据错误进行调整
return jsonify({'code': 0, 'msg': 'Error'})
CORS(app, resources=r'/*')
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)
main.py
import time
from io import BytesIO
import PIL
import face_recognition
import os
from numpy import array # 若不加这一行,对从文件中读取已保存的128维人脸编码执行eval()时,报错name 'array' is not defined
import cv2
import facedata
import requests
from PIL import Image
import numpy as np
import base64
import re
t1 = int(time.time())
# http://wotui2020.zhiqingco.com/202304241505568951a8189.png
# 获取列表的第二个元素
def takeSecond(elem):
return elem[1]
# 判断图片是否清晰,参数是图片的绝对路径
def getImageVar(imgPath: str) -> bool:
image = cv2.imread(imgPath)
img2gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
imageVar = cv2.Laplacian(img2gray, cv2.CV_64F).var() # 一般大于100时认为图片是清晰的,实际需根据情况调节
if imageVar <= 100:
return False
elif imageVar > 100:
return True
# 检测图片中是否有人脸。能接受不完整的人脸,如缺少鼻子以下,此时是能否检测到人脸的阈值之一,人脸中其他部位的缺少情况未测试
def check_face_img(img_path: str) -> bool:
img = face_recognition.load_image_file(img_path) # 加载图像。会将图像加载到 numpy 数组中。
result = face_recognition.face_locations(img) # 返回图像中每张人脸的人脸特征位置列表。图像中没有人脸时,返回空列表
if len(result) > 0:
return True
else:
return False
# 获取图片库中每张图片的128维人脸编码,用于传给face_recognition.face_distance()的face_encodings参数
def get_face_encodings(img_folder_path):
img_face_encoding_list = [] # 每个元素是文件夹中所有图片的128维人脸编码
img_path_list = [] # 每个元素是文件夹中图片的绝对路径
img_list = os.listdir(img_folder_path)
for img in img_list:
img_path = os.path.join(img_folder_path, img)
# if getImageVar(img_path) and check_face_img(img_path):
if check_face_img(img_path):
img_path_list.append(img_path)
img = face_recognition.load_image_file(img_path) # 加载图像
img_face_encoding = face_recognition.face_encodings(img)[
0] # 返回图像中每张人脸的 128 维人脸编码。后面使用face_recognition.face_distance()时,face_recognition.face_distance()的face_encodings参数中的值不能是由face_recognition.face_encodings(img)组成的,而应由face_recognition.face_encodings(img)[0]组成。
img_face_encoding_list.append(img_face_encoding)
elif not getImageVar(img_path):
print(f'{img_path}图片太模糊,请重新拍摄,入库时已忽略此图片')
elif not check_face_img(img_path):
print(f'{img_path}没有检测到完整人脸,请重新拍摄,入库时已忽略此图片')
return img_face_encoding_list, img_path_list
def get_on_face_encodings(img_path):
img_face_encoding_list = [] # 每个元素是文件夹中所有图片的128维人脸编码
img_path_list = [] # 每个元素是文件夹中图片的绝对路径
if img_path.startswith("http"):
img_data = requests.get(img_path, stream=True)
img_path = BytesIO(img_data.content)
else:
img_path = os.path.join(img_path)
if check_face_img(img_path):
img_path_list.append(img_path)
img = face_recognition.load_image_file(img_path) # 加载图像
img_face_encoding = face_recognition.face_encodings(img)[
0] # 返回图像中每张人脸的 128 维人脸编码。后面使用face_recognition.face_distance()时,face_recognition.face_distance()的face_encodings参数中的值不能是由face_recognition.face_encodings(img)组成的,而应由face_recognition.face_encodings(img)[0]组成。
img_face_encoding_list.append(img_face_encoding)
elif not check_face_img(img_path):
print(f'{img_path}没有检测到完整人脸,请重新拍摄,入库时已忽略此图片')
return img_face_encoding_list
# 从本地文件读取已保存的图片库中每张图片的128维人脸编码,节省计算时间
def get_face_encodings_from_file(img_folder_path):
with open("face_encod_list.txt", 'r', encoding='utf-8') as f:
img_face_encoding_list = eval(f.read()) # 需from numpy import array,否则eval()报错name 'array' is not defined
img_path_list = []
img_list = os.listdir(img_folder_path)
for img in img_list:
img_path = os.path.join(img_folder_path, img)
img_path_list.append(img_path)
return img_face_encoding_list, img_path_list
# 匹配
def pipei_image(image, image_path):
if check_face_img(images):
img_face_encoding_list, img_path_list = get_face_encodings_from_file(image_path)
img = face_recognition.load_image_file(images)
source_img_face_encoding = face_recognition.face_encodings(img)[0]
result = face_recognition.face_distance(face_encodings=img_face_encoding_list,
face_to_compare=source_img_face_encoding) # 给定人脸编码列表,将它们与已知的人脸编码进行比较,并得到每个比较人脸的欧氏距离。距离大小为面孔的相似程度。欧氏距离越小相似度越大。欧氏距离的典型阈值是0.6,即小于0.6的可认为匹配成功。face_encodings是要比较的人脸编码列表,face_to_compare是要与之进行比较的人脸编码。
temp_list = list(zip(img_path_list, result))
temp_list.sort(key=takeSecond) # 将原列表按列表中每个子元素中的第二个元素的值进行升序排列
if temp_list[0][-1] < 0.6: # 升序排列后第一个元素的相似度最高,如果它的欧氏距离小于0.6,认为匹配成功
print(temp_list[0])
else:
print('匹配失败') # 有可能不是本人,也有可能是本次拍摄时不清晰
elif not check_face_img(images):
print('没有检测到完整人脸,请重新拍摄')
images = os.getcwd() + "/image/9.png"
image_path = os.getcwd() + "/image"
if __name__ == "__main__":
img_b64encode = "base64"
base64_data = re.sub('^data:image/.+;base64,', '', img_b64encode)
byte_data = base64.b64decode(base64_data)
image_data = BytesIO(byte_data)
print(check_face_img(image_data))
# while True:
# user_id = 0
# name = "ATTUG4VKQ032cd"
# file_path = ''
# d = facedata.select_data(file_path, user_id, name)
# print(d)
# time.sleep(1)
pass
# print(get_on_face_encodings(images))
# print(check_face_img(images))
# get_face_encodings(image_path)
# img_face_encoding_list = get_on_face_encodings(images)
# print(img_face_encoding_list)
# get_face_encodings_from_file(image_path)
# pipei_image(images,image_path)
# t3 = int(time.time())
# print(t3 - t1)
# facedata.drop_data_tabel('face_feature_vector_admin')
# facedata.create_data_table()
# facedata.insert_one_data(images,102,100)
# if check_face_img(images):
# facedata.select_data(images)
# else:
# print("不是人脸")
# wl_img = ""
# facedata.insert_one_data(wl_img, 102, 100)
# facedata.select_data(wl_img)
facedata.py
from io import BytesIO
import requests
from pymilvus import connections, CollectionSchema, FieldSchema, DataType, Collection, utility
import os
import main
import face_recognition
import base64
import re
def init_data():
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
pass
name = 1
try:
collection = Collection(name) # Get an existing collection.
collection.load()
except Exception as e:
pass
connections.disconnect("default") # 断开 Milvus 连接
def query_data(name):
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
collection = Collection(name) # Get an existing collection.
res = collection.query(
expr="face_id in [1]",
output_fields=["face_id", "file_id"],
limit=100,
consistency_level="Strong"
)
connections.disconnect("default") # 断开 Milvus 连接
return res
def delete_data(expr,name):
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
collection = Collection(name) # Get an existing collection.
result = collection.delete(expr)
connections.disconnect("default") # 断开 Milvus 连接
return result
def counts(name):
collection = Collection(name)
state = collection.get_compaction_state()
connections.disconnect("default") # 断开 Milvus 连接
def select_data(source_img_file_path, user_id,name):
if source_img_file_path.startswith("http"):
img_data = requests.get(source_img_file_path, stream=True)
file_path = BytesIO(img_data.content)
elif source_img_file_path.startswith("data:image"):
base64_data = re.sub('^data:image/.+;base64,', '', source_img_file_path)
byte_data = base64.b64decode(base64_data)
file_path = BytesIO(byte_data)
else:
file_path = os.path.join(source_img_file_path)
if main.check_face_img(file_path):
try:
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
if source_img_file_path.startswith("http"):
img_data = requests.get(source_img_file_path, stream=True)
source_img_file_path = BytesIO(img_data.content)
elif source_img_file_path.startswith("data:image"):
source_img_file_path = file_path
else:
source_img_file_path = os.path.join(source_img_file_path)
collection = Collection(name)
img = face_recognition.load_image_file(source_img_file_path) # 加载图像
img_face_encoding = face_recognition.face_encodings(img)
img_face_encoding = img_face_encoding[0]
search_params = {"metric_type": "L2", "params": {
"nprobe": 10}} # 准备适合你的搜索场景的参数。下面的示例定义了搜索将使用欧式距离计算,并从 IVF_FLAT 索引构建的十个最近的聚类中检索向量。
search_param = {
"data": [img_face_encoding],
"anns_field": "face_feature_vector",
"param": search_params,
"limit": 1,
"timeout": 10,
#"expr": "user_id in [" + user_id + "]",
"output_fields": ['user_id', 'face_id'],
"consistency_level": "Strong"
}
results = collection.search(**search_param)
# 查看最相似向量的 primary key 及其距离值
id_list = results[0].ids
distance_list = results[0].distances
connections.disconnect("default") # 断开 Milvus 连接
if (len(list(id_list)) > 0) and (list(distance_list)[0] <= 0.2):
file_id = 0
user_id = 0
for hit in results[0]:
file_id = hit.entity.get("face_id")
user_id = hit.entity.get('user_id')
return {'code': 1, 'msg': "图片已匹配", 'data': {"user_id": user_id, 'file_id': file_id}}
else:
return {'code': 0, 'msg': "没有匹配人脸"}
except Exception as e:
print(e)
connections.disconnect("default") # 断开 Milvus 连接
return {'code': 0, 'msg': "图片有误"}
else:
return {'code': 0, 'msg': "人脸有误"}
def drop_data_tabel(name):
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
utility.drop_collection(name)
def create_data_table(name):
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
if utility.has_collection(name)==True:
return True
# 准备架构,包括字段架构、集合架构和集合名称。
face_id = FieldSchema(
name="face_id",
dtype=DataType.INT64,
is_primary=True,
auto_id=False # 关闭自动赋值自增id,实际人员的id由前端传入
)
user_id = FieldSchema(
name="user_id",
dtype=DataType.INT64,
auto_id=False
)
face_feature_vector = FieldSchema(
name='face_feature_vector',
dtype=DataType.FLOAT_VECTOR,
# 128维人脸编码是浮点数。一个集合中必须有一个字段是DataType.FLOAT_VECTOR或DataType.BINARY_VECTOR类型,文档原话The collection to create must contain a primary key field and a vector field. INT64 is the only supported data type for the primary key field in current release of Milvus. from https://milvus.io/cn/docs/v2.0.0/create_collection.md#Prepare-Schema
dim=128 # face_recognition.face_encodings()返回的列表再取第一个元素,值是128维人脸编码。向量列数,一个向量有多少个元素就有多少列。
)
schema = CollectionSchema(
fields=[face_id, user_id, face_feature_vector],
description=name
)
collection_name = name
# 使用架构创建集合
Collection(
name=collection_name,
schema=schema,
using='default', # 服务器别名,要在哪个服务器创建集合
shards_num=2,
consistency_level="Strong"
)
collection = Collection(name)
collection.load()
pass
def insert_one_data(img_path, user_id, file_id,name):
connections.connect(alias="default", host='127.0.0.1', port='19530') # 构建一个 Milvus 连接,alias是创建的Milvus连接的别名
if img_path.startswith("http"):
img_data = requests.get(img_path, stream=True)
img_path = BytesIO(img_data.content)
else:
img_path = os.path.join(img_path)
if main.check_face_img(img_path):
img = face_recognition.load_image_file(img_path) # 加载图像
img_face_encoding = face_recognition.face_encodings(img)[
0] # 返回图像中每张人脸的 128 维人脸编码。后面使用face_recognition.face_distance()时,face_recognition.face_distance()的face_encodings参数中的值不能是由face_recognition.face_encodings(img)组成的,而应由face_recognition.face_encodings(img)[0]组成。
collection = Collection(name) # Get an existing collection.
collection.insert(
[[file_id], [user_id], [img_face_encoding]])
connections.disconnect("default") # 断开 Milvus 连接
return {'code': 1, 'msg': '入库成功'}
elif not main.check_face_img(img_path):
connections.disconnect("default") # 断开 Milvus 连接
return {'code': 0, 'msg': '没有检测到完整人脸,请重新拍摄'}
def create_index():
index_params = { # 准备索引参数
"metric_type": "L2",
"index_type": "IVF_FLAT",
"params": {"nlist": 1024}
}
collection = Collection("face_feature_vector_admin")
collection.create_index(
field_name="face_feature_vector",
index_params=index_params
)
def insert_data(name):
# 准备要插入的数据。要插入的数据的数据类型必须与集合的架构匹配,否则 Milvus 将引发异常。
img_folder_path = os.getcwd() + "/image"
img_list = os.listdir(img_folder_path)
for img in img_list:
img_path = os.path.join(img_folder_path, img)
if main.check_face_img(img_path):
img = face_recognition.load_image_file(img_path) # 加载图像
img_face_encoding = face_recognition.face_encodings(img)[
0] # 返回图像中每张人脸的 128 维人脸编码。后面使用face_recognition.face_distance()时,face_recognition.face_distance()的face_encodings参数中的值不能是由face_recognition.face_encodings(img)组成的,而应由face_recognition.face_encodings(img)[0]组成。
temp_id = 101
file_id = 100
collection = Collection(name) # Get an existing collection.
mr = collection.insert(
[[temp_id], [file_id], [img_face_encoding]])
print(mr) # 插入操作的执行结果
elif not main.check_face_img(img_path):
print(f'{img_path}没有检测到完整人脸,请重新拍摄,入库时已忽略此图片')