Non-Maximum Suppression
A detector can report several rectangles around the same seed. We will rank candidates, keep a strong one, and remove sufficiently overlapping alternatives while preserving candidates for other objects.
13 sections · 4 questions · 2 coding challenges
The arrow keys move between sections too.
Making Duplicate Detections Count as Errors
BOTIE
Greedy non-maximum suppression is one commonly used procedure for that job. This example connects the method to a documented evaluation problem without claiming that the VOC challenge invented it. The procedure makes a local decision about overlaps; it does not establish the true number of objects.
Sources for this history
Did Botie Find Three Seeds, or the Same Seed Three Times?
Our detector has several plausible rectangles around one seed. Each is a slightly different guess about its boundary. If Botie counts every rectangle, he reports more seeds than the tray contains.
We need a rule for deciding which candidates are competing explanations of the same object. A common starting point is to keep the highest-scoring candidate and remove lower-scoring candidates that overlap it too much. Let's follow that procedure carefully, because the order matters.
The boxes and scores are supplied examples from one object class.
Step 1 of 3
Several candidates describe the same place
Two proposed rectangles overlap heavily, while a third is far away. Scores rank the candidates; their positions determine overlap.
Vertical: Vertical position
Horizontal: Horizontal position
Step 2 of 3
Keep the strongest remaining candidate
Candidate A has the largest score. Compare other candidates against A, removing those whose IoU exceeds the selected threshold.
| Candidate | Score | Relationship to A |
|---|---|---|
| A (index 0) | 0.9 | Selected first |
| B (index 1) | 0.8 | Large overlap |
| C (index 2) | 0.7 | No overlap |
A (index 0)
- Score
- 0.9
- Relationship to A
- Selected first
B (index 1)
- Score
- 0.8
- Relationship to A
- Large overlap
C (index 2)
- Score
- 0.7
- Relationship to A
- No overlap
Step 3 of 3
Repeat with candidates still available
B was removed, so it cannot remove another candidate later. C remains and is kept next.
| Candidate | Decision |
|---|---|
| A (index 0) | Keep |
| B (index 1) | Remove: overlaps selected A |
| C (index 2) | Keep |
A (index 0)
- Decision
- Keep
B (index 1)
- Decision
- Remove: overlaps selected A
C (index 2)
- Decision
- Keep
Ranking determines which overlapping candidate gets the first decision
Ranking determines which candidate gets the first decision. Each kept candidate removes only still-available boxes with overlap strictly above the threshold.
Which detections describe the same seed?
A high score does not tell us whether two boxes duplicate one object. Change scores or the overlap threshold and watch which candidate gets to remove the overlapping alternatives.
BOTIE
Coordinates of the retained boxes
| 0 | 0 | 4 | 4 |
| 7 | 0 | 9 | 2 |
Follow the operations
1. Candidate seed rectangles
0 0 4 4 0.5 0 4.5 4 7 0 9 2 2. Candidate scores
- 00.9
- 10.8
- 20.7
3. Rank indices by descending score
- 00
- 11
- 22
4. IoU suppression threshold
0.5
5. Suppress overlapping alternatives
- 00
- 12
6. Gather selected entries
0 0 4 4 7 0 9 2 7. Result
0 0 4 4 7 0 9 2
All candidates here belong to one class. Ties use the original index; real library tie behaviour can differ. Suppression can mistakenly remove nearby real objects.
1. Keep Scores and Boxes in the Same Order
Each candidate has a rectangle and a score. Sorting the scores produces indices into the box array. Preserve that mapping so the highest score still refers to its own rectangle.
We will process candidates from greatest score to least. To make the teaching examples reproducible, tied scores use the lower original index first. Production libraries may use a different tie rule, including differences across devices.
Scores = [0.7, 0.9, 0.8]
Descending-score indices = [1, 2, 0]The score threshold for discarding weak candidates and the overlap threshold for removing duplicates are different controls.
2. Keep One Candidate and Compare It With the Remaining Ones
Remove the highest-scoring available index from the waiting list and append it to the kept list. Compute its IoU with each remaining candidate. Suppress a remaining candidate when that IoU is strictly greater than the chosen threshold.
Here, equality stays. Both the inequality and coordinate convention must match the implementation you intend to use.
Overlap threshold = 0.5
IoU = 0.7 → suppress
IoU = 0.5 → keep available
IoU = 0.2 → keep availableSuppressing means removing a candidate from consideration. We do not merge its coordinates with the retained box.
3. Repeat Only With Candidates That Survived
After keeping the highest-scoring box, remove its sufficiently overlapping alternatives. Then choose the highest score among the boxes still waiting. Removed boxes take no further part in the decisions.
Suppose A overlaps B, and B overlaps C, but A does not overlap C enough to suppress it. Keeping A removes B while leaving C eligible. If the removed B were allowed to suppress C too, we would be running a different algorithm.
Waiting: A, B, C
Keep A; suppress B
Waiting: C
Keep CThis procedure can still discard a real nearby object. The overlap heuristic cannot perfectly resolve crowded scenes.
4. Decide Whether Candidates Compete Across Classes
The examples in this lesson concern one class: seeds. In a class-aware detector, suppression is normally applied separately within each class. Otherwise, a high-scoring box for one class might erase a valid overlapping box for another.
Some applications deliberately use class-agnostic suppression. State the intended policy and evaluate its errors rather than assuming one choice fits every detector.
A class ID is not a geometric coordinate. Keep it alongside the box and score when filtering or gathering detections.
Inspect one suppression comparison
Candidate B overlaps A substantially, while C is separate. This demonstrates the first iteration, before any loop is needed.
import numpy as np
boxes = np.array([[0.,0.,4.,4.], [0.5,0.,4.5,4.], [7.,0.,9.,2.]])
def iou(a, b):
overlap = np.maximum(0, np.minimum(a[2:], b[2:]) - np.maximum(a[:2], b[:2]))
intersection = np.prod(overlap)
union = np.prod(a[2:] - a[:2]) + np.prod(b[2:] - b[:2]) - intersection
return intersection / union
overlaps = np.array([iou(boxes[0], box) for box in boxes[1:]])
print(np.round(overlaps, 4).tolist())
print((overlaps <= 0.5).tolist())[0.7778, 0.0]
[False, True]The Boolean array identifies which remaining candidates survive this comparison.
5. Keep the Full Procedure Visible
The workshop divides the work into score ordering, greedy suppression, and gathering retained boxes. The suppression component performs the loop we just traced. In Python, implement that loop yourself to see how the waiting list changes.
This step cleans up supplied detections. It does not train a detector, repair inaccurate coordinates, or prove that each kept rectangle contains a real seed. Review the retained boxes against the photograph and labelled examples.
order = indices sorted by descending score
kept = []
while order is not empty:
current = remove first index
append current to kept
retain only indices whose IoU with current <= threshold
return boxes at kept indicesA higher overlap threshold suppresses fewer candidates under this procedure. It may preserve close objects while also retaining more duplicates.
6. A Removed Candidate Must Not Remove Another Candidate
Three proposals can form an overlap chain: A overlaps B, B overlaps C, but A and C can both describe separate objects. After keeping A and removing B, compare C with the boxes actually kept.
Deleting every box that overlaps any higher-scored box implements a different operation. The waiting list is state: each greedy choice changes which candidates are allowed to influence the remaining decisions.
Trace suppression using a supplied overlap table
Scores descend in index order. Off-diagonal values are the supplied pairwise IoUs for this trace.
import numpy as np
overlap = np.array([[1., .6, .2], [.6, 1., .6], [.2, .6, 1.]])
waiting = [0,1,2]
kept = []
while waiting:
current = waiting.pop(0)
kept.append(current)
waiting = [j for j in waiting if overlap[current,j] <= .5]
print(kept)[0, 2]Candidate one is suppressed and never gets a turn to suppress candidate two. The full box implementation computes these overlaps from coordinates.
7. Crowded Objects Can Be Correctly Overlapping
Two real seeds may overlap in the photograph. A strict suppression threshold can erase one legitimate detection; a loose threshold can retain several copies of the same seed. NMS cannot resolve that ambiguity from overlap alone.
Evaluate the retained detections on crowded scenes as well as isolated objects. Inspect missed objects and duplicate counts separately. A score-decay method or a detector trained for the setting may help, but changing postprocessing cannot create evidence that the original candidates lack.
Check yourself
Questions on Parts 1 to 7
Question 1 of 4
Which candidates survive greedy suppression?
Use the information below.
A, B, and C have descending scores. IoU(A,B)=0.8, IoU(B,C)=0.8, IoU(A,C)=0.2. Threshold=0.5.Try it with Botie
Build with Botie
Write and run Python