face_recognition_api.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. #!/usr/bin/env python3
  2. """
  3. 人脸识别API工具
  4. 功能:
  5. 1. 提供人脸识别API接口
  6. 2. 支持从图像中检测和识别人脸
  7. 3. 支持添加新人脸到数据库
  8. """
  9. import os
  10. import sys
  11. import time
  12. import json
  13. import pickle
  14. # 首先尝试导入numpy,如果失败则提供友好的错误信息
  15. try:
  16. import numpy as np
  17. np_version = np.__version__
  18. test_array = np.array([1, 2, 3])
  19. except ImportError as e:
  20. print(f"❌ 导入numpy失败: {e}")
  21. print("ℹ️ 请确保已安装正确版本的numpy: pip install numpy==1.24.3")
  22. sys.exit(1)
  23. # 导入OpenCV
  24. try:
  25. import cv2
  26. except ImportError as e:
  27. print(f"❌ 导入OpenCV失败: {e}")
  28. sys.exit(1)
  29. # 导入insightface相关库
  30. try:
  31. import insightface
  32. from insightface.app import FaceAnalysis
  33. from insightface.data import get_image as ins_get_image
  34. except ImportError as e:
  35. print(f"❌ 导入insightface失败: {e}")
  36. print("ℹ️ 请确保已安装insightface: pip install insightface")
  37. sys.exit(1)
  38. class FaceRecognitionAPI:
  39. """人脸识别API类"""
  40. def __init__(self, db_path="face_database.pkl",
  41. model_name="buffalo_l", force_cuda=False):
  42. """
  43. 初始化人脸识别系统
  44. Args:
  45. db_path: 人脸数据库路径
  46. model_name: 模型名称
  47. force_cuda: 是否强制使用CUDA(默认False,直接使用CPU)
  48. """
  49. self.db_path = db_path
  50. self.model_name = model_name
  51. self.force_cuda = force_cuda
  52. self.face_database = {}
  53. self.app = None
  54. self.font_path = None
  55. # 初始化
  56. self._init_model()
  57. self._load_database()
  58. self._init_font()
  59. def _init_model(self):
  60. """初始化人脸分析模型"""
  61. print("初始化人脸识别模型...")
  62. # 直接使用CPU模式,不尝试GPU
  63. print("直接使用CPU模式...")
  64. # 禁用CUDA,强制使用CPU
  65. os.environ['CUDA_VISIBLE_DEVICES'] = ''
  66. # 确保使用CPU执行
  67. os.environ['ORT_ENABLE_CUDA'] = '0' # 禁用ONNX Runtime CUDA支持
  68. try:
  69. # 初始化FaceAnalysis,仅使用必要的模块
  70. self.app = FaceAnalysis(name=self.model_name, allowed_modules=['detection', 'recognition'])
  71. # 使用ctx_id=-1表示CPU
  72. self.app.prepare(ctx_id=-1, det_size=(640, 640))
  73. print("✅ CPU模式初始化成功")
  74. # 更新force_cuda标志为False
  75. self.force_cuda = False
  76. except Exception as e:
  77. print(f"❌ CPU模式初始化失败: {e}")
  78. raise RuntimeError(f"无法初始化人脸分析模型: {e}")
  79. print(f"✅ 模型初始化完成,使用CPU")
  80. def _load_database(self):
  81. """加载人脸数据库"""
  82. if os.path.exists(self.db_path):
  83. try:
  84. with open(self.db_path, 'rb') as f:
  85. self.face_database = pickle.load(f)
  86. print(f"✅ 从 {self.db_path} 加载了 {len(self.face_database)} 张人脸")
  87. except Exception as e:
  88. print(f"⚠️ 加载人脸数据库失败: {e}")
  89. self.face_database = {}
  90. else:
  91. print("ℹ️ 未找到人脸数据库,将创建新数据库")
  92. self.face_database = {}
  93. def _save_database(self):
  94. """保存人脸数据库"""
  95. try:
  96. with open(self.db_path, 'wb') as f:
  97. pickle.dump(self.face_database, f)
  98. print(f"✅ 人脸数据库已保存到 {self.db_path}")
  99. except Exception as e:
  100. print(f"❌ 保存人脸数据库失败: {e}")
  101. def _init_font(self):
  102. """初始化字体"""
  103. font_paths = [
  104. "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
  105. "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
  106. "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
  107. "/usr/share/fonts/truetype/arphic/uming.ttc",
  108. "/usr/share/fonts/truetype/arphic/ukai.ttc",
  109. ]
  110. for font_path in font_paths:
  111. if os.path.exists(font_path):
  112. self.font_path = font_path
  113. print(f"✅ 找到字体: {font_path}")
  114. break
  115. if not self.font_path:
  116. print("⚠️ 未找到中文字体,将使用默认字体")
  117. def _put_text_freetype(self, img, text, pos, font_size=20, color=(0, 255, 0)):
  118. """在图像上添加文字"""
  119. if not self.font_path:
  120. # 使用OpenCV默认字体
  121. cv2.putText(img, text, pos, cv2.FONT_HERSHEY_SIMPLEX,
  122. font_size / 30.0, color, 2)
  123. return
  124. try:
  125. import freetype
  126. # 初始化freetype字体
  127. if not hasattr(self, '_ft_face'):
  128. self._ft_face = freetype.Face(self.font_path)
  129. # 设置字体大小
  130. self._ft_face.set_pixel_sizes(0, int(font_size))
  131. # 确保text是字符串
  132. if isinstance(text, bytes):
  133. text = text.decode('utf-8')
  134. # 获取图像高度和宽度
  135. img_height, img_width = img.shape[:2]
  136. # 绘制字符
  137. pen_x, pen_y = pos
  138. for char in text:
  139. # 加载字符
  140. self._ft_face.load_char(char, freetype.FT_LOAD_RENDER)
  141. # 获取字符的bitmap
  142. bitmap = self._ft_face.glyph.bitmap
  143. bitmap_width = bitmap.width
  144. bitmap_height = bitmap.rows
  145. # 获取字符的偏移量
  146. left = self._ft_face.glyph.bitmap_left
  147. top = self._ft_face.glyph.bitmap_top
  148. # 计算字符在图像中的位置
  149. char_x = pen_x + left
  150. char_y = pen_y - top
  151. # 绘制字符的每个像素
  152. for y in range(bitmap_height):
  153. for x in range(bitmap_width):
  154. # 计算在bitmap buffer中的索引
  155. index = y * bitmap.pitch + x
  156. if index < len(bitmap.buffer):
  157. alpha = bitmap.buffer[index]
  158. if alpha > 0:
  159. # 计算在图像中的坐标
  160. img_x = char_x + x
  161. img_y = char_y + y
  162. # 确保坐标在图像范围内
  163. if 0 <= img_x < img_width and 0 <= img_y < img_height:
  164. # 计算混合后的颜色(考虑透明度)
  165. if len(img.shape) == 3:
  166. # 彩色图像
  167. img[img_y, img_x] = color
  168. else:
  169. # 灰度图像
  170. img[img_y, img_x] = 255
  171. # 更新笔位置
  172. pen_x += self._ft_face.glyph.advance.x >> 6
  173. except Exception as e:
  174. print(f"使用freetype绘制文字失败: {e}")
  175. # 回退到OpenCV默认字体,但尝试使用支持中文的字体
  176. cv2.putText(img, text, pos, cv2.FONT_HERSHEY_TRIPLEX,
  177. font_size / 30.0, color, 2)
  178. def recognize_face(self, face, threshold=0.6):
  179. """
  180. 识别人脸
  181. Args:
  182. face: 人脸对象
  183. threshold: 相似度阈值(余弦相似度,范围[-1, 1])
  184. Returns:
  185. tuple: (名字, 相似度分数)
  186. """
  187. if 'embedding' not in face:
  188. return "Unknown", 0.0
  189. embedding = face['embedding']
  190. best_name = "Unknown"
  191. best_score = threshold
  192. if not self.face_database:
  193. return "Unknown", 0.0
  194. for name, db_embedding in self.face_database.items():
  195. # 计算真正的余弦相似度
  196. # 余弦相似度 = 点积 / (embedding的模长 * db_embedding的模长)
  197. embedding_norm = np.linalg.norm(embedding)
  198. db_embedding_norm = np.linalg.norm(db_embedding)
  199. if embedding_norm == 0 or db_embedding_norm == 0:
  200. similarity = 0.0
  201. else:
  202. similarity = np.dot(embedding, db_embedding) / (embedding_norm * db_embedding_norm)
  203. if similarity > best_score:
  204. best_score = similarity
  205. best_name = name
  206. return best_name, best_score
  207. def detect_and_recognize(self, image):
  208. """
  209. 检测并识别人脸
  210. Args:
  211. image: 输入图像 (numpy array)
  212. Returns:
  213. dict: 检测和识别结果
  214. """
  215. try:
  216. # 检测人脸
  217. # 添加try-except避免get方法崩溃
  218. try:
  219. faces = self.app.get(image)
  220. except Exception as get_e:
  221. print(f"⚠️ 人脸检测失败: {get_e}")
  222. return {
  223. "detected": False,
  224. "error": f"人脸检测失败: {str(get_e)}",
  225. "faces": [],
  226. "count": 0
  227. }
  228. results = {
  229. "detected": len(faces) > 0,
  230. "faces": [],
  231. "count": len(faces)
  232. }
  233. for face in faces:
  234. # 识别人脸 - 增加安全检查
  235. if 'embedding' not in face:
  236. continue
  237. name, similarity = self.recognize_face(face)
  238. # 获取边界框 - 增加安全检查
  239. if 'bbox' not in face:
  240. continue
  241. bbox = face['bbox']
  242. # 确保bbox是有效的列表/数组
  243. if not isinstance(bbox, (list, tuple, np.ndarray)) or len(bbox) < 4:
  244. continue
  245. bbox = list(map(int, bbox))
  246. # 获取置信度
  247. confidence = face.get('det_score', 0.0)
  248. face_info = {
  249. "name": name,
  250. "confidence": float(confidence),
  251. "similarity": float(similarity),
  252. "bbox": bbox
  253. }
  254. results["faces"].append(face_info)
  255. return results
  256. except Exception as e:
  257. print(f"❌ 人脸识别总错误: {e}")
  258. import traceback
  259. traceback.print_exc()
  260. return {
  261. "detected": False,
  262. "error": str(e),
  263. "faces": [],
  264. "count": 0
  265. }
  266. def add_face_from_image(self, image_path, name):
  267. """
  268. 从图像文件添加人脸
  269. Args:
  270. image_path: 图像文件路径
  271. name: 人脸对应的名字
  272. Returns:
  273. bool: 添加成功返回True,失败返回False
  274. """
  275. try:
  276. # 检查文件是否存在
  277. if not os.path.exists(image_path):
  278. print(f"❌ 文件不存在: {image_path}")
  279. return False
  280. # 读取图像
  281. frame = cv2.imread(image_path)
  282. if frame is None:
  283. print(f"❌ 无法读取图像: {image_path}")
  284. return False
  285. # 检测人脸
  286. faces = self.app.get(frame)
  287. if len(faces) == 0:
  288. print("❌ 图像中未检测到人脸")
  289. return False
  290. elif len(faces) > 1:
  291. print("⚠️ 图像中检测到多张人脸,仅使用第一张")
  292. # 获取人脸特征
  293. face = faces[0]
  294. if 'embedding' not in face:
  295. print("❌ 无法提取人脸特征")
  296. return False
  297. # 添加到数据库
  298. self.face_database[name] = face['embedding']
  299. self._save_database()
  300. print(f"✅ 成功添加人脸: {name}")
  301. return True
  302. except Exception as e:
  303. print(f"❌ 添加人脸失败: {e}")
  304. return False
  305. def run_camera_recognition(self, camera_id=10, save_db_on_exit=True):
  306. """
  307. 运行摄像头实时人脸识别
  308. Args:
  309. camera_id: 摄像头ID
  310. save_db_on_exit: 退出时是否保存数据库
  311. """
  312. print(f"打开摄像头 {camera_id}...")
  313. cap = cv2.VideoCapture(camera_id)
  314. if not cap.isOpened():
  315. print(f"❌ 无法打开摄像头 {camera_id}")
  316. return False
  317. print("开始实时人脸识别。按 'q' 键退出。")
  318. while True:
  319. ret, frame = cap.read()
  320. if not ret:
  321. print("❌ 无法读取帧")
  322. break
  323. # 检测和识别人脸
  324. results = self.detect_and_recognize(frame)
  325. # 绘制结果
  326. for face_info in results.get("faces", []):
  327. bbox = face_info["bbox"]
  328. name = face_info["name"]
  329. confidence = face_info["confidence"]
  330. similarity = face_info["similarity"]
  331. # 绘制边界框
  332. cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
  333. # 绘制名字
  334. name_y = max(20, bbox[1] - 10)
  335. self._put_text_freetype(frame, name, (bbox[0], name_y),
  336. font_size=18, color=(0, 255, 0))
  337. # 绘制置信度和相似度
  338. conf_y = bbox[3] + 20
  339. self._put_text_freetype(frame, f"置信度: {confidence:.2f}, 相似度: {similarity:.2f}",
  340. (bbox[0], conf_y), font_size=12, color=(0, 255, 0))
  341. # 显示结果
  342. cv2.imshow('实时人脸识别', frame)
  343. # 退出
  344. key = cv2.waitKey(1) & 0xFF
  345. if key == ord('q'):
  346. break
  347. cap.release()
  348. cv2.destroyAllWindows()
  349. if save_db_on_exit:
  350. self._save_database()
  351. return True
  352. def get_database_info(self):
  353. """
  354. 获取人脸数据库信息
  355. Returns:
  356. dict: 数据库信息
  357. """
  358. return {
  359. "db_path": self.db_path,
  360. "face_count": len(self.face_database),
  361. "faces": list(self.face_database.keys())
  362. }
  363. def main():
  364. """主函数"""
  365. import argparse
  366. parser = argparse.ArgumentParser(description="人脸识别API工具")
  367. parser.add_argument("--add-face", nargs=2, metavar=("IMAGE_PATH", "NAME"),
  368. help="添加人脸到数据库")
  369. parser.add_argument("--add-webcam", metavar="NAME",
  370. help="从摄像头添加人脸到数据库")
  371. parser.add_argument("--db-path", default="/home/ubuntu/eyes/face_database.pkl",
  372. help="人脸数据库路径")
  373. parser.add_argument("--camera-id", type=int, default=10,
  374. help="摄像头ID")
  375. parser.add_argument("--model", default="buffalo_l",
  376. help="使用的模型名称")
  377. parser.add_argument("--force-cuda", action="store_true",
  378. help="强制使用CUDA(默认使用CPU)")
  379. parser.add_argument("--recognize", metavar="IMAGE_PATH",
  380. help="识别图像中的人脸")
  381. parser.add_argument("--api", action="store_true",
  382. help="启动API服务")
  383. parser.add_argument("--info", action="store_true",
  384. help="显示数据库信息")
  385. args = parser.parse_args()
  386. # 初始化系统,默认使用CPU,只有显式指定--force-cuda时才使用CUDA
  387. force_cuda = args.force_cuda
  388. system = FaceRecognitionAPI(db_path=args.db_path,
  389. model_name=args.model,
  390. force_cuda=force_cuda)
  391. # 添加人脸
  392. if args.add_face:
  393. image_path, name = args.add_face
  394. system.add_face_from_image(image_path, name)
  395. system._save_database()
  396. # 从摄像头添加人脸
  397. elif args.add_webcam:
  398. system.run_camera_recognition(camera_id=args.camera_id, save_db_on_exit=True)
  399. # 识别图像
  400. elif args.recognize:
  401. image = cv2.imread(args.recognize)
  402. if image is not None:
  403. results = system.detect_and_recognize(image)
  404. print(json.dumps(results, indent=2, ensure_ascii=False))
  405. else:
  406. print(f"❌ 无法读取图像: {args.recognize}")
  407. # 显示数据库信息
  408. elif args.info:
  409. info = system.get_database_info()
  410. print(json.dumps(info, indent=2, ensure_ascii=False))
  411. # 启动API服务
  412. elif args.api:
  413. print("ℹ️ API服务功能待实现")
  414. # 默认运行摄像头
  415. else:
  416. system.run_camera_recognition(camera_id=args.camera_id, save_db_on_exit=True)
  417. if __name__ == "__main__":
  418. main()