evaluation.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. """
  2. WiderFace evaluation code
  3. author: wondervictor
  4. mail: tianhengcheng@gmail.com
  5. copyright@wondervictor
  6. """
  7. import os
  8. import tqdm
  9. import pickle
  10. import argparse
  11. import numpy as np
  12. from scipy.io import loadmat
  13. from bbox import bbox_overlaps
  14. from IPython import embed
  15. def get_gt_boxes(gt_dir):
  16. """ gt dir: (wider_face_val.mat, wider_easy_val.mat, wider_medium_val.mat, wider_hard_val.mat)"""
  17. gt_mat = loadmat(os.path.join(gt_dir, 'wider_face_val.mat'))
  18. hard_mat = loadmat(os.path.join(gt_dir, 'wider_hard_val.mat'))
  19. medium_mat = loadmat(os.path.join(gt_dir, 'wider_medium_val.mat'))
  20. easy_mat = loadmat(os.path.join(gt_dir, 'wider_easy_val.mat'))
  21. facebox_list = gt_mat['face_bbx_list']
  22. event_list = gt_mat['event_list']
  23. file_list = gt_mat['file_list']
  24. hard_gt_list = hard_mat['gt_list']
  25. medium_gt_list = medium_mat['gt_list']
  26. easy_gt_list = easy_mat['gt_list']
  27. return facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list
  28. def get_gt_boxes_from_txt(gt_path, cache_dir):
  29. cache_file = os.path.join(cache_dir, 'gt_cache.pkl')
  30. if os.path.exists(cache_file):
  31. f = open(cache_file, 'rb')
  32. boxes = pickle.load(f)
  33. f.close()
  34. return boxes
  35. f = open(gt_path, 'r')
  36. state = 0
  37. lines = f.readlines()
  38. lines = list(map(lambda x: x.rstrip('\r\n'), lines))
  39. boxes = {}
  40. print(len(lines))
  41. f.close()
  42. current_boxes = []
  43. current_name = None
  44. for line in lines:
  45. if state == 0 and '--' in line:
  46. state = 1
  47. current_name = line
  48. continue
  49. if state == 1:
  50. state = 2
  51. continue
  52. if state == 2 and '--' in line:
  53. state = 1
  54. boxes[current_name] = np.array(current_boxes).astype('float32')
  55. current_name = line
  56. current_boxes = []
  57. continue
  58. if state == 2:
  59. box = [float(x) for x in line.split(' ')[:4]]
  60. current_boxes.append(box)
  61. continue
  62. f = open(cache_file, 'wb')
  63. pickle.dump(boxes, f)
  64. f.close()
  65. return boxes
  66. def read_pred_file(filepath):
  67. with open(filepath, 'r') as f:
  68. lines = f.readlines()
  69. img_file = lines[0].rstrip('\n\r')
  70. lines = lines[2:]
  71. # b = lines[0].rstrip('\r\n').split(' ')[:-1]
  72. # c = float(b)
  73. # a = map(lambda x: [[float(a[0]), float(a[1]), float(a[2]), float(a[3]), float(a[4])] for a in x.rstrip('\r\n').split(' ')], lines)
  74. boxes = []
  75. for line in lines:
  76. line = line.rstrip('\r\n').split(' ')
  77. if line[0] == '':
  78. continue
  79. # a = float(line[4])
  80. boxes.append([float(line[0]), float(line[1]), float(line[2]), float(line[3]), float(line[4])])
  81. boxes = np.array(boxes)
  82. # boxes = np.array(list(map(lambda x: [float(a) for a in x.rstrip('\r\n').split(' ')], lines))).astype('float')
  83. return img_file.split('/')[-1], boxes
  84. def get_preds(pred_dir):
  85. events = os.listdir(pred_dir)
  86. boxes = dict()
  87. pbar = tqdm.tqdm(events)
  88. for event in pbar:
  89. pbar.set_description('Reading Predictions ')
  90. event_dir = os.path.join(pred_dir, event)
  91. event_images = os.listdir(event_dir)
  92. current_event = dict()
  93. for imgtxt in event_images:
  94. imgname, _boxes = read_pred_file(os.path.join(event_dir, imgtxt))
  95. current_event[imgname.rstrip('.jpg')] = _boxes
  96. boxes[event] = current_event
  97. return boxes
  98. def norm_score(pred):
  99. """ norm score
  100. pred {key: [[x1,y1,x2,y2,s]]}
  101. """
  102. max_score = 0
  103. min_score = 1
  104. for _, k in pred.items():
  105. for _, v in k.items():
  106. if len(v) == 0:
  107. continue
  108. _min = np.min(v[:, -1])
  109. _max = np.max(v[:, -1])
  110. max_score = max(_max, max_score)
  111. min_score = min(_min, min_score)
  112. diff = max_score - min_score
  113. for _, k in pred.items():
  114. for _, v in k.items():
  115. if len(v) == 0:
  116. continue
  117. v[:, -1] = (v[:, -1] - min_score)/diff
  118. def image_eval(pred, gt, ignore, iou_thresh):
  119. """ single image evaluation
  120. pred: Nx5
  121. gt: Nx4
  122. ignore:
  123. """
  124. _pred = pred.copy()
  125. _gt = gt.copy()
  126. pred_recall = np.zeros(_pred.shape[0])
  127. recall_list = np.zeros(_gt.shape[0])
  128. proposal_list = np.ones(_pred.shape[0])
  129. _pred[:, 2] = _pred[:, 2] + _pred[:, 0]
  130. _pred[:, 3] = _pred[:, 3] + _pred[:, 1]
  131. _gt[:, 2] = _gt[:, 2] + _gt[:, 0]
  132. _gt[:, 3] = _gt[:, 3] + _gt[:, 1]
  133. overlaps = bbox_overlaps(_pred[:, :4], _gt)
  134. for h in range(_pred.shape[0]):
  135. gt_overlap = overlaps[h]
  136. max_overlap, max_idx = gt_overlap.max(), gt_overlap.argmax()
  137. if max_overlap >= iou_thresh:
  138. if ignore[max_idx] == 0:
  139. recall_list[max_idx] = -1
  140. proposal_list[h] = -1
  141. elif recall_list[max_idx] == 0:
  142. recall_list[max_idx] = 1
  143. r_keep_index = np.where(recall_list == 1)[0]
  144. pred_recall[h] = len(r_keep_index)
  145. return pred_recall, proposal_list
  146. def img_pr_info(thresh_num, pred_info, proposal_list, pred_recall):
  147. pr_info = np.zeros((thresh_num, 2)).astype('float')
  148. for t in range(thresh_num):
  149. thresh = 1 - (t+1)/thresh_num
  150. r_index = np.where(pred_info[:, 4] >= thresh)[0]
  151. if len(r_index) == 0:
  152. pr_info[t, 0] = 0
  153. pr_info[t, 1] = 0
  154. else:
  155. r_index = r_index[-1]
  156. p_index = np.where(proposal_list[:r_index+1] == 1)[0]
  157. pr_info[t, 0] = len(p_index)
  158. pr_info[t, 1] = pred_recall[r_index]
  159. return pr_info
  160. def dataset_pr_info(thresh_num, pr_curve, count_face):
  161. _pr_curve = np.zeros((thresh_num, 2))
  162. for i in range(thresh_num):
  163. _pr_curve[i, 0] = pr_curve[i, 1] / pr_curve[i, 0]
  164. _pr_curve[i, 1] = pr_curve[i, 1] / count_face
  165. return _pr_curve
  166. def voc_ap(rec, prec):
  167. # correct AP calculation
  168. # first append sentinel values at the end
  169. mrec = np.concatenate(([0.], rec, [1.]))
  170. mpre = np.concatenate(([0.], prec, [0.]))
  171. # compute the precision envelope
  172. for i in range(mpre.size - 1, 0, -1):
  173. mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
  174. # to calculate area under PR curve, look for points
  175. # where X axis (recall) changes value
  176. i = np.where(mrec[1:] != mrec[:-1])[0]
  177. # and sum (\Delta recall) * prec
  178. ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
  179. return ap
  180. def evaluation(pred, gt_path, iou_thresh=0.5):
  181. pred = get_preds(pred)
  182. norm_score(pred)
  183. facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list = get_gt_boxes(gt_path)
  184. event_num = len(event_list)
  185. thresh_num = 1000
  186. settings = ['easy', 'medium', 'hard']
  187. setting_gts = [easy_gt_list, medium_gt_list, hard_gt_list]
  188. aps = []
  189. for setting_id in range(3):
  190. # different setting
  191. gt_list = setting_gts[setting_id]
  192. count_face = 0
  193. pr_curve = np.zeros((thresh_num, 2)).astype('float')
  194. # [hard, medium, easy]
  195. pbar = tqdm.tqdm(range(event_num))
  196. for i in pbar:
  197. pbar.set_description('Processing {}'.format(settings[setting_id]))
  198. event_name = str(event_list[i][0][0])
  199. img_list = file_list[i][0]
  200. pred_list = pred[event_name]
  201. sub_gt_list = gt_list[i][0]
  202. # img_pr_info_list = np.zeros((len(img_list), thresh_num, 2))
  203. gt_bbx_list = facebox_list[i][0]
  204. for j in range(len(img_list)):
  205. pred_info = pred_list[str(img_list[j][0][0])]
  206. gt_boxes = gt_bbx_list[j][0].astype('float')
  207. keep_index = sub_gt_list[j][0]
  208. count_face += len(keep_index)
  209. if len(gt_boxes) == 0 or len(pred_info) == 0:
  210. continue
  211. ignore = np.zeros(gt_boxes.shape[0])
  212. if len(keep_index) != 0:
  213. ignore[keep_index-1] = 1
  214. pred_recall, proposal_list = image_eval(pred_info, gt_boxes, ignore, iou_thresh)
  215. _img_pr_info = img_pr_info(thresh_num, pred_info, proposal_list, pred_recall)
  216. pr_curve += _img_pr_info
  217. pr_curve = dataset_pr_info(thresh_num, pr_curve, count_face)
  218. propose = pr_curve[:, 0]
  219. recall = pr_curve[:, 1]
  220. ap = voc_ap(recall, propose)
  221. aps.append(ap)
  222. print("==================== Results ====================")
  223. print("Easy Val AP: {}".format(aps[0]))
  224. print("Medium Val AP: {}".format(aps[1]))
  225. print("Hard Val AP: {}".format(aps[2]))
  226. print("=================================================")
  227. if __name__ == '__main__':
  228. parser = argparse.ArgumentParser()
  229. parser.add_argument('-p', '--pred', default="./widerface_txt/")
  230. parser.add_argument('-g', '--gt', default='./ground_truth/')
  231. args = parser.parse_args()
  232. evaluation(args.pred, args.gt)