修改代码与文档

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 import os
# Redis config # 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_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!')) redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
num_workers = int(os.getenv("NUM_WORKERS", 10)) 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 = onnxruntime.SessionOptions()
so.log_severity_level = 3 # 0=VERBOSE, 1=INFO, 2=WARNING, 3=ERROR, 4=FATAL so.log_severity_level = 3 # 0=VERBOSE, 1=INFO, 2=WARNING, 3=ERROR, 4=FATAL
# # 获取workers
# 获取workers # if "NUM_WORKERS" not in os.environ:
if "NUM_WORKERS" not in os.environ: # raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
raise RuntimeError("Environment variable NUM_WORKERS is required but not set.")
NUM_WORKERS = int(os.getenv("NUM_WORKERS", 10)) NUM_WORKERS = int(os.getenv("NUM_WORKERS", 10))
# max readers # max readers
max_readers = int(os.getenv("MAX_READERS",60)) max_readers = int(os.getenv("MAX_READERS", 60))
# 连接到 Redis # 连接到 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_port = int(os.getenv("REDIS_PORT", 2012))
redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!')) redis_password = str(os.getenv("REDIS_PASSWORD", 'Xjsfzb@Redis123!'))
# connected # connected
@ -73,26 +72,31 @@ ErrorMsg = {
"30019": "identity already exists; for database protection, operation rejected at this time" "30019": "identity already exists; for database protection, operation rejected at this time"
} }
class FileError(Exception): class FileError(Exception):
def __init__(self, arg:str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class NotImpltError(Exception): class NotImpltError(Exception):
def __init__(self, arg:str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class FaceError(Exception): class FaceError(Exception):
def __init__(self, arg:str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
class UpdatedbError(Exception): class UpdatedbError(Exception):
def __init__(self, arg:str): def __init__(self, arg: str):
self.code = arg self.code = arg
self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"] self.args = [f"{str(self.__class__.__name__)} {str(arg)}: {ErrorMsg[arg]}"]
# setting Logger # setting Logger
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
log_dir = f"{os.path.dirname(os.path.abspath(__file__))}/log" 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__) # @@@@ logger = logging.getLogger(__name__) # @@@@
print(log_dir) print(log_dir)
def list_images(path: str): def list_images(path: str):
""" """
List images in a given path List images in a given path
@ -150,19 +155,20 @@ def find_image_hash(file_path: str) -> str:
hasher.update(properties.encode("utf-8")) hasher.update(properties.encode("utf-8"))
return hasher.hexdigest() return hasher.hexdigest()
# 支持base64 local-path url 等多种检索图片的方式,返回 numpy # 支持base64 local-path url 等多种检索图片的方式,返回 numpy
def load_img(img_path:str): def load_img(img_path: str):
image = None image = None
try: try:
if img_path.startswith(("http","www")): # url if img_path.startswith(("http", "www")): # url
response = requests.get(url=img_path, stream=True, timeout=60, proxies={"http": None, "https": None}) response = requests.get(url=img_path, stream=True, timeout=60, proxies={"http": None, "https": None})
response.raise_for_status() response.raise_for_status()
image_array = np.asarray(bytearray(response.raw.read()), dtype=np.uint8) image_array = np.asarray(bytearray(response.raw.read()), dtype=np.uint8)
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
elif img_path.startswith(("./","/","C:","D:","E:",".\\")) or os.path.isfile(img_path): # local-path elif img_path.startswith(("./", "/", "C:", "D:", "E:", ".\\")) or os.path.isfile(img_path): # local-path
if not os.path.isfile(img_path): if not os.path.isfile(img_path):
raise FileError("30004") # push: invalid file path raise FileError("30004") # push: invalid file path
elif not img_path.lower().endswith((".jpg",'.jpeg','.png')): elif not img_path.lower().endswith((".jpg", '.jpeg', '.png')):
raise FileError("30005") # push: invaild file suffix raise FileError("30005") # push: invaild file suffix
else: else:
image = cv2.imread(img_path) image = cv2.imread(img_path)
@ -170,7 +176,7 @@ def load_img(img_path:str):
encoded_data_parts = img_path.split(",") encoded_data_parts = img_path.split(",")
if len(encoded_data_parts) <= 0: if len(encoded_data_parts) <= 0:
raise FileError("104") # push: base64 is empty raise FileError("104") # push: base64 is empty
print( "base64 is empty" ) print("base64 is empty")
encoded_data = encoded_data_parts[-1] encoded_data = encoded_data_parts[-1]
nparr = np.fromstring(base64.b64decode(encoded_data), np.uint8) nparr = np.fromstring(base64.b64decode(encoded_data), np.uint8)
image = cv2.imdecode(nparr, cv2.IMREAD_COLOR) image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
@ -184,7 +190,7 @@ def load_img(img_path:str):
return image return image
def encoder_img2base64(img:np.ndarray): def encoder_img2base64(img: np.ndarray):
success, encoded_img = cv2.imencode('.png', img) success, encoded_img = cv2.imencode('.png', img)
if success: if success:
img_base64 = base64.b64encode(encoded_img).decode("utf-8") img_base64 = base64.b64encode(encoded_img).decode("utf-8")
@ -195,7 +201,7 @@ def encoder_img2base64(img:np.ndarray):
# from seetaface.api import * # from seetaface.api import *
class FaceHelper: class FaceHelper:
def __init__(self, db_dir, config_path = './config.yaml'): def __init__(self, db_dir, config_path='./config.yaml'):
self.db_dir = os.path.abspath(db_dir) self.db_dir = os.path.abspath(db_dir)
self.pid = PID_id self.pid = PID_id
self.db_embeddings = None self.db_embeddings = None
@ -206,25 +212,31 @@ class FaceHelper:
config = yaml.safe_load(f) config = yaml.safe_load(f)
self.sim_threshold = config['faceReg']['sim_threshold'] # 0.7 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'],
self.db_path = os.path.join( db_dir, "seetaface6.pkl" ).lower() 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.fd = FaceBoxesV2(config['ck_paths']['FcBx'],
self.ld5er = Landmark5er( onnx_path1 = config['ck_paths']['landmk1'], # "./checkpoints/face_landmarker_pts5_net1.onnx", config['ck_paths']['num_threads']) # r"./checkpoints/faceboxesv2-640x640.onnx" 4
onnx_path2 = config['ck_paths']['landmk2'], # "./checkpoints/face_landmarker_pts5_net2.onnx", 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 num_threads=config['ck_paths']['num_threads'] # 4
) )
self.fa = FaceAlign() 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'], self.qc = QualityChecker(config['brightness']['v0'], config['brightness']['v1'],
config['brightness']['v2'], config['brightness']['v3'], config['brightness']['v2'], config['brightness']['v3'],
hw = (config['resolution']['height'], config['resolution']['width']) hw=(config['resolution']['height'], config['resolution']['width'])
) # v0=70.0, v1=100.0, v2=210.0, v3=230.0 ) # v0=70.0, v1=100.0, v2=210.0, v3=230.0
self.qpose = QualityOfPose(yaw_thrd=config['pose']['yaw_thrd'], pitch_thrd=config['pose']['pitch_thrd'], self.qpose = QualityOfPose(yaw_thrd=config['pose']['yaw_thrd'], pitch_thrd=config['pose']['pitch_thrd'],
var_onnx_path = config['pose']['var_onnx_path'], # './checkpoints/fsanet-var.onnx', var_onnx_path=config['pose']['var_onnx_path'], # './checkpoints/fsanet-var.onnx',
conv_onnx_path = config['pose']['conv_onnx_path'], # './checkpoints/fsanet-conv.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 # refresh the db
try: try:
@ -237,7 +249,7 @@ class FaceHelper:
logger.info(f"db_dir: {self.db_dir} ; PID: {self.pid}") logger.info(f"db_dir: {self.db_dir} ; PID: {self.pid}")
# 读操作 # 读操作
def faceRecognition(self, img_path:str): def faceRecognition(self, img_path: str):
rw_lock.acquire_read() rw_lock.acquire_read()
if int(redis_client.get(f"worker_{self.pid}")) == 0: # 说明self中的db和磁盘中的db不同步 if int(redis_client.get(f"worker_{self.pid}")) == 0: # 说明self中的db和磁盘中的db不同步
with open(self.db_path, "rb") as f: with open(self.db_path, "rb") as f:
@ -268,7 +280,7 @@ class FaceHelper:
landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角 landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角
# print("5点关键点",landmarks5) # print("5点关键点",landmarks5)
# 输入image 和5点特征点位置(基于原图image的位置) , return all cropped aligned face (裁剪后的对齐后的人脸部分图像, 简写为aligned_faces # 输入image 和5点特征点位置(基于原图image的位置) , return all cropped aligned face (裁剪后的对齐后的人脸部分图像, 简写为aligned_faces
landmarks5 = [ [ld5[0],ld5[1]] for ld5 in landmarks5] landmarks5 = [[ld5[0], ld5[1]] for ld5 in landmarks5]
cropped_face = self.fa.align(image, landmarks5=landmarks5) cropped_face = self.fa.align(image, landmarks5=landmarks5)
# 输入aligned_faces return all features of aligned_faces # 输入aligned_faces return all features of aligned_faces
feature = self.fr.inference(cropped_face) feature = self.fr.inference(cropped_face)
@ -278,8 +290,8 @@ class FaceHelper:
unknown_embeddings = np.vstack(unknown_embeddings) unknown_embeddings = np.vstack(unknown_embeddings)
results = np.dot(unknown_embeddings, self.db_embeddings.T) results = np.dot(unknown_embeddings, self.db_embeddings.T)
max_values = np.max(results,axis=1) max_values = np.max(results, axis=1)
max_idxs = np.argmax(results,axis=1) max_idxs = np.argmax(results, axis=1)
for i, (idx, value) in enumerate(zip(max_idxs, max_values)): for i, (idx, value) in enumerate(zip(max_idxs, max_values)):
name = "unknown" name = "unknown"
@ -290,15 +302,15 @@ class FaceHelper:
ret_data = [] ret_data = []
for i, (facebox, name) in enumerate(zip(detect_result, names)): for i, (facebox, name) in enumerate(zip(detect_result, names)):
if name != 'unknown': if name != 'unknown':
ret_data.append({'code':"30000", 'msg': ErrorMsg["30000"], 'data':[name,facebox]}) ret_data.append({'code': "30000", 'msg': ErrorMsg["30000"], 'data': [name, facebox]})
else: else:
code = self.check_face("None", image, facebox, prefix='facereg') code = self.check_face("None", image, facebox, prefix='facereg')
if code == "30002": if code == "30002":
ret_data.append({'code':"30001", 'msg': ErrorMsg["30001"], 'data':[name,facebox]}) ret_data.append({'code': "30001", 'msg': ErrorMsg["30001"], 'data': [name, facebox]})
else: else:
ret_data.append({'code':code, 'msg': ErrorMsg[code], 'data':[name,facebox]}) ret_data.append({'code': code, 'msg': ErrorMsg[code], 'data': [name, facebox]})
if len(ret_data) != 1: if len(ret_data) != 1:
ret_data = {'code':"30008", 'msg': ErrorMsg["30008"], 'data': ret_data} ret_data = {'code': "30008", 'msg': ErrorMsg["30008"], 'data': ret_data}
else: else:
ret_data = ret_data[0] ret_data = ret_data[0]
@ -314,12 +326,10 @@ class FaceHelper:
# return names, [ encoder_img2base64(det) for det in cropped_images] # return names, [ encoder_img2base64(det) for det in cropped_images]
# 只是提取人脸特征,不要对别的有任何影响 # 只是提取人脸特征,不要对别的有任何影响
def featureDetect(self, img_path:str): def featureDetect(self, img_path: str):
rw_lock.acquire_read() rw_lock.acquire_read()
try: try:
image = load_img(img_path) # get bgr numpy image image = load_img(img_path) # get bgr numpy image
unknown_embeddings = [] unknown_embeddings = []
image = self.rotadjust(image) # 调整角度 image = self.rotadjust(image) # 调整角度
detect_result = self.fd.detect(image) detect_result = self.fd.detect(image)
@ -332,7 +342,7 @@ class FaceHelper:
landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角 landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角
# print("5点关键点",landmarks5) # print("5点关键点",landmarks5)
# 输入image 和5点特征点位置(基于原图image的位置) , return all cropped aligned face (裁剪后的对齐后的人脸部分图像, 简写为aligned_faces # 输入image 和5点特征点位置(基于原图image的位置) , return all cropped aligned face (裁剪后的对齐后的人脸部分图像, 简写为aligned_faces
landmarks5 = [ [ld5[0],ld5[1]] for ld5 in landmarks5] landmarks5 = [[ld5[0], ld5[1]] for ld5 in landmarks5]
cropped_face = self.fa.align(image, landmarks5=landmarks5) cropped_face = self.fa.align(image, landmarks5=landmarks5)
# 输入aligned_faces return all features of aligned_faces # 输入aligned_faces return all features of aligned_faces
feature = self.fr.inference(cropped_face) feature = self.fr.inference(cropped_face)
@ -341,10 +351,10 @@ class FaceHelper:
ret_data = [] ret_data = []
for _, (facebox, feature) in enumerate(zip(detect_result, unknown_embeddings)): for _, (facebox, feature) in enumerate(zip(detect_result, unknown_embeddings)):
code = self.check_face("None", image, facebox, prefix='featuredetect') 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: if len(ret_data) != 1:
ret_data = {'code':"30008", 'msg': ErrorMsg["30008"], 'data': ret_data} ret_data = {'code': "30008", 'msg': ErrorMsg["30008"], 'data': ret_data}
else: else:
ret_data = ret_data[0] ret_data = ret_data[0]
@ -358,7 +368,7 @@ class FaceHelper:
# opt in ['add','delete','replace'] identity作为检索的标识符img_path只是提供文件路径 # opt in ['add','delete','replace'] identity作为检索的标识符img_path只是提供文件路径
# 写操作 # 写操作
def updateDB(self, img_path :str, opt :str, identity :str, Onlyrefresh=False): def updateDB(self, img_path: str, opt: str, identity: str, Onlyrefresh=False):
global rw_lock global rw_lock
rw_lock.acquire_write() # 写锁定 rw_lock.acquire_write() # 写锁定
print("come in the updatedb") print("come in the updatedb")
@ -371,14 +381,16 @@ class FaceHelper:
self.db_embeddings, self.db_identities = None, None self.db_embeddings, self.db_identities = None, None
else: else:
self.db_embeddings = np.array([rep["embedding"] for rep in representations], dtype=np.float32) 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) # 同步完毕 redis_client.set(f"worker_{self.pid}", 1) # 同步完毕
img = load_img(img_path) img = load_img(img_path)
img = self.rotadjust(img) # 调整角度 img = self.rotadjust(img) # 调整角度
if opt in ["add","replace"]: if opt in ["add", "replace"]:
if opt == "add" and self.db_identities is not None and identity in self.db_identities: 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: else:
detect_result = self.fd.detect(img) detect_result = self.fd.detect(img)
@ -388,28 +400,30 @@ class FaceHelper:
raise FaceError("30018") # push: no face in the image raise FaceError("30018") # push: no face in the image
else: # 获取最大的face,然后进行check else: # 获取最大的face,然后进行check
# H, W = img.shape[:2] # H, W = img.shape[:2]
areas = [ box.area() for box in detect_result] areas = [box.area() for box in detect_result]
max_idx = areas.index(max(areas)) max_idx = areas.index(max(areas))
facebox = detect_result[max_idx] facebox = detect_result[max_idx]
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point facebox = (
FaceError_number = self.check_face(img_path=img_path[:200], img=img, facebox=facebox, prefix='update') 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": if FaceError_number != "30002":
raise FaceError(FaceError_number) 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": elif opt == "delete":
try: try:
os.remove(os.path.join(self.db_dir, identity+'.jpg')) os.remove(os.path.join(self.db_dir, identity + '.jpg'))
except FileNotFoundError: except FileNotFoundError:
pass pass
else: else:
raise NotImpltError("30007") # push: this updateDB type is not support raise NotImpltError("30007") # push: this updateDB type is not support
print("end the updateDB") print("end the updateDB")
logger.info(f"end the updateDB") logger.info(f"end the updateDB")
self.refresh_database(check = Onlyrefresh) # 结束时刷新下db, 并通知别的进程dirty self.refresh_database(check=Onlyrefresh) # 结束时刷新下db, 并通知别的进程dirty
except Exception as e: except Exception as e:
logger.info(f"{e}") logger.info(f"{e}")
rw_lock.release_write() rw_lock.release_write()
@ -418,7 +432,7 @@ class FaceHelper:
rw_lock.release_write() rw_lock.release_write()
return 0 return 0
def refresh_database(self, check = True): def refresh_database(self, check=True):
# ensure db exist # ensure db exist
os.makedirs(self.db_dir, exist_ok=True) os.makedirs(self.db_dir, exist_ok=True)
if not os.path.exists(self.db_path): if not os.path.exists(self.db_path):
@ -442,11 +456,13 @@ class FaceHelper:
if ext == '.jpg': if ext == '.jpg':
continue continue
iimg = cv2.imread(img_path) iimg = cv2.imread(img_path)
cv2.imwrite(base_path+'.jpg', iimg, [cv2.IMWRITE_JPEG_QUALITY, 100]) cv2.imwrite(base_path + '.jpg', iimg, [cv2.IMWRITE_JPEG_QUALITY, 100])
storage_images[idx] = base_path+'.jpg' storage_images[idx] = base_path + '.jpg'
must_save_pickle = False 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)) new_images = list(set(storage_images) - set(pickle_images))
old_images = list(set(pickle_images) - set(storage_images)) old_images = list(set(pickle_images) - set(storage_images))
@ -486,18 +502,19 @@ class FaceHelper:
else: else:
logger.info(f"{new_image}: find one face, perfect!") logger.info(f"{new_image}: find one face, perfect!")
areas = [ box.area() for box in detect_result] areas = [box.area() for box in detect_result]
max_idx = areas.index(max(areas)) max_idx = areas.index(max(areas))
facebox = detect_result[max_idx] facebox = detect_result[max_idx]
facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point facebox = (facebox.x1, facebox.y1, facebox.x2, facebox.y2) # top_left point, bottom_right point
if check: 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": if FaceError_number != "30002":
continue continue
landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角 landmarks5 = self.ld5er.inference(image, facebox) # return: [(),(),(),(),()] 左眼 右眼 鼻子 左嘴角 右嘴角
landmarks5 = [ [ld5[0],ld5[1]] for ld5 in landmarks5] landmarks5 = [[ld5[0], ld5[1]] for ld5 in landmarks5]
cropped_face = self.fa.align(image, landmarks5=landmarks5) cropped_face = self.fa.align(image, landmarks5=landmarks5)
feature = self.fr.inference(cropped_face) feature = self.fr.inference(cropped_face)
@ -550,10 +567,10 @@ class FaceHelper:
# 中心裁剪到 224x224 # 中心裁剪到 224x224
start = (256 - 224) // 2 start = (256 - 224) // 2
image = image[start:start+224, start:start+224] image = image[start:start + 224, start:start + 224]
# 将单通道灰度图像转换为三通道 # 将单通道灰度图像转换为三通道
image = np.stack((image,)*3, axis=-1) image = np.stack((image,) * 3, axis=-1)
# 转换为符合 ONNX 需要的格式 # 转换为符合 ONNX 需要的格式
image = image.astype(np.float32) / 255.0 # 归一化 image = image.astype(np.float32) / 255.0 # 归一化
@ -582,11 +599,11 @@ class FaceHelper:
def get_feature(self, img: np.ndarray): def get_feature(self, img: np.ndarray):
time.sleep(0.08) time.sleep(0.08)
# assert img.shape[0] == img.shape[1] and img.shape[0] == 256 and img.shape[2] == 3 # assert img.shape[0] == img.shape[1] and img.shape[0] == 256 and img.shape[2] == 3
img = cv2.resize( img, (256,256) ) img = cv2.resize(img, (256, 256))
input_feed = {} input_feed = {}
# crop_img = cv2.resize(img,(248,248)) # crop_img = cv2.resize(img,(248,248))
# crop_img = crop_img[...,::-1] # crop_img = crop_img[...,::-1]
crop_img = img[4:252, 4:252, :][...,::-1] # 注意要考虑 长或宽 < 248的情况 crop_img = img[4:252, 4:252, :][..., ::-1] # 注意要考虑 长或宽 < 248的情况
input_data = crop_img.transpose((2, 0, 1)) input_data = crop_img.transpose((2, 0, 1))
# resize_img = cv2.resize(img, (248, 248)) # resize_img = cv2.resize(img, (248, 248))
# input_data = resize_img.transpose((2, 0, 1)) # input_data = resize_img.transpose((2, 0, 1))
@ -610,7 +627,7 @@ class FaceHelper:
logger.info(f"{img_path}: when {prefix}, face shifted top/bottom") logger.info(f"{img_path}: when {prefix}, face shifted top/bottom")
print(f"{img_path}: when {prefix}, face shifted top/bottom") print(f"{img_path}: when {prefix}, face shifted top/bottom")
return "30011" # face shifted top/bottom, partially not captured. return "30011" # face shifted top/bottom, partially not captured.
face_img = img[ max(0,int(facebox[1])):int(facebox[3]), max(0,int(facebox[0])):int(facebox[2]) ] face_img = img[max(0, int(facebox[1])):int(facebox[3]), max(0, int(facebox[0])):int(facebox[2])]
if not self.qc.check_bright(face_img): if not self.qc.check_bright(face_img):
logger.info(f"{img_path}: when {prefix}, bad bright face in the image") logger.info(f"{img_path}: when {prefix}, bad bright face in the image")
print(f"{img_path}: when {prefix}, bad bright face in the image") print(f"{img_path}: when {prefix}, bad bright face in the image")
@ -631,3 +648,70 @@ class FaceHelper:
return "30017" # poor clarity of face in the image return "30017" # poor clarity of face in the image
return "30002" 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 pydantic import BaseModel
from process import FaceHelper, FileError, ErrorMsg from process import FaceHelper, FileError, ErrorMsg
import threading import threading
import os
import time import time
# 创建 FastAPI 实例
app = FastAPI() app = FastAPI()
# 创建线程锁,确保在多线程环境中对共享资源的访问是线程安全的
lock = threading.Lock() lock = threading.Lock()
# 全局变量 facehelper稍后将用于管理人脸识别相关操作
facehelper = None
facehelper = FaceHelper(
db_dir="./dbface",
)
class face(BaseModel): def get_facehelper():
img:str """
懒加载模式初始化 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 DBFaceRequest(BaseModel):
img: str
optMode: str
uniqueKey: str
# 接两图片对比的参数
class ImageComparison(BaseModel):
firstImage: str
secondImage: str
class dbface(BaseModel):
img:str
optMode:str
uniqueKey:str
@app.post("/refreshdb") @app.post("/refreshdb")
@app.post("/refreshdb/")
def refresh(): def refresh():
global facehelper """
刷新人脸数据库的接口
该接口将调用 facehelper updateDB 方法刷新数据库内容
"""
facehelper = get_facehelper()
try: try:
# 加锁确保数据库更新操作的线程安全性
with lock: with lock:
facehelper.updateDB(None, None, None, Onlyrefresh=True) facehelper.updateDB(None, None, None, Onlyrefresh=True)
except FileError as e: 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: else:
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': 'None'} # 成功刷新数据库后返回相应的状态码和消息
return {'code': "30002", 'msg': ErrorMsg['30002'], 'data': None}
@app.post("/facerecognition") @app.post("/facerecognition")
@app.post("/facerecognition/") def faceRecognition(input: FaceRequest):
def faceRecognition(input:face): """
start = time.time() 进行人脸识别的接口
global facehelper 接收前端传递的图像数据并使用 facehelper 进行人脸识别
返回识别结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try: try:
# 调用 facehelper 的 faceRecognition 方法进行人脸识别
ret_data = facehelper.faceRecognition(input.img) ret_data = facehelper.faceRecognition(input.img)
print("finished recognition...")
end = time.time()
print("runtime: ", end-start)
except Exception as e: 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: finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Recognition finished. Runtime: {end - start:.2f} seconds")
# 返回识别结果
return ret_data return ret_data
return {"status":1, "name":identity, "resImg":res_img_base64}
@app.post("/featuredetect") @app.post("/featuredetect")
@app.post("/featuredetect/") def featureDetect(input: FaceRequest):
def featureDetect(input:face): """
start = time.time() 特征检测接口
global facehelper 接收前端传递的图像数据并使用 facehelper 进行特征检测
返回检测结果和运行时间
"""
facehelper = get_facehelper()
start = time.time() # 记录开始时间
try: try:
# 调用 facehelper 的 featureDetect 方法进行特征检测
ret_data = facehelper.featureDetect(input.img) ret_data = facehelper.featureDetect(input.img)
print("finished featuredetect...")
end = time.time()
print("runtime: ", end-start)
except Exception as e: 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: finally:
end = time.time() # 记录结束时间
# 打印运行时间
print(f"Feature detection finished. Runtime: {end - start:.2f} seconds")
# 返回检测结果
return ret_data 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: try:
# 加锁确保数据库更新操作的线程安全性
with lock: with lock:
facehelper.updateDB(input.img, input.optMode, input.uniqueKey) facehelper.updateDB(input.img, input.optMode, input.uniqueKey)
except Exception as e: 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: 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)}