修改代码与文档

This commit is contained in:
jiang 2024-09-12 17:14:01 +08:00
parent 7c63e11132
commit 3a1c18c29d
5 changed files with 424 additions and 233 deletions

View File

@ -2,7 +2,7 @@ import redis
import os
# Redis config
redis_host = str(os.getenv("REDIS_HOST", 'localhost'))
redis_host = str(os.getenv("REDIS_HOST", '192.168.0.14'))
redis_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
num_workers = int(os.getenv("NUM_WORKERS", 10))

View File

@ -20,17 +20,16 @@ from models import FaceRecoger, FaceBoxesV2, Landmark5er, FaceAlign, QualityOfCl
so = onnxruntime.SessionOptions()
so.log_severity_level = 3 # 0=VERBOSE, 1=INFO, 2=WARNING, 3=ERROR, 4=FATAL
# 获取workers
if "NUM_WORKERS" not in os.environ:
raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
# # 获取workers
# if "NUM_WORKERS" not in os.environ:
# raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
NUM_WORKERS = int(os.getenv("NUM_WORKERS", 10))
# max readers
max_readers = int(os.getenv("MAX_READERS", 60))
# 连接到 Redis
redis_host = str(os.getenv("REDIS_HOST", 'localhost'))
redis_host = str(os.getenv("REDIS_HOST", '192.168.0.14'))
redis_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
# connected
@ -73,26 +72,31 @@ ErrorMsg = {
"30019": "identity already exists; for database protection, operation rejected at this time"
}
class FileError(Exception):
def __init__(self, arg: str):
self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class NotImpltError(Exception):
def __init__(self, arg: str):
self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class FaceError(Exception):
def __init__(self, arg: str):
self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class UpdatedbError(Exception):
def __init__(self, arg: str):
self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
# setting Logger
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
log_dir = f"{os.path.dirname(os.path.abspath(__file__))}/log"
@ -102,6 +106,7 @@ logging.basicConfig(filename=f'{log_dir}/{current_time}.log', level=logging.INFO
logger = logging.getLogger(__name__) # @@@@
print(log_dir)
def list_images(path: str):
"""
List images in a given path
@ -150,6 +155,7 @@ def find_image_hash(file_path: str) -> str:
hasher.update(properties.encode("utf-8"))
return hasher.hexdigest()
# 支持base64 local-path url 等多种检索图片的方式,返回 numpy
def load_img(img_path: str):
image = None
@ -206,16 +212,21 @@ class FaceHelper:
config = yaml.safe_load(f)
self.sim_threshold = config['faceReg']['sim_threshold'] # 0.7
self.rotclsifer = onnxruntime.InferenceSession( config['ck_paths']['rotifer'], so) # "./checkpoints/model_gray_mobilenetv2_rotcls.onnx"
self.rotclsifer = onnxruntime.InferenceSession(config['ck_paths']['rotifer'],
so) # "./checkpoints/model_gray_mobilenetv2_rotcls.onnx"
self.db_path = os.path.join(db_dir, "seetaface6.pkl").lower()
self.fd = FaceBoxesV2(config['ck_paths']['FcBx'], config['ck_paths']['num_threads'] ) # r"./checkpoints/faceboxesv2-640x640.onnx" 4
self.ld5er = Landmark5er( onnx_path1 = config['ck_paths']['landmk1'], # "./checkpoints/face_landmarker_pts5_net1.onnx",
onnx_path2 = config['ck_paths']['landmk2'], # "./checkpoints/face_landmarker_pts5_net2.onnx",
self.fd = FaceBoxesV2(config['ck_paths']['FcBx'],
config['ck_paths']['num_threads']) # r"./checkpoints/faceboxesv2-640x640.onnx" 4
self.ld5er = Landmark5er(onnx_path1=config['ck_paths']['landmk1'],
# "./checkpoints/face_landmarker_pts5_net1.onnx",
onnx_path2=config['ck_paths']['landmk2'],
# "./checkpoints/face_landmarker_pts5_net2.onnx",
num_threads=config['ck_paths']['num_threads'] # 4
)
self.fa = FaceAlign()
self.fr = FaceRecoger(onnx_path = config['ck_paths']['FcReg'], num_threads= config['ck_paths']['num_threads'] ) # "./checkpoints/face_recognizer.onnx" 4
self.fr = FaceRecoger(onnx_path=config['ck_paths']['FcReg'],
num_threads=config['ck_paths']['num_threads']) # "./checkpoints/face_recognizer.onnx" 4
self.qc = QualityChecker(config['brightness']['v0'], config['brightness']['v1'],
config['brightness']['v2'], config['brightness']['v3'],
hw=(config['resolution']['height'], config['resolution']['width'])
@ -224,7 +235,8 @@ class FaceHelper:
var_onnx_path=config['pose']['var_onnx_path'], # './checkpoints/fsanet-var.onnx',
conv_onnx_path=config['pose']['conv_onnx_path'], # './checkpoints/fsanet-conv.onnx'
)
self.qclarity = QualityOfClarity(low_thresh=config['clarity']['low_thrd'], high_thresh=config['clarity']['high_thrd'])
self.qclarity = QualityOfClarity(low_thresh=config['clarity']['low_thrd'],
high_thresh=config['clarity']['high_thrd'])
# refresh the db
try:
@ -316,10 +328,8 @@ class FaceHelper:
# 只是提取人脸特征,不要对别的有任何影响
def featureDetect(self, img_path: str):
rw_lock.acquire_read()
try:
image = load_img(img_path) # get bgr numpy image
unknown_embeddings = []
image = self.rotadjust(image) # 调整角度
detect_result = self.fd.detect(image)
@ -341,7 +351,7 @@ class FaceHelper:
ret_data = []
for _, (facebox, feature) in enumerate(zip(detect_result, unknown_embeddings)):
code = self.check_face("None", image, facebox, prefix='featuredetect')
ret_data.append({'code':code, 'msg': ErrorMsg[code], 'data': [f"{feature.tobytes()},{str(feature.dtype)}", facebox]})
ret_data.append({'code': code, 'msg': ErrorMsg[code], 'data': [{str(num) for num in feature}, facebox]})
if len(ret_data) != 1:
ret_data = {'code': "30008", 'msg': ErrorMsg["30008"], 'data': ret_data}
@ -371,14 +381,16 @@ class FaceHelper:
self.db_embeddings, self.db_identities = None, None
else:
self.db_embeddings = np.array([rep["embedding"] for rep in representations], dtype=np.float32)
self.db_identities = [os.path.splitext(os.path.basename(rep["identity"]))[0] for rep in representations]
self.db_identities = [os.path.splitext(os.path.basename(rep["identity"]))[0] for rep in
representations]
redis_client.set(f"worker_{self.pid}", 1) # 同步完毕
img = load_img(img_path)
img = self.rotadjust(img) # 调整角度
if opt in ["add", "replace"]:
if opt == "add" and self.db_identities is not None and identity in self.db_identities:
raise UpdatedbError("30019") # push: identity has exist. to pretect the db, reject opt of this time
raise UpdatedbError(
"30019") # push: identity has exist. to pretect the db, reject opt of this time
else:
detect_result = self.fd.detect(img)
@ -391,12 +403,15 @@ class FaceHelper:
areas = [box.area() for box in detect_result]
max_idx = areas.index(max(areas))
facebox = detect_result[max_idx]
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
FaceError_number = self.check_face(img_path=img_path[:200], img=img, facebox=facebox, prefix='update')
facebox = (
facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
FaceError_number = self.check_face(img_path=img_path[:200], img=img, facebox=facebox,
prefix='update')
if FaceError_number != "30002":
raise FaceError(FaceError_number)
cv2.imwrite(os.path.join(self.db_dir, identity+'.jpg'),img,[cv2.IMWRITE_JPEG_QUALITY, 100]) # 如果file已经存在则会替换它
cv2.imwrite(os.path.join(self.db_dir, identity + '.jpg'), img,
[cv2.IMWRITE_JPEG_QUALITY, 100]) # 如果file已经存在则会替换它
elif opt == "delete":
try:
@ -405,7 +420,6 @@ class FaceHelper:
pass
else:
raise NotImpltError("30007") # push: this updateDB type is not support
print("end the updateDB")
logger.info(f"end the updateDB")
@ -446,7 +460,9 @@ class FaceHelper:
storage_images[idx] = base_path + '.jpg'
must_save_pickle = False
new_images = []; old_images = []; replaced_images = []
new_images = [];
old_images = [];
replaced_images = []
new_images = list(set(storage_images) - set(pickle_images))
old_images = list(set(pickle_images) - set(storage_images))
@ -492,7 +508,8 @@ class FaceHelper:
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
if check:
FaceError_number = self.check_face(img_path=new_image[:200], img=image, facebox=facebox, prefix='refreshdb')
FaceError_number = self.check_face(img_path=new_image[:200], img=image, facebox=facebox,
prefix='refreshdb')
if FaceError_number != "30002":
continue
@ -631,3 +648,70 @@ class FaceHelper:
return "30017" # poor clarity of face in the image
return "30002"
def imageComparison(self, firstImage, secondImage):
rw_lock.acquire_read()
try:
# Load and adjust images
imageFirst = self.rotadjust(load_img(firstImage))
imageSecond = self.rotadjust(load_img(secondImage))
print("1")
# Detect faces
detect_result_first = self.fd.detect(imageFirst)
detect_result_second = self.fd.detect(imageSecond)
detect_result_first = [(box.x1, box.y1, box.x2, box.y2) for box in detect_result_first]
detect_result_second = [(box.x1, box.y1, box.x2, box.y2) for box in detect_result_second]
if len(detect_result_first) < 1:
raise FaceError("30018")
if len(detect_result_second) < 1:
raise FaceError("30018")
if len(detect_result_first) > 1:
raise FaceError("30008")
if len(detect_result_second) > 1:
raise FaceError("30008")
# Validate detected faces
self._validate_face_detection(detect_result_first, "firstImage")
self._validate_face_detection(detect_result_second, "secondImage")
# 提取人脸特征
features_first = self._extract_single_feature(imageFirst, detect_result_first)
features_second = self._extract_single_feature(imageSecond, detect_result_second)
# Calculate the Euclidean distance between features
distance = calculate_euclidean_distance(features_first, features_second)
# Compare distance with threshold
return distance >= 0.75
except Exception as e:
logger.error(f"Error occurred: {e}")
raise e
finally:
rw_lock.release_read()
def _validate_face_detection(self, detect_result, image_name):
if not detect_result:
logger.warning(f"No face detected in the image: {image_name}")
raise FaceError("30018") # no face in the image
def extract_features(self, image, detect_result):
return [
self._extract_single_feature(image, facebox)
for facebox in detect_result
]
def _extract_single_feature(self, image, detect_result):
for facebox in detect_result:
landmarks5 = self.ld5er.inference(image, facebox)
landmarks5 = np.array([[ld5[0], ld5[1]] for ld5 in landmarks5])
cropped_face = self.fa.align(image, landmarks5=landmarks5)
return self.fr.inference(cropped_face)
def calculate_euclidean_distance(features1, features2):
if len(features1) != len(features2):
raise ValueError("Feature arrays must have the same length")
# Calculate the Euclidean distance
total = 0
for f1, f2 in zip(features1, features2):
total += f1 * f2
return total

View File

@ -0,0 +1,36 @@
@echo off
setlocal enabledelayedexpansion
rem 从 redis.yaml 文件中读取 Redis 相关配置信息
set "REDIS_CONFIG="
for /f "delims=" %%i in (redis.yaml) do (
set "REDIS_CONFIG=!REDIS_CONFIG! %%i"
)
rem 工作进程数 workers
set "NUM_WORKERS=%3"
rem 提取 Redis 配置信息
for %%i in (!REDIS_CONFIG!) do (
for /f "tokens=1,* delims==" %%j in (%%i) do (
if "%%j"=="REDIS_HOST" set "REDIS_HOST=%%k"
if "%%j"=="REDIS_PORT" set "REDIS_PORT=%%k"
if "%%j"=="REDIS_PASSWORD" set "REDIS_PASSWORD=%%k"
)
)
echo num_workers: %NUM_WORKERS%
echo redis_host: %REDIS_HOST%; redis_port: %REDIS_PORT%
rem 运行 Python 脚本
set REDIS_HOST=%REDIS_HOST%
set REDIS_PORT=%REDIS_PORT%
set REDIS_PASSWORD=%REDIS_PASSWORD%
set NUM_WORKERS=%NUM_WORKERS%
rem 确保 Python 脚本能够正确读取环境变量
python flushredis.py
rem 启动 uvicorn
set MAX_READERS=%4%
uvicorn webmain:app --host %1 --port %2 --workers %NUM_WORKERS%

View File

@ -1,83 +1,154 @@
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from process import FaceHelper, FileError, ErrorMsg
import threading
import os
import time
# 创建 FastAPI 实例
app = FastAPI()
# 创建线程锁,确保在多线程环境中对共享资源的访问是线程安全的
lock = threading.Lock()
# 全局变量 facehelper稍后将用于管理人脸识别相关操作
facehelper = None
facehelper = FaceHelper(
db_dir="./dbface",
)
class face(BaseModel):
def get_facehelper():
"""
懒加载模式初始化 facehelper 对象
只有在第一次调用时才会创建 FaceHelper 实例
使用线程锁以确保 facehelper 实例在多线程环境下的安全初始化
"""
global facehelper
if facehelper is None:
with lock:
if facehelper is None:
facehelper = FaceHelper(db_dir="./dbface")
return facehelper
# 定义请求模型,用于接收前端传递的图像数据
class FaceRequest(BaseModel):
img: str
class dbface(BaseModel):
# 定义请求模型,用于接收前端传递的数据库操作数据
class DBFaceRequest(BaseModel):
img: str
optMode: str
uniqueKey: str
# 接两图片对比的参数
class ImageComparison(BaseModel):
firstImage: str
secondImage: str
@app.post("/refreshdb")
@app.post("/refreshdb/")
def refresh():
global facehelper
"""
刷新人脸数据库的接口
该接口将调用 facehelper updateDB 方法刷新数据库内容
"""
facehelper = get_facehelper()
try:
# 加锁确保数据库更新操作的线程安全性
with lock:
facehelper.updateDB(None, None, None, Onlyrefresh=True)
except FileError as e:
# return {"status":e.code, "detail":f"{e}"}
return {'code': e.code, 'msg': f"{e}", 'data': 'None'}
# 处理文件错误,返回相应的错误代码和信息
return {'code': e.code, 'msg': str(e), 'data': None}
else:
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': 'None'}
# 成功刷新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': None}
@app.post("/facerecognition")
@app.post("/facerecognition/")
def faceRecognition(input:face):
start = time.time()
global facehelper
def faceRecognition(input: FaceRequest):
"""
进行人脸识别的接口
接收前端传递的图像数据并使用 facehelper 进行人脸识别
返回识别结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try:
# 调用 facehelper 的 faceRecognition 方法进行人脸识别
ret_data = facehelper.faceRecognition(input.img)
print("finished recognition...")
end = time.time()
print("runtime: ", end-start)
except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'}
# return {"status":f"{e.code}", "detail":f"{e}"}
else:
# 处理异常,返回相应的错误代码和信息
raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Recognition finished. Runtime: {end - start:.2f} seconds")
# 返回识别结果
return ret_data
return {"status":1, "name":identity, "resImg":res_img_base64}
@app.post("/featuredetect")
@app.post("/featuredetect/")
def featureDetect(input:face):
start = time.time()
global facehelper
def featureDetect(input: FaceRequest):
"""
特征检测接口
接收前端传递的图像数据并使用 facehelper 进行特征检测
返回检测结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try:
# 调用 facehelper 的 featureDetect 方法进行特征检测
ret_data = facehelper.featureDetect(input.img)
print("finished featuredetect...")
end = time.time()
print("runtime: ", end-start)
except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'}
# return {"status":f"{e.code}", "detail":f"{e}"}
else:
# 处理异常,返回相应的错误代码和信息
raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Feature detection finished. Runtime: {end - start:.2f} seconds")
# 返回检测结果
return ret_data
@app.post("/updatedb")
@app.post("/updatedb/")
def updateDB(input:dbface):
global facehelper
# input.uniqueKey = os.path.splitext(os.path.basename(input.uniqueKey))[0]
@app.post("/updatedb")
def updateDB(input: DBFaceRequest):
"""
更新人脸数据库的接口
接收前端传递的数据库操作数据并使用 facehelper 更新数据库
"""
facehelper = get_facehelper()
try:
# 加锁确保数据库更新操作的线程安全性
with lock:
facehelper.updateDB(input.img, input.optMode, input.uniqueKey)
except Exception as e:
return {'code': e.code, 'msg': f"{e}", 'data': 'None'}
# return {"status":f"{e.code}", "detail":f"{e}"}
# 处理异常,返回相应的错误代码和信息
raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else:
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': 'None'}
# 成功更新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': None}
@app.post("/imageComparison")
def imageComparison(input: ImageComparison):
"""
更新人脸数据库的接口
接收前端传递的数据库操作数据并使用 facehelper 更新数据库
"""
facehelper = get_facehelper()
try:
# 加锁确保数据库更新操作的线程安全性
with lock:
state = facehelper.imageComparison(input.firstImage, input.secondImage)
except Exception as e:
# 处理异常,返回相应的错误代码和信息
raise HTTPException(status_code=400, detail={'code': e.code, 'msg': str(e), 'data': None})
else:
# 成功更新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': bool(state)}