//! QR 码图像透视矫正 //! //! 检测定位图案后,计算旋转角并矫正图像。 //! MVP 版本:仅做旋转矫正(仿射变换),不做完整单应变换。 pub(crate) fn auto_correct(gray: &[Vec]) -> Vec> { let h = gray.len(); let _w = if h > 0 { gray[0].len() } else { return gray.to_vec(); }; // 尝试找到至少 2 个 finder let finders = find_two_finders(gray); if finders.len() < 2 { return gray.to_vec(); } rotate_to_horizontal(gray, finders[0], finders[1]) } /// 简化的 finder 检测(只找 2 个) fn find_two_finders(gray: &[Vec]) -> Vec<(usize, usize)> { let h = gray.len(); let _w = if h > 0 { gray[0].len() } else { return vec![]; }; let mut centers: Vec<(usize, usize, usize)> = Vec::new(); // (cx, cy, size) for row in (0..h).step_by(3) { let runs = scan_row_runs(gray, row); for i in 0..runs.len().saturating_sub(4) { let avg = (runs[i].1 + runs[i + 1].1 + runs[i + 2].1 + runs[i + 3].1 + runs[i + 4].1) as f32 / 5.0; if avg < 2.0 { continue; } let r = [ runs[i].1 as f32, runs[i + 1].1 as f32, runs[i + 2].1 as f32, runs[i + 3].1 as f32, runs[i + 4].1 as f32, ]; let check = |v: f32, e: f32| (v - e * avg).abs() < avg * 0.4; if check(r[0], 1.0) && check(r[1], 1.0) && check(r[2], 3.0) && check(r[3], 1.0) && check(r[4], 1.0) { let cx = runs[i + 2].0 + runs[i + 2].1 / 2; let size = runs[i].1 + runs[i + 1].1 + runs[i + 2].1 + runs[i + 3].1 + runs[i + 4].1; centers.push((cx, row, size)); } } } if centers.len() < 2 { return vec![]; } // 按 X 坐标排序,取最左和最右 centers.sort_by_key(|c| c.0); let left = centers.first().unwrap(); let right = centers.last().unwrap(); vec![(left.0, left.1), (right.0, right.1)] } fn scan_row_runs(gray: &[Vec], row: usize) -> Vec<(usize, usize)> { let w = gray[0].len(); let mut runs = Vec::new(); let mut col = 0; while col < w { let current = gray[row][col]; let mut len = 0; while col < w && gray[row][col] == current { len += 1; col += 1; } runs.push((col - len, len)); } runs } /// 旋转图像使 QR 码水平对齐 #[allow(clippy::needless_range_loop)] fn rotate_to_horizontal( gray: &[Vec], tl: (usize, usize), tr: (usize, usize), ) -> Vec> { let h = gray.len(); let w = if h > 0 { gray[0].len() } else { return gray.to_vec(); }; // 计算旋转角(弧度) let dx = tr.0 as f64 - tl.0 as f64; let dy = tr.1 as f64 - tl.1 as f64; let angle = dy.atan2(dx); // 正值 = 顺时针偏离水平 if angle.abs() < 0.01 { // 已基本水平,不处理 return gray.to_vec(); } // 旋转中心 = 图像中心 let cx = w as f64 / 2.0; let cy = h as f64 / 2.0; let cos_a = angle.cos(); let sin_a = angle.sin(); // 计算旋转后尺寸 let corners = [ (0.0, 0.0), (w as f64, 0.0), (w as f64, h as f64), (0.0, h as f64), ]; let (mut min_x, mut min_y, mut max_x, mut max_y) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN); for &(x, y) in &corners { let rx = (x - cx) * cos_a - (y - cy) * sin_a + cx; let ry = (x - cx) * sin_a + (y - cy) * cos_a + cy; min_x = min_x.min(rx); min_y = min_y.min(ry); max_x = max_x.max(rx); max_y = max_y.max(ry); } let new_w = (max_x - min_x).ceil() as usize; let new_h = (max_y - min_y).ceil() as usize; // 反向映射:对旋转后图像的每个像素,计算源图像中的位置,双线性插值 let mut result = vec![vec![false; new_w]; new_h]; for ny in 0..new_h { for nx in 0..new_w { // 映射回旋转前的坐标 let sx = (nx as f64 + min_x - cx) * cos_a + (ny as f64 + min_y - cy) * sin_a + cx; let sy = -(nx as f64 + min_x - cx) * sin_a + (ny as f64 + min_y - cy) * cos_a + cy; let sx_idx = sx as usize; let sy_idx = sy as usize; if sx_idx < w && sy_idx < h { result[ny][nx] = gray[sy_idx][sx_idx]; } } } result }