Implementation:Tencent Ncnn NMS Sorted Bboxes
| Knowledge Sources | |
|---|---|
| Domains | Computer_Vision, Object_Detection |
| Last Updated | 2026-02-09 00:00 GMT |
Overview
Pattern implementation for non-maximum suppression of object detections using quicksort and IoU filtering, as demonstrated in ncnn detection examples.
Description
This is a Pattern Doc documenting the NMS pattern from ncnn detection examples. The implementation consists of two functions: qsort_descent_inplace (custom quicksort by confidence) and nms_sorted_bboxes (greedy IoU-based suppression on pre-sorted detections).
The pattern supports both class-agnostic NMS (all classes suppressed together) and class-aware NMS (separate suppression per class, using coordinate offsets to prevent cross-class suppression).
Usage
Implement this pattern after bounding box decoding. Apply to the merged proposals from all detection strides.
Code Reference
Source Location
- Repository: ncnn
- File: examples/yolov8.cpp
- Lines: L73-116 (qsort_descent_inplace), L118-157 (nms_sorted_bboxes)
Signature
// Pattern: NMS for object detection
// User-implemented functions from ncnn examples
// Sort detections by confidence (descending) in-place
static void qsort_descent_inplace(std::vector<Object>& objects);
// Apply NMS to pre-sorted detections
// Returns indices of surviving detections in 'picked'
static void nms_sorted_bboxes(
const std::vector<Object>& objects, // sorted by confidence
std::vector<int>& picked, // output: surviving indices
float nms_threshold, // IoU threshold (e.g., 0.45)
bool agnostic = false // true = class-agnostic NMS
);
Import
#include <vector>
#include <algorithm> // std::swap
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| objects | std::vector<Object> | Yes | Candidate detections from decode step |
| nms_threshold | float | Yes | IoU threshold for suppression (e.g., 0.45) |
| agnostic | bool | No | If true, suppress across all classes (default: false) |
Outputs
| Name | Type | Description |
|---|---|---|
| picked | std::vector<int> | Indices of surviving detections after NMS |
Usage Examples
Complete NMS Pipeline
// After decoding proposals from all strides
std::vector<Object> proposals;
// ... fill proposals from generate_proposals()
// Sort by confidence
qsort_descent_inplace(proposals);
// Apply NMS with IoU threshold 0.45
std::vector<int> picked;
nms_sorted_bboxes(proposals, picked, 0.45f);
// Extract final detections
int count = picked.size();
std::vector<Object> objects(count);
for (int i = 0; i < count; i++)
{
objects[i] = proposals[picked[i]];
// Adjust coordinates for letterbox padding
float x0 = (objects[i].rect.x - (wpad / 2)) / scale;
float y0 = (objects[i].rect.y - (hpad / 2)) / scale;
float x1 = (objects[i].rect.x + objects[i].rect.width - (wpad / 2)) / scale;
float y1 = (objects[i].rect.y + objects[i].rect.height - (hpad / 2)) / scale;
// Clip to image bounds
objects[i].rect.x = std::max(x0, 0.f);
objects[i].rect.y = std::max(y0, 0.f);
objects[i].rect.width = std::min(x1, (float)img_w) - objects[i].rect.x;
objects[i].rect.height = std::min(y1, (float)img_h) - objects[i].rect.y;
}