Section 21.7.1
Purpose
Find an optimal one-to-one assignment between two groups of bounding boxes based on proximity. The book describes uses such as associating detections between frames or matching detections from different sources.
This utility performs one matching operation. Your application supplies the two groups and decides what their association means.
Section 21.7.2
Why It Exists
Choosing the nearest candidate independently for each detection can produce conflicts or a poor overall assignment. A candidate that is convenient for one primary box may be the only reasonable choice for another.
The Hungarian solver considers the cost matrix as a whole while ensuring that each primary and each candidate is used at most once.
A small assignment example
Suppose primary centers lie at X = 0 and X = 4, and candidate centers lie at X = 1 and X = −3, all on the same horizontal line. Their distances are:
| Candidate A · X = 1 | Candidate B · X = −3 | |
|---|---|---|
| Primary A · X = 0 | 1 | 3 |
| Primary B · X = 4 | 3 | 7 |
Taking the first nearest pair gives a total cost of 1 + 7 = 8. Assigning Primary A to Candidate B and Primary B to Candidate A gives 3 + 3 = 6. The overall assignment is better even though the first primary does not receive its nearest candidate.
Section 21.7.3
Input
Supply two array<BoundingBox@> inputs, primary_boxes and candidate_boxes, plus these user settings:
array<int> target_primary_label_id_arr = {0,1};
array<int> target_candidate_label_id_arr = {2,7};
int max_pixel_distance_for_match = 400;Each array is filtered using its own target-label list. Non-target labels are excluded from both matching and the returned unmatched arrays.
Each box must supply label_id, upper-left x and y, width, and height. Use valid, non-null handles and a common image coordinate system. Centers are calculated with floating-point half-widths and half-heights.
This matcher uses Euclidean center-to-center pixel distance, unlike the edge-to-edge gap in the Mini-Box Combiner. A pair at exactly the maximum distance is allowed.
You may pass the same array for both inputs; the two target-label lists establish the roles. If those lists overlap, a box can be eligible for both roles, including a zero-distance match to itself. Use disjoint lists when that is not intended.
Review 21.1 · Inference Data & BoundingBox →Section 21.7.4
Output
The main function returns a HungarianMatchResult containing matched pairs and unmatched boxes:
class HungarianMatchPair {
BoundingBox@ primary_box;
BoundingBox@ candidate_box;
float cost;
};
class HungarianMatchResult {
array<HungarianMatchPair> matches;
array<BoundingBox@> unmatched_primary_boxes;
array<BoundingBox@> unmatched_candidate_boxes;
};Each pair contains handles to the actual input boxes and its center-to-center distance in pixels. Each filtered box appears in at most one pair within its role.
If one filtered group is empty, all boxes in the other filtered group are returned as unmatched. Non-target labels do not appear in any result array.
How the cost matrix works
The matrix is square, sized to the larger filtered group. Real pairs beyond the distance limit receive an invalid cost of 999999.0. Zero-cost dummy rows or columns support unequal group sizes.
The solver minimizes the total matrix cost. When converting assignments back to objects, the code rejects dummy and invalid pairings and collects the corresponding unmatched boxes. Keep valid pixel-distance costs comfortably below the invalid-cost constant.
The returned associations are based on proximity and your filters; they are not proof of identity or persistent tracking IDs.
Section 21.7.5
How To Use
Add the supplied classes and functions to your inference scripting environment. This is AngelScript-style runtime code, not a standalone Python utility.
Set the label lists and maximum distance, obtain the two box arrays from your application, then call:
HungarianMatchResult result = match_bounding_boxes_hungarian(
primary_boxes, candidate_boxes
);Using a single inference array
If the boxes are in the same array, supply it for both inputs. The configured label lists filter each role:
HungarianMatchResult result = match_bounding_boxes_hungarian(
bbox_arr, bbox_arr
);Read matches and unmatched boxes
for (uint i = 0; i < result.matches.length(); i++) {
HungarianMatchPair pair = result.matches[i];
BoundingBox@ primary = pair.primary_box;
BoundingBox@ candidate = pair.candidate_box;
float distance_pixels = pair.cost;
// Use the matched objects in your application.
}
uint unmatched_primary_count = result.unmatched_primary_boxes.length();
uint unmatched_candidate_count = result.unmatched_candidate_boxes.length();No reset method is needed. The caller supplies the relevant frame or source arrays, retains input objects for as long as their handles are needed, and manages any history or tracking state.
Before integrating
- Confirm the target label lists and use a non-negative distance threshold.
- Ensure all coordinates refer to a common image coordinate system.
- Supply valid, non-null objects and your runtime’s array support and math functions.
- Test unequal group sizes, no valid matches, empty filtered groups, and competing candidates.
- Handle unmatched results explicitly in your application.
Inference acquisition, image drawing, and frame-to-frame state management are outside this helper. No Linux / Windows terminal setup is required.
Section 21.7.6
Code
The complete helper from section 21.7, with PDF line wrapping and indentation restored. The input BoundingBox class is supplied by your runtime. All matching helpers and result classes are included in this download.
// User-adjustable variables.
array<int> target_primary_label_id_arr = {0,1};
array<int> target_candidate_label_id_arr = {2,7};
int max_pixel_distance_for_match = 400;
class HungarianMatchPair {
BoundingBox@ primary_box;
BoundingBox@ candidate_box;
float cost;
};
class HungarianMatchResult {
array<HungarianMatchPair> matches;
array<BoundingBox@> unmatched_primary_boxes;
array<BoundingBox@> unmatched_candidate_boxes;
};
HungarianMatchResult match_bounding_boxes_hungarian(
array<BoundingBox@> primary_boxes,
array<BoundingBox@> candidate_boxes) {
array<BoundingBox@> target_primary_boxes_arr;
array<BoundingBox@> target_candidate_boxes_arr;
target_primary_boxes_arr = isolate_target_boxes_in_arr(
primary_boxes, target_primary_label_id_arr);
target_candidate_boxes_arr = isolate_target_boxes_in_arr(
candidate_boxes, target_candidate_label_id_arr);
const float INVALID_COST = 999999.0f;
HungarianMatchResult result;
uint num_primary = target_primary_boxes_arr.length();
uint num_candidates = target_candidate_boxes_arr.length();
// Empty filtered inputs have no possible matches.
if (num_primary == 0 || num_candidates == 0) {
result.unmatched_primary_boxes = target_primary_boxes_arr;
result.unmatched_candidate_boxes = target_candidate_boxes_arr;
return result;
}
uint matrix_size = max(num_primary, num_candidates);
array<array<float>> cost_matrix;
cost_matrix = cost_matrix_calculate_all_pairwise_distances(
target_primary_boxes_arr, target_candidate_boxes_arr,
num_primary, num_candidates, matrix_size, INVALID_COST,
max_pixel_distance_for_match);
// assignment[primary row] = candidate column.
array<int> assignments = hungarian_solve(cost_matrix);
array<bool> candidate_was_matched(num_candidates, false);
convert_indices_to_boundingbox(
target_primary_boxes_arr, target_candidate_boxes_arr,
assignments, num_primary, num_candidates, result,
cost_matrix, candidate_was_matched, INVALID_COST);
result.unmatched_candidate_boxes = store_unmatched_candidates(
num_candidates, target_candidate_boxes_arr, candidate_was_matched);
return result;
}
array<int> hungarian_solve(const array<array<float>> &in cost_matrix) {
uint matrix_size = cost_matrix.length();
array<int> assignment(matrix_size, -1);
if (matrix_size == 0) {
return assignment;
}
for (uint i = 0; i < matrix_size; i++) {
if (cost_matrix[i].length() != matrix_size) {
return assignment;
}
}
// Row/column adjustments, current matches, and augmenting-path links.
array<float> row_potential(matrix_size + 1, 0.0f);
array<float> column_potential(matrix_size + 1, 0.0f);
array<int> matched_row(matrix_size + 1, 0);
array<int> previous_column(matrix_size + 1, 0);
for (uint row = 1; row <= matrix_size; row++) {
matched_row[0] = int(row);
int current_column = 0;
array<float> minimum_cost(matrix_size + 1, 1e30f);
array<bool> column_used(matrix_size + 1, false);
do {
column_used[current_column] = true;
int current_row = matched_row[current_column];
float delta = 1e30f;
int next_column = 0;
// Find the lowest-cost unused column available from this row.
for (uint column = 1; column <= matrix_size; column++) {
if (column_used[column]) {
continue;
}
float current_cost = cost_matrix[current_row - 1][column - 1]
- row_potential[current_row] - column_potential[column];
if (current_cost < minimum_cost[column]) {
minimum_cost[column] = current_cost;
previous_column[column] = current_column;
}
if (minimum_cost[column] < delta) {
delta = minimum_cost[column];
next_column = int(column);
}
}
// Update row and column potentials and remaining adjusted costs.
for (uint column = 0; column <= matrix_size; column++) {
if (column_used[column]) {
row_potential[matched_row[column]] += delta;
column_potential[column] -= delta;
} else {
minimum_cost[column] -= delta;
}
}
current_column = next_column;
} while (matched_row[current_column] != 0);
// Follow the recorded path backward to revise the assignment.
do {
int next_column = previous_column[current_column];
matched_row[current_column] = matched_row[next_column];
current_column = next_column;
} while (current_column != 0);
}
// Convert column-based matches to row-to-column assignments.
for (uint column = 1; column <= matrix_size; column++) {
int row = matched_row[column];
if (row > 0 && row <= int(matrix_size)) {
assignment[row - 1] = int(column) - 1;
}
}
return assignment;
}
array<array<float>> cost_matrix_calculate_all_pairwise_distances(
array<BoundingBox@> primary_boxes,
array<BoundingBox@> candidate_boxes,
uint num_primary, uint num_candidates, uint matrix_size,
const float INVALID_COST, int max_pixel_distance_for_match) {
array<array<float>> cost_matrix;
cost_matrix.resize(matrix_size);
for (uint i = 0; i < matrix_size; i++) {
cost_matrix[i].resize(matrix_size);
for (uint j = 0; j < matrix_size; j++) {
cost_matrix[i][j] = INVALID_COST;
}
}
for (uint i = 0; i < num_primary; i++) {
BoundingBox@ primary_box = primary_boxes[i];
float primary_center_x = float(primary_box.x) + float(primary_box.width) / 2.0f;
float primary_center_y = float(primary_box.y) + float(primary_box.height) / 2.0f;
for (uint j = 0; j < num_candidates; j++) {
BoundingBox@ candidate_box = candidate_boxes[j];
float candidate_center_x = float(candidate_box.x) + float(candidate_box.width) / 2.0f;
float candidate_center_y = float(candidate_box.y) + float(candidate_box.height) / 2.0f;
float dx = candidate_center_x - primary_center_x;
float dy = candidate_center_y - primary_center_y;
float distance = sqrt(dx * dx + dy * dy);
if (distance <= float(max_pixel_distance_for_match)) {
cost_matrix[i][j] = distance;
}
}
}
// Zero-cost dummy rows/columns support unequal group sizes.
for (uint i = 0; i < matrix_size; i++) {
for (uint j = 0; j < matrix_size; j++) {
if (i >= num_primary || j >= num_candidates) {
cost_matrix[i][j] = 0.0f;
}
}
}
return cost_matrix;
}
void convert_indices_to_boundingbox(
array<BoundingBox@> primary_boxes,
array<BoundingBox@> candidate_boxes,
array<int> assignments, uint num_primary, uint num_candidates,
HungarianMatchResult &inout result,
array<array<float>> cost_matrix,
array<bool> &inout candidate_was_matched, float INVALID_COST) {
for (uint i = 0; i < num_primary; i++) {
BoundingBox@ primary_box = primary_boxes[i];
int candidate_index = assignments[i];
if (candidate_index < 0 || candidate_index >= int(num_candidates)) {
result.unmatched_primary_boxes.insertLast(primary_box);
continue;
}
if (cost_matrix[i][candidate_index] >= INVALID_COST) {
result.unmatched_primary_boxes.insertLast(primary_box);
continue;
}
BoundingBox@ candidate_box = candidate_boxes[candidate_index];
HungarianMatchPair match;
@match.primary_box = primary_box;
@match.candidate_box = candidate_box;
match.cost = cost_matrix[i][candidate_index];
result.matches.insertLast(match);
candidate_was_matched[candidate_index] = true;
}
}
array<BoundingBox@> store_unmatched_candidates(
uint num_candidates, array<BoundingBox@> candidate_boxes,
array<bool> candidate_was_matched) {
array<BoundingBox@> unmatched_candidates_arr;
for (uint j = 0; j < num_candidates; j++) {
BoundingBox@ candidate_box = candidate_boxes[j];
if (candidate_was_matched[j] == false) {
unmatched_candidates_arr.insertLast(candidate_box);
}
}
return unmatched_candidates_arr;
}
array<BoundingBox@> isolate_target_boxes_in_arr(
array<BoundingBox@> bbox_arr, array<int> target_label_id_arr) {
array<BoundingBox@> isolated_arr;
for (uint i = 0; i < bbox_arr.length(); i++) {
BoundingBox@ a_bbox = bbox_arr[i];
int label_id = a_bbox.label_id;
if (target_label_id_arr.find(label_id) >= 0) {
isolated_arr.insertLast(a_bbox);
}
}
return isolated_arr;
}
Section 21.7.7
Prompt to Reproduce This Utility with AI
Copy the complete request into your AI chat. Review and test the result before integrating it.
Replace [your favorite programming language] with your chosen language. The supplied download retains the book’s inference-scripting syntax.
Programming language: [your favorite programming language]
Create a generic Hungarian Matrix matching utility that performs optimal one-to-one matching between two arrays of BoundingBox objects named primary_boxes and candidate_boxes. Avoid application-specific acquisition or inference logic.
User-adjustable settings:
- target_primary_label_id_arr = {0,1}
- target_candidate_label_id_arr = {2,7}
- max_pixel_distance_for_match = 400
Filter each input array by its respective target-label list before matching. Exclude non-target boxes from both matches and unmatched results. Assume all supplied BoundingBox objects are valid and non-null, with label_id, x, y, width, and height. Coordinates are upper-left pixels in a common coordinate system.
Define a HungarianMatchPair containing primary_box, candidate_box, and a floating-point cost. Define a HungarianMatchResult containing matches, unmatched_primary_boxes, and unmatched_candidate_boxes. Matched and unmatched objects must refer to the actual input boxes rather than only their indices. Use value semantics for result and pair containers and references or handles for input boxes where supported by the chosen language.
The main function match_bounding_boxes_hungarian takes the two input arrays and returns HungarianMatchResult. If either filtered array is empty, return the other filtered array as unmatched and no matches. The same input array may be passed twice; the two label lists determine each role.
Calculate each box center using x plus half the width and y plus half the height, retaining fractional centers. Use Euclidean center-to-center distance as the pairing cost. Build a square matrix whose size is the larger filtered group size. Initialize real pairs to an invalid cost of 999999.0. Replace that cost with the distance only when it is less than or equal to max_pixel_distance_for_match. Use zero-cost dummy rows and columns for unequal group sizes.
Keep hungarian_solve in a separate function. Require a square matrix and return assignment[row] = column. For an empty matrix return an empty assignment; for a non-square matrix return unassigned values. Use row and column potentials and augmenting paths to minimize the total assignment cost. No row or column may be used more than once.
Convert the solver's assignments to matched input-object pairs, excluding dummy or invalid assignments. Record each accepted pair's distance as its cost. Return rejected or dummy-assigned primary boxes as unmatched, and any unused filtered candidate boxes as unmatched.
Organize helpers for filtering target labels, building all pairwise distances, solving the assignment, converting indices into matched objects, and collecting unmatched candidates. Add concise comments explaining the main stages and the solver's major arrays without explaining every mathematical operation. Keep all helpers and result classes in one complete code block. Include a small integration example, without adding frame tracking or drawing.

