Merge pull request #2887 from ilya-lavrenov:ipp_morph_fix
[platform/upstream/opencv.git] / modules / softcascade / misc / sft.py
1 #!/usr/bin/env python
2
3 import cv2, re, glob
4 import numpy             as np
5 import matplotlib.pyplot as plt
6 from itertools import izip
7
8 """ Convert numPy matrices with rectangles and confidences to sorted list of detections."""
9 def convert2detections(rects, confs, crop_factor = 0.125):
10     if rects is None:
11         return []
12
13     dts = zip(*[rects.tolist(), confs.tolist()])
14     dts = zip(dts[0][0], dts[0][1])
15     dts = [Detection(r,c) for r, c in dts]
16
17     dts.sort(lambda x, y : -1  if (x.conf - y.conf) > 0 else 1)
18
19     for dt in dts:
20         dt.crop(crop_factor)
21
22     return dts
23
24 """ Create new instance of soft cascade."""
25 def cascade(min_scale, max_scale, nscales, f):
26     # where we use nms cv::SoftCascadeDetector::DOLLAR == 2
27     c = cv2.softcascade_Detector(min_scale, max_scale, nscales, 2)
28     xml = cv2.FileStorage(f, 0)
29     dom = xml.getFirstTopLevelNode()
30     assert c.load(dom)
31     return c
32
33 """ Compute prefix sum for en array."""
34 def cumsum(n):
35     cum = []
36     y = 0
37     for i in n:
38         y += i
39         cum.append(y)
40     return cum
41
42 """ Compute x and y arrays for ROC plot."""
43 def computeROC(confidenses, tp, nannotated, nframes, ignored):
44     confidenses, tp, ignored = zip(*sorted(zip(confidenses, tp, ignored), reverse = True))
45
46     fp = [(1 - x) for x in tp]
47     fp = [(x - y) for x, y in izip(fp, ignored)]
48
49     fp = cumsum(fp)
50     tp = cumsum(tp)
51     miss_rate = [(1 - x / (nannotated + 0.000001)) for x in tp]
52     fppi = [x / float(nframes) for x in fp]
53
54     return fppi, miss_rate
55
56 """ Crop rectangle by factor."""
57 def crop_rect(rect, factor):
58     val_x = factor * float(rect[2])
59     val_y = factor * float(rect[3])
60     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)]
61     return x
62
63 """ Initialize plot axises."""
64 def initPlot(name):
65     plt.xlabel("fppi")
66     plt.ylabel("miss rate")
67     plt.title(name)
68     plt.grid(True)
69     plt.xscale('log')
70     plt.yscale('log')
71
72 """ Draw plot."""
73 def plotLogLog(fppi, miss_rate, c):
74     plt.loglog(fppi, miss_rate, color = c, linewidth = 2)
75
76 """ Show resulted plot."""
77 def showPlot(file_name, labels):
78     plt.axis((pow(10, -3), pow(10, 1), .035, 1))
79     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'] )
80     plt.legend(labels, loc = "lower left")
81     plt.savefig(file_name)
82     plt.show()
83
84 """ Filter true positives and ignored detections for cascade detector output."""
85 def match(gts, dts):
86     matches_gt     = [0]*len(gts)
87     matches_dt     = [0]*len(dts)
88     matches_ignore = [0]*len(dts)
89
90     if len(gts) == 0:
91         return matches_dt, matches_ignore
92
93     # Cartesian product for each detection BB_dt with each BB_gt
94     overlaps = [[dt.overlap(gt) for gt in gts]for dt in dts]
95
96     for idx, row in enumerate(overlaps):
97         imax = row.index(max(row))
98
99         # try to match ground truth
100         if (matches_gt[imax] == 0 and row[imax] > 0.5):
101             matches_gt[imax] = 1
102             matches_dt[idx]  = 1
103
104     for idx, dt in enumerate(dts):
105         # try to math ignored
106         if matches_dt[idx] == 0:
107             row = gts
108             row = [i for i in row if (i[3] - i[1]) < 53 or (i[3] - i[1]) >  256]
109             for each in row:
110                 if dts[idx].overlapIgnored(each) > 0.5:
111                     matches_ignore[idx] = 1
112     return matches_dt, matches_ignore
113
114
115 """ Draw detections or ground truth on image."""
116 def draw_rects(img, rects, color, l = lambda x, y : x + y):
117     if rects is not None:
118         for x1, y1, x2, y2 in rects:
119             cv2.rectangle(img, (x1, y1), (l(x1, x2), l(y1, y2)), color, 2)
120
121
122 def draw_dt(img, dts, color, l = lambda x, y : x + y):
123     if dts is not None:
124         for dt in dts:
125             bb = dt.bb
126             x1, y1, x2, y2 = dt.bb[0], dt.bb[1], dt.bb[2], dt.bb[3]
127
128             cv2.rectangle(img, (x1, y1), (l(x1, x2), l(y1, y2)), color, 2)
129
130 class Detection:
131     def __init__(self, bb, conf):
132         self.bb = bb
133         self.conf = conf
134         self.matched = False
135
136     def crop(self, factor):
137         self.bb = crop_rect(self.bb, factor)
138
139     # we use rect-style for dt and box style for gt. ToDo: fix it
140     def overlap(self, b):
141
142         a = self.bb
143         w = min( a[0] + a[2], b[2]) - max(a[0], b[0]);
144         h = min( a[1] + a[3], b[3]) - max(a[1], b[1]);
145
146         cross_area = 0.0 if (w < 0 or h < 0) else float(w * h)
147         union_area = (a[2] * a[3]) + ((b[2] - b[0]) * (b[3] - b[1])) - cross_area;
148
149         return cross_area / union_area
150
151         # we use rect-style for dt and box style for gt. ToDo: fix it
152     def overlapIgnored(self, b):
153
154         a = self.bb
155         w = min( a[0] + a[2], b[2]) - max(a[0], b[0]);
156         h = min( a[1] + a[3], b[3]) - max(a[1], b[1]);
157
158         cross_area = 0.0 if (w < 0 or h < 0) else float(w * h)
159         self_area = (a[2] * a[3]);
160
161         return cross_area / self_area
162
163     def mark_matched(self):
164         self.matched = True
165
166 """Parse INPIA annotation format"""
167 def parse_inria(ipath, f):
168     bbs = []
169     path = None
170     for l in f:
171         box = None
172         if l.startswith("Bounding box"):
173             b = [x.strip() for x in l.split(":")[1].split("-")]
174             c = [x[1:-1].split(",") for x in b]
175             d = [int(x) for x in sum(c, [])]
176             bbs.append(d)
177
178         if l.startswith("Image filename"):
179             path = l.split('"')[-2]
180
181     return Sample(path, bbs)
182
183
184 def glob_set(pattern):
185     return [__n for __n in glob.iglob(pattern)]
186
187 """ Parse ETH idl file. """
188 def parse_idl(f):
189     map = {}
190     for l in open(f):
191         l = re.sub(r"^\"left\/", "{\"", l)
192         l = re.sub(r"\:", ":[", l)
193         l = re.sub(r"(\;|\.)$", "]}", l)
194         map.update(eval(l))
195     return map
196
197 """ Normalize detection box to unified aspect ration."""
198 def norm_box(box, ratio):
199     middle = float(box[0] + box[2]) / 2.0
200     new_half_width = float(box[3] - box[1]) * ratio / 2.0
201     return (int(round(middle - new_half_width)), box[1], int(round(middle + new_half_width)), box[3])
202
203 """ Process array of boxes."""
204 def norm_acpect_ratio(boxes, ratio):
205     return [ norm_box(box, ratio)  for box in boxes]
206
207 """ Filter detections out of extended range. """
208 def filter_for_range(boxes, scale_range, ext_ratio):
209     boxes = norm_acpect_ratio(boxes, 0.5)
210     boxes = [b for b in boxes if (b[3] - b[1]) > scale_range[0] / ext_ratio]
211     boxes = [b for b in boxes if (b[3] - b[1]) < scale_range[1] * ext_ratio]
212     return boxes
213
214 """ Resize sample for training."""
215 def resize_sample(image, d_w, d_h):
216     h, w, _ = image.shape
217     if (d_h < h) or (d_w < w):
218         ratio = min(d_h / float(h), d_w / float(w))
219
220         kernel_size = int( 5 / (2 * ratio))
221         sigma = 0.5 / ratio
222         image_to_resize = cv2.filter2D(image, cv2.CV_8UC3, cv2.getGaussianKernel(kernel_size, sigma))
223         interpolation_type = cv2.INTER_AREA
224     else:
225         image_to_resize = image
226         interpolation_type = cv2.INTER_CUBIC
227
228     return cv2.resize(image_to_resize,(d_w, d_h), None, 0, 0, interpolation_type)
229
230 newobj = re.compile("^lbl=\'(\w+)\'\s+str=(\d+)\s+end=(\d+)\s+hide=0$")
231
232 class caltech:
233     @staticmethod
234     def extract_objects(f):
235         objects = []
236         tmp = []
237         for l in f:
238             if newobj.match(l) is not None:
239                 objects.append(tmp)
240                 tmp = []
241             tmp.append(l)
242         return objects[1:]
243
244     @staticmethod
245     def parse_header(f):
246         _    = f.readline() # skip first line (version string)
247         head = f.readline()
248         (nFrame, nSample) = re.search(r'nFrame=(\d+) n=(\d+)', head).groups()
249         return (int(nFrame), int(nSample))
250
251     @staticmethod
252     def parse_pos(l):
253         pos = re.match(r'^posv?\s*=(\[[\d\s\.\;]+\])$', l).group(1)
254         pos = re.sub(r"(\[)(\d)", "\\1[\\2", pos)
255         pos = re.sub(r"\s", ", ", re.sub(r"\;\s+(?=\])", "]", re.sub(r"\;\s+(?!\])", "],[", pos)))
256         return eval(pos)
257
258     @staticmethod
259     def parse_occl(l):
260         occl = re.match(r'^occl\s*=(\[[\d\s\.\;]+\])$', l).group(1)
261         occl = re.sub(r"\s(?!\])", ",", occl)
262         return eval(occl)
263
264 def parse_caltech(f):
265     (nFrame, nSample) = caltech.parse_header(f)
266     objects = caltech.extract_objects(f)
267
268     annotations = [[] for i in range(nFrame)]
269     for obj in objects:
270         (type, start, end) = re.search(r'^lbl=\'(\w+)\'\s+str=(\d+)\s+end=(\d+)\s+hide=0$', obj[0]).groups()
271         print type, start, end
272         start = int(start) -1
273         end   = int(end)
274         pos   = caltech.parse_pos(obj[1])
275         posv  = caltech.parse_pos(obj[2])
276         occl  = caltech.parse_occl(obj[3])
277
278         for idx, (p, pv, oc) in enumerate(zip(*[pos, posv, occl])):
279             annotations[start + idx].append((type, p, oc, pv))
280
281     return annotations