mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
8.5 KiB
281 lines
8.5 KiB
#!/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::SCascade::DOLLAR == 2 |
|
c = cv2.SCascade(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 |