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:
+1
-1
@@ -266,7 +266,7 @@ fn build_mode(mode: &str, opts: &EncodeOpts, fb: &str) -> Result<String> {
|
|||||||
let pwd = opts
|
let pwd = opts
|
||||||
.password
|
.password
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.or_else(|| env_pwd.as_deref())
|
.or(env_pwd.as_deref())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
Ok(text_builder::build_wifi_text(
|
Ok(text_builder::build_wifi_text(
|
||||||
s,
|
s,
|
||||||
|
|||||||
+59
-35
@@ -51,12 +51,14 @@ fn scan_row(gray: &[Vec<bool>], row: usize) -> Vec<(usize, usize, usize)> {
|
|||||||
if base < 2.0 {
|
if base < 2.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let tolerance = 0.4;
|
let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差
|
||||||
let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance;
|
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) {
|
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;
|
// ZXing 的 end-based 中心公式: end - r4 - r3 - r2/2 (浮点精度更高)
|
||||||
centers.push((cx, row, total as usize));
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tolerance = 0.4;
|
let tolerance = 0.5; // ZXing 标准:moduleSize/2 容差
|
||||||
let check = |v: f32, expected: f32| (v - expected * base).abs() < base * tolerance;
|
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) {
|
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 end = runs[i + 4].0 + runs[i + 4].1;
|
||||||
let total_px = total as usize;
|
let cy = end as f32 - r4 - r3 - r2 / 2.0;
|
||||||
centers.push((col, cy, total_px));
|
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| {
|
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;
|
let db = b.cx * b.cx + b.cy * b.cy;
|
||||||
da.cmp(&db)
|
da.cmp(&db)
|
||||||
});
|
});
|
||||||
|
|
||||||
// 区分右上(X 最大)和左下(Y 最大)
|
|
||||||
if finders[1].cx < finders[2].cx {
|
if finders[1].cx < finders[2].cx {
|
||||||
finders.swap(1, 2);
|
finders.swap(1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let f0 = finders.remove(0);
|
let f0 = finders.remove(0);
|
||||||
let f1 = finders.remove(0);
|
let f1 = finders.remove(1);
|
||||||
let f2 = finders.remove(0);
|
let f2 = finders.remove(2);
|
||||||
|
|
||||||
|
// ZXing 几何验证:module_size 一致性 + 勾股定理
|
||||||
|
if !validate_finder_geometry(&[&f0, &f1, &f2]) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
Some([f0, f1, f2])
|
Some([f0, f1, f2])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 估算定位图案大小(像素)
|
/// ZXing 风格几何验证:3 个定位图案的 module_size 必须一致且构成近似直角三角形
|
||||||
fn estimate_finder_size(gray: &[Vec<bool>], cx: usize, cy: usize) -> usize {
|
fn validate_finder_geometry(finders: &[&FinderMatch; 3]) -> bool {
|
||||||
// 从中心点水平扫描连续暗像素
|
let s0 = finders[0].size as f64;
|
||||||
let mut left = cx;
|
let s1 = finders[1].size as f64;
|
||||||
while left > 0 {
|
let s2 = finders[2].size as f64;
|
||||||
if cy < gray.len() && left < gray[0].len() && gray[cy][left] {
|
|
||||||
left -= 1;
|
// module_size 一致性: 最大偏差 < 10%
|
||||||
} else {
|
let avg_size = (s0 + s1 + s2) / 3.0;
|
||||||
break;
|
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() {
|
// 勾股定理验证: |C - sqrt(A² + B²)| / min(C, sqrt(A² + B²)) < 15%
|
||||||
if cy < gray.len() && gray[cy][right] {
|
let ax = finders[1].cx as f64 - finders[0].cx as f64;
|
||||||
right += 1;
|
let ay = finders[1].cy as f64 - finders[0].cy as f64;
|
||||||
} else {
|
let bx = finders[2].cx as f64 - finders[0].cx as f64;
|
||||||
break;
|
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 布尔矩阵
|
/// 从二值化图像中提取 QR 布尔矩阵
|
||||||
@@ -360,7 +384,7 @@ fn check_quiet_zone(modules: &[Vec<bool>], size: usize) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 计算定位图案比例偏差(与理想 1:1:3:1:1 的偏差,0=完美, 1=最大)
|
/// 计算定位图案比例偏差(0=完美, 1=最大),使用 base=total/7 对齐 ZXing
|
||||||
fn compute_finder_deviation(gray: &[Vec<bool>], finder: &FinderMatch) -> f64 {
|
fn compute_finder_deviation(gray: &[Vec<bool>], finder: &FinderMatch) -> f64 {
|
||||||
let row = finder.cy;
|
let row = finder.cy;
|
||||||
let mut runs: Vec<usize> = Vec::new();
|
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;
|
let mut best_dev = 1.0f64;
|
||||||
for i in 0..runs.len().saturating_sub(4) {
|
for i in 0..runs.len().saturating_sub(4) {
|
||||||
let avg =
|
let total = (runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64;
|
||||||
(runs[i] + runs[i + 1] + runs[i + 2] + runs[i + 3] + runs[i + 4]) as f64 / 5.0;
|
let base = total / 7.0;
|
||||||
if avg < 2.0 {
|
if base < 2.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let expected = [avg, avg, 3.0 * avg, avg, avg];
|
let expected = [base, base, 3.0 * base, base, base];
|
||||||
let dev: f64 = (0..5)
|
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>()
|
.sum::<f64>()
|
||||||
/ 5.0;
|
/ 5.0;
|
||||||
if dev < best_dev {
|
if dev < best_dev {
|
||||||
|
|||||||
@@ -143,9 +143,10 @@ fn count_finder_hits(gray: &[Vec<bool>]) -> usize {
|
|||||||
let (mut col,mut runs)=(0,vec![]);
|
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); }
|
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) {
|
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;
|
let total=(runs[i]+runs[i+1]+runs[i+2]+runs[i+3]+runs[i+4])as f64;
|
||||||
if avg<2.0{continue;}
|
let base=total/7.0;
|
||||||
let ck=|v:f64,e:f64|(v-e*avg).abs()<avg*0.4;
|
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;}
|
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;}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
+1
-2
@@ -69,7 +69,7 @@ fn generate_qr_inner(text: &str, level_str: &str, margin: u8, size: u8, fmt: &st
|
|||||||
if margin > 20 {
|
if margin > 20 {
|
||||||
return (StatusCode::BAD_REQUEST, "边距过大(最大 20)").into_response();
|
return (StatusCode::BAD_REQUEST, "边距过大(最大 20)").into_response();
|
||||||
}
|
}
|
||||||
if size < 1 || size > 20 {
|
if !(1..=20).contains(&size) {
|
||||||
return (StatusCode::BAD_REQUEST, "模块大小需在 1-20 之间").into_response();
|
return (StatusCode::BAD_REQUEST, "模块大小需在 1-20 之间").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,6 @@ fn generate_qr_inner(text: &str, level_str: &str, margin: u8, size: u8, fmt: &st
|
|||||||
level,
|
level,
|
||||||
version: VersionMode::Auto,
|
version: VersionMode::Auto,
|
||||||
margin,
|
margin,
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let qr = match QrCode::encode(text, config) {
|
let qr = match QrCode::encode(text, config) {
|
||||||
|
|||||||
Reference in New Issue
Block a user