use crate::error::{CvError, CvResult};
pub fn hungarian_algorithm(cost_matrix: &[Vec<f64>]) -> Vec<Option<usize>> {
if cost_matrix.is_empty() {
return Vec::new();
}
let n_rows = cost_matrix.len();
let n_cols = cost_matrix[0].len();
let size = n_rows.max(n_cols);
let mut costs = vec![vec![1e9; size]; size];
for (i, row) in cost_matrix.iter().enumerate() {
for (j, &cost) in row.iter().enumerate() {
costs[i][j] = cost;
}
}
let assignments = hungarian_solve(&costs);
let mut result = vec![None; n_rows];
for (i, &j) in assignments.iter().enumerate().take(n_rows) {
if j < n_cols && costs[i][j] < 1e8 {
result[i] = Some(j);
}
}
result
}
fn hungarian_solve(cost_matrix: &[Vec<f64>]) -> Vec<usize> {
let n = cost_matrix.len();
let mut costs = cost_matrix.to_vec();
for row in &mut costs {
let row_min = row.iter().copied().fold(f64::INFINITY, f64::min);
for val in row {
*val -= row_min;
}
}
for j in 0..n {
let col_min = (0..n).map(|i| costs[i][j]).fold(f64::INFINITY, f64::min);
for i in 0..n {
costs[i][j] -= col_min;
}
}
let mut row_assigned = vec![false; n];
let mut col_assigned = vec![false; n];
let mut assignments = vec![usize::MAX; n];
for i in 0..n {
for j in 0..n {
if costs[i][j].abs() < 1e-10 && !col_assigned[j] {
assignments[i] = j;
row_assigned[i] = true;
col_assigned[j] = true;
break;
}
}
}
loop {
let unassigned_row = (0..n).find(|&i| !row_assigned[i]);
let Some(start_row) = unassigned_row else {
break; };
if !augment_path(
&mut costs,
&mut assignments,
&mut row_assigned,
&mut col_assigned,
start_row,
) {
update_costs(&mut costs, &row_assigned, &col_assigned);
}
}
assignments
}
fn augment_path(
costs: &mut [Vec<f64>],
assignments: &mut [usize],
row_assigned: &mut [bool],
col_assigned: &mut [bool],
start_row: usize,
) -> bool {
let n = costs.len();
let mut visited_rows = vec![false; n];
let mut visited_cols = vec![false; n];
let mut parent_row = vec![usize::MAX; n];
let mut queue = vec![start_row];
visited_rows[start_row] = true;
while !queue.is_empty() {
let mut next_queue = Vec::new();
for &row in &queue {
for col in 0..n {
if !visited_cols[col] && costs[row][col].abs() < 1e-10 {
visited_cols[col] = true;
parent_row[col] = row;
if !col_assigned[col] {
let mut curr_col = col;
loop {
let curr_row = parent_row[curr_col];
let prev_col = assignments[curr_row];
assignments[curr_row] = curr_col;
row_assigned[curr_row] = true;
col_assigned[curr_col] = true;
if curr_row == start_row {
break;
}
curr_col = prev_col;
}
return true;
}
if let Some(assigned_row) = (0..n).find(|&r| assignments[r] == col) {
if !visited_rows[assigned_row] {
visited_rows[assigned_row] = true;
next_queue.push(assigned_row);
}
}
}
}
}
queue = next_queue;
}
false
}
fn update_costs(costs: &mut [Vec<f64>], row_assigned: &[bool], col_assigned: &[bool]) {
let n = costs.len();
let mut min_val = f64::INFINITY;
for i in 0..n {
if !row_assigned[i] {
for j in 0..n {
if !col_assigned[j] {
min_val = min_val.min(costs[i][j]);
}
}
}
}
if min_val.is_infinite() {
return;
}
for i in 0..n {
if !row_assigned[i] {
for j in 0..n {
costs[i][j] -= min_val;
}
}
}
for j in 0..n {
if col_assigned[j] {
for i in 0..n {
costs[i][j] += min_val;
}
}
}
}
pub fn compute_iou(bbox1: &crate::detect::BoundingBox, bbox2: &crate::detect::BoundingBox) -> f64 {
let x1 = bbox1.x.max(bbox2.x);
let y1 = bbox1.y.max(bbox2.y);
let x2 = (bbox1.x + bbox1.width).min(bbox2.x + bbox2.width);
let y2 = (bbox1.y + bbox1.height).min(bbox2.y + bbox2.height);
if x2 <= x1 || y2 <= y1 {
return 0.0;
}
let intersection = ((x2 - x1) * (y2 - y1)) as f64;
let area1 = (bbox1.width * bbox1.height) as f64;
let area2 = (bbox2.width * bbox2.height) as f64;
let union = area1 + area2 - intersection;
if union > 0.0 {
intersection / union
} else {
0.0
}
}
pub fn create_iou_cost_matrix(
tracks: &[crate::detect::BoundingBox],
detections: &[crate::detect::BoundingBox],
) -> Vec<Vec<f64>> {
let mut costs = vec![vec![0.0; detections.len()]; tracks.len()];
for (i, track) in tracks.iter().enumerate() {
for (j, detection) in detections.iter().enumerate() {
let iou = compute_iou(track, detection);
costs[i][j] = 1.0 - iou; }
}
costs
}
pub fn filter_assignments_by_cost(
assignments: &[Option<usize>],
cost_matrix: &[Vec<f64>],
max_cost: f64,
) -> Vec<Option<usize>> {
let mut filtered = assignments.to_vec();
for (i, &assignment) in assignments.iter().enumerate() {
if let Some(j) = assignment {
if i < cost_matrix.len() && j < cost_matrix[i].len() && cost_matrix[i][j] > max_cost {
filtered[i] = None;
}
}
}
filtered
}
pub fn greedy_assignment(cost_matrix: &[Vec<f64>], max_cost: f64) -> Vec<Option<usize>> {
if cost_matrix.is_empty() {
return Vec::new();
}
let n_rows = cost_matrix.len();
let n_cols = cost_matrix[0].len();
let mut costs = Vec::new();
for (i, row) in cost_matrix.iter().enumerate() {
for (j, &cost) in row.iter().enumerate() {
if cost <= max_cost {
costs.push((cost, i, j));
}
}
}
costs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let mut assignments = vec![None; n_rows];
let mut col_taken = vec![false; n_cols];
for (_, i, j) in costs {
if assignments[i].is_none() && !col_taken[j] {
assignments[i] = Some(j);
col_taken[j] = true;
}
}
assignments
}