evaluation.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import argparse
  2. import cv2 as cv
  3. import glob
  4. import numpy as np
  5. import os
  6. import time
  7. # This tool is intended for evaluation of different background subtraction algorithms presented in OpenCV.
  8. # Several presets with different settings are available. You can see them below.
  9. # This tool measures quality metrics as well as speed.
  10. ALGORITHMS_TO_EVALUATE = [
  11. (cv.bgsegm.createBackgroundSubtractorMOG, 'MOG', {}),
  12. (cv.bgsegm.createBackgroundSubtractorGMG, 'GMG', {}),
  13. (cv.bgsegm.createBackgroundSubtractorCNT, 'CNT', {}),
  14. (cv.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-vanilla', {'nSamples': 20, 'LSBPRadius': 4, 'Tlower': 2.0, 'Tupper': 200.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 5.0, 'Rincdec': 0.05, 'LSBPthreshold': 8}),
  15. (cv.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-speed', {'nSamples': 10, 'LSBPRadius': 16, 'Tlower': 2.0, 'Tupper': 32.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 10.0, 'Rincdec': 0.005, 'LSBPthreshold': 8}),
  16. (cv.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-quality', {'nSamples': 20, 'LSBPRadius': 16, 'Tlower': 2.0, 'Tupper': 32.0, 'Tinc': 1.0, 'Tdec': 0.05, 'Rscale': 10.0, 'Rincdec': 0.005, 'LSBPthreshold': 8}),
  17. (cv.bgsegm.createBackgroundSubtractorLSBP, 'LSBP-camera-motion-compensation', {'mc': 1}),
  18. (cv.bgsegm.createBackgroundSubtractorGSOC, 'GSOC', {}),
  19. (cv.bgsegm.createBackgroundSubtractorGSOC, 'GSOC-camera-motion-compensation', {'mc': 1})
  20. ]
  21. def contains_relevant_files(root):
  22. return os.path.isdir(os.path.join(root, 'groundtruth')) and os.path.isdir(os.path.join(root, 'input'))
  23. def find_relevant_dirs(root):
  24. relevant_dirs = []
  25. for d in sorted(os.listdir(root)):
  26. d = os.path.join(root, d)
  27. if os.path.isdir(d):
  28. if contains_relevant_files(d):
  29. relevant_dirs += [d]
  30. else:
  31. relevant_dirs += find_relevant_dirs(d)
  32. return relevant_dirs
  33. def load_sequence(root):
  34. gt_dir, frames_dir = os.path.join(root, 'groundtruth'), os.path.join(root, 'input')
  35. gt = sorted(glob.glob(os.path.join(gt_dir, '*.png')))
  36. f = sorted(glob.glob(os.path.join(frames_dir, '*.jpg')))
  37. assert(len(gt) == len(f))
  38. return gt, f
  39. def evaluate_algorithm(gt, frames, algo, algo_arguments):
  40. bgs = algo(**algo_arguments)
  41. mask = []
  42. t_start = time.time()
  43. for i in range(len(gt)):
  44. frame = np.uint8(cv.imread(frames[i], cv.IMREAD_COLOR))
  45. mask.append(bgs.apply(frame))
  46. average_duration = (time.time() - t_start) / len(gt)
  47. average_precision, average_recall, average_f1, average_accuracy = [], [], [], []
  48. for i in range(len(gt)):
  49. gt_mask = np.uint8(cv.imread(gt[i], cv.IMREAD_GRAYSCALE))
  50. roi = ((gt_mask == 255) | (gt_mask == 0))
  51. if roi.sum() > 0:
  52. gt_answer, answer = gt_mask[roi], mask[i][roi]
  53. tp = ((answer == 255) & (gt_answer == 255)).sum()
  54. tn = ((answer == 0) & (gt_answer == 0)).sum()
  55. fp = ((answer == 255) & (gt_answer == 0)).sum()
  56. fn = ((answer == 0) & (gt_answer == 255)).sum()
  57. if tp + fp > 0:
  58. average_precision.append(float(tp) / (tp + fp))
  59. if tp + fn > 0:
  60. average_recall.append(float(tp) / (tp + fn))
  61. if tp + fn + fp > 0:
  62. average_f1.append(2.0 * tp / (2.0 * tp + fn + fp))
  63. average_accuracy.append(float(tp + tn) / (tp + tn + fp + fn))
  64. return average_duration, np.mean(average_precision), np.mean(average_recall), np.mean(average_f1), np.mean(average_accuracy)
  65. def evaluate_on_sequence(seq, summary):
  66. gt, frames = load_sequence(seq)
  67. category, video_name = os.path.basename(os.path.dirname(seq)), os.path.basename(seq)
  68. print('=== %s:%s ===' % (category, video_name))
  69. for algo, algo_name, algo_arguments in ALGORITHMS_TO_EVALUATE:
  70. print('Algorithm name: %s' % algo_name)
  71. sec_per_step, precision, recall, f1, accuracy = evaluate_algorithm(gt, frames, algo, algo_arguments)
  72. print('Average accuracy: %.3f' % accuracy)
  73. print('Average precision: %.3f' % precision)
  74. print('Average recall: %.3f' % recall)
  75. print('Average F1: %.3f' % f1)
  76. print('Average sec. per step: %.4f' % sec_per_step)
  77. print('')
  78. if category not in summary:
  79. summary[category] = {}
  80. if algo_name not in summary[category]:
  81. summary[category][algo_name] = []
  82. summary[category][algo_name].append((precision, recall, f1, accuracy))
  83. def main():
  84. parser = argparse.ArgumentParser(description='Evaluate all background subtractors using Change Detection 2014 dataset')
  85. parser.add_argument('--dataset_path', help='Path to the directory with dataset. It may contain multiple inner directories. It will be scanned recursively.', required=True)
  86. parser.add_argument('--algorithm', help='Test particular algorithm instead of all.')
  87. args = parser.parse_args()
  88. dataset_dirs = find_relevant_dirs(args.dataset_path)
  89. assert len(dataset_dirs) > 0, ("Passed directory must contain at least one sequence from the Change Detection dataset. There is no relevant directories in %s. Check that this directory is correct." % (args.dataset_path))
  90. if args.algorithm is not None:
  91. global ALGORITHMS_TO_EVALUATE
  92. ALGORITHMS_TO_EVALUATE = filter(lambda a: a[1].lower() == args.algorithm.lower(), ALGORITHMS_TO_EVALUATE)
  93. summary = {}
  94. for seq in dataset_dirs:
  95. evaluate_on_sequence(seq, summary)
  96. for category in summary:
  97. for algo_name in summary[category]:
  98. summary[category][algo_name] = np.mean(summary[category][algo_name], axis=0)
  99. for category in summary:
  100. print('=== SUMMARY for %s (Precision, Recall, F1, Accuracy) ===' % category)
  101. for algo_name in summary[category]:
  102. print('%05s: %.3f %.3f %.3f %.3f' % ((algo_name,) + tuple(summary[category][algo_name])))
  103. if __name__ == '__main__':
  104. main()