diff --git a/ddddocr-core/src/det.rs b/ddddocr-core/src/det.rs index c1383f7..d28342e 100644 --- a/ddddocr-core/src/det.rs +++ b/ddddocr-core/src/det.rs @@ -1,3 +1,5 @@ +//! 目标检测模块:检测器构建器与执行入口。 + mod builder; mod executor; diff --git a/ddddocr-core/src/det/builder.rs b/ddddocr-core/src/det/builder.rs index 7d6a0a2..3df77b6 100644 --- a/ddddocr-core/src/det/builder.rs +++ b/ddddocr-core/src/det/builder.rs @@ -1,11 +1,15 @@ +//! 检测器构建器。 + use crate::det::executor::Detector; // use ddddocr_tract::det::session::DetSession; use crate::traits::DetEngine; + +/// 检测器构建器,通过 [`crate::Detector::builder`] 创建。 #[derive(Default)] pub struct DetBuilder; impl DetBuilder { - fn build_with(self, session: &E) -> Detector<'_> { - Detector { session } + fn build_with(self, runtime: &E) -> Detector<'_> { + Detector { runtime } } } diff --git a/ddddocr-core/src/det/executor.rs b/ddddocr-core/src/det/executor.rs index 22aeaaf..5ebe73c 100644 --- a/ddddocr-core/src/det/executor.rs +++ b/ddddocr-core/src/det/executor.rs @@ -1,3 +1,5 @@ +//! 目标检测执行器:检测后处理与预测入口。 + use crate::error::{Result, TensorError}; use image::{DynamicImage, GenericImageView, imageops::FilterType}; use ndarray::{Array2, Array3, Array4, Axis, prelude::*, s}; @@ -7,6 +9,8 @@ use std::fmt; // use ddddocr_tract::det::session::DetSession; use crate::{DetBuilder, DetOutput, OcrBuilder}; use crate::traits::DetEngine; + +/// 目标检测结果:原图像素坐标系下的边界框、置信度与类别 ID。 #[derive(Debug, Clone, Copy)] pub struct DetectionResult { pub x1: i32, @@ -28,24 +32,25 @@ impl fmt::Display for DetectionResult { } } +/// 目标检测器:对输入图像执行检测并返回结果。 pub struct Detector<'a> { - pub(crate) session: &'a dyn DetEngine, + pub(crate) runtime: &'a dyn DetEngine, } impl<'a> Detector<'a> { - pub fn new(session: &'a dyn DetEngine) -> Self { - Detector { session } + pub fn new(runtime: &'a dyn DetEngine) -> Self { + Detector { runtime } } pub fn builder() -> DetBuilder { DetBuilder::default() } } impl<'a> Detector<'a> { + /// 对输入图像执行目标检测,返回检测框列表。 pub fn predict(&self, image: &DynamicImage) -> Result> { // Rust 中通常在调用层处理文件/PIL转换,这里直接进入核心逻辑 Ok(self.get_bbox(image)?) } - /// 2. preproc: 纯 Rust 实现 (替代 OpenCV) fn preproc(&self, image: &DynamicImage, input_size: (u32, u32)) -> (Array4, f32) { let (target_h, target_w) = input_size; let (img_w, img_h) = image.dimensions(); @@ -87,7 +92,6 @@ impl<'a> Detector<'a> { (array, r) } - /// 3. demo_postprocess (逻辑与 Python 一致) fn demo_postprocess(&self, mut outputs: Array3, img_size: (i32, i32)) -> Array3 { let strides = [8, 16, 32]; @@ -123,7 +127,6 @@ impl<'a> Detector<'a> { outputs } - /// 4. nms fn nms(&self, boxes: &Array2, scores: &Array1, nms_thr: f32) -> Vec { let mut keep = Vec::new(); let x1 = boxes.column(0); @@ -183,8 +186,7 @@ impl<'a> Detector<'a> { keep } - /// 5. multiclass_nms - //multiclass_nms_class_agnostic + /// 多类别 NMS 后处理:按分数阈值筛选候选框,并用 NMS 阈值去重。 pub fn multiclass_nms( &self, boxes: &Array2, // [25200, 4] -> xyxy 格式 @@ -241,7 +243,7 @@ impl<'a> Detector<'a> { }) .collect() } - /// 6. get_bbox (完全解耦 OpenCV) + /// 对图像执行完整检测流程(预处理、推理、后处理),返回像素坐标系下的检测框。 pub fn get_bbox( &self, dynamic_img: &DynamicImage, @@ -254,7 +256,7 @@ impl<'a> Detector<'a> { // tract 推理 // let outputs = self.session.session.run(tvec!(input_tensor.into()))?; - let outputs = self.session.inference(input_tensor)?; + let outputs = self.runtime.inference(input_tensor)?; // let output_array = outputs[0] // 2. 无缝、安全地解包出标准 3维 矩阵 let DetOutput::Detection(output_array) = outputs; diff --git a/ddddocr-core/src/ocr.rs b/ddddocr-core/src/ocr.rs index 7245367..ccb0454 100644 --- a/ddddocr-core/src/ocr.rs +++ b/ddddocr-core/src/ocr.rs @@ -1,3 +1,5 @@ +//! OCR 模块:识别器构建器、执行入口及元数据、字符集等类型。 + mod builder; mod charset; mod color_filter; diff --git a/ddddocr-core/src/ocr/builder.rs b/ddddocr-core/src/ocr/builder.rs index bf4f3dd..50a55d9 100644 --- a/ddddocr-core/src/ocr/builder.rs +++ b/ddddocr-core/src/ocr/builder.rs @@ -1,8 +1,12 @@ +//! OCR 构建器。 + use crate::ocr::executor::Ocr; // use ddddocr_tract::session::OcrSession; use crate::traits::OcrEngine; use crate::ocr::color_filter::ColorFilter; use crate::ocr::token_filter::TokenFilter; + +/// OCR 构建器:配置识别选项后绑定引擎会话构建 [`crate::Ocr`]。 #[derive(Default)] pub struct OcrBuilder { /// 是否修复PNG格式问题 @@ -49,6 +53,7 @@ impl OcrBuilder { self.charset_restrict = Some(Box::new(restrict)); self } + /// 绑定引擎会话并构建 OCR 识别器。 pub fn build_with(self, runtime: &E) -> Ocr<'_> { // 1. 原地解析颜色过滤器 let final_color_ranges = match &self.color_filter { diff --git a/ddddocr-core/src/ocr/charset.rs b/ddddocr-core/src/ocr/charset.rs index c738611..a128fc5 100644 --- a/ddddocr-core/src/ocr/charset.rs +++ b/ddddocr-core/src/ocr/charset.rs @@ -1,9 +1,12 @@ +//! 字符集:token 列表与索引的双向映射。 + use std::borrow::Cow; use std::collections::HashMap; // ========================================== // 3. 字符集核心结构体 (重命名为 Charset) // ========================================== +/// 字符集:token 列表与索引的双向映射。 #[derive(Debug, Clone)] pub struct Charset { // 使用 Cow 统一静态切片和动态读取的 Vec,内部实现真正的零拷贝 @@ -32,7 +35,7 @@ impl Charset { // --- 业务策略方法 --- - /// 将字符转为索引,不存在返回 -1 (保持与原 Python 库行为一致) + /// 字符转索引,不存在时返回 -1(与 Python 版行为一致)。 pub fn char_to_index(&self, char_str: &str) -> i32 { if let Some(&idx) = self.char_to_idx.get(char_str) { idx as i32 @@ -41,7 +44,7 @@ impl Charset { } } - /// 将索引转为字符引用,零拷贝。若越界返回 None + /// 索引转字符引用,越界时返回 `None`。 pub fn index_to_char_ref(&self, index: usize) -> Option<&str> { self.tokens.get(index).map(|cow| cow.as_ref()) } diff --git a/ddddocr-core/src/ocr/color_filter.rs b/ddddocr-core/src/ocr/color_filter.rs index f37884b..f2f4ebd 100644 --- a/ddddocr-core/src/ocr/color_filter.rs +++ b/ddddocr-core/src/ocr/color_filter.rs @@ -1,9 +1,10 @@ +//! 颜色过滤:HSV 区间匹配与颜色预设。 + use crate::error::{ImagePreprocessError, Result}; use crate::utils::image_processor::rgb_to_opencv_hsv; use image::{DynamicImage, ImageBuffer, Rgb}; use std::str::FromStr; -/// 核心区间判定辅助函数 #[inline(always)] fn is_pixel_matched(ranges: &[HsvRange], h: u8, s: u8, v: u8) -> bool { ranges.iter().any(|range| { @@ -15,6 +16,7 @@ fn is_pixel_matched(ranges: &[HsvRange], h: u8, s: u8, v: u8) -> bool { && v <= range.upper.2 }) } +/// 按 HSV 区间过滤图像:未命中任一区间的像素刷白。 pub fn apply_to_image( image: &DynamicImage, hsv_ranges: &[HsvRange], @@ -58,6 +60,7 @@ pub fn apply_to_image( } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// HSV 颜色区间,下界与上界各为 `(H, S, V)`。 pub struct HsvRange { pub lower: (u8, u8, u8), // (H, S, V) pub upper: (u8, u8, u8), // (H, S, V) @@ -69,8 +72,7 @@ impl HsvRange { } } impl HsvRange { - /// 验证当前 HSV 范围是否合法 - /// 对应 Python 逻辑:H 在 0-180,S/V 在 0-255,且下界 <= 上界 + /// 校验区间是否合法(H 0-180,S/V 0-255,且下界 <= 上界)。 pub fn validate(&self) -> Result<(), ImagePreprocessError> { // 1. 校验 H 通道边界 (OpenCV 中 H 范围是 0-180) if self.lower.0 > 180 || self.upper.0 > 180 { @@ -93,6 +95,7 @@ impl HsvRange { } } #[derive(Debug, Clone, PartialEq, Eq)] +/// 颜色预设:常见颜色对应的 HSV 区间集合。 pub enum ColorPreset { Red, Blue, @@ -108,9 +111,7 @@ pub enum ColorPreset { } impl ColorPreset { - /// 纯裸数据定义,没有任何结构体包装,干净利落 - /// 返回值:(范围数量, 范围数组) - /// 完美的零成本抽象:利用常量提升将数据直接打入只读数据段 (.rodata) + /// 返回预设对应的 HSV 区间列表。 pub fn matches(&self) -> &[HsvRange] { match self { ColorPreset::Red => &[ @@ -162,7 +163,7 @@ impl ColorPreset { ColorPreset::Custom(ranges) => ranges, } } - /// 校验逻辑:在这里实现完美的“责任分离” + /// 校验预设的 HSV 区间是否合法。 pub fn validate(&self) -> Result<(), ImagePreprocessError> { match self { // 1. 快捷变体:完全绕过,根本不校验,0 运行时开销放行! @@ -202,23 +203,20 @@ impl FromStr for ColorPreset { // 3. 颜色约束特征(Trait)与组合子设计模式 // ===================================================================== +/// 颜色匹配上下文:当前像素的 HSV 值。 pub struct PixelCtx { pub hsv: (u8, u8, u8), } -/// 统一的颜色约束接口 +/// 颜色过滤约束接口:提供一组 HSV 区间。 pub trait ColorFilter { - /// 将自身的有效约束平铺追加到统一的目标容器中 fn append_ranges(&self, target: &mut Vec); - /// 预估范围数量,借助原生内置的 len() 实现 O(1) 完美控容 fn estimated_count(&self) -> usize; - /// 将自身的有效约束平铺追加到统一目标容器中 - /// 验证当前过滤器是否合法,默认直接放行(Ok(())) + /// 验证过滤器配置是否合法,默认直接放行。 fn validate_self(&self) -> Result<(), ImagePreprocessError> { Ok(()) } - /// 【新扩展的架构方法】将自身安全的合并到已有的普通容器中,并完成去重和排序 - /// 完美的责任分离:Builder 不再需要关心怎么分配内存、怎么排序去重 + /// 收集全部有效区间并排序去重;无有效区间时返回 `None`。 fn collect_to_vec(&self) -> Result>, ImagePreprocessError> { // 1. 触发自检 self.validate_self()?; @@ -288,6 +286,7 @@ impl<'a> ColorFilter for MultiOrColorRestrict<'a> { // 4. 声明式宏:一语定乾坤 // ===================================================================== +/// 组合多个颜色过滤器为「或」关系的快捷宏。 #[macro_export] macro_rules! color_any_of { ($only:expr) => { diff --git a/ddddocr-core/src/ocr/executor.rs b/ddddocr-core/src/ocr/executor.rs index ea8aaa6..cd46d91 100644 --- a/ddddocr-core/src/ocr/executor.rs +++ b/ddddocr-core/src/ocr/executor.rs @@ -1,41 +1,37 @@ +//! OCR 执行器:预测入口与结果类型。 + use crate::ocr::metadata::Resize; use crate::ocr::color_filter::{HsvRange, apply_to_image}; // use ddddocr_tract::session::{ModelOutput, OcrSession}; +use crate::error::{ImagePreprocessError, Result, TensorError}; +use crate::traits::OcrEngine; use crate::utils::image_convert::png_rgba_white_preprocess; use crate::utils::image_processor::{convert_to_grayscale, resize_image}; +use crate::{OcrBuilder, OcrOutput}; use image::DynamicImage; +use ndarray::ArrayView2; use std::borrow::Cow; use std::fmt; -// use tract_onnx::prelude::tract_ndarray::{ Ix2, s}; -// use tract_onnx::prelude::{DatumType, Tensor, tract_ndarray}; -// !!!【核心纠正】:彻底弃用 tract_ndarray,全线转用标准 ndarray -use ndarray::ArrayView2; -// pub enum ModelOutput { -// Indices(ndarray::Array1), // 拥有完整所有权的 1维数组,可任意传递和返回 -// Logits(ndarray::Array2), // 拥有完整所有权的 2维矩阵,可任意传递和返回 -// } -use crate::error::{ImagePreprocessError, Result, TensorError}; -use crate::{OcrBuilder, OcrOutput}; -use crate::traits::OcrEngine; -use tracing::{ warn}; +use tracing::warn; +/// OCR 识别结果:纯文本或携带概率的文本。 #[derive(Debug, Clone)] pub enum OcrResult { - /// 纯文本分支(对应 probability = false) + /// 纯文本结果(`probability = false` 时返回)。 Text(String), - /// 包含全量概率的分支(对应 probability = true) + /// 携带概率的结果(`probability = true` 时返回)。 Probability { text: String, - /// 满额概率矩阵 [Steps, Classes] + /// 全量概率矩阵 `[Steps, Classes]`。 probabilities: Vec>, - /// 全局平均置信度 + /// 全局平均置信度。 confidence: f64, }, - /// 不支持的模型或未知输出 + /// 不支持的模型或未知输出。 Unsupported { message: String }, } impl OcrResult { - /// 消费自身,直接提取最终文本 + /// 消费自身并提取最终文本。 pub fn into_text(self) -> String { match self { OcrResult::Text(text) => text, @@ -103,6 +99,7 @@ impl fmt::Display for OcrResult { } } +/// OCR 识别器:预处理、推理调度与后处理解码。 pub struct Ocr<'a> { pub(crate) runtime: &'a dyn OcrEngine, pub(crate) png_fix: bool, @@ -129,9 +126,9 @@ impl<'a> Ocr<'a> { pub fn builder() -> OcrBuilder { OcrBuilder::default() } - } impl<'a> Ocr<'a> { + /// 对输入图像执行 OCR 识别并返回结果。 pub fn predict(&self, image: &DynamicImage) -> Result { println!("当前颜色过滤器状态: {:?}", self.final_color_ranges); @@ -179,9 +176,10 @@ impl<'a> Ocr<'a> { let ocr_output = self.process_model_output(raw_tensor)?; Ok(ocr_output) } - /// 对应 Python 的 _preprocess_image - /// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换 - fn preprocess_image(&self, img: &DynamicImage) -> Result,ImagePreprocessError> { + fn preprocess_image( + &self, + img: &DynamicImage, + ) -> Result, ImagePreprocessError> { // 1. 获取模型元数据配置 let meta = self.runtime.metadata(); let norm = &meta.normalization; // 获取归一化器 @@ -255,30 +253,10 @@ impl<'a> Ocr<'a> { } }; Ok(array4) - // Ok(tensor) - - // let h = 64u32; - // let w = (current_img.width() as f32 * (h as f32 / current_img.height() as f32)) as u32; - // let gray_img = convert_to_grayscale(¤t_img); - // let resized = resize_image(&gray_img, w, h); - // // resized.save("debug_preprocessed.png").unwrap(); - // // 1. 预处理:转灰度 -> Resize -> 归一化 - // // let resized = img.resize_exact(w, h, FilterType::Lanczos3).to_luma8(); - // - // // 使用 tract_ndarray 构造,避免版本冲突 - // let array = - // tract_ndarray::Array4::from_shape_fn((1, 1, h as usize, w as usize), |(_, _, y, x)| { - // let pixel = resized.get_pixel(x as u32, y as u32)[0] as f32; - // (pixel / 255.0 - 0.5) / 0.5 - // }); - // - // let tensor = Tensor::from(array); - // - // Ok(tensor) } // 这段代码未来直接放入 ddddocr-core - fn process_model_output(&self, output: OcrOutput) -> Result { + fn process_model_output(&self, output: OcrOutput) -> Result { match output { OcrOutput::Indices(array1) => { // 对应你原来的 process_i64_tensor @@ -340,8 +318,7 @@ impl<'a> Ocr<'a> { None => true, } } - /// 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印 - /// 这里的 &str 完美借用了自 tokens,依然是彻底的零拷贝! + /// 返回当前生效的可用 token 列表。 pub fn valid_tokens(&self) -> Vec<&str> { let charset = &self.runtime.metadata().charset; let tokens = &charset.tokens; @@ -360,7 +337,6 @@ impl<'a> Ocr<'a> { None => self.runtime.metadata().charset.tokens.len(), } } - /// 变体 B 核心处理器:单次遍历 2D 视图,融合计算 Softmax、Argmax、置信度并输出概率大包 fn compute_f32_full_probability( &self, matrix_view: ArrayView2, @@ -502,7 +478,6 @@ impl<'a> Ocr<'a> { // Ok(OcrResult::Text(final_text)) // } // } - /// 获取有效字符索引列表 (用于外部验证或过滤) fn ctc_decode_to_string(&self, predicted_indices: &[i64]) -> String { println!("indices模型输出原始数据: {:?}", predicted_indices); let charset = &self.runtime.metadata().charset; @@ -542,8 +517,7 @@ impl<'a> Ocr<'a> { // 5. 字符映射 if let Some(char_str) = tokens.get(u_idx) { res.push_str(char_str); - } - else { + } else { warn!("警告: 预测索引 {} 超出字符集范围", u_idx); } } diff --git a/ddddocr-core/src/ocr/metadata.rs b/ddddocr-core/src/ocr/metadata.rs index 8d9a0eb..2d00e66 100644 --- a/ddddocr-core/src/ocr/metadata.rs +++ b/ddddocr-core/src/ocr/metadata.rs @@ -1,3 +1,5 @@ +//! OCR 模型元数据:归一化、缩放策略与模型信息。 + // ===================================================================== // 1. 辅助定义的枚举与结构体 // ===================================================================== @@ -5,6 +7,7 @@ use crate::ocr::Charset; use std::borrow::Cow; +/// 像素归一化策略。 #[derive(Debug, Clone, Copy)] pub enum Normalization { /// 映射到 [0.0, 1.0] -> pixel / 255.0 @@ -14,7 +17,6 @@ pub enum Normalization { } impl Normalization { - /// 统一归一化计算逻辑 #[inline(always)] pub fn normalize(&self, pixel: f32) -> f32 { match self { @@ -35,17 +37,15 @@ pub enum Resize { Square(u32), } +/// OCR 模型元数据:字符集、缩放策略、通道数与归一化配置。 #[derive(Debug, Clone)] pub struct ModelMetadata { - /// 字符集管理器 pub charset: Charset, /// 是否为单字识别模型 pub word: bool, - /// 预处理的缩放策略 pub resize: Resize, - /// 图像通道数 (1 或 3) + /// 图像通道数(1 或 3) pub channel: u8, - /// 新增:传递给核心业务使用的归一化配置 pub normalization: Normalization, } @@ -66,7 +66,7 @@ impl ModelMetadata { } } // --- 优雅的工厂模式构造器 --- - /// 通用的静态切片转换构造器 + /// 从静态字符切片构建元数据并自动生成字符集。 pub fn from_static_slice( slice: &[&'static str], word: bool, diff --git a/ddddocr-core/src/ocr/token_filter.rs b/ddddocr-core/src/ocr/token_filter.rs index 8034c9b..be00fb2 100644 --- a/ddddocr-core/src/ocr/token_filter.rs +++ b/ddddocr-core/src/ocr/token_filter.rs @@ -1,20 +1,22 @@ +//! 字符集限制:按字符属性或索引过滤识别范围。 + use std::borrow::Cow; -/// 字符集范围限制枚举 +/// 字符集校验上下文:当前 token 的文本与索引。 pub struct ValidationCtx<'a> { pub text: &'a str, // 当前 Token 的文本内容 pub token_id: usize, // 当前 Token 的 ID 索引 } -/// 统一的约束接口 +/// 字符集限制接口:决定某个 token 是否放行。 pub trait TokenFilter { fn matches(&self, ctx: &ValidationCtx) -> bool; - /// 预估容量提示,帮助精准开辟 Vec 内存 + /// 预估匹配数量的容量提示。 fn estimated_capacity(&self) -> usize { 128 } - /// 统一接管全量字符集的密集遍历、CTC Blank放行、去重、排序及空交集退化兜底 + /// 遍历全量字符集筛选可用索引(放行 CTC blank、排序去重、空交集返回 `None`)。 fn apply_to_charset(&self, tokens: &[Cow]) -> Option> { let mut has_any_match = false; let estimated_capacity = self.estimated_capacity(); @@ -58,6 +60,7 @@ pub trait TokenFilter { } } +/// 按字符属性限制:数字、大小写字母或自定义列表。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum CharRestrict { Digit, @@ -84,6 +87,7 @@ impl TokenFilter for CharRestrict { } } +/// 按索引限制:前 N 个、索引范围或索引列表。 #[derive(Debug, Clone, PartialEq, Eq)] pub enum IdRestrict { TopN(usize), @@ -128,7 +132,7 @@ impl<'a> TokenFilter for MultiOrRestrict<'a> { } } -/// 声明式宏:解决组合扩展痛苦 +/// 组合多个字符限制规则为「或」关系的快捷宏。 #[macro_export] macro_rules! any_of { // 场景 A:如果用户只传了一个规则,免去构建 Vec 的开销,直接返回其引用 diff --git a/ddddocr-core/src/slide.rs b/ddddocr-core/src/slide.rs index 7ba5067..b1af8fa 100644 --- a/ddddocr-core/src/slide.rs +++ b/ddddocr-core/src/slide.rs @@ -1,3 +1,5 @@ +//! 滑块验证码匹配:模板匹配与差异比较两种模式。 + use crate::error::{ImagePreprocessError, Result}; use crate::utils::image_convert::{ColorMode, image_to_ndarray}; use crate::utils::image_processor; @@ -12,8 +14,11 @@ use imageproc::region_labelling::{Connectivity, connected_components}; use imageproc::template_matching::{MatchTemplateMethod, match_template}; use ndarray::{ArrayView2, ArrayView3}; use std::fmt; + +/// 滑块匹配结果:检测中心坐标与置信度。 #[derive(Debug)] pub struct SlideResult { + /// 检测中心坐标 `[x, y]`。 pub target: [i32; 2], pub target_x: i32, pub target_y: i32, @@ -29,13 +34,14 @@ impl fmt::Display for SlideResult { } } +/// 滑块匹配服务:提供模板匹配与差异比较两种识别模式。 pub struct Slider; impl Slider { pub fn new() -> Self { Self } - /// 对应 Python: slide_match 滑块匹配接口 + /// 模板匹配滑块:在背景图中定位滑块中心(对应 Python 的 `slide_match`)。 pub fn slide_match( &self, target_image: &DynamicImage, @@ -48,8 +54,7 @@ impl Slider { self.perform_slide_match(target_array.view(), background_array.view(), simple_target) .map_err(Into::into) } - /// 对应 Python: slide_comparison 差异比较接口 - /// 用于比较带坑位的图片与原始背景图,定位差异点 + /// 差异比较滑块:对比带坑位的图与原始背景图,定位差异中心(对应 Python 的 `slide_comparison`)。 pub fn slide_comparison( &self, target_image: &DynamicImage, @@ -63,7 +68,7 @@ impl Slider { self.perform_slide_comparison(target_array.view(), background_array.view()) .map_err(Into::into) } - /// 对应 Python: _perform_slide_comparison + /// 差异比较核心实现(对应 Python 的 `_perform_slide_comparison`)。 pub fn perform_slide_comparison( &self, target: ArrayView3, @@ -133,7 +138,7 @@ impl Slider { } } - /// 对应 Python: _perform_slide_match + /// 模板匹配核心实现(对应 Python 的 `_perform_slide_match`)。 // 在 SlideEngine 中修改此入口进行测试 fn perform_slide_match( &self, @@ -180,9 +185,7 @@ impl Slider { Ok(self.edge_based_match(target_gray.view(), background_gray.view())) } } - /// 对应 Python: _simple_template_match - /// 使用 SAD (Sum of Absolute Differences) 算法 - /// 核心模板匹配:SAD + 有效像素过滤 + /// 简单模式模板匹配:直接对灰度图做归一化互相关(对应 Python 的 `_simple_template_match`)。 fn simple_template_match( &self, target: ArrayView2, @@ -221,8 +224,7 @@ impl Slider { } } - /// 对应 Python: _edge_based_match - /// 基于边缘检测的滑块匹配 (对齐 Python _edge_based_match) + /// 边缘模式模板匹配:基于 Canny 边缘检测后再匹配(对应 Python 的 `_edge_based_match`)。 pub fn edge_based_match( &self, target: ArrayView2, diff --git a/ddddocr-core/src/traits.rs b/ddddocr-core/src/traits.rs index d720ea0..c4e66fa 100644 --- a/ddddocr-core/src/traits.rs +++ b/ddddocr-core/src/traits.rs @@ -1,19 +1,20 @@ +//! 推理引擎统一抽象接口。 + use crate::error::TensorError; use crate::types::{ModelInfo, TensorInfo}; use crate::{DetOutput, ModelMetadata, OcrOutput}; use std::path::Path; - +/// 查询模型输入/输出信息的接口。 pub trait Info { fn input_info(&self) -> crate::error::Result>; fn output_info(&self) -> crate::error::Result>; fn model_info(&self) -> crate::error::Result; } -/// 核心层定义的统一推理引擎接口。 -/// 未来的 ddddocr-tract 和 ddddocr-ort 都必须实现这个 Trait +/// 推理引擎统一抽象接口,由 ddddocr-tract、ddddocr-ort 等引擎 crate 实现。 pub trait InferenceEngine { - /// 关联类型:具体的 Session 需要声明自己到底产出什么枚举 + /// 引擎产出的输出枚举(OCR 为 [`crate::OcrOutput`],检测为 [`crate::DetOutput`])。 type Output; fn inference( &self, @@ -21,12 +22,15 @@ pub trait InferenceEngine { ) -> crate::error::Result; } +/// OCR 引擎接口:输出 [`crate::OcrOutput`],并提供模型元数据。 pub trait OcrEngine: InferenceEngine + Info { fn metadata(&self) -> &ModelMetadata; } +/// 目标检测引擎接口:输出 [`crate::DetOutput`]。 pub trait DetEngine: InferenceEngine {} +/// 模型加载器:从本地路径或字节流构建引擎会话。 pub trait Loader { type Session; type Error; diff --git a/ddddocr-core/src/utils.rs b/ddddocr-core/src/utils.rs index 40342e4..084fcf4 100644 --- a/ddddocr-core/src/utils.rs +++ b/ddddocr-core/src/utils.rs @@ -1,3 +1,5 @@ +//! 图像与张量工具:加载、格式转换、图像处理与归一化。 + pub mod image_convert; mod image_helper; pub mod image_processor; diff --git a/ddddocr-core/src/utils/image_convert.rs b/ddddocr-core/src/utils/image_convert.rs index 5900116..27eef47 100644 --- a/ddddocr-core/src/utils/image_convert.rs +++ b/ddddocr-core/src/utils/image_convert.rs @@ -1,7 +1,10 @@ +//! 图像格式转换:DynamicImage 与 ndarray 数组互转。 + use crate::error::{ImagePreprocessError, Result}; use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Rgb, Rgba}; use ndarray::{Array3, ArrayViewD}; +/// 图像通道模式。 #[derive(Debug)] pub enum ColorMode { RGB, @@ -9,7 +12,7 @@ pub enum ColorMode { L, } -/// 封装数组转图像的逻辑, +/// 将 ndarray 数组转换为图像(自动识别 HWC 通道数)。 // 对应 Python 版 _numpy_to_pil_image pub fn ndarray_to_hwc_image(array: ArrayViewD) -> Result { let shape = array.shape(); diff --git a/ddddocr-core/src/utils/image_helper.rs b/ddddocr-core/src/utils/image_helper.rs index 8cec67d..d6a21bb 100644 --- a/ddddocr-core/src/utils/image_helper.rs +++ b/ddddocr-core/src/utils/image_helper.rs @@ -1,3 +1,5 @@ +//! 图像输入源:从路径、Base64、字节等统一加载图片。 + use crate::error::{DdddError, Result}; use crate::utils::image_convert::ndarray_to_hwc_image; use base64::{engine::general_purpose, Engine as _}; @@ -8,8 +10,9 @@ use std::fmt::{Debug, Formatter}; use std::fs; use std::path::Path; use std::path::PathBuf; +/// Base64 字符串包装。 pub struct Base64<'a>(pub &'a str); -/// 专属图像输入源转换器 +/// 统一图像输入源,包装加载完成的图像。 pub struct ImageSource { inner: DynamicImage, } @@ -21,6 +24,7 @@ impl ImageSource { } } +/// 从多种输入类型转换为 [`ImageSource`] 的转换接口。 pub trait TryFromImage: Sized { // 唯一的转换入口,通过目标类型来调用 fn try_from_image(value: T) -> Result; @@ -109,7 +113,7 @@ impl<'a> TryFromImage> for ImageSource { } } -/// 模拟 Python 的 load_image_from_input +/// 从任意受支持的输入类型加载图像。 #[allow(dead_code)] pub fn load_image_from_input(input: I) -> Result where @@ -119,7 +123,7 @@ where Ok(img) } -/// 将base64编码的图片转换为 DynamicImage +/// 将 Base64 编码的图片转换为 DynamicImage。 pub fn base64_to_image(b64_str: &str) -> Result { // 过滤掉可能存在的 base64 前缀,例如 "data:utils/png;base64," let clean_b64 = if let Some(pos) = b64_str.find(",") { diff --git a/ddddocr-core/src/utils/image_processor.rs b/ddddocr-core/src/utils/image_processor.rs index 39d103c..b1c3447 100644 --- a/ddddocr-core/src/utils/image_processor.rs +++ b/ddddocr-core/src/utils/image_processor.rs @@ -1,10 +1,12 @@ +//! 图像处理算法:OpenCV 风格的常用函数封装。 + use image::{imageops::FilterType, DynamicImage, GrayImage, ImageBuffer, Luma}; use ndarray::{azip, Array2, Array3, ArrayView2, ArrayView3}; use std::cmp::{max, min}; // 模拟openCV -/// 1. 计算两个数组的绝对差值 (对应 cv2.absdiff) +/// 计算两个 HWC 数组的绝对差值(对应 cv2.absdiff)。 pub fn abs_diff(a: &ArrayView3, b: &ArrayView3) -> Array3 { // 利用 ndarray 的 map_collect,生成差值的绝对值数组 // 或者直接使用 zip_mut_with 处理以减少内存分配 @@ -27,7 +29,7 @@ pub fn rgb_to_gray(rgb: ArrayView3) -> Array2 { }) } -/// 寻找匹配结果图中的最大值及其坐标 (模拟 cv2.minMaxLoc 的一部分) +/// 查找匹配结果图中的最大值及其坐标(对应 cv2.minMaxLoc)。 pub fn min_max_loc(result_map: &ImageBuffer, Vec>) -> (f32, (u32, u32)) { // 4. 找到最佳匹配位置 (对齐 cv2.minMaxLoc) let mut max_val: f32 = -1.0; @@ -48,8 +50,7 @@ pub fn min_max_loc(result_map: &ImageBuffer, Vec>) -> (f32, (u32, (max_val, max_loc) } -/// 1. 模拟 findContours 并获取最大面积区域的 Label -/// 返回 Option,如果找不到任何区域则返回 None +/// 模拟 findContours:返回面积最大的连通域标签,找不到时返回 `None`。 pub fn find_contours_and_max(labelled: &ImageBuffer, Vec>) -> Option { // 统计每个标签出现的频率(即面积) let mut max_label = 0; @@ -74,9 +75,7 @@ pub fn find_contours_and_max(labelled: &ImageBuffer, Vec>) -> Opt Some(max_label) } } -/// 根据目标连通域标签,计算其在图像中的外接矩形边界框(对应 `cv2.boundingRect`) -/// -/// 返回格式: `(min_x, min_y, width, height)` +/// 计算指定连通域标签的外接矩形(对应 cv2.boundingRect),返回 `(min_x, min_y, width, height)`。 pub fn bounding_rect( labelled: &ImageBuffer, Vec>, max_label: u32, @@ -109,9 +108,7 @@ pub fn calculate_center(top_left: (u32, u32), width: usize, height: usize) -> (i (center_x, center_y) } -/// 高性能转换:将 `ndarray` 2D 灰度视图规整为 `image::ImageBuffer` 格式 -/// -/// 放弃低效的逐像素显式嵌套循环,采用原生内存池直接构造,减少寻址开销 +/// 将 2D 灰度 ndarray 视图转换为灰度 ImageBuffer。 pub fn ndarray_to_luma8(array: ArrayView2) -> ImageBuffer, Vec> { let (height, width) = array.dim(); // 技巧:直接将已有的规整连续内存打平转换,或用 from_raw 包装 @@ -128,6 +125,7 @@ pub fn ndarray_to_luma8(array: ArrayView2) -> ImageBuffer, Vec> // 5. 核心高性能图像转换算法 (纯 Rust 编写) // ===================================================================== +/// RGB 像素转换为 OpenCV 风格的 HSV 值。 #[inline(always)] pub fn rgb_to_opencv_hsv(r: u8, g: u8, b: u8) -> (u8, u8, u8) { // 1. 规避高昂的除法,直接转为 f32 进行比对 @@ -174,15 +172,13 @@ pub fn rgb_to_opencv_hsv(r: u8, g: u8, b: u8) -> (u8, u8, u8) { (h_opencv, s_opencv, v_opencv) } -/// 对应 Python 的 convert_to_grayscale -/// 将图像转换为灰度图 (L模式) +/// 将图像转换为灰度图(L 模式)。 pub fn convert_to_grayscale(image: &DynamicImage) -> GrayImage { // Rust utils 库的 to_luma8 会根据标准的亮度公式进行转换 image.to_luma8() } -/// 对应 Python 的 resize_image -/// 调整图像尺寸。当前版本仅实现 keep_aspect_ratio=false +/// 按指定宽高调整图像尺寸。 pub fn resize_image( image: &DynamicImage, target_width: u32, diff --git a/ddddocr-core/src/utils/tensor_transform.rs b/ddddocr-core/src/utils/tensor_transform.rs index 8eddc7e..4b45bb0 100644 --- a/ddddocr-core/src/utils/tensor_transform.rs +++ b/ddddocr-core/src/utils/tensor_transform.rs @@ -1,7 +1,10 @@ +//! 张量变换:将异构形状的模型输出规整为统一格式。 + use crate::OcrOutput; use crate::error::{Result, TensorError}; use ndarray::s; -/// 核心层复用资产:将异构的动态维度矩阵转化为标准 OCR 2D Logits 矩阵 + +/// 将异构形状的模型输出规整为标准 `[Steps, Classes]` Logits 矩阵。 pub fn normalize_ocr_logits(array: ndarray::ArrayViewD, shape: &[usize]) -> Result { let (steps, classes, data_dyn_view) = match shape.len() { 3 => {