282 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| import cv2, re, glob
 | |
| import numpy             as np
 | |
| import matplotlib.pyplot as plt
 | |
| from itertools import izip
 | |
| 
 | |
| """ Convert numPy matrices with rectangles and confidences to sorted list of detections."""
 | |
| def convert2detections(rects, confs, crop_factor = 0.125):
 | |
|     if rects is None:
 | |
|         return []
 | |
| 
 | |
|     dts = zip(*[rects.tolist(), confs.tolist()])
 | |
|     dts = zip(dts[0][0], dts[0][1])
 | |
|     dts = [Detection(r,c) for r, c in dts]
 | |
| 
 | |
|     dts.sort(lambda x, y : -1  if (x.conf - y.conf) > 0 else 1)
 | |
| 
 | |
|     for dt in dts:
 | |
|         dt.crop(crop_factor)
 | |
| 
 | |
|     return dts
 | |
| 
 | |
| """ Create new instance of soft cascade."""
 | |
| def cascade(min_scale, max_scale, nscales, f):
 | |
|     # where we use nms cv::SoftCascadeDetector::DOLLAR == 2
 | |
|     c = cv2.softcascade_Detector(min_scale, max_scale, nscales, 2)
 | |
|     xml = cv2.FileStorage(f, 0)
 | |
|     dom = xml.getFirstTopLevelNode()
 | |
|     assert c.load(dom)
 | |
|     return c
 | |
| 
 | |
| """ Compute prefix sum for en array."""
 | |
| def cumsum(n):
 | |
|     cum = []
 | |
|     y = 0
 | |
|     for i in n:
 | |
|         y += i
 | |
|         cum.append(y)
 | |
|     return cum
 | |
| 
 | |
| """ Compute x and y arrays for ROC plot."""
 | |
| def computeROC(confidenses, tp, nannotated, nframes, ignored):
 | |
|     confidenses, tp, ignored = zip(*sorted(zip(confidenses, tp, ignored), reverse = True))
 | |
| 
 | |
|     fp = [(1 - x) for x in tp]
 | |
|     fp = [(x - y) for x, y in izip(fp, ignored)]
 | |
| 
 | |
|     fp = cumsum(fp)
 | |
|     tp = cumsum(tp)
 | |
|     miss_rate = [(1 - x / (nannotated + 0.000001)) for x in tp]
 | |
|     fppi = [x / float(nframes) for x in fp]
 | |
| 
 | |
|     return fppi, miss_rate
 | |
| 
 | |
| """ Crop rectangle by factor."""
 | |
| def crop_rect(rect, factor):
 | |
|     val_x = factor * float(rect[2])
 | |
|     val_y = factor * float(rect[3])
 | |
|     x = [int(rect[0] + val_x), int(rect[1] + val_y), int(rect[2] - 2.0 * val_x), int(rect[3] - 2.0 * val_y)]
 | |
|     return x
 | |
| 
 | |
| """ Initialize plot axises."""
 | |
| def initPlot(name):
 | |
|     plt.xlabel("fppi")
 | |
|     plt.ylabel("miss rate")
 | |
|     plt.title(name)
 | |
|     plt.grid(True)
 | |
|     plt.xscale('log')
 | |
|     plt.yscale('log')
 | |
| 
 | |
| """ Draw plot."""
 | |
| def plotLogLog(fppi, miss_rate, c):
 | |
|     plt.loglog(fppi, miss_rate, color = c, linewidth = 2)
 | |
| 
 | |
| """ Show resulted plot."""
 | |
| def showPlot(file_name, labels):
 | |
|     plt.axis((pow(10, -3), pow(10, 1), .035, 1))
 | |
|     plt.yticks( [0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.64, 0.8, 1], ['.05', '.10', '.20', '.30', '.40', '.50', '.64', '.80', '1'] )
 | |
|     plt.legend(labels, loc = "lower left")
 | |
|     plt.savefig(file_name)
 | |
|     plt.show()
 | |
| 
 | |
| """ Filter true positives and ignored detections for cascade detector output."""
 | |
| def match(gts, dts):
 | |
|     matches_gt     = [0]*len(gts)
 | |
|     matches_dt     = [0]*len(dts)
 | |
|     matches_ignore = [0]*len(dts)
 | |
| 
 | |
|     if len(gts) == 0:
 | |
|         return matches_dt, matches_ignore
 | |
| 
 | |
|     # Cartesian product for each detection BB_dt with each BB_gt
 | |
|     overlaps = [[dt.overlap(gt) for gt in gts]for dt in dts]
 | |
| 
 | |
|     for idx, row in enumerate(overlaps):
 | |
|         imax = row.index(max(row))
 | |
| 
 | |
