safer
This commit is contained in:
@@ -2,10 +2,8 @@ import cv2
|
||||
import mediapipe as mp
|
||||
import time
|
||||
import numpy as np
|
||||
import threading
|
||||
import queue
|
||||
import multiprocessing as mp_proc
|
||||
from multiprocessing import shared_memory
|
||||
from collections import deque
|
||||
from geometry_utils import (
|
||||
calculate_ear,
|
||||
@@ -65,46 +63,20 @@ class MonitorSystem:
|
||||
self.current_emotion = "Neutral"
|
||||
|
||||
self.frame_shape = (720, 1280, 3)
|
||||
frame_size = int(np.prod(self.frame_shape))
|
||||
|
||||
# 必须先解除可能存在的残留 (Windows上有时不需要,但保持好习惯)
|
||||
# 最好是随机生成一个名字,确保每次运行都是新的
|
||||
import secrets
|
||||
auth_key = secrets.token_hex(4)
|
||||
shm_unique_name = f"monitor_shm_{auth_key}"
|
||||
|
||||
try:
|
||||
self.shm = shared_memory.SharedMemory(create=True, size=frame_size, name=shm_unique_name)
|
||||
except FileExistsError:
|
||||
# 如果真的点背碰上了,就 connect 这一块
|
||||
self.shm = shared_memory.SharedMemory(name=shm_unique_name)
|
||||
|
||||
print(f"[Main] 共享内存已创建: {self.shm.name} (Size: {frame_size} bytes)")
|
||||
# 使用 spawn 避免 fork 复制 OpenCV/MediaPipe/ONNXRuntime 等 C++ 运行时状态。
|
||||
self.mp_ctx = mp_proc.get_context("spawn")
|
||||
self.task_queue = self.mp_ctx.Queue(maxsize=2)
|
||||
self.result_queue = self.mp_ctx.Queue(maxsize=2)
|
||||
|
||||
# 本地 numpy 包装器
|
||||
self.shared_frame_array = np.ndarray(
|
||||
self.frame_shape, dtype=np.uint8, buffer=self.shm.buf
|
||||
)
|
||||
# 初始化为全黑,避免噪音
|
||||
self.shared_frame_array.fill(0)
|
||||
|
||||
# 跨进程队列
|
||||
self.task_queue = mp_proc.Queue(maxsize=2)
|
||||
self.result_queue = mp_proc.Queue(maxsize=2) # 1就够了,最新的覆盖
|
||||
|
||||
# 3. 启动进程
|
||||
# Windows下传参,只传名字字符串是安全的
|
||||
self.worker_proc = mp_proc.Process(
|
||||
self.worker_proc = self.mp_ctx.Process(
|
||||
target=background_worker_process,
|
||||
args=(
|
||||
self.shm.name,
|
||||
self.frame_shape,
|
||||
self.task_queue,
|
||||
self.result_queue,
|
||||
face_db,
|
||||
),
|
||||
)
|
||||
self.worker_proc.daemon = True
|
||||
self.worker_proc.start()
|
||||
|
||||
def _get_smoothed_value(self, history, current_val):
|
||||
@@ -125,12 +97,6 @@ class MonitorSystem:
|
||||
frame = cv2.resize(frame, (target_w, target_h))
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
# 现在肯定匹配了,放心写入
|
||||
try:
|
||||
self.shared_frame_array[:] = frame[:]
|
||||
except Exception:
|
||||
# 极端情况:数组形状不匹配 (比如通道数变了)
|
||||
pass
|
||||
|
||||
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
@@ -257,9 +223,7 @@ class MonitorSystem:
|
||||
max(0, sface_loc[3] - spad),
|
||||
)
|
||||
|
||||
if self.task_queue.full():
|
||||
self.task_queue.get()
|
||||
self.task_queue.put((sface_loc, 0))
|
||||
self._put_latest_task((0, frame.copy(), sface_loc))
|
||||
|
||||
self.last_identity_check_time = now
|
||||
|
||||
@@ -283,20 +247,14 @@ class MonitorSystem:
|
||||
y_max = min(h, y_max + pad_y)
|
||||
|
||||
face_loc = (y_min, x_max, y_max, x_min)
|
||||
face_crop = frame[y_min:y_max, x_min:x_max].copy()
|
||||
|
||||
if self.task_queue.full():
|
||||
self.task_queue.get()
|
||||
self.task_queue.put((face_loc, 1))
|
||||
if face_crop.size > 0:
|
||||
self._put_latest_task((1, face_crop, None))
|
||||
|
||||
self.last_emotion_check_time = now
|
||||
|
||||
while not self.result_queue.empty():
|
||||
type_, data = self.result_queue.get()
|
||||
if type_ == "identity":
|
||||
self.current_user = data
|
||||
elif type_ == "emotion":
|
||||
self.cached_emotion["label"] = data.get("emotion", "unknown")
|
||||
self.cached_emotion["va"] = data.get("vaVal", (0.0, 0.0))
|
||||
self._drain_results()
|
||||
|
||||
analysis_data["identity"] = self.current_user
|
||||
analysis_data["emotion_label"] = self.cached_emotion["label"]
|
||||
@@ -304,6 +262,52 @@ class MonitorSystem:
|
||||
|
||||
return analysis_data
|
||||
|
||||
def _put_latest_task(self, task):
|
||||
try:
|
||||
if self.task_queue.full():
|
||||
self.task_queue.get_nowait()
|
||||
self.task_queue.put_nowait(task)
|
||||
except queue.Full:
|
||||
try:
|
||||
self.task_queue.get_nowait()
|
||||
self.task_queue.put_nowait(task)
|
||||
except (queue.Empty, queue.Full):
|
||||
pass
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
def _drain_results(self):
|
||||
while True:
|
||||
try:
|
||||
type_, data = self.result_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
break
|
||||
|
||||
if type_ == "identity":
|
||||
self.current_user = data
|
||||
elif type_ == "emotion":
|
||||
self.cached_emotion["label"] = data.get("emotion", "unknown")
|
||||
self.cached_emotion["va"] = data.get("vaVal", (0.0, 0.0))
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self._put_latest_task(None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.worker_proc.is_alive():
|
||||
self.worker_proc.join(timeout=3)
|
||||
|
||||
if self.worker_proc.is_alive():
|
||||
print("[Worker] 未正常退出,强制结束")
|
||||
self.worker_proc.terminate()
|
||||
self.worker_proc.join(timeout=2)
|
||||
|
||||
try:
|
||||
self.face_mesh.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# def _id_emo_loop(self):
|
||||
# while True:
|
||||
# try:
|
||||
@@ -337,16 +341,10 @@ class MonitorSystem:
|
||||
|
||||
|
||||
def background_worker_process(
|
||||
shm_name, # 共享内存的名字
|
||||
frame_shape, # 图像大小 (h, w, 3)
|
||||
task_queue, # 任务队列 (主 -> 从)
|
||||
result_queue, # 结果队列 (从 -> 主)
|
||||
face_db_data, # 把人脸库数据传过去初始化
|
||||
):
|
||||
existing_shm = shared_memory.SharedMemory(name=shm_name)
|
||||
# 创建 numpy 数组视图,无需复制数据
|
||||
shared_frame = np.ndarray(frame_shape, dtype=np.uint8, buffer=existing_shm.buf)
|
||||
|
||||
print("[Worker] 正在加载模型...")
|
||||
from face_library import FaceLibrary
|
||||
|
||||
@@ -362,39 +360,38 @@ def background_worker_process(
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 阻塞等待任务
|
||||
# task_info = (task_type, face_loc)
|
||||
face_loc, task_type = task_queue.get()
|
||||
task = task_queue.get()
|
||||
if task is None:
|
||||
break
|
||||
|
||||
# 注意:这里读取的是共享内存里的图,不需要传图!
|
||||
# 切片操作也是零拷贝
|
||||
# 为了安全,这里 copy 一份出来处理,避免主进程修改
|
||||
# 但实际上如果主进程只写新帧,这里读旧帧也问题不大
|
||||
# 为了绝对安全和解耦,我们假定主进程已经写入了对应的帧
|
||||
|
||||
# (实战技巧:通常我们会用一个信号量或多块共享内存来实现乒乓缓存)
|
||||
# 简化版:我们直接从 shared_frame 读。
|
||||
# 由于主进程跑得快,可能SharedMemory里已经是下一帧了。
|
||||
# 但对于识别身份来说,差一两帧根本没区别!这才是优化的精髓。
|
||||
|
||||
current_frame_view = shared_frame.copy() # .copy() 如果你怕读写冲突
|
||||
task_type, frame_data, face_loc = task
|
||||
|
||||
if task_type == 0: # Identity
|
||||
# RGB转换
|
||||
rgb = cv2.cvtColor(current_frame_view, cv2.COLOR_BGR2RGB)
|
||||
rgb = cv2.cvtColor(frame_data, cv2.COLOR_BGR2RGB)
|
||||
res = face_lib.identify(rgb, face_location=face_loc)
|
||||
if res:
|
||||
result_queue.put(("identity", res["info"]))
|
||||
_put_latest_result(result_queue, ("identity", res["info"]))
|
||||
|
||||
elif task_type == 1 and has_emo: # Emotion
|
||||
# BGR 直接切
|
||||
roi = current_frame_view[
|
||||
face_loc[0] : face_loc[2], face_loc[3] : face_loc[1]
|
||||
]
|
||||
if roi.size > 0:
|
||||
emo_res = analyze_emotion_with_hsemotion(roi)
|
||||
if frame_data.size > 0:
|
||||
emo_res = analyze_emotion_with_hsemotion(frame_data)
|
||||
if emo_res:
|
||||
result_queue.put(("emotion", emo_res[0]))
|
||||
_put_latest_result(result_queue, ("emotion", emo_res[0]))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Worker Error] {e}")
|
||||
|
||||
|
||||
def _put_latest_result(result_queue, result):
|
||||
try:
|
||||
if result_queue.full():
|
||||
result_queue.get_nowait()
|
||||
result_queue.put_nowait(result)
|
||||
except queue.Full:
|
||||
try:
|
||||
result_queue.get_nowait()
|
||||
result_queue.put_nowait(result)
|
||||
except (queue.Empty, queue.Full):
|
||||
pass
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user