From 4fd38022fde4df1f4ce84dd066f922230fcc116f Mon Sep 17 00:00:00 2001 From: CNWei Date: Sat, 11 Jul 2026 17:38:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20core=20?= =?UTF-8?q?=E5=8C=85=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E5=B9=B6=E6=B6=88?= =?UTF-8?q?=E9=99=A4=E6=97=A7=E7=89=88=20mod.rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化 剥离 models,algo 层并平铺业务模块 - 重构 统一使用现代 filename.rs + 文件夹结构替代旧版 mod.rs --- ddddocr-core/src/algo/mod.rs | 3 - .../src/{models/det/mod.rs => det.rs} | 0 ddddocr-core/src/{models => }/det/builder.rs | 2 +- ddddocr-core/src/{models => }/det/executor.rs | 0 ddddocr-core/src/lib.rs | 16 +- ddddocr-core/src/models/mod.rs | 2 - .../src/{models/ocr/mod.rs => ocr.rs} | 0 ddddocr-core/src/{models => }/ocr/builder.rs | 6 +- .../src/{models => }/ocr/color_filter.rs | 2 +- ddddocr-core/src/{models => }/ocr/executor.rs | 4 +- ddddocr-core/src/{models => }/ocr/metadata.rs | 0 .../src/{models => }/ocr/token_filter.rs | 0 ddddocr-core/src/{algo => }/slide.rs | 20 +- ddddocr-core/src/{utils/mod.rs => utils.rs} | 4 +- ddddocr-core/src/utils/image_proc.rs | 174 ---------------- ddddocr-core/src/utils/image_processor.rs | 196 ++++++++++++++++-- ddddocr-tract/src/{det/mod.rs => det.rs} | 0 ddddocr-tract/src/det/session.rs | 11 +- ddddocr-tract/src/loader.rs | 12 -- ddddocr-tract/src/{ocr/mod.rs => ocr.rs} | 0 ddddocr-tract/tests/char_slice.rs | 4 +- ddddocr-tract/tests/ocr_test.rs | 4 +- 22 files changed, 209 insertions(+), 251 deletions(-) delete mode 100644 ddddocr-core/src/algo/mod.rs rename ddddocr-core/src/{models/det/mod.rs => det.rs} (100%) rename ddddocr-core/src/{models => }/det/builder.rs (92%) rename ddddocr-core/src/{models => }/det/executor.rs (100%) delete mode 100644 ddddocr-core/src/models/mod.rs rename ddddocr-core/src/{models/ocr/mod.rs => ocr.rs} (100%) rename ddddocr-core/src/{models => }/ocr/builder.rs (93%) rename ddddocr-core/src/{models => }/ocr/color_filter.rs (99%) rename ddddocr-core/src/{models => }/ocr/executor.rs (99%) rename ddddocr-core/src/{models => }/ocr/metadata.rs (100%) rename ddddocr-core/src/{models => }/ocr/token_filter.rs (100%) rename ddddocr-core/src/{algo => }/slide.rs (93%) rename ddddocr-core/src/{utils/mod.rs => utils.rs} (55%) delete mode 100644 ddddocr-core/src/utils/image_proc.rs rename ddddocr-tract/src/{det/mod.rs => det.rs} (100%) rename ddddocr-tract/src/{ocr/mod.rs => ocr.rs} (100%) diff --git a/ddddocr-core/src/algo/mod.rs b/ddddocr-core/src/algo/mod.rs deleted file mode 100644 index 16155cf..0000000 --- a/ddddocr-core/src/algo/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod slide; - -pub use slide::{SlideResult, Slider}; diff --git a/ddddocr-core/src/models/det/mod.rs b/ddddocr-core/src/det.rs similarity index 100% rename from ddddocr-core/src/models/det/mod.rs rename to ddddocr-core/src/det.rs diff --git a/ddddocr-core/src/models/det/builder.rs b/ddddocr-core/src/det/builder.rs similarity index 92% rename from ddddocr-core/src/models/det/builder.rs rename to ddddocr-core/src/det/builder.rs index 72a4a8e..27d4520 100644 --- a/ddddocr-core/src/models/det/builder.rs +++ b/ddddocr-core/src/det/builder.rs @@ -1,4 +1,4 @@ -use crate::models::det::executor::Detector; +use crate::det::executor::Detector; // use ddddocr_tract::det::session::DetSession; use crate::DetEngine; diff --git a/ddddocr-core/src/models/det/executor.rs b/ddddocr-core/src/det/executor.rs similarity index 100% rename from ddddocr-core/src/models/det/executor.rs rename to ddddocr-core/src/det/executor.rs diff --git a/ddddocr-core/src/lib.rs b/ddddocr-core/src/lib.rs index cc4a6c1..bdb1f08 100644 --- a/ddddocr-core/src/lib.rs +++ b/ddddocr-core/src/lib.rs @@ -1,13 +1,15 @@ -mod algo; +pub mod det; pub mod error; -pub mod models; +pub mod ocr; +mod slide; pub mod utils; -pub use crate::algo::{SlideResult, Slider}; use crate::error::Result; -pub use crate::models::det::{DetBuilder, DetectionResult, Detector}; -pub use crate::models::ocr::{Ocr, OcrBuilder, OcrResult}; -pub use models::ocr::metadata::ModelMetadata; + +pub use crate::slide::{SlideResult, Slider}; +pub use crate::det::{DetBuilder, DetectionResult, Detector}; +pub use crate::ocr::{Ocr, OcrBuilder, OcrResult}; +pub use ocr::metadata::ModelMetadata; // DetSession pub enum OcrOutput { @@ -33,5 +35,3 @@ pub trait OcrEngine: InferenceEngine { } pub trait DetEngine: InferenceEngine {} - - diff --git a/ddddocr-core/src/models/mod.rs b/ddddocr-core/src/models/mod.rs deleted file mode 100644 index 7a43b5d..0000000 --- a/ddddocr-core/src/models/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod ocr; -pub mod det; \ No newline at end of file diff --git a/ddddocr-core/src/models/ocr/mod.rs b/ddddocr-core/src/ocr.rs similarity index 100% rename from ddddocr-core/src/models/ocr/mod.rs rename to ddddocr-core/src/ocr.rs diff --git a/ddddocr-core/src/models/ocr/builder.rs b/ddddocr-core/src/ocr/builder.rs similarity index 93% rename from ddddocr-core/src/models/ocr/builder.rs rename to ddddocr-core/src/ocr/builder.rs index f3c0b4b..cfc0329 100644 --- a/ddddocr-core/src/models/ocr/builder.rs +++ b/ddddocr-core/src/ocr/builder.rs @@ -1,7 +1,7 @@ -use crate::models::ocr::executor::Ocr; +use crate::ocr::executor::Ocr; // use ddddocr_tract::session::OcrSession; -use crate::models::ocr::color_filter::ColorFilter; -use crate::models::ocr::token_filter::TokenFilter; +use crate::ocr::color_filter::ColorFilter; +use crate::ocr::token_filter::TokenFilter; use crate::OcrEngine; pub struct OcrBuilder { diff --git a/ddddocr-core/src/models/ocr/color_filter.rs b/ddddocr-core/src/ocr/color_filter.rs similarity index 99% rename from ddddocr-core/src/models/ocr/color_filter.rs rename to ddddocr-core/src/ocr/color_filter.rs index 1ba9889..da18be5 100644 --- a/ddddocr-core/src/models/ocr/color_filter.rs +++ b/ddddocr-core/src/ocr/color_filter.rs @@ -1,4 +1,4 @@ -use crate::utils::image_proc::rgb_to_opencv_hsv; +use crate::utils::image_processor::rgb_to_opencv_hsv; use anyhow::anyhow; use image::{DynamicImage, ImageBuffer, Rgb}; use std::str::FromStr; diff --git a/ddddocr-core/src/models/ocr/executor.rs b/ddddocr-core/src/ocr/executor.rs similarity index 99% rename from ddddocr-core/src/models/ocr/executor.rs rename to ddddocr-core/src/ocr/executor.rs index 0b9d532..fd7071f 100644 --- a/ddddocr-core/src/models/ocr/executor.rs +++ b/ddddocr-core/src/ocr/executor.rs @@ -1,6 +1,6 @@ -use crate::models::ocr::metadata::Resize; +use crate::ocr::metadata::Resize; -use crate::models::ocr::color_filter::{HsvRange, apply_to_image}; +use crate::ocr::color_filter::{HsvRange, apply_to_image}; // use ddddocr_tract::session::{ModelOutput, OcrSession}; use crate::utils::image_io::png_rgba_white_preprocess; use crate::utils::image_processor::{convert_to_grayscale, resize_image}; diff --git a/ddddocr-core/src/models/ocr/metadata.rs b/ddddocr-core/src/ocr/metadata.rs similarity index 100% rename from ddddocr-core/src/models/ocr/metadata.rs rename to ddddocr-core/src/ocr/metadata.rs diff --git a/ddddocr-core/src/models/ocr/token_filter.rs b/ddddocr-core/src/ocr/token_filter.rs similarity index 100% rename from ddddocr-core/src/models/ocr/token_filter.rs rename to ddddocr-core/src/ocr/token_filter.rs diff --git a/ddddocr-core/src/algo/slide.rs b/ddddocr-core/src/slide.rs similarity index 93% rename from ddddocr-core/src/algo/slide.rs rename to ddddocr-core/src/slide.rs index bf6896e..e9f7ad5 100644 --- a/ddddocr-core/src/algo/slide.rs +++ b/ddddocr-core/src/slide.rs @@ -1,6 +1,6 @@ -use crate::utils::image_proc; -use crate::utils::image_proc::{abs_diff, min_max_loc, ndarray_to_luma8, rgb_to_gray}; use crate::utils::image_io::image_to_ndarray; +use crate::utils::image_processor; +use crate::utils::image_processor::{abs_diff, min_max_loc, ndarray_to_luma8, rgb_to_gray}; use anyhow::{Result, anyhow}; use image::DynamicImage; use image::Luma; @@ -10,8 +10,8 @@ use imageproc::edges::canny; use imageproc::morphology::{close, open}; use imageproc::region_labelling::{Connectivity, connected_components}; use imageproc::template_matching::{MatchTemplateMethod, match_template}; -use std::fmt; use ndarray::{ArrayView2, ArrayView3}; +use std::fmt; #[derive(Debug)] pub struct SlideResult { pub target: [i32; 2], @@ -67,7 +67,6 @@ impl Slider { target: ArrayView3, background: ArrayView3, ) -> Result { - // 1. 计算差异数组 (复用 cv2::absdiff) let (th, tw, tc) = target.dim(); let (bh, bw, bc) = background.dim(); @@ -111,11 +110,12 @@ impl Slider { // // 统计每个标签出现的频率(即面积) // 4. 寻找最大连通区域 (对应 findContours + max area) - if let Some(max_label) = image_proc::find_contours_and_max(&labelled) { + if let Some(max_label) = image_processor::find_contours_and_max(&labelled) { // 5. 计算最大区域的边界框 (对应 cv2.boundingRect) - let (x, y, w, h) = image_proc::bounding_rect(&labelled, max_label); + let (x, y, w, h) = image_processor::bounding_rect(&labelled, max_label); // 6. 计算中心点 (调用之前封装的 calculate_center) - let (center_x, center_y) = image_proc::calculate_center((x, y), w as usize, h as usize); + let (center_x, center_y) = + image_processor::calculate_center((x, y), w as usize, h as usize); Ok(SlideResult { target: [center_x, center_y], @@ -206,7 +206,8 @@ impl Slider { // 4. 计算中心点 (与 Python 逻辑完全一致) let (th, tw) = target.dim(); - let (center_x, center_y) = image_proc::calculate_center(max_loc, tw as usize, th as usize); + let (center_x, center_y) = + image_processor::calculate_center(max_loc, tw as usize, th as usize); // println!("Rust Target Width (tw): {}", tw); // println!("Rust Best Max Loc X: {}", max_loc.0); // println!("Rust Final Center X: {}", center_x); @@ -251,7 +252,8 @@ impl Slider { // 5. 计算中心位置 (对齐 Python 逻辑) // target_w, target_h 来自输入数组的维度 let (th, tw) = target.dim(); - let (center_x, center_y) = image_proc::calculate_center(max_loc, tw as usize, th as usize); + let (center_x, center_y) = + image_processor::calculate_center(max_loc, tw as usize, th as usize); // 打印调试信息,方便与 Python 对比 // println!("Edge Match: max_val: {}, max_loc: {:?}", max_val, max_loc); diff --git a/ddddocr-core/src/utils/mod.rs b/ddddocr-core/src/utils.rs similarity index 55% rename from ddddocr-core/src/utils/mod.rs rename to ddddocr-core/src/utils.rs index 0451e38..f8020b5 100644 --- a/ddddocr-core/src/utils/mod.rs +++ b/ddddocr-core/src/utils.rs @@ -1,7 +1,5 @@ pub mod image_io; pub mod image_processor; -pub mod image_proc; mod tensor_transform; // 对外统一暴露干净的 API 语义层 -pub use image_proc::*; -pub use tensor_transform::normalize_ocr_logits; \ No newline at end of file +pub use tensor_transform::normalize_ocr_logits; diff --git a/ddddocr-core/src/utils/image_proc.rs b/ddddocr-core/src/utils/image_proc.rs deleted file mode 100644 index 53db3a6..0000000 --- a/ddddocr-core/src/utils/image_proc.rs +++ /dev/null @@ -1,174 +0,0 @@ -use image::{ImageBuffer, Luma}; -use ndarray::{Array2, Array3, ArrayView2, ArrayView3, azip}; -use std::cmp::{max, min}; - -// 模拟openCV -/// 1. 计算两个数组的绝对差值 (对应 cv2.absdiff) -pub fn abs_diff(a: &ArrayView3, b: &ArrayView3) -> Array3 { - // 利用 ndarray 的 map_collect,生成差值的绝对值数组 - // 或者直接使用 zip_mut_with 处理以减少内存分配 - let mut diff = Array3::zeros(a.dim()); - azip!((res in &mut diff, &va in a, &vb in b) { - *res = (va as i16 - vb as i16).abs() as u8; - }); - diff -} - -/// RGB 到灰度转换 -pub fn rgb_to_gray(rgb: ArrayView3) -> Array2 { - let (h, w, _) = rgb.dim(); - Array2::from_shape_fn((h, w), |(y, x)| { - let r = rgb[[y, x, 0]] as f32; - let g = rgb[[y, x, 1]] as f32; - let b = rgb[[y, x, 2]] as f32; - // 完全忽略 a,只按权重计算 - (0.299 * r + 0.587 * g + 0.114 * b) as u8 - }) -} - -/// 寻找匹配结果图中的最大值及其坐标 (模拟 cv2.minMaxLoc 的一部分) -pub fn min_max_loc(result_map: &ImageBuffer, Vec>) -> (f32, (u32, u32)) { - // 4. 找到最佳匹配位置 (对齐 cv2.minMaxLoc) - let mut max_val: f32 = -1.0; - let mut max_loc = (0, 0); - - // 遍历匹配得分图 - for (x, y, score) in result_map.enumerate_pixels() { - let s = score.0[0]; - - // 可以在此处加入你之前验证过的起始位过滤 - // if x < 15 { continue; } - - if s > max_val { - max_val = s; - max_loc = (x, y); - } - } - (max_val, max_loc) -} - -/// 1. 模拟 findContours 并获取最大面积区域的 Label -/// 返回 Option,如果找不到任何区域则返回 None -pub fn find_contours_and_max(labelled: &ImageBuffer, Vec>) -> Option { - // 统计每个标签出现的频率(即面积) - let mut max_label = 0; - let mut max_area = 0; - let mut areas = std::collections::HashMap::new(); - - for pixel in labelled.pixels() { - let label = pixel.0[0]; - if label == 0 { - continue; - } // 跳过背景 - let count = areas.entry(label).or_insert(0); - *count += 1; - if *count > max_area { - max_area = *count; - max_label = label; - } - } - if max_label == 0 { - None - } else { - Some(max_label) - } -} -/// 根据目标连通域标签,计算其在图像中的外接矩形边界框(对应 `cv2.boundingRect`) -/// -/// 返回格式: `(min_x, min_y, width, height)` -pub fn bounding_rect( - labelled: &ImageBuffer, Vec>, - max_label: u32, -) -> (u32, u32, u32, u32) { - // 5. 计算最大区域的边界框 (对应 cv2.boundingRect) - let mut min_x = labelled.width(); - let mut max_x = 0; - let mut min_y = labelled.height(); - let mut max_y = 0; - - for (x, y, pixel) in labelled.enumerate_pixels() { - if pixel.0[0] == max_label { - min_x = min(min_x, x); - max_x = max(max_x, x); - min_y = min(min_y, y); - max_y = max(max_y, y); - } - } - - let w = max_x - min_x; - let h = max_y - min_y; - (min_x, min_y, w, h) -} - -/// 根据左上角坐标与矩形长宽,计算其中央核心点坐标 -#[inline] -pub fn calculate_center(top_left: (u32, u32), width: usize, height: usize) -> (i32, i32) { - let center_x = top_left.0 as i32 + (width as i32 / 2); - let center_y = top_left.1 as i32 + (height as i32 / 2); - (center_x, center_y) -} - -/// 高性能转换:将 `ndarray` 2D 灰度视图规整为 `image::ImageBuffer` 格式 -/// -/// 放弃低效的逐像素显式嵌套循环,采用原生内存池直接构造,减少寻址开销 -pub fn ndarray_to_luma8(array: ArrayView2) -> ImageBuffer, Vec> { - let (height, width) = array.dim(); - // 技巧:直接将已有的规整连续内存打平转换,或用 from_raw 包装 - // 此处保留安全的一步转换,但用更内聚的迭代器或切片拷贝进行速度优化 - let mut buffer = ImageBuffer::new(width as u32, height as u32); - for y in 0..height { - for x in 0..width { - buffer.put_pixel(x as u32, y as u32, Luma([array[[y, x]]])); - } - } - buffer -} -// ===================================================================== -// 5. 核心高性能图像转换算法 (纯 Rust 编写) -// ===================================================================== - -#[inline(always)] -pub fn rgb_to_opencv_hsv(r: u8, g: u8, b: u8) -> (u8, u8, u8) { - // 1. 规避高昂的除法,直接转为 f32 进行比对 - let r_f = r as f32; - let g_f = g as f32; - let b_f = b as f32; - - let max = r_f.max(g_f).max(b_f); - let min = r_f.min(g_f).min(b_f); - let delta = max - min; - - // 2. 计算 H (色调) - 移除负数取余陷阱,改用平铺分支 - let h = if delta == 0.0 { - 0.0 - } else if max == r_f { - let mut diff = (g_f - b_f) / delta; - if diff < 0.0 { - diff += 6.0; // 规避 Rust f32 % 负数的行为 - } - 60.0 * diff - } else if max == g_f { - 60.0 * (((b_f - r_f) / delta) + 2.0) - } else { - 60.0 * (((r_f - g_f) / delta) + 4.0) - }; - - // OpenCV 的 H 量化:H / 2 - // 注意:OpenCV 底层使用截断还是四舍五入与特定版本有关, - // 标准的 cvtColor 内部实现通常是: h * (180.0 / 360.0) -> h * 0.5 - // 这里使用强转(截断),若单测对齐发现差1,可改为 (h * 0.5 + 0.5) 或 round() - let h_opencv = (h * 0.5) as u8; - - // 3. 计算 S (饱和度) - // OpenCV 公式: S = max == 0 ? 0 : 255 * delta / max - let s_opencv = if max == 0.0 { - 0 - } else { - ((255.0 * delta) / max) as u8 - }; - - // 4. 计算 V (明度) - let v_opencv = max as u8; - - (h_opencv, s_opencv, v_opencv) -} diff --git a/ddddocr-core/src/utils/image_processor.rs b/ddddocr-core/src/utils/image_processor.rs index 42d1ebe..7068f72 100644 --- a/ddddocr-core/src/utils/image_processor.rs +++ b/ddddocr-core/src/utils/image_processor.rs @@ -1,7 +1,180 @@ -use image::{DynamicImage, GrayImage, imageops::FilterType, Rgb, ImageBuffer}; -use anyhow::{anyhow, Result}; -use crate::models::ocr::color_filter::HsvRange; -use crate::utils::image_proc::rgb_to_opencv_hsv; +use image::{DynamicImage, GrayImage, ImageBuffer, Luma, imageops::FilterType}; + +use ndarray::{Array2, Array3, ArrayView2, ArrayView3, azip}; +use std::cmp::{max, min}; + +// 模拟openCV +/// 1. 计算两个数组的绝对差值 (对应 cv2.absdiff) +pub fn abs_diff(a: &ArrayView3, b: &ArrayView3) -> Array3 { + // 利用 ndarray 的 map_collect,生成差值的绝对值数组 + // 或者直接使用 zip_mut_with 处理以减少内存分配 + let mut diff = Array3::zeros(a.dim()); + azip!((res in &mut diff, &va in a, &vb in b) { + *res = (va as i16 - vb as i16).abs() as u8; + }); + diff +} + +/// RGB 到灰度转换 +pub fn rgb_to_gray(rgb: ArrayView3) -> Array2 { + let (h, w, _) = rgb.dim(); + Array2::from_shape_fn((h, w), |(y, x)| { + let r = rgb[[y, x, 0]] as f32; + let g = rgb[[y, x, 1]] as f32; + let b = rgb[[y, x, 2]] as f32; + // 完全忽略 a,只按权重计算 + (0.299 * r + 0.587 * g + 0.114 * b) as u8 + }) +} + +/// 寻找匹配结果图中的最大值及其坐标 (模拟 cv2.minMaxLoc 的一部分) +pub fn min_max_loc(result_map: &ImageBuffer, Vec>) -> (f32, (u32, u32)) { + // 4. 找到最佳匹配位置 (对齐 cv2.minMaxLoc) + let mut max_val: f32 = -1.0; + let mut max_loc = (0, 0); + + // 遍历匹配得分图 + for (x, y, score) in result_map.enumerate_pixels() { + let s = score.0[0]; + + // 可以在此处加入你之前验证过的起始位过滤 + // if x < 15 { continue; } + + if s > max_val { + max_val = s; + max_loc = (x, y); + } + } + (max_val, max_loc) +} + +/// 1. 模拟 findContours 并获取最大面积区域的 Label +/// 返回 Option,如果找不到任何区域则返回 None +pub fn find_contours_and_max(labelled: &ImageBuffer, Vec>) -> Option { + // 统计每个标签出现的频率(即面积) + let mut max_label = 0; + let mut max_area = 0; + let mut areas = std::collections::HashMap::new(); + + for pixel in labelled.pixels() { + let label = pixel.0[0]; + if label == 0 { + continue; + } // 跳过背景 + let count = areas.entry(label).or_insert(0); + *count += 1; + if *count > max_area { + max_area = *count; + max_label = label; + } + } + if max_label == 0 { + None + } else { + Some(max_label) + } +} +/// 根据目标连通域标签,计算其在图像中的外接矩形边界框(对应 `cv2.boundingRect`) +/// +/// 返回格式: `(min_x, min_y, width, height)` +pub fn bounding_rect( + labelled: &ImageBuffer, Vec>, + max_label: u32, +) -> (u32, u32, u32, u32) { + // 5. 计算最大区域的边界框 (对应 cv2.boundingRect) + let mut min_x = labelled.width(); + let mut max_x = 0; + let mut min_y = labelled.height(); + let mut max_y = 0; + + for (x, y, pixel) in labelled.enumerate_pixels() { + if pixel.0[0] == max_label { + min_x = min(min_x, x); + max_x = max(max_x, x); + min_y = min(min_y, y); + max_y = max(max_y, y); + } + } + + let w = max_x - min_x; + let h = max_y - min_y; + (min_x, min_y, w, h) +} + +/// 根据左上角坐标与矩形长宽,计算其中央核心点坐标 +#[inline] +pub fn calculate_center(top_left: (u32, u32), width: usize, height: usize) -> (i32, i32) { + let center_x = top_left.0 as i32 + (width as i32 / 2); + let center_y = top_left.1 as i32 + (height as i32 / 2); + (center_x, center_y) +} + +/// 高性能转换:将 `ndarray` 2D 灰度视图规整为 `image::ImageBuffer` 格式 +/// +/// 放弃低效的逐像素显式嵌套循环,采用原生内存池直接构造,减少寻址开销 +pub fn ndarray_to_luma8(array: ArrayView2) -> ImageBuffer, Vec> { + let (height, width) = array.dim(); + // 技巧:直接将已有的规整连续内存打平转换,或用 from_raw 包装 + // 此处保留安全的一步转换,但用更内聚的迭代器或切片拷贝进行速度优化 + let mut buffer = ImageBuffer::new(width as u32, height as u32); + for y in 0..height { + for x in 0..width { + buffer.put_pixel(x as u32, y as u32, Luma([array[[y, x]]])); + } + } + buffer +} +// ===================================================================== +// 5. 核心高性能图像转换算法 (纯 Rust 编写) +// ===================================================================== + +#[inline(always)] +pub fn rgb_to_opencv_hsv(r: u8, g: u8, b: u8) -> (u8, u8, u8) { + // 1. 规避高昂的除法,直接转为 f32 进行比对 + let r_f = r as f32; + let g_f = g as f32; + let b_f = b as f32; + + let max = r_f.max(g_f).max(b_f); + let min = r_f.min(g_f).min(b_f); + let delta = max - min; + + // 2. 计算 H (色调) - 移除负数取余陷阱,改用平铺分支 + let h = if delta == 0.0 { + 0.0 + } else if max == r_f { + let mut diff = (g_f - b_f) / delta; + if diff < 0.0 { + diff += 6.0; // 规避 Rust f32 % 负数的行为 + } + 60.0 * diff + } else if max == g_f { + 60.0 * (((b_f - r_f) / delta) + 2.0) + } else { + 60.0 * (((r_f - g_f) / delta) + 4.0) + }; + + // OpenCV 的 H 量化:H / 2 + // 注意:OpenCV 底层使用截断还是四舍五入与特定版本有关, + // 标准的 cvtColor 内部实现通常是: h * (180.0 / 360.0) -> h * 0.5 + // 这里使用强转(截断),若单测对齐发现差1,可改为 (h * 0.5 + 0.5) 或 round() + let h_opencv = (h * 0.5) as u8; + + // 3. 计算 S (饱和度) + // OpenCV 公式: S = max == 0 ? 0 : 255 * delta / max + let s_opencv = if max == 0.0 { + 0 + } else { + ((255.0 * delta) / max) as u8 + }; + + // 4. 计算 V (明度) + let v_opencv = max as u8; + + (h_opencv, s_opencv, v_opencv) +} + + /// 对应 Python 的 convert_to_grayscale /// 将图像转换为灰度图 (L模式) @@ -18,23 +191,8 @@ pub fn resize_image( target_height: u32, // resample 参数我们直接使用 FilterType,Lanczos3 是最接近 Python LANCZOS 的 ) -> DynamicImage { - // image::imageops::resize 的最高层封装 // FilterType::Lanczos3 与 Python Pillow 的 Image.LANCZOS 算法完全对齐,缩放质量最高 image.resize_exact(target_width, target_height, FilterType::Lanczos3) } -// pub fn resize_image( -// image: &GrayImage, -// target_width: u32, -// target_height: u32, -// // resample 参数我们直接使用 FilterType,Lanczos3 是最接近 Python LANCZOS 的 -// ) -> GrayImage { -// // 使用 resize 算法进行精确缩放 -// image::imageops::resize( -// image, -// target_width, -// target_height, -// FilterType::Lanczos3 -// ) -// } diff --git a/ddddocr-tract/src/det/mod.rs b/ddddocr-tract/src/det.rs similarity index 100% rename from ddddocr-tract/src/det/mod.rs rename to ddddocr-tract/src/det.rs diff --git a/ddddocr-tract/src/det/session.rs b/ddddocr-tract/src/det/session.rs index f51f0f3..f50fd28 100644 --- a/ddddocr-tract/src/det/session.rs +++ b/ddddocr-tract/src/det/session.rs @@ -1,4 +1,4 @@ -use crate::loader::{ModelLoader, ModelSession, ModelType}; +use crate::loader::ModelLoader; use anyhow::Context; use ddddocr_core::error::{DdddError, Result}; use ddddocr_core::{DetEngine, DetOutput, InferenceEngine}; @@ -10,15 +10,6 @@ pub struct DetSession { pub session: RunnableModel, Graph>>, } -impl ModelSession for DetSession { - fn get_model_type(&self) -> ModelType { - todo!() - } - fn desc(&self) -> String { - "Detection Model 加载成功".to_string() - } -} - impl DetSession { pub fn new