|         # try to match ground truth
 | |
|         if (matches_gt[imax] == 0 and row[imax] > 0.5):
 | |
|             matches_gt[imax] = 1
 | |
|             matches_dt[idx]  = 1
 | |
| 
 | |
|     for idx, dt in enumerate(dts):
 | |
|         # try to math ignored
 | |
|         if matches_dt[idx] == 0:
 | |
|             row = gts
 | |
|             row = [i for i in row if (i[3] - i[1]) < 53 or (i[3] - i[1]) >  256]
 | |
|             for each in row:
 | |
|                 if dts[idx].overlapIgnored(each) > 0.5:
 | |
|                     matches_ignore[idx] = 1
 | |
|     return matches_dt, matches_ignore
 | |
| 
 | |
| 
 | |
| """ Draw detections or ground truth on image."""
 | |
| def draw_rects(img, rects, color, l = lambda x, y : x + y):
 | |
|     if rects is not None:
 | |
|         for x1, y1, x2, y2 in rects:
 | |
|             cv2.rectangle(img, (x1, y1), (l(x1, x2), l(y1, y2)), color, 2)
 | |
| 
 | |
| 
 | |
| def draw_dt(img, dts, color, l = lambda x, y : x + y):
 | |
|     if dts is not None:
 | |
|         for dt in dts:
 | |
|             bb = dt.bb
 | |
|             x1, y1, x2, y2 = dt.bb[0], dt.bb[1], dt.bb[2], dt.bb[3]
 | |
| 
 | |
|             cv2.rectangle(img, (x1, y1), (l(x1, x2), l(y1, y2)), color, 2)
 | |
| 
 | |
| class Detection:
 | |
|     def __init__(self, bb, conf):
 | |
|         self.bb = bb
 | |
|         self.conf = conf
 | |
|         self.matched = False
 | |
| 
 | |
|     def crop(self, factor):
 | |
|         self.bb = crop_rect(self.bb, factor)
 | |
| 
 | |
|     # we use rect-style for dt and box style for gt. ToDo: fix it
 | |
|     def overlap(self, b):
 | |
| 
 | |
|         a = self.bb
 | |
|         w = min( a[0] + a[2], b[2]) - max(a[0], b[0]);
 | |
|         h = min( a[1] + a[3], b[3]) - max(a[1], b[1]);
 | |
| 
 | |
|         cross_area = 0.0 if (w < 0 or h < 0) else float(w * h)
 | |
|         union_area = (a[2] * a[3]) + ((b[2] - b[0]) * (b[3] - b[1])) - cross_area;
 | |
| 
 | |
|         return cross_area / union_area
 | |
| 
 | |
|         # we use rect-style for dt and box style for gt. ToDo: fix it
 | |
|     def overlapIgnored(self, b):
 | |
| 
 | |
|         a = self.bb
 | |
|         w = min( a[0] + a[2], b[2]) - max(a[0], b[0]);
 | |
|         h = min( a[1] + a[3], b[3]) - max(a[1], b[1]);
 | |
| 
 | |
|         cross_area = 0.0 if (w < 0 or h < 0) else float(w * h)
 | |
|         self_area = (a[2] * a[3]);
 | |
| 
 | |
|         return cross_area / self_area
 | |
| 
 | |
|     def mark_matched(self):
 | |
|         self.matched = True
 | |
| 
 | |
| """Parse INPIA annotation format"""
 | |
| def parse_inria(ipath, f):
 | |
|     bbs = []
 | |
|     path = None
 | |
|     for l in f:
 | |
|         box = None
 | |
|         if l.startswith("Bounding box"):
 | |
|             b = [x.strip() for x in l.split(":")[1].split("-")]
 | |
|             c = [x[1:-1].split(",") for x in b]
 | |
|             d = [int(x) for x in sum(c, [])]
 | |
|             bbs.append(d)
 | |
| 
 | |
|         if l.startswith("Image filename"):
 | |
|             path = l.split('"')[-2]
 | |
| 
 | |
|     return Sample(path, bbs)
 | |
| 
 | |
| 
 | |
| def glob_set(pattern):
 | |
|     return [__n for __n in glob.iglob(pattern)]
 | |
| 
 | |
| """ Parse ETH idl file. """
 | |
| def parse_idl(f):
 | |
|     map = {}
 | |
|     for l in open(f):
 | |
|         l = re.sub(r"^\"left\/", "{\"", l)
 | |
|         l = re.sub(r"\:", ":[", l)
 | |
|         l = re.sub(r"(\;|\.)$", "]}", l)
 | |
|         map.update(eval(l))
 | |
|     return map
 | |
| 
 | |
| """ Normalize detection box to unified aspect ration."""
 | |
