face_recognition.py 36 KB

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