get_map.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import os, torch, cv2, math, tqdm, time, caffe, shutil, argparse
  2. import numpy as np
  3. from quantization import *
  4. from yolo import non_max_suppression, plot_one_box, yolov8layer, letterbox, xywh2xyxy, box_iou
  5. from config import image_base_path, label_base_path, net_shape
  6. PROJECT_NAME = os.environ['PROJECT_NAME']
  7. def clip_boxes(boxes, shape):
  8. # Clip boxes (xyxy) to image shape (height, width)
  9. if isinstance(boxes, torch.Tensor): # faster individually
  10. boxes[..., 0].clamp_(0, shape[1]) # x1
  11. boxes[..., 1].clamp_(0, shape[0]) # y1
  12. boxes[..., 2].clamp_(0, shape[1]) # x2
  13. boxes[..., 3].clamp_(0, shape[0]) # y2
  14. else: # np.array (faster grouped)
  15. boxes[..., [0, 2]] = boxes[..., [0, 2]].clip(0, shape[1]) # x1, x2
  16. boxes[..., [1, 3]] = boxes[..., [1, 3]].clip(0, shape[0]) # y1, y2
  17. def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):
  18. # Rescale boxes (xyxy) from img1_shape to img0_shape
  19. if ratio_pad is None: # calculate from img0_shape
  20. gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
  21. pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
  22. else:
  23. gain = ratio_pad[0][0]
  24. pad = ratio_pad[1]
  25. boxes[..., [0, 2]] -= pad[0] # x padding
  26. boxes[..., [1, 3]] -= pad[1] # y padding
  27. boxes[..., :4] /= gain
  28. clip_boxes(boxes, img0_shape)
  29. return boxes
  30. def process_batch(detections, labels, iouv):
  31. """
  32. Return correct prediction matrix
  33. Arguments:
  34. detections (array[N, 6]), x1, y1, x2, y2, conf, class
  35. labels (array[M, 5]), class, x1, y1, x2, y2
  36. Returns:
  37. correct (array[N, 10]), for 10 IoU levels
  38. """
  39. correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
  40. iou = box_iou(labels[:, 1:], detections[:, :4])
  41. correct_class = labels[:, 0:1] == detections[:, 5]
  42. for i in range(len(iouv)):
  43. x = torch.where((iou >= iouv[i]) & correct_class) # IoU > threshold and classes match
  44. if x[0].shape[0]:
  45. matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() # [label, detect, iou]
  46. if x[0].shape[0] > 1:
  47. matches = matches[matches[:, 2].argsort()[::-1]]
  48. matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
  49. # matches = matches[matches[:, 2].argsort()[::-1]]
  50. matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
  51. correct[matches[:, 1].astype(int), i] = True
  52. return torch.tensor(correct, dtype=torch.bool, device=iouv.device)
  53. def smooth(y, f=0.05):
  54. # Box filter of fraction f
  55. nf = round(len(y) * f * 2) // 2 + 1 # number of filter elements (must be odd)
  56. p = np.ones(nf // 2) # ones padding
  57. yp = np.concatenate((p * y[0], y, p * y[-1]), 0) # y padded
  58. return np.convolve(yp, np.ones(nf) / nf, mode='valid') # y-smoothed
  59. def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=(), eps=1e-16, prefix=''):
  60. """ Compute the average precision, given the recall and precision curves.
  61. Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
  62. # Arguments
  63. tp: True positives (nparray, nx1 or nx10).
  64. conf: Objectness value from 0-1 (nparray).
  65. pred_cls: Predicted object classes (nparray).
  66. target_cls: True object classes (nparray).
  67. plot: Plot precision-recall curve at mAP@0.5
  68. save_dir: Plot save directory
  69. # Returns
  70. The average precision as computed in py-faster-rcnn.
  71. """
  72. # Sort by objectness
  73. i = np.argsort(-conf)
  74. tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
  75. # Find unique classes
  76. unique_classes, nt = np.unique(target_cls, return_counts=True)
  77. nc = unique_classes.shape[0] # number of classes, number of detections
  78. # Create Precision-Recall curve and compute AP for each class
  79. px, py = np.linspace(0, 1, 1000), [] # for plotting
  80. ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))
  81. for ci, c in enumerate(unique_classes):
  82. i = pred_cls == c
  83. n_l = nt[ci] # number of labels
  84. n_p = i.sum() # number of predictions
  85. if n_p == 0 or n_l == 0:
  86. continue
  87. # Accumulate FPs and TPs
  88. fpc = (1 - tp[i]).cumsum(0)
  89. tpc = tp[i].cumsum(0)
  90. # Recall
  91. recall = tpc / (n_l + eps) # recall curve
  92. r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
  93. # Precision
  94. precision = tpc / (tpc + fpc) # precision curve
  95. p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score
  96. # AP from recall-precision curve
  97. for j in range(tp.shape[1]):
  98. ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
  99. if plot and j == 0:
  100. py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5
  101. # Compute F1 (harmonic mean of precision and recall)
  102. f1 = 2 * p * r / (p + r + eps)
  103. i = smooth(f1.mean(0), 0.1).argmax() # max F1 index
  104. p, r, f1 = p[:, i], r[:, i], f1[:, i]
  105. tp = (r * nt).round() # true positives
  106. fp = (tp / (p + eps) - tp).round() # false positives
  107. return tp, fp, p, r, f1, ap, unique_classes.astype(int)
  108. def compute_ap(recall, precision):
  109. """ Compute the average precision, given the recall and precision curves
  110. # Arguments
  111. recall: The recall curve (list)
  112. precision: The precision curve (list)
  113. # Returns
  114. Average precision, precision curve, recall curve
  115. """
  116. # Append sentinel values to beginning and end
  117. mrec = np.concatenate(([0.0], recall, [1.0]))
  118. mpre = np.concatenate(([1.0], precision, [0.0]))
  119. # Compute the precision envelope
  120. mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
  121. # Integrate area under curve
  122. method = 'interp' # methods: 'continuous', 'interp'
  123. if method == 'interp':
  124. x = np.linspace(0, 1, 101) # 101-point interp (COCO)
  125. ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
  126. else: # 'continuous'
  127. i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes
  128. ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
  129. return ap, mpre, mrec
  130. class CustomDataset(BaseDataset):
  131. def __init__(self, image_path_list):
  132. super().__init__()
  133. self.image_path_list = image_path_list
  134. def __getitem__(self, item):
  135. print(item, end=' ')
  136. # read image
  137. ori_image = cv2.imdecode(np.fromfile(self.image_path_list[item], np.uint8), cv2.IMREAD_COLOR)
  138. # letterbox
  139. process_image, ratio, (dw, dh) = letterbox(ori_image, new_shape=net_shape, auto=False)
  140. # process_image = cv2.resize(ori_image, (640, 640))
  141. srcnp = cv2.cvtColor(process_image, cv2.COLOR_BGR2RGB)
  142. srcnp = srcnp.astype(np.float32) / 256
  143. srcnp = np.array(srcnp)
  144. srcnp = np.transpose(srcnp, [2,0,1])
  145. return srcnp
  146. def __len__(self):
  147. return len(self.image_path_list)
  148. def get_result_from_caffe(image_path_list, im_shape=(640, 640) ,conf_thres=0.01, iou_thres=0.45):
  149. prototxt_file = f'{PROJECT_NAME}/model_caffe/model_0.prototxt'
  150. caffemodel_file = f'{PROJECT_NAME}/model_caffe/model_0.caffemodel'
  151. dataset = CustomDataset(image_path_list)
  152. print('begin caffe_forward....')
  153. since = time.time()
  154. pred = net.src_forward(prototxt_file, caffemodel_file, dataset, 64)
  155. print(f'\ncaffe_forward finish. using time:{time.time() - since:.5f}.')
  156. out = yolov8layer([pred['output0'].copy(), pred['output1'].copy(), pred['output2'].copy()])
  157. out = non_max_suppression([torch.from_numpy(out)], conf_thres=conf_thres, iou_thres=iou_thres)
  158. for i in tqdm.tqdm(range(len(out)), desc='processing scale_boxes.'):
  159. ori_image = cv2.imread(image_path_list[i])
  160. out[i][:, :4] = scale_boxes(im_shape, out[i][:, :4], ori_image.shape[:2])
  161. # vis pred result for debug
  162. # for j in out[i]:
  163. # cv2.rectangle(ori_image, (int(j[0]), int(j[1])), (int(j[2]), int(j[3])), (0, 0, 255), thickness=2)
  164. # cv2.imwrite('./test.png', ori_image)
  165. return out
  166. def get_result_from_qkqb(image_path_list, im_shape=(640, 640) ,conf_thres=0.01, iou_thres=0.45):
  167. qk_file = f'{PROJECT_NAME}/model_quantization/checkpoint_quan.qk'
  168. qb_file = f'{PROJECT_NAME}/model_quantization/checkpoint_quan.qb'
  169. dataset = CustomDataset(image_path_list)
  170. print('begin easy_forward....')
  171. since = time.time()
  172. pred = net.easy_forward(qk_file, qb_file, dataset, 64)
  173. print(f'easy_forward finish. using time:{time.time() - since:.5f}.')
  174. out = yolov8layer([pred['/model.22/Concat'].copy(), pred['/model.22/Concat_1'].copy(), pred['/model.22/Concat_2'].copy()])
  175. out = non_max_suppression([torch.from_numpy(out)], conf_thres=conf_thres,iou_thres=iou_thres)
  176. for i in tqdm.tqdm(range(len(out)), desc='processing scale_boxes.'):
  177. ori_image = cv2.imdecode(np.fromfile(image_path_list[i], np.uint8), cv2.IMREAD_COLOR)
  178. out[i][:, :4] = scale_boxes(im_shape, out[i][:, :4], ori_image.shape[:2])
  179. # vis pred result for debug
  180. # for j in out[i]:
  181. # cv2.rectangle(ori_image, (int(j[0]), int(j[1])), (int(j[2]), int(j[3])), (0, 0, 255), thickness=2)
  182. # cv2.imwrite('./test.png', ori_image)
  183. return out
  184. def parse_opt():
  185. parser = argparse.ArgumentParser()
  186. parser.add_argument('--type', type=str, choices=['caffe', 'qkqb'], required=True, help='caffe or qkqb')
  187. parser.add_argument('--iou', type=float, default=0.45, help='iou threshold')
  188. parser.add_argument('--conf', type=float, default=0.01, help='conf threshold')
  189. parser.add_argument('--save_path', type=str, default=f'{PROJECT_NAME}/map_img_save', help='image save path')
  190. parser.add_argument('--device', type=int, default=1, help='device')
  191. opt = parser.parse_known_args()[0]
  192. return opt
  193. if __name__ == '__main__':
  194. opt = parse_opt()
  195. net = Net(opt.device)
  196. iouv = torch.linspace(0.5, 0.95, 10) # iou vector for mAP@0.5:0.95
  197. niou = iouv.numel()
  198. stats = []
  199. name_list = [os.path.splitext(i) for i in os.listdir(image_base_path)]
  200. image_listdir = [f'{image_base_path}/{i[0]}{i[1]}' for i in name_list]
  201. label_listdir = [f'{label_base_path}/{i[0]}.txt' for i in name_list]
  202. if os.path.exists(opt.save_path):
  203. shutil.rmtree(opt.save_path)
  204. os.makedirs(opt.save_path, exist_ok=True)
  205. if opt.type == 'caffe':
  206. preds = get_result_from_caffe(image_listdir, net_shape, conf_thres=opt.conf, iou_thres=opt.iou)
  207. elif opt.type == 'qkqb':
  208. preds = get_result_from_qkqb(image_listdir, net_shape, conf_thres=opt.conf, iou_thres=opt.iou)
  209. net.release()
  210. for idx, path in enumerate(tqdm.tqdm(name_list)):
  211. image_path = f'{image_base_path}/{path[0]}{path[1]}'
  212. label_path = f'{label_base_path}/{path[0]}.txt'
  213. # read and processing
  214. ori_image = cv2.imread(image_path)
  215. height, width, _ = ori_image.shape
  216. # read label
  217. with open(label_path) as f:
  218. label = f.readlines()
  219. if len(label) == 0:
  220. label = np.empty(shape=(0, 5))
  221. else:
  222. label = np.array(list(map(lambda x:np.array(x.strip().split(), dtype=np.float), label)))
  223. label[:, 1:] *= np.array([width, height, width, height])
  224. label[:, 1:] = xywh2xyxy(label[:, 1:])
  225. pred = preds[idx]
  226. # vis label result for debug
  227. for i in label:
  228. cv2.rectangle(ori_image, (int(i[1]), int(i[2])), (int(i[3]), int(i[4])), (0, 0, 255), thickness=2) # red label
  229. for i in pred:
  230. cv2.rectangle(ori_image, (int(i[0]), int(i[1])), (int(i[2]), int(i[3])), (0, 255, 0), thickness=2) # green pred
  231. cv2.imwrite(f'{opt.save_path}/{path[0]}{path[1]}', ori_image)
  232. nl, npr = label.shape[0], pred.shape[0]
  233. correct = torch.zeros(npr, niou, dtype=torch.bool)
  234. if npr == 0:
  235. if nl:
  236. stats.append((correct, *torch.zeros((2, 0)), torch.from_numpy(label[:, 0])))
  237. continue
  238. if nl:
  239. correct = process_batch(pred, torch.from_numpy(label), iouv)
  240. stats.append((correct, pred[:, 4], pred[:, 5], torch.from_numpy(label[:, 0])))
  241. stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]
  242. tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats)
  243. print(f'precision:{p}')
  244. print(f'recall:{r}')
  245. print(f'mAP@0.5:{ap[:, 0]}')