detection_performance_loss_test.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. """
  2. 针对目标检测模型的测试性能损失脚本,通过比较推理过程中CPU、GPU占用、推理时间来进行计算
  3. 需要安装指定python库实现功能
  4. pip install psutil gputil pynvml pycocotools
  5. """
  6. import argparse
  7. import os
  8. import psutil
  9. # import GPUtil
  10. import numpy as np
  11. import time
  12. from threading import Thread
  13. from pycocotools.coco import COCO
  14. import xml.etree.ElementTree as ET
  15. from watermark_verify.inference.rcnn_inference import FasterRCNNInference
  16. from watermark_verify.inference.ssd_inference import SSDInference
  17. from watermark_verify.inference.yolox_inference import YOLOXInference
  18. from watermark_verify.tools.evaluate_tool import calculate_iou
  19. import subprocess
  20. import re
  21. def get_npu_usage():
  22. try:
  23. output = subprocess.check_output("npu-smi info", shell=True, encoding="utf-8")
  24. percent = None
  25. for line in output.splitlines():
  26. line = line.strip()
  27. # 提取 like "1524 / 44280" 值大于0的行
  28. m = re.search(r"([1-9]\d*)\s*/\s*([1-9]\d*)", line)
  29. if m:
  30. used = int(m.group(1))
  31. total = int(m.group(2))
  32. percent = used * 100.0 / total if total > 0 else 0
  33. return percent
  34. print("未找到NPU 0 显存占用信息")
  35. return None
  36. except Exception as e:
  37. print("获取NPU信息失败:", e)
  38. return None
  39. # 定义监控函数
  40. class UsageMonitor:
  41. def __init__(self, interval=0.5):
  42. self.interval = interval
  43. self.cpu_usage = []
  44. self.gpu_usage = []
  45. self.running = False
  46. def start(self):
  47. self.running = True
  48. self.monitor_thread = Thread(target=self._monitor)
  49. self.monitor_thread.start()
  50. def _monitor(self):
  51. while self.running:
  52. # 记录 CPU 使用率
  53. self.cpu_usage.append(psutil.cpu_percent(interval=None))
  54. # 记录 GPU 使用率
  55. # gpus = GPUtil.getGPUs()
  56. gpus = get_npu_usage()
  57. if gpus:
  58. # self.gpu_usage.append(gpus[0].load * 100) # 获取第一个 GPU 的使用率
  59. self.gpu_usage.append(gpus)
  60. else:
  61. self.gpu_usage.append(0) # 若没有 GPU 则记为 0
  62. time.sleep(self.interval)
  63. def stop(self):
  64. self.running = False
  65. self.monitor_thread.join()
  66. def get_average_usage(self):
  67. avg_cpu_usage = np.mean(self.cpu_usage)
  68. avg_gpu_usage = np.mean(self.gpu_usage)
  69. return avg_cpu_usage, avg_gpu_usage
  70. def parse_voc_annotation(annotation_path):
  71. """
  72. 解析单个VOC XML文件,并提取边界框和类别信息
  73. :param annotation_path: XML标注文件路径
  74. :return: List[[x1, y1, x2, y2, category_id]]
  75. """
  76. voc_label_map = { # 类别名称到ID的映射字典
  77. 'aeroplane': 0,
  78. 'bicycle': 1,
  79. 'bird': 2,
  80. 'boat': 3,
  81. 'bottle': 4,
  82. 'bus': 5,
  83. 'car': 6,
  84. 'cat': 7,
  85. 'chair': 8,
  86. 'cow': 9,
  87. 'diningtable': 10,
  88. 'dog': 11,
  89. 'horse': 12,
  90. 'motorbike': 13,
  91. 'person': 14,
  92. 'pottedplant': 15,
  93. 'sheep': 16,
  94. 'sofa': 17,
  95. 'train': 18,
  96. 'tvmonitor': 19
  97. }
  98. tree = ET.parse(annotation_path)
  99. root = tree.getroot()
  100. annotations = []
  101. for obj in root.findall("object"):
  102. # 类别名称
  103. class_name = obj.find("name").text
  104. category_id = voc_label_map.get(class_name, -1) # 找不到时返回 -1
  105. # 边界框信息
  106. bndbox = obj.find("bndbox")
  107. x1 = int(bndbox.find("xmin").text)
  108. y1 = int(bndbox.find("ymin").text)
  109. x2 = int(bndbox.find("xmax").text)
  110. y2 = int(bndbox.find("ymax").text)
  111. # 添加到结果
  112. annotations.append([x1, y1, x2, y2, category_id])
  113. return annotations
  114. def get_dataset_images_annotations(args, num):
  115. """
  116. 从指定数据集的验证集中获取指定数量的图片及其标注信息
  117. :param args: 参数
  118. :param num: 获取数量
  119. :return: (image_path, annotations)
  120. """
  121. result = []
  122. dataset_type = args.dataset_type # dataset_type: 数据集类型,可选参数:voc,coco
  123. if dataset_type == 'voc':
  124. voc_val_path = args.val_dataset_dir
  125. voc_annotations_path = os.path.join(voc_val_path, 'Annotations')
  126. voc_images_path = os.path.join(voc_val_path, 'JPEGImages')
  127. annotation_files = os.listdir(voc_annotations_path)
  128. selected_files = annotation_files[:num] # 前5张图片
  129. for file in selected_files:
  130. annotation_path = os.path.join(voc_annotations_path, file)
  131. image_path = os.path.join(voc_images_path, file.replace('.xml', '.jpg'))
  132. annotations = parse_voc_annotation(annotation_path)
  133. result.append((image_path, annotations))
  134. return result
  135. if dataset_type == 'coco':
  136. # 加载COCO验证集
  137. coco = COCO(args.val_annotation)
  138. image_ids = coco.getImgIds()[:num] # 前5张图片
  139. images = coco.loadImgs(image_ids)
  140. for image_info in images:
  141. img_path = f"{args.val_dataset_dir}/{image_info['file_name']}"
  142. # 获取标签信息
  143. ann_ids = coco.getAnnIds(imgIds=image_info['id'])
  144. anns = coco.loadAnns(ann_ids)
  145. annotations = []
  146. for anno in anns:
  147. gt_box = anno['bbox'] # [x, y, width, height]
  148. gt_box = [gt_box[0], gt_box[1], gt_box[0] + gt_box[2], gt_box[1] + gt_box[3]]
  149. gt_class = anno['category_id'] - 1
  150. annotations.append([gt_box[0], gt_box[1], gt_box[2], gt_box[3], gt_class])
  151. result.append((img_path, annotations))
  152. return result
  153. def output_process(model_type, pred):
  154. """
  155. 将目标检测模型输出结果转换为统一结果输出
  156. :param model_type: 目标检测模型类型,可选值:yolox,ssd,faster-rcnn
  157. :param pred: 模型预测结果
  158. :return: bbox,score,cls
  159. """
  160. pred_box = None
  161. score = None
  162. pred_class = None
  163. if model_type == 'yolox':
  164. pred_box = pred[:4] # 前4个值为bbox:[xmin,ymin,xmax,ymax]
  165. score = pred[4] # 第5位为置信度
  166. pred_class = int(pred[5]) # 第6位为类别
  167. elif model_type == 'ssd':
  168. pred_box = np.array([pred[1], pred[0], pred[3], pred[2]]) # 前4个值为bbox:[ymin,xmin,ymax,xmax]
  169. pred_class = int(pred[4]) # 第5位为类别
  170. score = pred[5] # 第6位为置信度
  171. elif model_type == 'faster-rcnn':
  172. pred_box = np.array([pred[1], pred[0], pred[3], pred[2]]) # 前4个值为bbox:[ymin,xmin,ymax,xmax]
  173. score = pred[4] # 第5位为置信度
  174. pred_class = int(pred[5]) # 第6位为类别
  175. return pred_box, score, pred_class
  176. # 模型推理函数
  177. def model_inference(args, model_filename, conf=0.3):
  178. """
  179. 模型推理验证集目录下所有图片
  180. :param args: 运行参数
  181. :param model_filename: 模型文件
  182. :param conf: 置信度阈值
  183. :return: 验证集推理准确率
  184. """
  185. model_type = args.model_type # 目标检测模型类型,可选值:yolox,ssd,faster-rcnn
  186. # 以下使用GPU进行推理出现问题,需要较新的CUDA版本,默认使用CPU进行推理
  187. # if ort.get_available_providers():
  188. # session = ort.InferenceSession(model_filename, providers=['CUDAExecutionProvider'])
  189. # else:
  190. # session = ort.InferenceSession(model_filename)
  191. # 初始化计数
  192. correct_count = 0
  193. total_count = 0
  194. iou_threshold = 0.5
  195. part_dataset = get_dataset_images_annotations(args, 5)
  196. for image_path, annotations in part_dataset:
  197. # 使用模型推理流程定义进行模型推理
  198. if model_type.lower() == 'yolox':
  199. preds = YOLOXInference(model_filename).predict(image_path)
  200. elif model_type.lower() == 'ssd':
  201. preds = SSDInference(model_filename).predict(image_path)
  202. preds = preds[0] # 只获取模型第一个输出
  203. elif model_type.lower() == 'faster-rcnn':
  204. preds = FasterRCNNInference(model_filename).predict(image_path)
  205. preds = preds[0]
  206. else:
  207. raise Exception("目标检测模型类型参数不合法")
  208. for anno in annotations:
  209. gt_box = [anno[0], anno[1], anno[2], anno[3]]
  210. gt_class = anno[4]
  211. total_count += 1
  212. # 比对推理结果
  213. for pred in preds:
  214. if len(pred) == 0:
  215. continue
  216. pred_box, score, pred_class = output_process(model_type, pred)
  217. if score < conf:
  218. continue
  219. iou = calculate_iou(gt_box, pred_box)
  220. if iou > iou_threshold and pred_class == gt_class:
  221. correct_count += 1
  222. break
  223. # 计算准确率
  224. accuracy = correct_count / total_count
  225. return accuracy
  226. if __name__ == '__main__':
  227. parser = argparse.ArgumentParser(description='模型推理性能验证脚本')
  228. parser.add_argument('--model_type', default='faster-rcnn', type=str, help='目标检测模型类型:yolox、ssd、faster-rcnn')
  229. parser.add_argument('--dataset_type', default='voc', type=str, help='验证集的数据集格式,支持的参数:coco,voc')
  230. parser.add_argument('--val_dataset_dir', default=None, type=str, help='验证集目录')
  231. parser.add_argument('--origin_model_file', default=None, type=str, help='待测试原始模型的onnx文件')
  232. parser.add_argument('--watermark_model_file', default=None, type=str, help='待测试水印模型的onnx文件')
  233. parser.add_argument('--val_annotation', default=None, type=str,
  234. help='验证集标注文件,仅有coco数据集需要这个参数')
  235. # parser.add_argument('--val_dataset_dir', default="VOC2007", type=str, help='验证集目录')
  236. # parser.add_argument('--origin_model_file', default="models/origin/faster-rcnn/model.onnx", type=str,
  237. # help='待测试原始模型的onnx文件')
  238. args, _ = parser.parse_known_args()
  239. if args.origin_model_file is None:
  240. raise Exception("待测试模型的onnx文件不可为空")
  241. if args.val_dataset_dir is None:
  242. raise Exception("验证集目录不可为空")
  243. if args.model_type is None:
  244. raise Exception("目标检测模型类型不可为空")
  245. monitor = UsageMonitor(interval=0.5) # 每隔 0.5 秒采样一次
  246. monitor.start()
  247. # 记录推理开始时间
  248. start_time = time.time()
  249. # 进行模型推理
  250. accuracy = model_inference(args, args.origin_model_file)
  251. # 记录推理结束时间
  252. end_time = time.time()
  253. monitor.stop()
  254. # 输出平均 CPU 和 GPU 使用率
  255. avg_cpu, avg_gpu = monitor.get_average_usage()
  256. print("原始模型推理性能:")
  257. print(f"平均 CPU 使用率:{avg_cpu:.2f}%")
  258. print(f"平均 GPU 使用率:{avg_gpu:.2f}%")
  259. print(f"模型推理时间: {end_time - start_time:.2f} 秒")
  260. print(f"准确率: {accuracy * 100:.2f}%")
  261. if args.watermark_model_file: # 加入存在比对模型,进行再次推理,然后统计性能指标
  262. time.sleep(20)
  263. monitor2 = UsageMonitor(interval=0.5) # 每隔 0.5 秒采样一次
  264. monitor2.start()
  265. # 记录推理开始时间
  266. start_time2 = time.time()
  267. # 进行模型推理
  268. accuracy2 = model_inference(args, args.watermark_model_file)
  269. # 记录推理结束时间
  270. end_time2 = time.time()
  271. monitor2.stop()
  272. # 输出平均 CPU 和 GPU 使用率
  273. avg_cpu2, avg_gpu2 = monitor2.get_average_usage()
  274. print("水印模型推理性能:")
  275. print(f"平均 CPU 使用率:{avg_cpu2:.2f}%")
  276. print(f"平均 GPU 使用率:{avg_gpu2:.2f}%")
  277. print(f"模型推理时间: {end_time2 - start_time2:.2f} 秒")
  278. print(f"准确率: {accuracy2 * 100:.2f}%")
  279. print("------------------性能指标如下-------------------------")
  280. print(f"嵌入后模型推理准确率下降值:{(accuracy - accuracy2) * 100:.2f}%")
  281. print(f"算力资源消耗增加值:{(avg_cpu2 - avg_cpu):.2f}%")
  282. print(
  283. f"运行效率降低值: {((end_time2 - start_time2) - (end_time - start_time)) * 100 / (end_time - start_time):.2f} %")