refactor: 抽象解耦推理引擎并重构为多Crate工作空间架构
- 移除 核心层与 tract/Tensor 的强耦合,前/后处理全线转用标准 ndarray - 针对 OCR 与目标检测(Det)分别设计独立的强类型输出小枚举(OcrOutput/DetOutput) - 利用 Trait 关联类型(Associated Type)InferenceEngine,OcrEngine,DetEngine 统一接口,实现多后端解耦 - 引入 thiserror 库,建立完备的强类型错误处理机制(DdddError/Result) - 完成项目结构初拆,剥离为 ddddocr-core 和 ddddocr-tract
This commit is contained in:
184
ddddocr-tract/tests/ocr_test.rs
Normal file
184
ddddocr-tract/tests/ocr_test.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use ddddocr_core::models::det::DetectionResult;
|
||||
use ddddocr_core::{DetBuilder, Detector, ModelMetadata, Ocr, Slider}; // 假设你的包名是这个
|
||||
use ddddocr_tract::{DetSession,OcrSession};
|
||||
use image::{DynamicImage, Rgb};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
mod char_slice;
|
||||
use char_slice::CHARSET_BETA;
|
||||
use ddddocr_core::models::ocr::metadata::{Normalization, Resize};
|
||||
|
||||
fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> {
|
||||
// 1. 先将泛型转为具体的 &Path 引用
|
||||
let path_ref = path.as_ref();
|
||||
|
||||
// 2. 调用 open 时传入引用(utils::open 支持 AsRef<Path>)
|
||||
image::open(path_ref).map_err(|e| {
|
||||
// 3. 此时 path_ref 依然有效,可以安全地在闭包中使用
|
||||
anyhow::anyhow!("无法加载图片 {:?}: {}", path_ref, e)
|
||||
})
|
||||
}
|
||||
/// 将检测结果绘制在图像上并保存
|
||||
fn save_debug_image(
|
||||
dynamic_img: &DynamicImage, // 【优化点 1】直接传入解码好的引用,拒绝重复解码
|
||||
bboxes: &[DetectionResult], // 【修改点 1】类型改为自定义结构体切片
|
||||
output_path: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// 删除了原本的 let dynamic_img = image::load_from_memory(image_bytes)?;
|
||||
let mut img = dynamic_img.to_rgb8();
|
||||
let (width, height) = img.dimensions();
|
||||
let red = Rgb([255u8, 0, 0]);
|
||||
|
||||
for bbox in bboxes {
|
||||
// 【修改点 2】将原来的索引 bbox[0].. 改为结构体字段访问 .x1, .y1 ..
|
||||
let x1 = bbox.x1.max(0).min(width as i32 - 1) as u32;
|
||||
let y1 = bbox.y1.max(0).min(height as i32 - 1) as u32;
|
||||
let x2 = bbox.x2.max(0).min(width as i32 - 1) as u32;
|
||||
let y2 = bbox.y2.max(0).min(height as i32 - 1) as u32;
|
||||
|
||||
// 绘制横向线条
|
||||
for x in x1..=x2 {
|
||||
img.put_pixel(x, y1, red);
|
||||
img.put_pixel(x, y2, red);
|
||||
if y1 + 1 < height {
|
||||
img.put_pixel(x, y1 + 1, red);
|
||||
}
|
||||
if y2.saturating_sub(1) > 0 {
|
||||
img.put_pixel(x, y2 - 1, red);
|
||||
}
|
||||
}
|
||||
// 绘制纵向线条
|
||||
for y in y1..=y2 {
|
||||
img.put_pixel(x1, y, red);
|
||||
img.put_pixel(x2, y, red);
|
||||
if x1 + 1 < width {
|
||||
img.put_pixel(x1 + 1, y, red);
|
||||
}
|
||||
if x2.saturating_sub(1) > 0 {
|
||||
img.put_pixel(x2 - 1, y, red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img.save(output_path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_full_classification() {
|
||||
// 1. 初始化模型
|
||||
let ocr = OcrSession::new(
|
||||
"D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx",
|
||||
ModelMetadata::from_static_slice(
|
||||
CHARSET_BETA,
|
||||
false,
|
||||
Resize::DynamicWidth(64),
|
||||
1,
|
||||
Normalization::MinusOneToOne,
|
||||
),
|
||||
)
|
||||
.expect("模型加载失败");
|
||||
|
||||
// 2. 加载测试图片
|
||||
let img = image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在");
|
||||
|
||||
// 3. 执行识别
|
||||
let result = Ocr::new(&ocr)
|
||||
.predict(&img)
|
||||
.expect("识别过程出错")
|
||||
.into_text();
|
||||
|
||||
println!("识别结果: {}", result);
|
||||
assert!(!result.is_empty());
|
||||
}
|
||||
#[test]
|
||||
fn test_det_load() -> anyhow::Result<()> {
|
||||
let det = DetSession::new("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx")?;
|
||||
let image_path = "D:/CNWei/CNW/Rust/ddddocr-rs/samples/det1.png";
|
||||
let image_bytes =
|
||||
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
|
||||
|
||||
println!("图片读取成功,字节大小: {}", image_bytes.len());
|
||||
|
||||
// 【修改点 1】将字节流解码为统一的 DynamicImage
|
||||
let img = image::load_from_memory(&image_bytes)
|
||||
.map_err(|e| anyhow::anyhow!("图片解码失败: {}", e))?;
|
||||
|
||||
// 【修改点 2】传入统一的 &DynamicImage 引用
|
||||
let bboxes = Detector::new(&det).predict(&img)?;
|
||||
// println!("{:?}", det);
|
||||
println!("检测到的目标数量: {}", bboxes.len());
|
||||
|
||||
if bboxes.is_empty() {
|
||||
println!("未检测到任何目标。");
|
||||
} else {
|
||||
// 如果 save_debug_image 报错,记得去把它的入参类型和内部访问也改为 DetectionResult
|
||||
save_debug_image(&img, &bboxes, "D:/CNWei/CNW/Rust/ddddocr-rs/samples/result.jpg")?;
|
||||
|
||||
for (i, bbox) in bboxes.iter().enumerate() {
|
||||
// 【修改点 3】将原来的 bbox[0].. 索引访问改为结构体字段访问
|
||||
println!("目标 [{}]: {}", i, bbox);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_real_slide_match() {
|
||||
let engine = Slider::new().unwrap();
|
||||
|
||||
// 1. 加载你准备好的测试图
|
||||
// 假设图片放在项目根目录下的 assets 文件夹
|
||||
let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/hua.png").expect("请确保 samples/hua.png 存在");
|
||||
let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/huatu.png").expect("请确保 samples/huatu.png 存在");
|
||||
|
||||
// 2. 执行匹配
|
||||
// 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false
|
||||
let start = std::time::Instant::now();
|
||||
let result = engine
|
||||
.slide_match(&target_img, &bg_img, false)
|
||||
.expect("Slide match 执行失败");
|
||||
let duration = start.elapsed();
|
||||
|
||||
// 3. 打印结果
|
||||
println!("-------------------------------------------");
|
||||
println!("{}", result);
|
||||
println!("耗时: {:?}", duration);
|
||||
println!("-------------------------------------------");
|
||||
|
||||
// 验证基本逻辑:坐标不应为 0 (除非匹配失败)
|
||||
assert_eq!(result.target_x, 237);
|
||||
assert_eq!(result.target_y, 77);
|
||||
assert!(result.confidence > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_real_slide_comparison() {
|
||||
let engine = Slider::new().unwrap();
|
||||
|
||||
// 1. 加载你准备好的测试图
|
||||
// 假设图片放在项目根目录下的 assets 文件夹
|
||||
let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/ken.jpg").expect("请确保 samples/ken.jpg 存在");
|
||||
let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/kenyuan.jpg").expect("请确保 samples/kenyuan.jpg 存在");
|
||||
|
||||
// 2. 执行匹配
|
||||
// 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false
|
||||
let start = std::time::Instant::now();
|
||||
let result = engine
|
||||
.slide_comparison(&target_img, &bg_img)
|
||||
.expect("Slide match 执行失败");
|
||||
let duration = start.elapsed();
|
||||
|
||||
// 3. 打印结果
|
||||
println!("-------------------------------------------");
|
||||
println!("滑块匹配测试结果:");
|
||||
println!("检测坐标: [x: {}, y: {}]", result.target_x, result.target_y);
|
||||
println!("置信度: {:.4}", result.confidence);
|
||||
println!("耗时: {:?}", duration);
|
||||
println!("-------------------------------------------");
|
||||
|
||||
// 验证基本逻辑:坐标不应为 0 (除非匹配失败)
|
||||
assert_eq!(result.target_x, 171);
|
||||
assert_eq!(result.target_y, 90);
|
||||
assert!(result.confidence > 0.0);
|
||||
}
|
||||
Reference in New Issue
Block a user