refactor: ZXing对齐 — 容差50pct + 几何验证 + end-based中心 + 废弃代码清理

- detect.rs: 容差从40%提升到50% (对齐ZXing的moduleSize/2)
- detect.rs: 中心点改用ZXing end-based公式 (浮点精度, end - r4 - r3 - r2/2)
- detect.rs: 新增validate_finder_geometry()几何验证
  module_size一致性<10% + 勾股定理偏差<15%
- detect.rs: compute_finder_deviation改用base=total/7
- detect.rs: 删除废弃的estimate_finder_size函数
- perspective.rs: count_finder_hits容差对齐 (base=total/7 + 50pct)
- web/main.rs + cli/main.rs: 修复Rust 1.96新版clippy规则
This commit is contained in:
2026-06-21 23:01:46 +08:00
parent a03ab95ce5
commit 309c9429ea
5 changed files with 65 additions and 41 deletions
+59 -35
View File
@@ -51,12 +51,14 @@ fn scan_row(gray: &[Vec<bool>], row: usize) -> Vec<(usize, usize, usize)> {
if base < 2.0 {
continue;
}
let tolerance = 0.4;
let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差
let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance;
if check(r0, 1.0) && check(r1, 1.0) && check(r2, 3.0) && check(r3, 1.0) && check(r4, 1.0) {
let cx = runs[i + 2].0 + runs[i + 2].1 / 2;
centers.push((cx, row, total as usize));
// ZXing 的 end-based 中心公式: end - r4 - r3 - r2/2 (浮点精度更高)
let end = runs[i + 4].0 + runs[i + 4].1;
let cx = end as f32 - r4 - r3 - r2 / 2.0;
centers.push((cx as usize, row, total as usize));
}
}
@@ -93,13 +95,13 @@ fn scan_col(gray: &[Vec<bool>], col: usize) -> Vec<(usize, usize, usize)> {
continue;
}
let tolerance = 0.4;
let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差
let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance;
if check(r0, 1.0) && check(r1, 1.0) && check(r2, 3.0) && check(r3, 1.0) && check(r4, 1.0) {
let cy = runs[i + 2].0 + runs[i + 2].1 / 2;
let total_px = total as usize;
centers.push((col, cy, total_px));
let end = runs[i + 4].0 + runs[i + 4].1;
let cy = end as f32 - r4 - r3 - r2 / 2.0;
centers.push((col, cy as usize, total as usize));
}
}
@@ -215,43 +217,65 @@ fn find_finders(gray: &[Vec<bool>]) -> Option<[FinderMatch; 3]> {
// 排序:左上、右上、左下
finders.sort_by(|a, b| {
let da = a.cx * a.cx + a.cy * a.cy; // 到原点的距离
let da = a.cx * a.cx + a.cy * a.cy;
let db = b.cx * b.cx + b.cy * b.cy;
da.cmp(&db)
});
// 区分右上(X 最大)和左下(Y 最大)
if finders[1].cx < finders[2].cx {
finders.swap(1, 2);
}
let f0 = finders.remove(0);
let f1 = finders.remove(0);
let f2 = finders.remove(0);
let f1 = finders.remove(1);
let f2 = finders.remove(2);
// ZXing 几何验证:module_size 一致性 + 勾股定理
if !validate_finder_geometry(&[&f0, &f1, &f2]) {
return None;
}
Some([f0, f1, f2])
}
/// 估算定位图案大小(像素)
fn estimate_finder_size(gray: &[Vec<bool>], cx: usize, cy: usize) -> usize {
// 从中心点水平扫描连续暗像素
let mut left = cx;
while left > 0 {
if cy < gray.len() && left < gray[0].len() && gray[cy][left] {
left -= 1;
} else {
break;
}
/// ZXing 风格几何验证:3 个定位图案的 module_size 必须一致且构成近似直角三角形
fn validate_finder_geometry(finders: &[&FinderMatch; 3]) -> bool {
let s0 = finders[0].size as f64;
let s1 = finders[1].size as f64;
let s2 = finders[2].size as f64;
// module_size 一致性: 最大偏差 < 10%
let avg_size = (s0 + s1 + s2) / 3.0;
let max_dev = avg_size * 0.10;
if (s0 - avg_size).abs() > max_dev
|| (s1 - avg_size).abs() > max_dev
|| (s2 - avg_size).abs() > max_dev
{
return false;
}
let mut right = cx;
while right + 1 < gray[0].len() {
if cy < gray.len() && gray[cy][right] {
right += 1;
} else {
break;
}
// 勾股定理验证: |C - sqrt(A² + B²)| / min(C, sqrt(A² + B²)) < 15%
let ax = finders[1].cx as f64 - finders[0].cx as f64;
let ay = finders[1].cy as f64 - finders[0].cy as f64;
let bx = finders[2].cx as f64 - finders[0].cx as f64;
let by = finders[2].cy as f64 - finders[0].cy as f64;
let a_len = (ax * ax + ay * ay).sqrt();
let b_len = (bx * bx + by * by).sqrt();
let c_len = {
let cx = finders[2].cx as f64 - finders[1].cx as f64;
let cy = finders[2].cy as f64 - finders[1].cy as f64;
(cx * cx + cy * cy).sqrt()
};
let hypotenuse = (a_len * a_len + b_len * b_len).sqrt();
let min = c_len.min(hypotenuse);
if min < 1.0 {
return true; // 太小,跳过检查
}
right - left
let deviation = (c_len - hypotenuse).abs() / min;
deviation < 0.15
}
/// 从二值化图像中提取 QR 布尔矩阵
@@ -360,7 +384,7 @@ fn check_quiet_zone(modules: &[Vec<bool>], size: usize) -> bool {
true
}
/// 计算定位图案比例偏差(与理想 1:1:3:1:1 的偏差,0=完美, 1=最大)
/// 计算定位图案比例偏差(0=完美, 1=最大),使用 base=total/7 对齐 ZXing
fn compute_finder_deviation(gray: &[Vec<bool>], finder: &FinderMatch) -> f64 {
let row = finder.cy;
let mut runs: Vec<usize> = Vec::new();
@@ -382,14 +406,14 @@ fn compute_finder_deviation(gray: &[Vec<bool>], finder: &FinderMatch) -> f64 {
let mut best_dev = 1.0f64;
for i in 0..runs.len().saturating_sub(4) {
let avg =
(runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64 / 5.0;
if avg < 2.0 {
let total = (runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64;
let base = total / 7.0;
if base < 2.0 {
continue;
}
let expected = [avg, avg, 3.0 * avg, avg, avg];
let expected = [base, base, 3.0 * base, base, base];
let dev: f64 = (0..5)
.map(|j| (runs[j] as f64 - expected[j]).abs() / avg)
.map(|j| (runs[i + j] as f64 - expected[j]).abs() / base)
.sum::<f64>()
/ 5.0;
if dev < best_dev {
+4 -3
View File
@@ -143,9 +143,10 @@ fn count_finder_hits(gray: &[Vec<bool>]) -> usize {
let (mut col,mut runs)=(0,vec![]);
while col<gray[row].len() { let c=gray[row][col]; let mut l=0; while col<gray[row].len()&&gray[row][col]==c{l+=1;col+=1;} runs.push(l); }
for i in 0..runs.len().saturating_sub(4) {
let avg=(runs[i]+runs[i+1]+runs[i+2]+runs[i+3]+runs[i+4])as f64/5.0;
if avg<2.0{continue;}
let ck=|v:f64,e:f64|(v-e*avg).abs()<avg*0.4;
let total=(runs[i]+runs[i+1]+runs[i+2]+runs[i+3]+runs[i+4])as f64;
let base=total/7.0;
if base<2.0{continue;}
let ck=|v:f64,e:f64|(v-e*base).abs()<base*0.5;
if ck(runs[i]as f64,1.0)&&ck(runs[i+1]as f64,1.0)&&ck(runs[i+2]as f64,3.0)&&ck(runs[i+3]as f64,1.0)&&ck(runs[i+4]as f64,1.0){hits+=1;}
}
}