Python+MTCNN+OpenCV多线程人脸检测:线程同步问题求助
问题描述
使用Python、OpenCV与MTCNN实现人脸检测,单线程方案运行正常但速度较慢,遂引入多线程优化性能。程序包含process_frame()函数及frame、frame_buffer两个变量,预期实现图像处理与视频流播放同步。目前发现process_frame()内的检测逻辑可生成带检测框的画面,但主循环仅能显示无检测框的原始视频流,怀疑是线程同步问题导致。已通过try-except捕获异常未发现问题,print确认ret为True,frame可正常获取摄像头帧,但无法定位问题。
相关代码
import cv2 import numpy as np from mtcnn import MTCNN import threading import time import tensorflow as tf from threading import Lock #import pdb #pdb.set_trace() lock = Lock() cap = cv2.VideoCapture(0) frame_buffer = [] frame = None def process_frame(): global frame_buffer, stop_event, lock, frame while not stop_event.is_set(): try: lock.acquire() if frame is None: print("frame vide") continue print ("frame copié avec succes") frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame = cv2.resize(frame, (320,240)) frame = frame_buffer except Exception as e: print("Une exception a été levée lors du traitement de l'image :", e) continue finally : lock.release() try: model = MTCNN(min_face_size=10) predictions = model.detect_faces(frame) print(f"{len(predictions)} visages détectés") except Exception as e: print("Une exception a été levée lors de la détection des visages :", e) try: predictions = [prediction for prediction in predictions if prediction['confidence'] > 0.5] print(f"{len(predictions)} visages filtrés") except Exception as e: print("Une exception a été levée lors de la filtration des prédictions :", e) for face in predictions: try: x, y, w, h = face['box'] with lock : cv2.rectangle(frame, (x,y), (x+w, y+h), (255, 0, 0), 2) cv2.imwrite("parth/image_traitee_process_frame.jpg", frame_buffer, [cv2.IMWRITE_JPEG_QUALITY, 100]) except Exception as e: print("Une exception a été levée lors de l'ajout de l'annotation du rectangle :", e) print ("frame mis a jour avec succes") #frame_buffer = frame print ("Copie de la liste de frame : ", frame, "dans frame_buffer : ", frame_buffer) time.sleep(0.01) stop_event = threading.Event() thread = threading.Thread(target=process_frame) thread.daemon = True thread.start() start_time = time.perf_counter() frame_count = 0 def stop_thread(): stop_event.set() thread.join() while True: ret, frame = cap.read() if ret: frame_buffer = frame.copy() else : frame = frame_buffer frame = frame_buffer print ("valeur de frame_buffer après cap.read : ", frame_buffer) print (cap.get(cv2.CAP_PROP_FRAME_WIDTH)) print (cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) cv2.imwrite("parth/image_traitee_boucle_while.jpg", frame_buffer, [cv2.IMWRITE_JPEG_QUALITY, 50]) if not ret: break lock.acquire() #frame_buffer = frame lock.release() frame_count += 1 elapsed_time = time.perf_counter() - start_time if elapsed_time > 1.0: print("Frames par seconde : ", frame_count / elapsed_time) start_time = time.perf_counter() frame_count = 0 lock.acquire() #print(frame_buffer.shape) cv2.imshow("Output", frame_buffer) lock.release() key = cv2.waitKey(1) & 0xFF if key == ord("q"): break cap.release() cv2.destroyAllWindows() stop_thread()
问题分析与修复
你的代码存在多个逻辑错误,导致处理后的帧无法传递到主循环显示:
核心错误点
- 变量赋值逻辑完全颠倒:
process_frame中执行frame = frame_buffer,导致处理的是旧缓冲区数据,且处理后的帧从未更新到共享缓冲区。 - MTCNN重复初始化:每次循环都创建
MTCNN实例,这会严重拖慢处理速度,应该只初始化一次。 - 颜色空间未适配显示要求:MTCNN需要RGB格式,但OpenCV显示依赖BGR,处理后未转换回对应格式,导致检测框显示异常。
- 线程同步范围错误:共享缓冲区的读写未完全被锁保护,且处理后的帧未正确写入
frame_buffer。 - 主循环变量混乱:
frame和frame_buffer的赋值逻辑混乱,导致主循环始终读取原始帧。
修复后的代码
import cv2 import numpy as np from mtcnn import MTCNN import threading import time from threading import Lock lock = Lock() cap = cv2.VideoCapture(0) # 初始化共享缓冲区为None frame_buffer = None stop_event = threading.Event() # 提前初始化MTCNN模型,避免重复创建开销 detector = MTCNN(min_face_size=10) def process_frame(): global frame_buffer, stop_event, lock while not stop_event.is_set(): current_frame = None # 线程安全读取共享缓冲区的帧 with lock: if frame_buffer is None: time.sleep(0.01) continue # 复制帧避免直接修改共享数据 current_frame = frame_buffer.copy() # 颜色空间转换:BGR转RGB(适配MTCNN要求) rgb_frame = cv2.cvtColor(current_frame, cv2.COLOR_BGR2RGB) # 人脸检测与结果过滤 try: predictions = detector.detect_faces(rgb_frame) predictions = [p for p in predictions if p['confidence'] > 0.5] print(f"检测到{len(predictions)}张人脸") except Exception as e: print(f"检测出错:{e}") continue # 绘制检测框(在复制的帧上操作,不影响原始数据) for face in predictions: x, y, w, h = face['box'] # 确保坐标为整数,避免OpenCV报错 x, y, w, h = int(x), int(y), int(w), int(h) cv2.rectangle(current_frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # 线程安全写入处理后的帧到共享缓冲区 with lock: frame_buffer = current_frame time.sleep(0.01) # 启动处理线程 thread = threading.Thread(target=process_frame) thread.daemon = True thread.start() start_time = time.perf_counter() frame_count = 0 def stop_thread(): stop_event.set() thread.join() while True: ret, original_frame = cap.read() if not ret: break # 线程安全写入原始帧到共享缓冲区 with lock: frame_buffer = original_frame.copy() # 统计帧率 frame_count += 1 elapsed_time = time.perf_counter() - start_time if elapsed_time > 1.0: print(f"帧率:{frame_count / elapsed_time:.2f} FPS") start_time = time.perf_counter() frame_count = 0 # 线程安全读取并显示处理后的帧 with lock: if frame_buffer is not None: cv2.imshow("人脸检测结果", frame_buffer) # 退出逻辑 key = cv2.waitKey(1) & 0xFF if key == ord("q"): break cap.release() cv2.destroyAllWindows() stop_thread()
修复说明
- 模型预初始化:在启动线程前创建MTCNN实例,避免重复初始化的巨大开销。
- 清晰的数据流:主循环将原始帧写入
frame_buffer,处理线程读取、处理后写回缓冲区,主循环读取缓冲区显示,形成完整闭环。 - 适配颜色空间:仅在检测时转换为RGB,绘制框直接在原始BGR帧的副本上操作,无需额外转换即可正常显示。
- 线程安全操作:所有对
frame_buffer的读写都通过with lock保证原子性,避免数据竞争。 - 变量职责明确:区分原始帧
original_frame和共享缓冲区frame_buffer,避免赋值冲突。
内容的提问来源于stack exchange,提问作者Pinigseu
相关产品推荐
相关产品推荐