| def norm_box(box, ratio):
 | |
|     middle = float(box[0] + box[2]) / 2.0
 | |
|     new_half_width = float(box[3] - box[1]) * ratio / 2.0
 | |
|     return (int(round(middle - new_half_width)), box[1], int(round(middle + new_half_width)), box[3])
 | |
| 
 | |
| """ Process array of boxes."""
 | |
| def norm_acpect_ratio(boxes, ratio):
 | |
|     return [ norm_box(box, ratio)  for box in boxes]
 | |
| 
 | |
| """ Filter detections out of extended range. """
 | |
| def filter_for_range(boxes, scale_range, ext_ratio):
 | |
|     boxes = norm_acpect_ratio(boxes, 0.5)
 | |
|     boxes = [b for b in boxes if (b[3] - b[1]) > scale_range[0] / ext_ratio]
 | |
|     boxes = [b for b in boxes if (b[3] - b[1]) < scale_range[1] * ext_ratio]
 | |
|     return boxes
 | |
| 
 | |
| """ Resize sample for training."""
 | |
| def resize_sample(image, d_w, d_h):
 | |
|     h, w, _ = image.shape
 | |
|     if (d_h < h) or (d_w < w):
 | |
|         ratio = min(d_h / float(h), d_w / float(w))
 | |
| 
 | |
|         kernel_size = int( 5 / (2 * ratio))
 | |
|         sigma = 0.5 / ratio
 | |
|         image_to_resize = cv2.filter2D(image, cv2.CV_8UC3, cv2.getGaussianKernel(kernel_size, sigma))
 | |
|         interpolation_type = cv2.INTER_AREA
 | |
|     else:
 | |
|         image_to_resize = image
 | |
|         interpolation_type = cv2.INTER_CUBIC
 | |
| 
 | |
|     return cv2.resize(image_to_resize,(d_w, d_h), None, 0, 0, interpolation_type)
 | |
| 
 | |
| newobj = re.compile("^lbl=\'(\w+)\'\s+str=(\d+)\s+end=(\d+)\s+hide=0$")
 | |
| 
 | |
| class caltech:
 | |
|     @staticmethod
 | |
|     def extract_objects(f):
 | |
|         objects = []
 | |
|         tmp = []
 | |
|         for l in f:
 | |
|             if newobj.match(l) is not None:
 | |
|                 objects.append(tmp)
 | |
|                 tmp = []
 | |
|             tmp.append(l)
 | |
|         return objects[1:]
 | |
| 
 | |
|     @staticmethod
 | |
|     def parse_header(f):
 | |
|         _    = f.readline() # skip first line (version string)
 | |
|         head = f.readline()
 | |
|         (nFrame, nSample) = re.search(r'nFrame=(\d+) n=(\d+)', head).groups()
 | |
|         return (int(nFrame), int(nSample))
 | |
| 
 | |
|     @staticmethod
 | |
|     def parse_pos(l):
 | |
|         pos = re.match(r'^posv?\s*=(\[[\d\s\.\;]+\])$', l).group(1)
 | |
|         pos = re.sub(r"(\[)(\d)", "\\1[\\2", pos)
 | |
|         pos = re.sub(r"\s", ", ", re.sub(r"\;\s+(?=\])", "]", re.sub(r"\;\s+(?!\])", "],[", pos)))
 | |
|         return eval(pos)
 | |
| 
 | |
|     @staticmethod
 | |
|     def parse_occl(l):
 | |
|         occl = re.match(r'^occl\s*=(\[[\d\s\.\;]+\])$', l).group(1)
 | |
|         occl = re.sub(r"\s(?!\])", ",", occl)
 | |
|         return eval(occl)
 | |
| 
 | |
| def parse_caltech(f):
 | |
|     (nFrame, nSample) = caltech.parse_header(f)
 | |
|     objects = caltech.extract_objects(f)
 | |
| 
 | |
|     annotations = [[] for i in range(nFrame)]
 | |
|     for obj in objects:
 | |
|         (type, start, end) = re.search(r'^lbl=\'(\w+)\'\s+str=(\d+)\s+end=(\d+)\s+hide=0$', obj[0]).groups()
 | |
|         print type, start, end
 | |
|         start = int(start) -1
 | |
|         end   = int(end)
 | |
|         pos   = caltech.parse_pos(obj[1])
 | |
|         posv  = caltech.parse_pos(obj[2])
 | |
|         occl  = caltech.parse_occl(obj[3])
 | |
| 
 | |
|         for idx, (p, pv, oc) in enumerate(zip(*[pos, posv, occl])):
 | |
|             annotations[start + idx].append((type, p, oc, pv))
 | |
| 
 | |
|     return annotations
 | 
