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:
+59
-35
@@ -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 {
|
||||
|
||||
@@ -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;}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user