You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-Python3- (otherwise Python2 -> import division from future and correct print statements)
4
+
5
+
Non maxima supression for match template
6
+
7
+
Let say we have a correlation map and we want to detect the N best location in this map, while we want to make sure the detected location do not overlap too much
8
+
The threshold for the overlap used is a maximal value of Intersection over Union (IoU). Large overlap will have large IoU.
9
+
The IoU is conveneient as it is normalised between 0 and 1 and is a value normalised by the intial area.
10
+
11
+
To keep the N best hit/bounding-box without overlap, we first take the N best hit as returned by the maxima detector
12
+
Then we loop over the best hit (by decreasing order of score, best score first) and compute the IoU with each remaning bounding box
13
+
If the IoU is too high, the second bounding box is deleted and replaced by the N+1 th (or N + offset) bounding box in the intiial list of hit
14
+
15
+
@author: Laurent Thomas
16
+
"""
17
+
18
+
defPoint_in_Rectangle(Point, Rectangle):
19
+
'''Return True if a point (x,y) is contained in a Rectangle(x, y, width, height)'''
20
+
# unpack variables
21
+
Px, Py=Point
22
+
Rx, Ry, w, h=Rectangle
23
+
24
+
return (Rx<=Px) and (Px<=Rx+w-1) and (Ry<=Py) and (Py<=Ry+h-1) # simply test if x_Point is in the range of x for the rectangle
25
+
26
+
27
+
defcomputeIoU(BBox1,BBox2):
28
+
'''
29
+
Compute the IoU (Intersection over Union) between 2 rectangular bounding boxes defined by the top left (Xtop,Ytop) and bottom right (Xbot, Ybot) pixel coordinates
30
+
Code adapted from https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/
31
+
'''
32
+
print('BBox1 : ', BBox1)
33
+
print('BBox2 : ', BBox2)
34
+
35
+
# Unpack input (python3 - tuple are no more supported as input in function definition - PEP3113 - Tuple can be used in as argument in a call but the function will not unpack it automatically)
36
+
Xleft1, Ytop1, Width1, Height1=BBox1
37
+
Xleft2, Ytop2, Width2, Height2=BBox2
38
+
39
+
# Compute bottom coordinates
40
+
Xright1=Xleft1+Width1-1# we remove -1 from the width since we start with 1 pixel already (the top one)
41
+
Ybot1=Ytop1+Height1-1# idem for the height
42
+
43
+
Xright2=Xleft2+Width2-1
44
+
Ybot2=Ytop2+Height2-1
45
+
46
+
# determine the (x, y)-coordinates of the top left and bottom right points of the intersection rectangle
Perform Non-Maxima supression : it compares the hits after maxima/minima detection, and removes the ones that are too close (too large overlap)
85
+
This function works both with an optionnal threshold on the score, and number of detected bbox
86
+
87
+
if a scoreThreshold is specified, we first discard any hit below/above the threshold (depending on sortDescending)
88
+
if sortDescending = True, the hit with score below the treshold are discarded (ie when high score means better prediction ex : Correlation)
89
+
if sortDescending = False, the hit with score above the threshold are discared (ie when low score means better prediction ex : Distance measure)
90
+
91
+
Then the hit are ordered so that we have the best hits first.
92
+
Then we iterate over the list of hits, taking one hit at a time and checking for overlap with the previous validated hit (the Final Hit list is directly iniitialised with the first best hit as there is no better hit with which to compare overlap)
93
+
94
+
This iteration is terminate once we have collected N best hit, or if there are no more hit left to test for overlap
95
+
96
+
INPUT
97
+
- ListHit : a list of dictionnary, with each dictionnary being a hit following the formating {'TemplateIdx'= (int),'BBox'=(x,y,width,height),'Score'=(float)}
98
+
the TemplateIdx is the row index in the panda/Knime table
99
+
100
+
- scoreThreshold : Float (or None), used to remove hit with too low prediction score.
101
+
If sortDescending=True (ie we use a correlation measure so we want to keep large scores) the scores above that threshold are kept
102
+
While if we use sortDescending=False (we use a difference measure ie we want to keep low score), the scores below that threshold are kept
103
+
104
+
- N : number of best hit to return (by increasing score). Min=1, eventhough it does not really make sense to do NMS with only 1 hit
105
+
- maxOverlap : float between 0 and 1, the maximal overlap authorised between 2 bounding boxes, above this value, the bounding box of lower score is deleted
106
+
- sortDescending : use True when high score means better prediction, False otherwise (ex : if score is a difference measure, then the best prediction are low difference and we sort by ascending order)
107
+
108
+
OUTPUT
109
+
List_nHit : List of the best detection after NMS, it contains max N detection (but potentially less)
110
+
'''
111
+
112
+
# Apply threshold on prediction score
113
+
ifscoreThreshold==None :
114
+
List_ThreshHit=List_Hit[:] # copy to avoid modifying the input list in place
115
+
116
+
elifsortDescending : # We keep hit above the threshold
# Sort score to have best predictions first (important as we loop testing the best boxes against the other boxes)
124
+
ifsortDescending:
125
+
List_ThreshHit.sort(key=lambdadico: dico['Score'], reverse=True) # Hit = [list of (x,y),score] - sort according to descending (best = high correlation)
126
+
else:
127
+
List_ThreshHit.sort(key=lambdadico: dico['Score']) # sort according to ascending score (best = small difference)
128
+
129
+
130
+
# Split the inital pool into Final Hit that are kept and restHit that can be tested
131
+
# Initialisation : 1st keep is kept for sure, restHit is the rest of the list
132
+
print("\nInitialise final hit list with first best hit")
133
+
FinalHit= [List_ThreshHit[0]]
134
+
restHit=List_ThreshHit[1:]
135
+
136
+
137
+
# Loop to compute overlap
138
+
whilelen(FinalHit)<NandrestHit : # second condition is restHit is not empty
139
+
140
+
# Report state of the loop
141
+
print("\n\n\nNext while iteration")
142
+
143
+
print("-> Final hit list")
144
+
forhitinFinalHit: print(hit)
145
+
146
+
print("\n-> Remaining hit list")
147
+
forhitinrestHit: print(hit)
148
+
149
+
# pick the next best peak in the rest of peak
150
+
test_hit=restHit[0]
151
+
test_bbox=test_hit['BBox']
152
+
print("\nTest BBox:{} for overlap against higher score bboxes".format(test_bbox))
153
+
154
+
# Loop over hit in FinalHit to compute successively overlap with test_peak
155
+
forhitinFinalHit:
156
+
157
+
# Recover Bbox from hit
158
+
bbox2=hit['BBox']
159
+
160
+
# Compute the Intersection over Union between test_peak and current peak
161
+
IoU=computeIoU(test_bbox, bbox2)
162
+
163
+
# Initialise the boolean value to true before test of overlap
164
+
ToAppend=True
165
+
166
+
ifIoU>maxOverlap:
167
+
ToAppend=False
168
+
print("IoU above threshold\n")
169
+
break# no need to test overlap with the other peaks
170
+
171
+
else:
172
+
print("IoU below threshold\n")
173
+
# no overlap for this particular (test_peak,peak) pair, keep looping to test the other (test_peak,peak)
174
+
continue
175
+
176
+
177
+
# After testing against all peaks (for loop is over), append or not the peak to final
178
+
ifToAppend:
179
+
# Move the test_hit from restHit to FinalHit
180
+
print("Append {} to list of final hits, remove it from Remaining hit list".format(test_hit))
181
+
FinalHit.append(test_hit)
182
+
restHit.remove(test_hit)
183
+
184
+
else:
185
+
# only remove the test_peak from restHit
186
+
print("Remove {} from Remaining hit list".format(test_hit))
187
+
restHit.remove(test_hit)
188
+
189
+
190
+
# Once function execution is done, return list of hit without overlap
191
+
print("\nCollected N expected hit, or no hit left to test")
0 commit comments