refactor(slide,det): 重构目标检测引擎并统一图像输入类型为 DynamicImage以及滑块匹配与比较引擎为 Rust 实现

- 统一 `predict` 和 `get_bbox` 接口参数为 `&DynamicImage`,消除多步处理时的重复图像解码开销。
- 引入轻量级 `DetectionResult` 结构体和固定大小数组 `[f32; 6]` 替代旧的嵌套 `Vec`,彻底消除后处理中的内存碎片。
- 优化 `preproc` 预处理逻辑,使用连续内存切片批量操作替代原有的逐像素迭代遍历。
- 移除多余的 `multiclass_nms_class_agnostic` 转发层,合并并精简 NMS 聚合函数。
- 优化 `calculate_center` 几何中心点计算函数,提高泛型语义并复用于两种匹配模式
- 在执行核心算法前增加尺寸与通道边界守卫(Guard Clauses),提升库的防防御性编程能力与崩溃安全性
- 移除多余的错误二次包装(map_err),改由 Rust 原生 Result 错误传播机制直接向上层抛出
This commit is contained in:
2026-07-03 17:51:28 +08:00
parent 22cc9709ad
commit 7f1ce04f50
7 changed files with 192 additions and 117 deletions

View File

@@ -1,8 +1,10 @@
use ddddocr_rs::models::slide::Slide;
use ddddocr_rs::{DdddOcr, DdddOcrBuilder}; // 假设你的包名是这个
use image::Rgb;
use image::{DynamicImage, Rgb};
use std::fs;
use std::path::Path;
use ddddocr_rs::models::det::DetectionResult;
fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> {
// 1. 先将泛型转为具体的 &Path 引用
let path_ref = path.as_ref();
@@ -15,27 +17,26 @@ fn load_image<P: AsRef<Path>>(path: P) -> anyhow::Result<image::DynamicImage> {
}
/// 将检测结果绘制在图像上并保存
fn save_debug_image(
image_bytes: &[u8],
bboxes: &Vec<Vec<i32>>,
dynamic_img: &DynamicImage, // 【优化点 1】直接传入解码好的引用拒绝重复解码
bboxes: &[DetectionResult], // 【修改点 1】类型改为自定义结构体切片
output_path: &str,
) -> anyhow::Result<()> {
let dynamic_img = image::load_from_memory(image_bytes)?;
// 删除了原本的 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 {
// 基础边界检查
let x1 = bbox[0].max(0).min(width as i32 - 1) as u32;
let y1 = bbox[1].max(0).min(height as i32 - 1) as u32;
let x2 = bbox[2].max(0).min(width as i32 - 1) as u32;
let y2 = bbox[3].max(0).min(height as i32 - 1) as u32;
// 【修改点 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);
}
@@ -47,7 +48,6 @@ fn save_debug_image(
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);
}
@@ -82,17 +82,27 @@ fn test_det_load() -> anyhow::Result<()> {
fs::read(image_path).map_err(|e| anyhow::anyhow!("无法读取图片 {}: {}", image_path, e))?;
println!("图片读取成功,字节大小: {}", image_bytes.len());
let bboxes = det.detection(&image_bytes)?;
// 【修改点 1】将字节流解码为统一的 DynamicImage
let img = image::load_from_memory(&image_bytes)
.map_err(|e| anyhow::anyhow!("图片解码失败: {}", e))?;
// 【修改点 2】传入统一的 &DynamicImage 引用
let bboxes = det.detection(&img)?;
println!(":?{}", det);
println!("检测到的目标数量: {}", bboxes.len());
if bboxes.is_empty() {
println!("未检测到任何目标。");
} else {
save_debug_image(&image_bytes, &bboxes, "samples/result.jpg")?;
// 如果 save_debug_image 报错,记得去把它的入参类型和内部访问也改为 DetectionResult
save_debug_image(&img, &bboxes, "samples/result.jpg")?;
for (i, bbox) in bboxes.iter().enumerate() {
// 【修改点 3】将原来的 bbox[0].. 索引访问改为结构体字段访问
println!(
"目标 [{}]: x1={}, y1={}, x2={}, y2={}",
i, bbox[0], bbox[1], bbox[2], bbox[3]
"目标 [{}]: x1={}, y1={}, x2={}, y2={}, 分数={:.4}, 类别ID={}",
i, bbox.x1, bbox.y1, bbox.x2, bbox.y2, bbox.score, bbox.class_id
);
}
}