(model_path: P) -> Result where diff --git a/ddddocr-tract/src/loader.rs b/ddddocr-tract/src/loader.rs index bb15987..762b039 100644 --- a/ddddocr-tract/src/loader.rs +++ b/ddddocr-tract/src/loader.rs @@ -3,18 +3,6 @@ use ddddocr_core::error::Result; use std::io::Cursor; use tract_onnx::onnx; use tract_onnx::prelude::*; // 引入核心层的统一错误类型 -/// OCR 模型:包含路径和字符集 - -pub enum ModelType { - Ocr, - Det, - Custom, -} -// 定义统一的 trait -pub trait ModelSession { - fn get_model_type(&self) -> ModelType; - fn desc(&self) -> String; -} pub struct ModelLoader { pub session: RunnableModel, Graph>>, diff --git a/ddddocr-tract/src/ocr/mod.rs b/ddddocr-tract/src/ocr.rs similarity index 100% rename from ddddocr-tract/src/ocr/mod.rs rename to ddddocr-tract/src/ocr.rs diff --git a/ddddocr-tract/tests/char_slice.rs b/ddddocr-tract/tests/char_slice.rs index 8eb73b7..d858300 100644 --- a/ddddocr-tract/tests/char_slice.rs +++ b/ddddocr-tract/tests/char_slice.rs @@ -2,8 +2,8 @@ use std::borrow::Cow; use std::fs::File; use std::path::Path; use anyhow::anyhow; -use ddddocr_core::models::ocr::metadata::Charset; -use ddddocr_core::models::ocr::metadata::{Normalization, Resize}; +use ddddocr_core::ocr::metadata::Charset; +use ddddocr_core::ocr::metadata::{Normalization, Resize}; pub const CHARSET_BETA: &[&str] = &[ "", "笤", "谴", "膀", "荔", "佰", "电", "臁", "矍", "同", "奇", "芄", "吠", "6", "曛", "荇", diff --git a/ddddocr-tract/tests/ocr_test.rs b/ddddocr-tract/tests/ocr_test.rs index 366d8ef..2409230 100644 --- a/ddddocr-tract/tests/ocr_test.rs +++ b/ddddocr-tract/tests/ocr_test.rs @@ -1,4 +1,4 @@ -use ddddocr_core::models::det::DetectionResult; +use ddddocr_core::det::DetectionResult; use ddddocr_core::{DetBuilder, Detector, ModelMetadata, Ocr, Slider}; // 假设你的包名是这个 use ddddocr_tract::{DetSession,OcrSession}; use image::{DynamicImage, Rgb}; @@ -6,7 +6,7 @@ use std::fs; use std::path::Path; mod char_slice; use char_slice::CHARSET_BETA; -use ddddocr_core::models::ocr::metadata::{Normalization, Resize}; +use ddddocr_core::ocr::metadata::{Normalization, Resize}; fn load_image>(path: P) -> anyhow::Result { // 1. 先将泛型转为具体的 &Path 引用