refactor(error): 规范化分层错误类型并优化异常捕捉
- 新增 tracing 记录异常,移除不必要的 Result - 重构 错误处理架构
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::error::{ImagePreprocessError, Result};
|
||||
use crate::utils::image_processor::rgb_to_opencv_hsv;
|
||||
use anyhow::anyhow;
|
||||
use image::{DynamicImage, ImageBuffer, Rgb};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -18,12 +18,13 @@ fn is_pixel_matched(ranges: &[HsvRange], h: u8, s: u8, v: u8) -> bool {
|
||||
pub fn apply_to_image(
|
||||
image: &DynamicImage,
|
||||
hsv_ranges: &[HsvRange],
|
||||
) -> anyhow::Result<DynamicImage> {
|
||||
) -> Result<DynamicImage, ImagePreprocessError> {
|
||||
// 1. 统一转换为连续内存的 RGB8 缓冲区 (对应 Python 的 Image 到 RGB/BGR 数组转换)
|
||||
let rgb_img = image.to_rgb8();
|
||||
let (width, height) = rgb_img.dimensions();
|
||||
let mut raw_pixels = rgb_img.into_raw();
|
||||
|
||||
let actual_len = raw_pixels.len();
|
||||
let expected_len = (width as usize) * (height as usize) * 3;
|
||||
// 2. 密集计算核心:原地流式迭代修改
|
||||
// 每次取出 3 个 u8 字节,分别代表 [R, G, B],无多余掩膜矩阵内存分配
|
||||
for chunk in raw_pixels.chunks_exact_mut(3) {
|
||||
@@ -45,8 +46,14 @@ pub fn apply_to_image(
|
||||
|
||||
// 3. 将扁平字节数组重新打包回 DynamicImage 容器
|
||||
let filtered_buffer = ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(width, height, raw_pixels)
|
||||
.ok_or_else(|| anyhow!("图像缓冲重新组装失败,维度与数据大小不匹配"))?;
|
||||
|
||||
// .ok_or_else(|| anyhow!("图像缓冲重新组装失败,维度与数据大小不匹配"))?;
|
||||
.ok_or_else(|| ImagePreprocessError::BufferLengthMismatch {
|
||||
expected: expected_len,
|
||||
actual: actual_len,
|
||||
width,
|
||||
height,
|
||||
channels: 3,
|
||||
})?;
|
||||
Ok(DynamicImage::ImageRgb8(filtered_buffer))
|
||||
}
|
||||
|
||||
@@ -64,16 +71,22 @@ impl HsvRange {
|
||||
impl HsvRange {
|
||||
/// 验证当前 HSV 范围是否合法
|
||||
/// 对应 Python 逻辑:H 在 0-180,S/V 在 0-255,且下界 <= 上界
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
pub fn validate(&self) -> Result<(), ImagePreprocessError> {
|
||||
// 1. 校验 H 通道边界 (OpenCV 中 H 范围是 0-180)
|
||||
if self.lower.0 > 180 || self.upper.0 > 180 {
|
||||
return Err("H通道值必须在 0-180 范围内".to_string());
|
||||
// return Err("H通道值必须在 0-180 范围内".to_string());
|
||||
return Err(ImagePreprocessError::InvalidHsvRange(
|
||||
"H通道值必须在 0-180 范围内".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// 2. 校验下界不能大于上界
|
||||
if self.lower.0 > self.upper.0 || self.lower.1 > self.upper.1 || self.lower.2 > self.upper.2
|
||||
{
|
||||
return Err("HSV范围下界不能大于上界".to_string());
|
||||
// return Err("HSV范围下界不能大于上界".to_string());
|
||||
return Err(ImagePreprocessError::InvalidHsvRange(
|
||||
"HSV范围下界不能大于上界".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -150,7 +163,7 @@ impl ColorPreset {
|
||||
}
|
||||
}
|
||||
/// 校验逻辑:在这里实现完美的“责任分离”
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
pub fn validate(&self) -> Result<(), ImagePreprocessError> {
|
||||
match self {
|
||||
// 1. 快捷变体:完全绕过,根本不校验,0 运行时开销放行!
|
||||
ColorPreset::Custom(ranges) => {
|
||||
@@ -166,7 +179,7 @@ impl ColorPreset {
|
||||
}
|
||||
|
||||
impl FromStr for ColorPreset {
|
||||
type Err = String;
|
||||
type Err = ImagePreprocessError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"red" => Ok(ColorPreset::Red),
|
||||
@@ -179,7 +192,8 @@ impl FromStr for ColorPreset {
|
||||
"black" => Ok(ColorPreset::Black),
|
||||
"white" => Ok(ColorPreset::White),
|
||||
"gray" => Ok(ColorPreset::Gray),
|
||||
_ => Err(format!("不支持的颜色预设: {}", s)),
|
||||
// _ => Err(format!("不支持的颜色预设: {}", s)),
|
||||
_ => Err(ImagePreprocessError::UnknownColorPreset(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,12 +214,12 @@ pub trait ColorFilter {
|
||||
fn estimated_count(&self) -> usize;
|
||||
/// 将自身的有效约束平铺追加到统一目标容器中
|
||||
/// 验证当前过滤器是否合法,默认直接放行(Ok(()))
|
||||
fn validate_self(&self) -> Result<(), String> {
|
||||
fn validate_self(&self) -> Result<(), ImagePreprocessError> {
|
||||
Ok(())
|
||||
}
|
||||
/// 【新扩展的架构方法】将自身安全的合并到已有的普通容器中,并完成去重和排序
|
||||
/// 完美的责任分离:Builder 不再需要关心怎么分配内存、怎么排序去重
|
||||
fn collect_to_vec(&self) -> Result<Option<Vec<HsvRange>>, String> {
|
||||
fn collect_to_vec(&self) -> Result<Option<Vec<HsvRange>>, ImagePreprocessError> {
|
||||
// 1. 触发自检
|
||||
self.validate_self()?;
|
||||
|
||||
@@ -237,7 +251,7 @@ impl ColorFilter for ColorPreset {
|
||||
// 直接获取切片长度
|
||||
self.matches().len()
|
||||
}
|
||||
fn validate_self(&self) -> Result<(), String> {
|
||||
fn validate_self(&self) -> Result<(), ImagePreprocessError> {
|
||||
// 直接调用我们在第一步中为 ColorPreset 实现的精细化分流校验
|
||||
// 快捷变体在这里会直接返回 Ok(()), 只有 Custom 才会去真正校验
|
||||
self.validate()
|
||||
@@ -261,7 +275,7 @@ impl<'a> ColorFilter for MultiOrColorRestrict<'a> {
|
||||
self.filters.iter().map(|f| f.estimated_count()).sum()
|
||||
}
|
||||
|
||||
fn validate_self(&self) -> Result<(), String> {
|
||||
fn validate_self(&self) -> Result<(), ImagePreprocessError> {
|
||||
// 递归政审:只要其中一个子过滤器校验失败(比如某个 Custom 变体非法),立刻熔断
|
||||
for f in &self.filters {
|
||||
f.validate_self()?;
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::ocr::color_filter::{HsvRange, apply_to_image};
|
||||
// use ddddocr_tract::session::{ModelOutput, OcrSession};
|
||||
use crate::utils::image_convert::png_rgba_white_preprocess;
|
||||
use crate::utils::image_processor::{convert_to_grayscale, resize_image};
|
||||
use anyhow::Result;
|
||||
use image::DynamicImage;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
@@ -17,7 +16,9 @@ use ndarray::ArrayView2;
|
||||
// Indices(ndarray::Array1<i64>), // 拥有完整所有权的 1维数组,可任意传递和返回
|
||||
// Logits(ndarray::Array2<f32>), // 拥有完整所有权的 2维矩阵,可任意传递和返回
|
||||
// }
|
||||
use crate::error::{ImagePreprocessError, Result, TensorError};
|
||||
use crate::{OcrEngine, OcrOutput};
|
||||
use tracing::{ warn};
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum OcrResult {
|
||||
/// 纯文本分支(对应 probability = false)
|
||||
@@ -107,7 +108,7 @@ pub struct Ocr<'a> {
|
||||
pub(crate) png_fix: bool,
|
||||
pub(crate) probability: bool,
|
||||
/// 颜色过滤:保留的颜色列表
|
||||
pub(crate) final_color_ranges: Result<Option<Vec<HsvRange>>, String>,
|
||||
pub(crate) final_color_ranges: Result<Option<Vec<HsvRange>>, ImagePreprocessError>,
|
||||
|
||||
/// 字符集范围
|
||||
pub(crate) final_charset_indices: Option<Vec<usize>>,
|
||||
@@ -127,7 +128,7 @@ impl<'a> Ocr<'a> {
|
||||
}
|
||||
}
|
||||
impl<'a> Ocr<'a> {
|
||||
pub fn predict(&self, image: &DynamicImage) -> anyhow::Result<OcrResult> {
|
||||
pub fn predict(&self, image: &DynamicImage) -> Result<OcrResult> {
|
||||
println!("当前颜色过滤器状态: {:?}", self.final_color_ranges);
|
||||
|
||||
// =====================================================================
|
||||
@@ -137,10 +138,13 @@ impl<'a> Ocr<'a> {
|
||||
// =====================================================================
|
||||
let img_cow = match &self.final_color_ranges {
|
||||
Err(err_msg) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"颜色过滤器初始化失败,全链路短路: {}",
|
||||
err_msg
|
||||
));
|
||||
// return Err(anyhow::anyhow!(
|
||||
// "颜色过滤器初始化失败,全链路短路: {}",
|
||||
// err_msg
|
||||
// ));
|
||||
return Err(ImagePreprocessError::FilterConfigInvalid(
|
||||
err_msg.to_string(),
|
||||
))?;
|
||||
}
|
||||
Ok(None) => {
|
||||
// 核心优化点:直接借用原图,不发生任何克隆
|
||||
@@ -168,12 +172,12 @@ impl<'a> Ocr<'a> {
|
||||
// let raw_indices = self.ocr.extract_indices_from_tensor(&raw_tensor)?;
|
||||
// // 步骤 2: 将索引切片 `&[i64]` 传给解码器进行 CTC 去重和字符映射
|
||||
// let final_text = self.ctc_decode_to_string(&raw_indices);
|
||||
let ocr_output = self.process_model_output(raw_tensor);
|
||||
ocr_output
|
||||
let ocr_output = self.process_model_output(raw_tensor)?;
|
||||
Ok(ocr_output)
|
||||
}
|
||||
/// 对应 Python 的 _preprocess_image
|
||||
/// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换
|
||||
fn preprocess_image(&self, img: &DynamicImage) -> anyhow::Result<ndarray::Array4<f32>> {
|
||||
fn preprocess_image(&self, img: &DynamicImage) -> Result<ndarray::Array4<f32>,ImagePreprocessError> {
|
||||
// 1. 获取模型元数据配置
|
||||
let meta = self.session.metadata();
|
||||
let norm = &meta.normalization; // 获取归一化器
|
||||
@@ -239,7 +243,12 @@ impl<'a> Ocr<'a> {
|
||||
array
|
||||
}
|
||||
|
||||
_ => return Err(anyhow::anyhow!("不支持的通道数配置: {}", meta.channel)),
|
||||
// _ => return Err(anyhow::anyhow!("不支持的通道数配置: {}", meta.channel)),
|
||||
_ => {
|
||||
return Err(ImagePreprocessError::UnsupportedChannels(
|
||||
meta.channel as usize,
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(array4)
|
||||
// Ok(tensor)
|
||||
@@ -265,13 +274,14 @@ impl<'a> Ocr<'a> {
|
||||
}
|
||||
|
||||
// 这段代码未来直接放入 ddddocr-core
|
||||
fn process_model_output(&self, output: OcrOutput) -> anyhow::Result<OcrResult> {
|
||||
fn process_model_output(&self, output: OcrOutput) -> Result<OcrResult,TensorError> {
|
||||
match output {
|
||||
OcrOutput::Indices(array1) => {
|
||||
// 对应你原来的 process_i64_tensor
|
||||
let slice = array1
|
||||
.as_slice()
|
||||
.ok_or_else(|| anyhow::anyhow!("内存不连续,无法执行零拷贝解码"))?;
|
||||
// .ok_or_else(|| anyhow::anyhow!("内存不连续,无法执行零拷贝解码"))?;
|
||||
.ok_or_else(|| TensorError::NonContiguousMemory)?;
|
||||
let final_text = self.ctc_decode_to_string(slice);
|
||||
|
||||
if self.probability {
|
||||
@@ -528,8 +538,9 @@ impl<'a> Ocr<'a> {
|
||||
// 5. 字符映射
|
||||
if let Some(char_str) = tokens.get(u_idx) {
|
||||
res.push_str(char_str);
|
||||
} else {
|
||||
eprintln!("警告: 预测索引 {} 超出字符集范围", u_idx);
|
||||
}
|
||||
else {
|
||||
warn!("警告: 预测索引 {} 超出字符集范围", u_idx);
|
||||
}
|
||||
}
|
||||
res
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use crate::error::{ Result};
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
Reference in New Issue
Block a user