face_recognition.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. #!/usr/bin/env python3
  2. """
  3. 实时人脸识别系统
  4. 功能:
  5. 1. 添加人脸到数据库并设定名字
  6. 2. 保存和加载人脸数据库
  7. 3. 摄像头实时人脸检测和识别
  8. 4. 在检测到的人脸上显示对应名字
  9. """
  10. import os
  11. import time
  12. import cv2
  13. import numpy as np
  14. import insightface
  15. from insightface.app import FaceAnalysis
  16. from insightface.data import get_image as ins_get_image
  17. import pickle
  18. # 导入FreeType库
  19. import freetype
  20. import sys
  21. import traceback
  22. class FaceRecognitionSystem:
  23. """实时人脸识别系统类"""
  24. # 关键:创建 ONNX Runtime 配置,禁用融合卷积
  25. session_options = ort.SessionOptions()
  26. # 禁用融合卷积(解决 FusedConv 算子报错)
  27. session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_BASIC
  28. # 或更精细控制:只禁用融合卷积,保留其他优化
  29. session_options.add_session_config_entry("ep.cuda.disable_fused_conv", "1")
  30. def __init__(self, db_path="face_database.pkl",
  31. model_name="buffalo_l",
  32. force_cuda=True):
  33. """
  34. 初始化人脸识别系统
  35. Args:
  36. db_path: 人脸数据库保存路径
  37. model_name: 使用的模型名称
  38. """
  39. self.db_path = db_path
  40. self.model_name = model_name
  41. # 初始化人脸分析应用
  42. print("初始化人脸识别模型...")
  43. print("尝试使用TensorRT加速...")
  44. # TensorRT执行提供程序的配置选项
  45. trt_options = {
  46. 'trt_engine_cache_enable': True, # 启用引擎缓存
  47. 'trt_engine_cache_path': './trt_cache', # 引擎缓存路径
  48. 'trt_fp16_enable': True, # 启用FP16精度以提升性能
  49. 'trt_max_workspace_size': 2 * 1024 * 1024 * 1024, # 最大工作空间2GB
  50. }
  51. try:
  52. # 优先使用TensorRT,然后是CUDA,最后是CPU
  53. self.app = FaceAnalysis(
  54. name=model_name,
  55. modules=['detection', 'recognition'],
  56. providers=[
  57. #('TensorrtExecutionProvider', trt_options),
  58. 'CUDAExecutionProvider',
  59. 'CPUExecutionProvider'
  60. ]
  61. )
  62. self.app.prepare(ctx_id=0, det_size=(640, 640))
  63. print("成功使用TensorRT加速初始化")
  64. except BaseException as e:
  65. print(f"TensorRT启动失败: {e}", file=sys.stderr)
  66. print("尝试使用CUDA执行...")
  67. try:
  68. # 回退到CUDA
  69. self.app = FaceAnalysis(
  70. name=model_name,
  71. modules=['detection', 'recognition'],
  72. providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
  73. )
  74. self.app.prepare(ctx_id=0, det_size=(640, 640))
  75. print("成功使用CUDA执行")
  76. except BaseException as e2:
  77. print(f"CUDA启动失败: {e2}", file=sys.stderr)
  78. # 确保self.app被正确初始化,使用CPU作为最后的备选方案
  79. print("尝试使用纯CPU执行...")
  80. try:
  81. self.app = FaceAnalysis(
  82. name=model_name,
  83. modules=['detection', 'recognition'],
  84. providers=['CPUExecutionProvider'] # 只使用CPU执行提供程序
  85. )
  86. self.app.prepare(ctx_id=-1, det_size=(640, 640))
  87. print("成功使用纯CPU执行")
  88. except BaseException as e3:
  89. print(f"纯CPU执行也失败: {e3}", file=sys.stderr)
  90. raise RuntimeError(f"无法初始化FaceAnalysis: {e3}") from e2
  91. # 初始化人脸数据库
  92. self.face_database = {}
  93. self.load_database()
  94. # 初始化FreeType字体
  95. self.font = None
  96. self._init_font()
  97. print(f"系统初始化完成。已加载 {len(self.face_database)} 张人脸。")
  98. def _reinit_app_with_fallback(self, use_tensorrt=True):
  99. """
  100. 重新初始化FaceAnalysis应用,支持TensorRT/CUDA/CPU回退
  101. Args:
  102. use_tensorrt: 是否尝试使用TensorRT
  103. Returns:
  104. bool: 初始化成功返回True,失败返回False
  105. """
  106. # TensorRT执行提供程序的配置选项
  107. trt_options = {
  108. 'trt_engine_cache_enable': True,
  109. 'trt_engine_cache_path': './trt_cache',
  110. 'trt_fp16_enable': True,
  111. 'trt_max_workspace_size': 2 * 1024 * 1024 * 1024,
  112. }
  113. if use_tensorrt:
  114. try:
  115. self.app = FaceAnalysis(
  116. name=self.model_name,
  117. modules=['detection', 'recognition'],
  118. providers=[
  119. ('TensorRTExecutionProvider', trt_options),
  120. 'CUDAExecutionProvider',
  121. 'CPUExecutionProvider'
  122. ]
  123. )
  124. self.app.prepare(ctx_id=0, det_size=(640, 640))
  125. print("成功使用TensorRT重新初始化")
  126. return True
  127. except Exception as e:
  128. print(f"TensorRT重新初始化失败: {e}")
  129. traceback.print_exc()
  130. # 回退到CUDA
  131. try:
  132. self.app = FaceAnalysis(
  133. name=self.model_name,
  134. modules=['detection', 'recognition'],
  135. providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
  136. )
  137. self.app.prepare(ctx_id=0, det_size=(640, 640))
  138. print("成功使用CUDA重新初始化")
  139. return True
  140. except Exception as e:
  141. print(f"CUDA重新初始化失败: {e}")
  142. traceback.print_exc()
  143. # 最后回退到CPU
  144. try:
  145. self.app = FaceAnalysis(
  146. name=self.model_name,
  147. modules=['detection', 'recognition'],
  148. providers=['CPUExecutionProvider']
  149. )
  150. self.app.prepare(ctx_id=-1, det_size=(640, 640))
  151. print("成功使用CPU重新初始化")
  152. return True
  153. except Exception as e:
  154. print(f"CPU重新初始化也失败: {e}")
  155. return False
  156. def _init_font(self):
  157. """
  158. 初始化FreeType字体
  159. """
  160. # Linux系统的中文字体路径
  161. font_paths = [
  162. "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", # 文泉驿微米黑
  163. "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
  164. "/usr/share/fonts/truetype/simhei.ttf", # 黑体
  165. "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", # 备用字体
  166. ]
  167. # 尝试加载字体
  168. for font_path in font_paths:
  169. if os.path.exists(font_path):
  170. try:
  171. self.font = freetype.Face(font_path)
  172. print(f"成功加载字体: {font_path}")
  173. return
  174. except Exception as e:
  175. print(f"加载字体 {font_path} 失败: {e}")
  176. print("警告:无法加载任何字体,将使用OpenCV默认字体")
  177. self.font = None
  178. def _put_text_freetype(self, image, text, pos, font_size=18, color=(0, 255, 0)):
  179. """
  180. 使用FreeType绘制文字(支持中文)
  181. Args:
  182. image: 要绘制文字的图像
  183. text: 要绘制的文字
  184. pos: 文字位置 (x, y)
  185. font_size: 字体大小
  186. color: 文字颜色 (B, G, R)
  187. Returns:
  188. image: 绘制了文字的图像
  189. """
  190. try:
  191. # 设置字体大小
  192. self.font.set_char_size(font_size * 64)
  193. # 分离颜色通道
  194. b, g, r = color
  195. # 获取文字位置
  196. x, y = pos
  197. # 绘制文字
  198. for char in text:
  199. # 加载字符
  200. self.font.load_char(char)
  201. # 获取字符的位图
  202. bitmap = self.font.glyph.bitmap
  203. width = bitmap.width
  204. height = bitmap.rows
  205. # 获取字符的偏移量
  206. left = self.font.glyph.bitmap_left
  207. top = self.font.glyph.bitmap_top
  208. # 计算字符在图像中的位置
  209. char_x = x + left
  210. char_y = y - top
  211. # 确保字符在图像范围内
  212. if char_x < 0 or char_y < 0 or char_x + width > image.shape[1] or char_y + height > image.shape[0]:
  213. # 跳过超出图像范围的字符
  214. x += (self.font.glyph.advance.x >> 6)
  215. continue
  216. # 绘制字符的每个像素
  217. for i in range(height):
  218. for j in range(width):
  219. if i < bitmap.rows and j < bitmap.width:
  220. pixel = bitmap.buffer[i * bitmap.pitch + j]
  221. if pixel > 0:
  222. # 设置像素颜色
  223. image[char_y + i, char_x + j] = (b, g, r)
  224. # 更新x坐标,准备绘制下一个字符
  225. x += (self.font.glyph.advance.x >> 6)
  226. except Exception as e:
  227. print(f"绘制文字时出错: {e}")
  228. # 回退到OpenCV默认字体
  229. cv2.putText(image, text, pos, cv2.FONT_HERSHEY_SIMPLEX, font_size/25, color, 2)
  230. return image
  231. def add_face(self, image_path, name):
  232. """
  233. 添加人脸到数据库
  234. Args:
  235. image_path: 包含人脸的图像路径
  236. name: 人脸对应的名字
  237. Returns:
  238. bool: 添加成功返回True,失败返回False
  239. """
  240. print(f"正在添加人脸: {name}...")
  241. # 读取图像
  242. img = cv2.imread(image_path)
  243. if img is None:
  244. print(f"错误:无法读取图像 {image_path}")
  245. return False
  246. try:
  247. # 检测人脸
  248. faces = self.app.get(img)
  249. print(f"DEBUG: 检测到 {len(faces)} 张人脸")
  250. if len(faces) == 0:
  251. # 尝试调整图像大小后再次检测
  252. print("尝试调整图像大小后再次检测...")
  253. h, w = img.shape[:2]
  254. max_size = 1280
  255. if h > max_size or w > max_size:
  256. scale = max_size / max(h, w)
  257. new_h, new_w = int(h * scale), int(w * scale)
  258. img_resized = cv2.resize(img, (new_w, new_h))
  259. faces = self.app.get(img_resized)
  260. print(f"DEBUG: 调整大小后检测到 {len(faces)} 张人脸")
  261. if len(faces) == 0:
  262. print("错误:图像中未检测到人脸")
  263. return False
  264. elif len(faces) > 1:
  265. print("警告:图像中检测到多张人脸,仅使用第一张")
  266. # 获取人脸特征
  267. face = faces[0]
  268. print(f"DEBUG: 人脸信息: {face.keys()}")
  269. if 'embedding' not in face:
  270. print("错误:无法提取人脸特征")
  271. print("DEBUG: 可用的人脸信息: {face.keys()}")
  272. return False
  273. # 添加到数据库
  274. self.face_database[name] = face['embedding']
  275. print(f"成功添加人脸: {name}")
  276. return True
  277. except Exception as e:
  278. error_msg = str(e)
  279. print(f"添加人脸时出错: {error_msg}")
  280. # 检查是否是执行提供程序相关错误
  281. is_provider_error = any(keyword in error_msg for keyword in
  282. ['CUDA', 'cuda', 'CUDNN', 'cudnn', 'GPU', 'gpu',
  283. 'FusedConv', 'TensorRT', 'tensorrt', 'TRT', 'trt'])
  284. if is_provider_error:
  285. print("检测到执行提供程序相关错误,尝试重新初始化...")
  286. # 重新初始化应用(会尝试TensorRT -> CUDA -> CPU的回退)
  287. if self._reinit_app_with_fallback(use_tensorrt=True):
  288. # 重新尝试添加人脸
  289. return self.add_face(image_path, name)
  290. else:
  291. print("重新初始化失败")
  292. return False
  293. return False
  294. def save_database(self):
  295. """
  296. 保存人脸数据库到文件
  297. """
  298. try:
  299. with open(self.db_path, 'wb') as f:
  300. pickle.dump(self.face_database, f)
  301. print(f"人脸数据库已保存到 {self.db_path}")
  302. return True
  303. except Exception as e:
  304. print(f"保存数据库失败: {e}")
  305. return False
  306. def load_database(self):
  307. """
  308. 从文件加载人脸数据库
  309. """
  310. if os.path.exists(self.db_path):
  311. try:
  312. with open(self.db_path, 'rb') as f:
  313. self.face_database = pickle.load(f)
  314. print(f"从 {self.db_path} 加载了 {len(self.face_database)} 张人脸")
  315. return True
  316. except Exception as e:
  317. print(f"加载数据库失败: {e}")
  318. self.face_database = {}
  319. else:
  320. print(f"未找到数据库文件 {self.db_path},将创建新数据库")
  321. self.face_database = {}
  322. return False
  323. def recognize_face(self, face, threshold=0.70):
  324. """
  325. 识别人脸,返回最匹配的名字和相似度分数
  326. Args:
  327. face: 检测到的人脸对象
  328. threshold: 识别阈值
  329. Returns:
  330. tuple: (str, float) - 识别出的名字和相似度分数,未识别出返回("Unknown", 0.0)
  331. """
  332. if 'embedding' not in face:
  333. return "Unknown", 0.0
  334. embedding = face['embedding']
  335. best_name = "Unknown"
  336. best_score = threshold
  337. # 检查数据库是否为空
  338. if not self.face_database:
  339. return "Unknown", 0.0
  340. # 遍历数据库,寻找最匹配的人脸
  341. for name, db_embedding in self.face_database.items():
  342. # 计算余弦相似度
  343. similarity = np.dot(embedding, db_embedding)
  344. if similarity > best_score:
  345. best_score = similarity
  346. best_name = name
  347. # 确保返回的名字是有效的字符串
  348. if not isinstance(best_name, str):
  349. best_name = str(best_name)
  350. # 移除可能的特殊字符
  351. best_name = ''.join(c for c in best_name if c.isprintable())
  352. if not best_name:
  353. best_name = "Unknown"
  354. return best_name, best_score
  355. def run_camera(self, camera_id, save_db_on_exit=True):
  356. """
  357. 运行摄像头实时人脸识别
  358. Args:
  359. camera_id: 摄像头ID,默认10
  360. save_db_on_exit: 退出时是否保存数据库
  361. """
  362. print(f"打开摄像头 {camera_id}...")
  363. # 尝试不同的摄像头打开方式
  364. cap = None
  365. # 方式1:直接使用摄像头ID
  366. try:
  367. cap = cv2.VideoCapture(camera_id)
  368. if cap.isOpened():
  369. print(f"成功打开摄像头 {camera_id}(直接方式)")
  370. else:
  371. print(f"直接方式打开摄像头 {camera_id}失败,尝试其他方式...")
  372. cap.release()
  373. cap = None
  374. except Exception as e:
  375. print(f"直接方式打开摄像头失败:{e}")
  376. cap = None
  377. # 方式3:使用cv2.CAP_V4L2(Linux特有)
  378. if cap is None and os.name == 'posix':
  379. try:
  380. cap = cv2.VideoCapture(camera_id, cv2.CAP_V4L2)
  381. if cap.isOpened():
  382. print(f"成功打开摄像头 {camera_id}(CAP_V4L2方式)")
  383. else:
  384. print(f"CAP_V4L2方式打开摄像头 {camera_id}失败,尝试其他方式...")
  385. cap.release()
  386. cap = None
  387. except Exception as e:
  388. print(f"CAP_V4L2方式打开摄像头失败:{e}")
  389. cap = None
  390. if cap is None:
  391. print(f"错误:无法打开摄像头 {camera_id}")
  392. # 列出可用摄像头
  393. print("尝试检测可用摄像头...")
  394. for i in range(3):
  395. test_cap = cv2.VideoCapture(i)
  396. if test_cap.isOpened():
  397. print(f"摄像头 {i} 可用")
  398. test_cap.release()
  399. return False
  400. # 设置摄像头参数
  401. print("设置摄像头参数...")
  402. # 设置分辨率和帧率
  403. cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
  404. cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
  405. cap.set(cv2.CAP_PROP_FPS, 30)
  406. # 尝试设置MJPG编码,提高兼容性(忽略可能的错误)
  407. try:
  408. cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
  409. print(" 使用MJPG编码")
  410. except Exception as e:
  411. print(f" 使用默认编码(MJPG设置失败:{e}")
  412. # 摄像头初始化后添加延迟,让摄像头稳定
  413. print("摄像头初始化完成,等待1秒让摄像头稳定...")
  414. time.sleep(1)
  415. # 预读取几帧,丢弃初始的不稳定帧
  416. for _ in range(5):
  417. ret, _ = cap.read()
  418. if not ret:
  419. # 短暂休眠后重试
  420. time.sleep(0.1)
  421. print("开始实时人脸识别。按 'q' 键退出,按 's' 键保存数据库。")
  422. frame_count = 0
  423. consecutive_failures = 0 # 连续失败计数器
  424. while True:
  425. # 读取帧
  426. ret, frame = cap.read()
  427. if not ret:
  428. consecutive_failures += 1
  429. print(f"警告:无法读取帧 {frame_count} (连续失败: {consecutive_failures})")
  430. # 仅等待后重试,不重新打开摄像头,确保摄像头持续开启
  431. # 连续失败多次后才考虑重新初始化
  432. if consecutive_failures >= 30: # 连续失败30次后才重新初始化
  433. print("连续读取失败30次,尝试重置摄像头...")
  434. consecutive_failures = 0 # 重置失败计数器
  435. # 释放并重新打开摄像头
  436. cap.release()
  437. time.sleep(1) # 等待1秒
  438. cap = cv2.VideoCapture(camera_id)
  439. if not cap.isOpened():
  440. print(f"错误:无法重新打开摄像头 {camera_id}")
  441. break
  442. # 重新设置参数
  443. cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
  444. cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
  445. cap.set(cv2.CAP_PROP_FPS, 30)
  446. else:
  447. time.sleep(0.1) # 短暂等待后重试
  448. continue
  449. # 读取成功,重置失败计数器
  450. consecutive_failures = 0
  451. frame_count += 1
  452. # 检查帧是否有效
  453. if frame is None or frame.size == 0:
  454. print(f"警告:无效帧 {frame_count}")
  455. continue
  456. # 创建空白结果帧,防止原帧有问题
  457. result_frame = np.zeros((480, 640, 3), dtype=np.uint8)
  458. try:
  459. # 检测人脸
  460. faces = self.app.get(frame)
  461. # 绘制结果
  462. result_frame = frame.copy()
  463. for face in faces:
  464. # 获取置信度
  465. confidence = face['det_score']
  466. # 识别人脸,获取名字和相似度
  467. name, similarity = self.recognize_face(face)
  468. # 只有当置信度大于0.7且相似度大于200时才显示对应名字,否则显示Unknown
  469. if confidence > 0.7 and similarity > 100:
  470. display_name = name
  471. else:
  472. display_name = "Unknown"
  473. # 获取边界框
  474. bbox = face['bbox']
  475. bbox = list(map(int, bbox))
  476. # 绘制边界框
  477. cv2.rectangle(result_frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
  478. # 绘制名字 - 确保文字在图像范围内
  479. name_y = max(20, bbox[1] - 10) # 确保文字不会超出图像顶部
  480. name_y = min(name_y, result_frame.shape[0] - 20) # 确保文字不会超出图像底部
  481. self._put_text_freetype(result_frame, display_name, (bbox[0], name_y),
  482. font_size=18, color=(0, 255, 0))
  483. # 绘制置信度和相似度
  484. conf_y = bbox[3] + 20
  485. conf_y = min(conf_y, result_frame.shape[0] - 10) # 确保文字不会超出图像底部
  486. self._put_text_freetype(result_frame, f"置信度: {confidence:.2f}, 相似度: {similarity:.2f}",
  487. (bbox[0], conf_y), font_size=12, color=(0, 255, 0))
  488. except Exception as e:
  489. error_msg = str(e)
  490. print(f"处理帧时出错: {error_msg}")
  491. # 检查是否是执行提供程序相关错误
  492. is_provider_error = any(keyword in error_msg for keyword in
  493. ['CUDA', 'cuda', 'CUDNN', 'cudnn', 'GPU', 'gpu',
  494. 'FusedConv', 'TensorRT', 'tensorrt', 'TRT', 'trt'])
  495. if is_provider_error:
  496. print("检测到执行提供程序相关错误,尝试重新初始化...")
  497. # 重新初始化应用(会尝试TensorRT -> CUDA -> CPU的回退)
  498. if self._reinit_app_with_fallback(use_tensorrt=True):
  499. try:
  500. # 尝试重新检测
  501. faces = self.app.get(frame)
  502. result_frame = frame.copy()
  503. for face in faces:
  504. confidence = face['det_score']
  505. name, similarity = self.recognize_face(face)
  506. if confidence > 0.7 and similarity > 100:
  507. display_name = name
  508. else:
  509. display_name = "Unknown"
  510. bbox = list(map(int, face['bbox']))
  511. cv2.rectangle(result_frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
  512. name_y = max(20, bbox[1] - 10)
  513. name_y = min(name_y, result_frame.shape[0] - 20)
  514. self._put_text_freetype(result_frame, display_name, (bbox[0], name_y),
  515. font_size=18, color=(0, 255, 0))
  516. conf_y = bbox[3] + 20
  517. conf_y = min(conf_y, result_frame.shape[0] - 10)
  518. self._put_text_freetype(result_frame, f"置信度: {confidence:.2f}, 相似度: {similarity:.2f}",
  519. (bbox[0], conf_y), font_size=12, color=(0, 255, 0))
  520. except Exception as retry_e:
  521. print(f"重新检测失败: {retry_e}")
  522. result_frame = np.zeros((480, 640, 3), dtype=np.uint8)
  523. self._put_text_freetype(result_frame, f"重新检测失败: {retry_e}", (10, 50),
  524. font_size=14, color=(0, 0, 255))
  525. else:
  526. result_frame = np.zeros((480, 640, 3), dtype=np.uint8)
  527. self._put_text_freetype(result_frame, "执行提供程序重新初始化失败", (10, 50),
  528. font_size=14, color=(0, 0, 255))
  529. else:
  530. # 使用空白帧,避免程序崩溃
  531. result_frame = np.zeros((480, 640, 3), dtype=np.uint8)
  532. self._put_text_freetype(result_frame, f"处理错误: {error_msg}", (10, 50),
  533. font_size=14, color=(0, 0, 255))
  534. # 显示 FPS
  535. fps = 1.0 / (frame_count + 1e-6) # 简单FPS计算
  536. self._put_text_freetype(result_frame, f"FPS: {fps:.1f}", (10, 30),
  537. font_size=22, color=(0, 0, 255))
  538. # 确保窗口持续显示的关键逻辑
  539. try:
  540. # 确保窗口已创建并保持打开
  541. if not hasattr(self, '_window_created') or not self._window_created:
  542. cv2.namedWindow('实时人脸识别', cv2.WINDOW_NORMAL)
  543. cv2.resizeWindow('实时人脸识别', 640, 480)
  544. self._window_created = True
  545. print("摄像头窗口已创建并持续显示")
  546. # 持续显示结果
  547. cv2.imshow('实时人脸识别', result_frame)
  548. # 键盘控制 - 使用waitKey(1)确保窗口持续刷新
  549. key = cv2.waitKey(1) & 0xFF
  550. if key == ord('q'):
  551. # 退出
  552. print("收到退出命令,正在关闭...")
  553. break
  554. elif key == ord('s'):
  555. # 保存数据库
  556. self.save_database()
  557. except Exception as e:
  558. print(f"窗口显示错误: {e}")
  559. # 窗口显示错误时重新创建窗口
  560. if hasattr(self, '_window_created'):
  561. delattr(self, '_window_created')
  562. # 不退出,继续尝试显示
  563. continue
  564. # 释放资源
  565. print("释放摄像头资源...")
  566. cap.release()
  567. print("关闭所有窗口...")
  568. cv2.destroyAllWindows()
  569. # 退出时保存数据库
  570. if save_db_on_exit:
  571. self.save_database()
  572. print("系统已退出")
  573. return True
  574. def add_face_interactive(self, name, camera_id):
  575. """
  576. 交互式添加人脸(从摄像头捕获)
  577. Args:
  578. name: 人脸对应的名字
  579. camera_id: 摄像头ID,默认10
  580. Returns:
  581. bool: 添加成功返回True,失败返回False
  582. """
  583. print(f"准备添加人脸: {name}")
  584. print("请面对摄像头,按 'c' 键捕获图像,按 'q' 键取消")
  585. # 尝试打开摄像头
  586. cap = cv2.VideoCapture(camera_id)
  587. if not cap.isOpened():
  588. print(f"❌ 无法打开摄像头,索引: {camera_id}")
  589. # 列出可用摄像头
  590. print("ℹ️ 尝试检测可用摄像头...")
  591. available_cameras = []
  592. for i in range(5): # 尝试检测前5个摄像头
  593. test_cap = cv2.VideoCapture(i)
  594. if test_cap.isOpened():
  595. available_cameras.append(i)
  596. test_cap.release()
  597. if available_cameras:
  598. print(f"✅ 检测到以下可用摄像头: {available_cameras}")
  599. else:
  600. print("❌ 未检测到任何可用摄像头")
  601. return False
  602. # 重新设置参数
  603. cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
  604. cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
  605. cap.set(cv2.CAP_PROP_FPS, 30)
  606. # 摄像头初始化后添加延迟,让摄像头稳定
  607. print("摄像头初始化完成,等待1秒让摄像头稳定...")
  608. time.sleep(1)
  609. # 预读取几帧,丢弃初始的不稳定帧
  610. for _ in range(5):
  611. ret, _ = cap.read()
  612. if not ret:
  613. # 短暂休眠后重试
  614. time.sleep(0.1)
  615. # 创建空白帧,防止摄像头初始帧为黑
  616. blank_frame = np.zeros((480, 640, 3), dtype=np.uint8)
  617. self._put_text_freetype(blank_frame, "初始化摄像头...", (10, 30),
  618. font_size=18, color=(0, 0, 255))
  619. # 创建窗口
  620. cv2.namedWindow('捕获人脸', cv2.WINDOW_NORMAL)
  621. cv2.resizeWindow('捕获人脸', 640, 480)
  622. frame_count = 0
  623. consecutive_failures = 0
  624. while True:
  625. ret, frame = cap.read()
  626. if not ret or frame is None or frame.size == 0:
  627. consecutive_failures += 1
  628. print(f"⚠️ 无法读取帧 ({frame_count}/{consecutive_failures})")
  629. # 使用空白帧
  630. display_frame = blank_frame.copy()
  631. self._put_text_freetype(display_frame, f"无法读取帧,尝试次数: {consecutive_failures}", (10, 60),
  632. font_size=14, color=(0, 0, 255))
  633. if consecutive_failures > 10:
  634. print("❌ 连续10次无法读取帧,退出")
  635. cap.release()
  636. cv2.destroyAllWindows()
  637. return False
  638. else:
  639. consecutive_failures = 0
  640. frame_count += 1
  641. # 确保帧尺寸正确
  642. if frame.shape[0] != 480 or frame.shape[1] != 640:
  643. display_frame = cv2.resize(frame, (640, 480))
  644. else:
  645. display_frame = frame.copy()
  646. # 在图像上显示提示信息
  647. self._put_text_freetype(display_frame, "按 'c' 键捕获,按 'q' 键取消", (10, 30),
  648. font_size=18, color=(0, 0, 255))
  649. # 显示当前帧计数
  650. self._put_text_freetype(display_frame, f"帧计数: {frame_count}", (10, 450),
  651. font_size=14, color=(0, 255, 0))
  652. # 显示图像
  653. cv2.imshow('捕获人脸', display_frame)
  654. # 等待键盘输入
  655. key = cv2.waitKey(1) & 0xFF
  656. if key == ord('q'):
  657. # 取消
  658. cap.release()
  659. cv2.destroyAllWindows()
  660. print("已取消添加")
  661. return False
  662. elif key == ord('c'):
  663. # 捕获图像
  664. cap.release()
  665. cv2.destroyAllWindows()
  666. # 确保捕获的是有效图像
  667. if consecutive_failures == 0 and frame is not None and frame.size > 0:
  668. # 检测并添加人脸
  669. return self.add_face_from_frame(frame, name)
  670. else:
  671. print("❌ 无法捕获有效图像")
  672. return False
  673. def add_face_from_frame(self, frame, name):
  674. """
  675. 从图像帧添加人脸
  676. Args:
  677. frame: 图像帧
  678. name: 人脸对应的名字
  679. Returns:
  680. bool: 添加成功返回True,失败返回False
  681. """
  682. try:
  683. # 检测人脸
  684. faces = self.app.get(frame)
  685. if len(faces) == 0:
  686. print("错误:图像中未检测到人脸")
  687. return False
  688. elif len(faces) > 1:
  689. print("警告:图像中检测到多张人脸,仅使用第一张")
  690. # 获取人脸特征
  691. face = faces[0]
  692. if 'embedding' not in face:
  693. print("错误:无法提取人脸特征")
  694. return False
  695. # 添加到数据库
  696. self.face_database[name] = face['embedding']
  697. print(f"成功添加人脸: {name}")
  698. return True
  699. except Exception as e:
  700. error_msg = str(e)
  701. print(f"从帧添加人脸时出错: {error_msg}")
  702. # 检查是否是执行提供程序相关错误
  703. is_provider_error = any(keyword in error_msg for keyword in
  704. ['CUDA', 'cuda', 'CUDNN', 'cudnn', 'GPU', 'gpu',
  705. 'FusedConv', 'TensorRT', 'tensorrt', 'TRT', 'trt'])
  706. if is_provider_error:
  707. print("检测到执行提供程序相关错误,尝试重新初始化...")
  708. # 重新初始化应用(会尝试TensorRT -> CUDA -> CPU的回退)
  709. if self._reinit_app_with_fallback(use_tensorrt=True):
  710. # 重新尝试添加人脸
  711. return self.add_face_from_frame(frame, name)
  712. else:
  713. print("重新初始化失败")
  714. return False
  715. return False
  716. def main():
  717. """主函数"""
  718. import argparse
  719. parser = argparse.ArgumentParser(description="实时人脸识别系统")
  720. parser.add_argument("--add-face", nargs=2, metavar=("IMAGE_PATH", "NAME"),
  721. help="添加人脸到数据库")
  722. parser.add_argument("--add-webcam", metavar="NAME",
  723. help="从摄像头添加人脸到数据库")
  724. parser.add_argument("--db-path", default="face_database.pkl",
  725. help="人脸数据库路径")
  726. parser.add_argument("--camera-id", type=int, default=10,
  727. help="摄像头ID")
  728. parser.add_argument("--model", default="buffalo_l",
  729. help="使用的模型名称")
  730. parser.add_argument("--force-cuda", action="store_true", default=True,
  731. help="是否强制使用CUDA,默认强制使用")
  732. parser.add_argument("--allow-cpu-fallback", action="store_false", dest="force_cuda",
  733. help="允许在CUDA不可用时回退到CPU")
  734. args = parser.parse_args()
  735. print(f"命令行参数: force_cuda={args.force_cuda}")
  736. # 初始化系统 - 强制使用CUDA
  737. system = FaceRecognitionSystem(db_path=args.db_path,
  738. model_name=args.model,
  739. force_cuda=args.force_cuda)
  740. # 添加人脸
  741. if args.add_face:
  742. image_path, name = args.add_face
  743. system.add_face(image_path, name)
  744. system.save_database()
  745. # 从摄像头添加人脸
  746. elif args.add_webcam:
  747. system.add_face_interactive(args.add_webcam, args.camera_id)
  748. system.save_database()
  749. # 运行摄像头
  750. else:
  751. system.run_camera(camera_id=args.camera_id)
  752. if __name__ == "__main__":
  753. main()