feat: 解码器全面增强 — 多遍二值化 + 4点透视 + ECI/FNC1
Phase 1 — 二值化+检测增强: - decoder/image.rs 重写: 中位数→Otsu→Sauvola 三遍自适应阈值 - 新增 load_gray() 公共 API 供调用方自定义预处理 - 反色QR码自动检测(5点采样法) - 静区验证 + 包围盒(BoundingBox) + 解码置信度评分 - 定位图案比例偏差计算 Phase 2 — 4点透视变换+多QR码: - decoder/perspective.rs 重写: DLT求解3x3单应矩阵 - 用3个定位图案+推算第4点构建4点对应 - 双线性插值替换最近邻采样 - 多角度旋转回退(7个预设角度) - decode_all() 返回图像中所有QR码(按置信度降序) Phase 3 — ISO合规: - decoder/bch.rs: decode_format_info/version_info 返回汉明距离 - decoder/format.rs: 副本冲突时选汉明距离更小者 - decoder/mode_decode.rs: ECI指派号识别, FNC1/GS1检测(0101/1001) - 结构化追加(0011)头部提取: position/total/parity - DecodeResult 扩展: is_gs1, eci_designator, structured_append 验证: cargo check+clippy通过, 81+24测试全部通过
This commit is contained in:
+128
-1
@@ -15,6 +15,9 @@ struct FinderMatch {
|
||||
pub(crate) struct DetectResult {
|
||||
pub modules: Vec<Vec<bool>>, // 布尔矩阵(含静区)
|
||||
pub version_estimate: u8,
|
||||
pub bounds: crate::decoder::BoundingBox,
|
||||
pub quiet_zone_ok: bool,
|
||||
pub finder_ratio_deviation: f64,
|
||||
}
|
||||
|
||||
/// 水平扫描查找 1:1:3:1:1 比例
|
||||
@@ -102,6 +105,47 @@ fn scan_col(gray: &[Vec<bool>], col: usize) -> Vec<(usize, usize)> {
|
||||
centers
|
||||
}
|
||||
|
||||
/// 检测 3 个定位图案,返回 (cx, cy) 像素坐标对(供透视矫正使用)
|
||||
pub(crate) fn find_finders_for_perspective(
|
||||
gray: &[Vec<bool>],
|
||||
) -> Result<[(usize, usize); 3], crate::error::QrError> {
|
||||
let finders = find_finders(gray)
|
||||
.ok_or_else(|| crate::error::QrError::DecodeFail("未找到 QR 码定位图案".into()))?;
|
||||
Ok([
|
||||
(finders[0].cx, finders[0].cy),
|
||||
(finders[1].cx, finders[1].cy),
|
||||
(finders[2].cx, finders[2].cy),
|
||||
])
|
||||
}
|
||||
|
||||
/// 从定位图案估算模块大小(像素/模块)
|
||||
pub(crate) fn estimate_module_size_from_finders(
|
||||
_gray: &[Vec<bool>],
|
||||
finders: &[(usize, usize); 3],
|
||||
) -> usize {
|
||||
// 用 TL-TR 和 TL-BL 距离估算
|
||||
let dx1 = (finders[1].0 as f64 - finders[0].0 as f64).abs();
|
||||
let dy1 = (finders[1].1 as f64 - finders[0].1 as f64).abs();
|
||||
let dx2 = (finders[2].0 as f64 - finders[0].0 as f64).abs();
|
||||
let dy2 = (finders[2].1 as f64 - finders[0].1 as f64).abs();
|
||||
let dist1 = (dx1 * dx1 + dy1 * dy1).sqrt();
|
||||
let dist2 = (dx2 * dx2 + dy2 * dy2).sqrt();
|
||||
let avg_dist = (dist1 + dist2) / 2.0;
|
||||
// 版本 1: dist ≈ 14 modules (7+1-1+7 = 14)
|
||||
// 我们并不知道确切版本,使用 iter 近似
|
||||
(avg_dist / 14.0).max(1.0) as usize
|
||||
}
|
||||
|
||||
/// 从 TL-TR 距离估算版本号
|
||||
pub(crate) fn estimate_version_from_tl_tr(tl: (f64, f64), tr: (f64, f64), module_size: usize) -> u8 {
|
||||
let dx = tr.0 - tl.0;
|
||||
let dy = tr.1 - tl.1;
|
||||
let dist_px = (dx * dx + dy * dy).sqrt() as f32;
|
||||
let dist_modules = dist_px / module_size as f32;
|
||||
let ver = ((dist_modules as i32 - 14) / 4) as u8;
|
||||
ver.clamp(1, 40)
|
||||
}
|
||||
|
||||
/// 检测 3 个定位图案(交叉验证水平+垂直扫描)
|
||||
fn find_finders(gray: &[Vec<bool>]) -> Option<[FinderMatch; 3]> {
|
||||
let height = gray.len();
|
||||
@@ -245,7 +289,6 @@ pub(crate) fn detect_and_extract(
|
||||
for my in 0..size {
|
||||
let mut row = Vec::with_capacity(size);
|
||||
for mx in 0..size {
|
||||
// 从采样网格映射到图像像素坐标
|
||||
let px = tl.cx as f32 + (mx as f32 - 3.5) * module_size as f32;
|
||||
let py = tl.cy as f32 + (my as f32 - 3.5) * module_size as f32;
|
||||
|
||||
@@ -266,8 +309,92 @@ pub(crate) fn detect_and_extract(
|
||||
modules.push(row);
|
||||
}
|
||||
|
||||
// 静区验证:检查矩阵四边各 4 行/列是否为白色
|
||||
let quiet_zone_ok = check_quiet_zone(&modules, size);
|
||||
|
||||
// 包围盒:从 3 个定位图案推算 QR 码在图像中的像素区域
|
||||
let margin_px = (module_size as u32).saturating_mul(4);
|
||||
let x_min = tl.cx.saturating_sub(3 * module_size) as u32;
|
||||
let y_min = tl.cy.saturating_sub(3 * module_size) as u32;
|
||||
let x_max = ((tr.cx + 3 * module_size) as u32).saturating_add(margin_px);
|
||||
let y_max = ((_bl.cy + 3 * module_size) as u32).saturating_add(margin_px);
|
||||
let bounds = crate::decoder::BoundingBox {
|
||||
x: x_min.saturating_sub(margin_px),
|
||||
y: y_min.saturating_sub(margin_px),
|
||||
width: x_max.saturating_sub(x_min).saturating_add(2 * margin_px),
|
||||
height: y_max.saturating_sub(y_min).saturating_add(2 * margin_px),
|
||||
};
|
||||
|
||||
// 定位图案比例偏差:实际 5 段比例与 1:1:3:1:1 的偏差
|
||||
let finder_ratio_deviation = compute_finder_deviation(gray, tl);
|
||||
|
||||
Ok(DetectResult {
|
||||
modules,
|
||||
version_estimate: version,
|
||||
bounds,
|
||||
quiet_zone_ok,
|
||||
finder_ratio_deviation,
|
||||
})
|
||||
}
|
||||
|
||||
/// 检查静区:矩阵四边各 4 行/列是否为全白色
|
||||
fn check_quiet_zone(modules: &[Vec<bool>], size: usize) -> bool {
|
||||
let quiet = 4usize;
|
||||
// 上边
|
||||
if modules.iter().take(quiet.min(size)).any(|row| row.iter().any(|&m| m)) {
|
||||
return false;
|
||||
}
|
||||
// 下边
|
||||
if modules.iter().skip(size.saturating_sub(quiet)).any(|row| row.iter().any(|&m| m)) {
|
||||
return false;
|
||||
}
|
||||
// 左边 & 右边
|
||||
for row in modules.iter() {
|
||||
if row.iter().take(quiet.min(size)).any(|&m| m) {
|
||||
return false;
|
||||
}
|
||||
if row.iter().skip(size.saturating_sub(quiet)).any(|&m| m) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// 计算定位图案比例偏差(与理想 1:1:3:1:1 的偏差,0=完美, 1=最大)
|
||||
fn compute_finder_deviation(gray: &[Vec<bool>], finder: &FinderMatch) -> f64 {
|
||||
let row = finder.cy;
|
||||
let mut runs: Vec<usize> = Vec::new();
|
||||
let mut col = finder.cx.saturating_sub(finder.size);
|
||||
let end = (finder.cx + finder.size).min(gray[0].len());
|
||||
while col < end {
|
||||
let current = gray[row][col];
|
||||
let mut run_len = 0;
|
||||
while col < end && gray[row][col] == current {
|
||||
run_len += 1;
|
||||
col += 1;
|
||||
}
|
||||
runs.push(run_len);
|
||||
}
|
||||
|
||||
if runs.len() < 5 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
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 {
|
||||
continue;
|
||||
}
|
||||
let expected = [avg, avg, 3.0 * avg, avg, avg];
|
||||
let dev: f64 = (0..5)
|
||||
.map(|j| (runs[j] as f64 - expected[j]).abs() / avg)
|
||||
.sum::<f64>()
|
||||
/ 5.0;
|
||||
if dev < best_dev {
|
||||
best_dev = dev;
|
||||
}
|
||||
}
|
||||
best_dev
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user