diff --git a/ddddocr-core/src/det.rs b/ddddocr-core/src/det.rs index b0c80dc..c1383f7 100644 --- a/ddddocr-core/src/det.rs +++ b/ddddocr-core/src/det.rs @@ -3,4 +3,3 @@ mod executor; pub use builder::DetBuilder; pub use executor::{DetectionResult, Detector}; -// pub use ddddocr_tract::det::session::DetSession; diff --git a/ddddocr-core/src/det/builder.rs b/ddddocr-core/src/det/builder.rs index 2469738..7d6a0a2 100644 --- a/ddddocr-core/src/det/builder.rs +++ b/ddddocr-core/src/det/builder.rs @@ -5,7 +5,7 @@ use crate::traits::DetEngine; pub struct DetBuilder; impl DetBuilder { - fn build(self, session: &E) -> Detector<'_> { + fn build_with(self, session: &E) -> Detector<'_> { Detector { session } } } diff --git a/ddddocr-core/src/error.rs b/ddddocr-core/src/error.rs index b0e2cb3..9aaa909 100644 --- a/ddddocr-core/src/error.rs +++ b/ddddocr-core/src/error.rs @@ -1,130 +1,44 @@ -pub(crate) const MODEL_DOWNLOAD_HELP: &str = "\ -================================================================================ -[ddddocr-rust] 错误:未找到默认的模型文件! --------------------------------------------------------------------------------- -由于打包体积限制,本库未内置 ONNX 模型。请按照以下步骤操作: - -1. 前往官方 GitHub 下载对应的模型权重: - - OCR 模型: https://github.com/sml2h3/ddddocr/raw/master/ddddocr/common_sml2h3_f32.onnx - - DET 模型: https://github.com/sml2h3/ddddocr/raw/master/ddddocr/common_det.onnx - -2. 配置加载方式(二选一): - A. 【推荐】设置环境变量指向您下载的文件: - Linux/macOS: export DDDD_OCR_MODEL=\"/path/to/common_sml2h3_f32.onnx\" - Windows (CMD): set DDDD_OCR_MODEL=C:\\path\\to\\common_sml2h3_f32.onnx - Windows (PowerShell): $env:DDDD_OCR_MODEL=\"C:\\path\\to\\common_sml2h3_f32.onnx\" - - B. 或者直接将模型文件重命名并放置在您运行程序的“当前工作目录”或“可执行文件同级目录”下。 -================================================================================"; +//! 分层错误类型:预处理、推理、解码三阶段的强类型错误。 use thiserror::Error; -// -// #[derive(Error, Debug)] -// pub enum DdddError { -// // 【新增】专门处理文件读取、路径不存在等原生 I/O 错误 -// #[error("系统网络或文件 I/O 异常: {0}")] -// Io(#[from] std::io::Error), -// -// #[error("图像预处理失败: {0}")] -// PreprocessError(#[from] ImagePreprocessReason), -// -// #[error("模型推理引擎内部发生异常: {0}")] -// EngineError(#[from] anyhow::Error), -// -// #[error("CTC 解码错误: {0}")] -// DecodeError(String), -// -// #[error("维度转换失败,预期维度 {expected},实际形状为 {actual:?}")] -// DimensionMismatch { -// expected: String, -// actual: Vec, -// }, -// -// #[error("内存不连续,无法执行零拷贝操作")] -// NonContiguousMemory, -// -// #[error("未知的模型输出格式")] -// UnknownOutputFormat, -// -// #[error("解析节点 Fact 失败")] -// InternalError(String), -// } -// -// /// 专门服务于预处理的子错误枚举,保留全部底层上下文 -// #[derive(Error, Debug)] -// pub enum ImagePreprocessReason { -// #[error("图片加载或文件 I/O 失败: {0}")] -// ImageIo(#[from] image::ImageError), -// -// #[error("图片转矩阵矩阵(ndarray)失败: {0}")] -// NdarrayError(#[from] ndarray::ShapeError), -// -// #[error("Base64 解码失败: {0}")] -// Base64(#[from] base64::DecodeError), -// -// #[error("Base64 头部格式不正确,缺少 ';base64,' 分隔符")] -// InvalidBase64Header, -// -// #[error("不支持的通道数: {0}")] -// UnsupportedChannels(usize), -// -// #[error("其他预处理错误: {0}")] -// Custom(String), -// } -/// 统一用我们自己的 DdddError 包装 Result +/// 全局统一的 `Result` 别名,默认错误类型为 [`DdddError`]。 // pub type Result = std::result::Result; pub type Result = std::result::Result; -// ===================================================================== -// 1. 顶层全局 Error 分流器 (去 anyhow 化,完全基于标准库/自定义类型) -// ===================================================================== +/// 顶层错误类型,聚合本库各阶段错误。 #[derive(Error, Debug)] pub enum DdddError { - // /// 系统文件、网络等原生 I/O 异常 (高优先级自动转换) - // #[error("系统网络或文件 I/O 异常: {0}")] - // Io(#[from] std::io::Error), - /// 图像预处理阶段发生异常 #[error("图像预处理失败: {0}")] Preprocess(#[from] ImagePreprocessError), - /// 推理引擎与张量操作阶段发生异常 #[error("推理与模型输入/输出张量异常: {0}")] Inference(#[from] TensorError), - /// 算法后处理解码阶段发生异常 #[error("后处理解码错误: {0}")] Decode(#[from] DecodeError), - /// 框架内部不可恢复的逻辑断言错误(例如解析节点 Fact 失败) + /// 框架内部不可恢复的逻辑断言错误(如解析节点 Fact 失败)。 #[error("内部严重逻辑错误: {0}")] Internal(String), - /// 【流派核心】接替 anyhow::Error 的用户自定义扩展错误 - /// 承载任何第三方扩展、解密、特定预处理插件在执行时产生的自定义错误 + + /// 用户自定义扩展错误,用于包装第三方插件产生的错误。 #[error("用户自定义扩展错误: {0}")] Other(#[source] Box), } -// ===================================================================== -// 2. 子领域 A: 图像预处理错误类型 -// ===================================================================== - +/// 图像预处理阶段错误类型。 #[derive(Error, Debug)] pub enum ImagePreprocessError { - // #[error("图片加载或解码失败: {0}")] - // ImageIo(#[from] image::ImageError), - // image_io #[error("图片转矩阵(ndarray)基础操作失败: {0}")] Ndarray(#[from] ndarray::ShapeError), - // image_io + #[error("图像矩阵维度不合规!预期: {expected},实际图像形状: {actual:?}")] InvalidDimensions { expected: String, actual: Vec, }, - // image_io - /// 从 ndarray 原始数据构建图像缓冲区时,缓冲区长度与分辨率/通道数不匹配 #[error( "图像缓冲区长度不匹配!预期大小: {expected},实际大小: {actual} (分辨率: {width}x{height}, 通道数: {channels})" )] @@ -135,24 +49,21 @@ pub enum ImagePreprocessError { height: u32, channels: usize, }, - // image_io + #[error("不支持的图像通道数: {0} (仅支持单通道灰度L、3通道RGB、4通道RGBA)")] UnsupportedChannels(usize), - // ================= 新增:针对 HSV 和 Preset 的强类型错误 ================= - /// HSV 颜色区间非法 (例如 H > 180 或 lower > upper) + #[error("HSV 颜色区间参数非法: {0}")] InvalidHsvRange(String), - /// 不支持或未知的颜色预设名称 #[error("不支持的颜色预设名称: {0}")] UnknownColorPreset(String), - /// 颜色过滤器/预处理规则配置非法导致失败 #[error("颜色过滤器配置无效或初始化失败: {0}")] FilterConfigInvalid(String), #[error("图像维度不匹配!{0}")] - MismatchDimensions (String), + MismatchDimensions(String), #[error("滑块模板尺寸 [{target_w}x{target_h}] 大于背景图 [{bg_w}x{bg_h}]")] TargetExceedsBackground { @@ -161,88 +72,46 @@ pub enum ImagePreprocessError { bg_w: usize, bg_h: usize, }, - // #[error("Base64 解码失败: {0}")] - // Base64(#[from] base64::DecodeError), - // - // #[error("Base64 头部格式不正确,缺少 ';base64,' 分隔符")] - // InvalidBase64Header, - - // #[error("其他预处理错误: {0}")] - // Other(String), } -// ===================================================================== -// 3. 子领域 B: 推理与张量操作错误类型 -// ===================================================================== - +/// 推理与张量操作阶段错误类型。 #[derive(Error, Debug)] pub enum TensorError { - /// 替换原有的 anyhow::Error,明确将 Tract/ONNX 引擎底层报错序列化为干净的 String #[error("推理引擎内部发生异常: {0}")] Engine(String), - /// 模型张量维度不匹配 (原有的顶层 DimensionMismatch 被优雅地归入本模块) #[error("模型张量维度不匹配!预期: {expected},实际 Tensor 形状: {actual:?}")] DimensionMismatch { expected: String, actual: Vec, }, - /// 新增:针对后处理 Logits 矩阵变形(Reshape)失败的精细化错误 - /// 直接包装 ndarray::ShapeError,保留强类型,完美支持 match #[error("OCR Logits 矩阵变形失败: {0}")] LogitsDimensionMismatch(#[from] ndarray::ShapeError), - /// 张量内存布局不是连续的 #[error("内存不连续,无法执行零拷贝操作")] NonContiguousMemory, - /// 模型的输出数据类型或格式不受支持 #[error("未知的模型输出格式")] UnknownOutputFormat, } -// ===================================================================== -// 4. 子领域 C: 算法解码错误类型 -// ===================================================================== - +/// 算法解码阶段错误类型。 #[derive(Error, Debug)] pub enum DecodeError { - /// CTC 解码器解码过程中的逻辑报错 #[error("CTC 解码异常: {0}")] Ctc(String), } -// ===================================================================== -// 5. 【自定义错误安全注入】不使用全局 `#[from]`,采用显式包装避免特化冲突 -// ===================================================================== - impl DdddError { - /// 提供类似 std::io::Error::new 的构造函数,方便手动且无痛地包装任意第三方错误 + /// 手动包装任意第三方错误为 [`DdddError::Other`]。 pub fn new(error: E) -> Self where E: Into>, { DdddError::Other(error.into()) } - // ----------------------------------------------------------------- - // 2.3 优化:提供一键判断与转换的快捷方法(Downcasting Helpers) - // ----------------------------------------------------------------- - /// 快速判断是否是系统 I/O 错误 - // pub fn is_io_error(&self) -> bool { - // matches!(self, DdddError::Io(_)) - // } - - /// 尝试将错误转换为引用形式的 `std::io::Error` - // pub fn as_io_error(&self) -> Option<&std::io::Error> { - // match self { - // DdddError::Io(err) => Some(err), - // _ => None, - // } - // } - - /// 快速判断是否是预处理阶段的图片维度不合规错误 pub fn is_invalid_dimensions(&self) -> bool { matches!( self, @@ -250,25 +119,10 @@ impl DdddError { ) } - /// 快速判断是否是因为图片通道数不合规导致的失败 pub fn is_unsupported_channels(&self) -> bool { matches!( self, DdddError::Preprocess(ImagePreprocessError::UnsupportedChannels(_)) ) } - - // 提取出底层最原始的那个错误(无论是 IO、预处理、推理、还是第三方扩展错误) - // 方便外层统一打印更深层的 `source` 链条 - // pub fn source_error(&self) -> Option<&(dyn std::error::Error + 'static)> { - // use std::error::Error; - // match self { - // // DdddError::Io(err) => Some(err), - // DdddError::Preprocess(err) => Some(err), - // DdddError::Inference(err) => Some(err), - // DdddError::Decode(err) => Some(err), - // DdddError::Other(err) => Some(err.as_ref()), - // DdddError::Internal(_) => None, // Internal 内部目前只有 String,没有底层的 Error source - // } - // } } diff --git a/ddddocr-core/src/lib.rs b/ddddocr-core/src/lib.rs index 09a5526..ba5a5e0 100644 --- a/ddddocr-core/src/lib.rs +++ b/ddddocr-core/src/lib.rs @@ -1,24 +1,35 @@ +//! # ddddocr-core +//! +//! `ddddocr-rs` 的核心库:提供与具体推理引擎解耦的 OCR 识别、目标检测(det)与滑块匹配(slide)能力。 +//! 推理能力由 [`traits::InferenceEngine`]、[`traits::OcrEngine`]、[`traits::DetEngine`] 抽象, +//! 由 `ddddocr-tract`、`ddddocr-ort` 等引擎 crate 实现。 +//! +//! 完整可运行示例见 `ddddocr-core/examples/quick_start.rs`。 + mod det; +/// 分层错误类型。 pub mod error; mod ocr; mod slide; +/// 图像加载、转换与处理工具。 pub mod utils; +/// 模型输入输出信息等共享类型。 pub mod types; +/// 推理引擎统一抽象接口。 pub mod traits; pub use crate::det::{DetBuilder, DetectionResult, Detector}; pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize}; pub use crate::slide::{SlideResult, Slider}; - -// DetSession - +/// OCR 模型的统一输出枚举,由推理引擎产出,供 [`Ocr`] 后处理。 pub enum OcrOutput { - Indices(ndarray::Array1), // 拥有完整所有权的 1维数组,可任意传递和返回 + Indices(ndarray::Array1), Logits(ndarray::Array2), } -/// 2. 目标检测专属的、编译期安全的输出枚举 + +/// 目标检测模型的统一输出枚举,由推理引擎产出,供 [`Detector`] 后处理。 pub enum DetOutput { - Detection(ndarray::Array3), // 拥有完整所有权的 2维矩阵,可任意传递和返回 + Detection(ndarray::Array3), } diff --git a/ddddocr-core/src/ocr.rs b/ddddocr-core/src/ocr.rs index 3eac2f1..7245367 100644 --- a/ddddocr-core/src/ocr.rs +++ b/ddddocr-core/src/ocr.rs @@ -10,4 +10,3 @@ pub use charset::Charset; pub use executor::{Ocr, OcrResult}; pub use metadata::{ModelMetadata, Normalization, Resize}; pub use token_filter::TokenFilter; -// pub use ddddocr_tract::session::OcrSession; diff --git a/ddddocr-core/src/ocr/builder.rs b/ddddocr-core/src/ocr/builder.rs index 628448a..bf4f3dd 100644 --- a/ddddocr-core/src/ocr/builder.rs +++ b/ddddocr-core/src/ocr/builder.rs @@ -49,7 +49,7 @@ impl OcrBuilder { self.charset_restrict = Some(Box::new(restrict)); self } - pub fn runner(self, runtime: &E) -> Ocr<'_> { + pub fn build_with(self, runtime: &E) -> Ocr<'_> { // 1. 原地解析颜色过滤器 let final_color_ranges = match &self.color_filter { Some(filter) => filter.collect_to_vec(), diff --git a/ddddocr-core/src/ocr/token_filter.rs b/ddddocr-core/src/ocr/token_filter.rs index dd5f58c..8034c9b 100644 --- a/ddddocr-core/src/ocr/token_filter.rs +++ b/ddddocr-core/src/ocr/token_filter.rs @@ -13,7 +13,7 @@ pub trait TokenFilter { fn estimated_capacity(&self) -> usize { 128 } - /// 【新引入的架构级核心方法】 + /// 统一接管全量字符集的密集遍历、CTC Blank放行、去重、排序及空交集退化兜底 fn apply_to_charset(&self, tokens: &[Cow]) -> Option> { let mut has_any_match = false; @@ -127,9 +127,8 @@ impl<'a> TokenFilter for MultiOrRestrict<'a> { self.filters.iter().map(|f| f.estimated_capacity()).sum() } } -// ===================================================================== -// 声明式宏:替代 `+` 运算符,解决组合扩展痛苦 -// ===================================================================== + +/// 声明式宏:解决组合扩展痛苦 #[macro_export] macro_rules! any_of { // 场景 A:如果用户只传了一个规则,免去构建 Vec 的开销,直接返回其引用 @@ -137,7 +136,7 @@ macro_rules! any_of { &$only as &dyn $crate::TokenFilter }; - // 场景 B:如果用户传入了多个规则,自动织成一张静态组合网 + // 场景 B:如果用户传入了多个规则,自动组合 ($($filter:expr),+ $(,)?) => { &$crate::MultiOrRestrict { filters: vec![ $( &$filter as &dyn $crate::TokenFilter ),+ ] diff --git a/ddddocr-core/src/types.rs b/ddddocr-core/src/types.rs index 3984400..36ab7e8 100644 --- a/ddddocr-core/src/types.rs +++ b/ddddocr-core/src/types.rs @@ -1,25 +1,27 @@ -#[derive(Debug,Clone)] -pub enum TensorType{ +//! 模型输入输出信息等共享类型,由 [`crate::traits::Info`] 接口返回。 + +/// 张量元素的数据类型标记。 +#[derive(Debug, Clone)] +pub enum TensorType { F32, I64, - Other + Other, } -/// 明确命名为 AxisDim,代表模型某一个轴的维度特征 + +/// 模型某个轴的维度特征:静态数值或动态符号。 #[derive(Clone, PartialEq, Eq)] pub enum AxisDim { - /// 静态固定维度(如通道数固定为 1,高度固定为 64) Static(usize), - /// 动态符号维度(如宽度是动态的 "image_width") Dynamic(String), } impl AxisDim { - /// 便捷方法:判断是否为动态维度 pub fn is_dynamic(&self) -> bool { matches!(self, AxisDim::Dynamic(_)) } } -/// 自定义 Debug 格式化输出,彻底融化套娃外壳,保证日志干净漂亮 + +/// 自定义 `Debug` 输出:静态维度显示数值,动态维度显示符号名。 impl std::fmt::Debug for AxisDim { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -28,19 +30,20 @@ impl std::fmt::Debug for AxisDim { } } } -/// 模拟 Python 的 input_info 和 output_info 结构 + +/// 单个张量(输入或输出)的名称、形状与数据类型描述。 #[derive(Debug, Clone)] pub struct TensorInfo { pub name: String, - pub shape: Vec, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号 - pub tensor_type: TensorType, // 对应 Python 的 type + pub shape: Vec, + pub tensor_type: TensorType, } -/// 最终返回的模型完整信息 +/// 模型的完整输入/输出信息。 #[derive(Debug, Clone)] pub struct ModelInfo { pub inputs: Vec, pub outputs: Vec, - /// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎) + /// 硬件执行提供者(`None` 表示使用引擎默认后端)。 pub providers: Option>, -} \ No newline at end of file +} diff --git a/ddddocr-ort/src/det/session.rs b/ddddocr-ort/src/det/session.rs index 93bcf21..ee55b78 100644 --- a/ddddocr-ort/src/det/session.rs +++ b/ddddocr-ort/src/det/session.rs @@ -5,7 +5,6 @@ use ddddocr_core::traits::{DetEngine, InferenceEngine}; use ndarray::Ix3; use ort::inputs; use ort::value::TensorRef; -// use tract_onnx::prelude::{tvec, IntoTensor, Tensor}; #[derive(Debug)] pub struct DetRuntime { diff --git a/ddddocr-ort/tests/ocr_test.rs b/ddddocr-ort/tests/ocr_test.rs index d9411e8..23d2bad 100644 --- a/ddddocr-ort/tests/ocr_test.rs +++ b/ddddocr-ort/tests/ocr_test.rs @@ -105,7 +105,7 @@ fn save_rust_result(result: &ImageBuffer, Vec>, filename: &str) { } #[test] fn test_full_classification() { - let model = OrtModelLoader::default().use_gpu(false) + let session = OrtModelLoader::default().use_gpu(false) .build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx") // .build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_old.onnx") .expect("模型加载失败"); @@ -117,7 +117,7 @@ fn test_full_classification() { Normalization::MinusOneToOne, ); // 1. 初始化模型 - let ocr = OcrRuntime::new(model, metadata); + let ocr = OcrRuntime::new(session, metadata); // 2. 加载测试图片 let img = image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在"); @@ -132,7 +132,7 @@ fn test_full_classification() { // .predict(&img) // .expect("识别过程出错") // .into_text(); - let res=Ocr::builder().runner(&ocr).predict(&img).expect("s").into_text(); + let res=Ocr::builder().build_with(&ocr).predict(&img).expect("s").into_text(); // println!("识别结果: {}", result); println!("识别结果: {}", res); @@ -141,10 +141,10 @@ fn test_full_classification() { } #[test] fn test_det_load() -> anyhow::Result<()> { - let det_model = OrtModelLoader::default() + let session = OrtModelLoader::default() .build_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx") .expect("模型加载失败"); - let det = DetRuntime::new(det_model); + let det = DetRuntime::new(session); 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))?; diff --git a/ddddocr-tract/tests/ocr_test.rs b/ddddocr-tract/tests/ocr_test.rs index 26a3616..be5c7cc 100644 --- a/ddddocr-tract/tests/ocr_test.rs +++ b/ddddocr-tract/tests/ocr_test.rs @@ -128,7 +128,7 @@ fn test_full_classification() { // .expect("识别过程出错") // .into_text(); let result = Ocr::builder() - .runner(&ocr_runtime) + .build_with(&ocr_runtime) .predict(&img) .expect("识别过程出错") .into_text();