refactor(ocr): 优化并精简 color_filter 架构设计
- 重构 `OcrBuilder`,将图像矩阵过滤与像素比对等执行层逻辑彻底剥离解耦。 - 优化 `OcrBuilder` 的 `color_filter` 链式调用,将其改造为无心智负担的单次覆盖(Overwrite)逻辑。 - 扩展 `ColorFilter` 特征,新增 `collect_to_vec` 方法,实现底层规则的高内聚收集、精准内存开辟与原地去重排序。
This commit is contained in:
@@ -533,6 +533,49 @@ pub trait TokenFilter {
|
|||||||
fn estimated_capacity(&self) -> usize {
|
fn estimated_capacity(&self) -> usize {
|
||||||
128
|
128
|
||||||
}
|
}
|
||||||
|
/// 【新引入的架构级核心方法】
|
||||||
|
/// 统一接管全量字符集的密集遍历、CTC Blank放行、去重、排序及空交集退化兜底
|
||||||
|
fn apply_to_charset(&self, tokens: &[Cow<str>]) -> Option<Vec<usize>> {
|
||||||
|
let mut has_any_match = false;
|
||||||
|
let estimated_capacity = self.estimated_capacity();
|
||||||
|
|
||||||
|
// 1. 精准开辟内存,完美利用容量提示,避免动态乱涨
|
||||||
|
let mut temp_indices = Vec::with_capacity(estimated_capacity.max(16));
|
||||||
|
|
||||||
|
// 2. 高性能原地单次流式迭代
|
||||||
|
for (idx, token) in tokens.iter().enumerate() {
|
||||||
|
let token_str = token.as_ref();
|
||||||
|
|
||||||
|
// 规则 A: CTC Blank 空字符串或 0 号索引无条件放行
|
||||||
|
if token_str.is_empty() || idx == 0 {
|
||||||
|
temp_indices.push(idx);
|
||||||
|
continue; // 关键:直接跳过,防止后续 matches 匹配成功导致重复 push 产生 Bug
|
||||||
|
}
|
||||||
|
|
||||||
|
// 规则 B: 组装无拷贝上下文
|
||||||
|
let ctx = ValidationCtx {
|
||||||
|
text: token_str,
|
||||||
|
token_id: idx,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 规则 C: 路由到各自具体实现的特异性匹配中(如 Digit 判定、TopN 判定、组合子判定等)
|
||||||
|
if self.matches(&ctx) {
|
||||||
|
temp_indices.push(idx);
|
||||||
|
has_any_match = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 终极防御:如果整个模型字符集除了 Blank,一个都没对上,直接退化为 None(全量识别)
|
||||||
|
if !has_any_match {
|
||||||
|
println!("警告:当前限制策略与模型字符集完全没有交集!已自动恢复全量识别。");
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// 4. 排序并去重,为 Ocr 引擎后续进行极其高频的『二分查找』筑起绝对安全的底层保障
|
||||||
|
temp_indices.sort_unstable();
|
||||||
|
temp_indices.dedup();
|
||||||
|
Some(temp_indices)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use crate::charset::{TokenFilter, ValidationCtx};
|
use crate::charset::{TokenFilter, ValidationCtx};
|
||||||
use crate::model_metadata::ModelMetadata;
|
use crate::model_metadata::ModelMetadata;
|
||||||
use crate::models::base::ModelArgs;
|
use crate::models::base::ModelArgs;
|
||||||
use crate::models::loader::{ModelLoader, ModelSession, ModelType};
|
use crate::models::loader::{ModelLoader, ModelSession, ModelType};
|
||||||
use crate::utils::color_filter::{filter_image, ColorFilter, HsvRange};
|
use crate::utils::color_filter::{ColorFilter, HsvRange, filter_image};
|
||||||
use crate::utils::image_io::png_rgba_white_preprocess;
|
use crate::utils::image_io::png_rgba_white_preprocess;
|
||||||
use crate::utils::image_processor::{convert_to_grayscale, resize_image};
|
use crate::utils::image_processor::{convert_to_grayscale, resize_image};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use image::{DynamicImage, ImageBuffer, Rgb};
|
use image::{DynamicImage, ImageBuffer, Rgb};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use tract_onnx::prelude::tract_ndarray::s;
|
use tract_onnx::prelude::tract_ndarray::s;
|
||||||
use tract_onnx::prelude::{
|
use tract_onnx::prelude::{
|
||||||
@@ -158,48 +158,13 @@ impl<'a> OcrBuilder<'a> {
|
|||||||
// 反复调用color_filter怎么处理?
|
// 反复调用color_filter怎么处理?
|
||||||
pub fn color_filter(mut self, filter: &dyn ColorFilter) -> Self {
|
pub fn color_filter(mut self, filter: &dyn ColorFilter) -> Self {
|
||||||
// self.color_filter = Some(value);
|
// self.color_filter = Some(value);
|
||||||
// 利用组合子预估能力,获取精准分配槽位数
|
|
||||||
let total_capacity = filter.estimated_count();
|
// 一句话把活全包了!错误信息无缝传递,完美熔断
|
||||||
if total_capacity == 0 {
|
match filter.collect_to_vec() {
|
||||||
return self;
|
Ok(new_ranges) => self.color_filter = Ok(new_ranges),
|
||||||
|
Err(err_msg) => self.color_filter = Err(err_msg), // 校验失败,Builder 正式中毒
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.color_filter {
|
|
||||||
Ok(mut ranges) => {
|
|
||||||
// 2. 触发特征多态自检:快捷预设秒过,Custom 变体严格政审
|
|
||||||
if let Err(err_msg) = filter.validate_self() {
|
|
||||||
// 校验失败,Builder 正式中毒,熔断器闭合
|
|
||||||
self.color_filter = Err(err_msg);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
match &mut ranges {
|
|
||||||
None => {
|
|
||||||
// 情况 A:这是用户第一次配置,直接一击必中分配精准内存
|
|
||||||
let mut v = Vec::with_capacity(total_capacity);
|
|
||||||
|
|
||||||
// 尝试倒入,如果出错,Builder 正式中毒
|
|
||||||
filter.append_ranges(&mut v);
|
|
||||||
ranges = Some(v);
|
|
||||||
}
|
|
||||||
Some(existing_vec) => {
|
|
||||||
// 情况 B:用户之前配置过,现在追加配置。
|
|
||||||
// 绝不让它动态乱涨!我们利用 reserve 方法,【仅此一次】精准追加所需的扩容空间!
|
|
||||||
existing_vec.reserve(total_capacity);
|
|
||||||
|
|
||||||
// 尝试追加倒入
|
|
||||||
filter.append_ranges(existing_vec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(v) = &mut ranges {
|
|
||||||
v.sort_unstable();
|
|
||||||
v.dedup();
|
|
||||||
}
|
|
||||||
// 5. 更新状态
|
|
||||||
self.color_filter = Ok(ranges);
|
|
||||||
}
|
|
||||||
Err(_) => return self,
|
|
||||||
};
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,42 +176,7 @@ impl<'a> OcrBuilder<'a> {
|
|||||||
let charset = &self.ocr.model_metadata.charset;
|
let charset = &self.ocr.model_metadata.charset;
|
||||||
let tokens = &charset.tokens;
|
let tokens = &charset.tokens;
|
||||||
// let mut temp_indices = Vec::new();
|
// let mut temp_indices = Vec::new();
|
||||||
let mut has_any_match = false;
|
self.charset_restrict = restrict.apply_to_charset(tokens);
|
||||||
|
|
||||||
let estimated_capacity = restrict.estimated_capacity();
|
|
||||||
// 精准开辟内存,完美避开 8210 个槽位的巨大空置浪费
|
|
||||||
let mut temp_indices = Vec::with_capacity(estimated_capacity);
|
|
||||||
|
|
||||||
for (idx, token) in tokens.iter().enumerate() {
|
|
||||||
let token_str = token.as_ref();
|
|
||||||
// CTC Blank 空字符串无条件放行,其余交给超高性能的 matches
|
|
||||||
if token_str.is_empty() || idx == 0 {
|
|
||||||
temp_indices.push(idx);
|
|
||||||
};
|
|
||||||
// 组装无拷贝上下文
|
|
||||||
let ctx = ValidationCtx {
|
|
||||||
text: token_str,
|
|
||||||
token_id: idx,
|
|
||||||
};
|
|
||||||
|
|
||||||
if restrict.matches(&ctx) {
|
|
||||||
temp_indices.push(idx);
|
|
||||||
has_any_match = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// self.charset_restrict = Some(restrict);
|
|
||||||
// 终极防御:如果除了 Blank 外什么都没匹配上,退化恢复为 None(全量识别)
|
|
||||||
if !has_any_match {
|
|
||||||
println!("警告:当前限制策略与模型字符集完全没有交集!已自动恢复全量识别。");
|
|
||||||
self.charset_restrict = None;
|
|
||||||
} else {
|
|
||||||
// 这一步非常重要:二分查找(binary_search)强依赖数组【有序】。
|
|
||||||
// TopN 天然有序,但如果是用户自定义的 CustomList 或者复杂的 Or 组合,
|
|
||||||
// 遍历出来的索引天然有序,但为了绝对的安全,我们在这里顺手排个序
|
|
||||||
temp_indices.sort_unstable();
|
|
||||||
self.charset_restrict = Some(temp_indices);
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +190,10 @@ impl<'a> OcrBuilder<'a> {
|
|||||||
// =====================================================================
|
// =====================================================================
|
||||||
let img_cow = match &self.color_filter {
|
let img_cow = match &self.color_filter {
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(anyhow::anyhow!("颜色过滤器初始化失败,全链路短路: {}", err_msg));
|
return Err(anyhow::anyhow!(
|
||||||
|
"颜色过滤器初始化失败,全链路短路: {}",
|
||||||
|
err_msg
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
// 核心优化点:直接借用原图,不发生任何克隆
|
// 核心优化点:直接借用原图,不发生任何克隆
|
||||||
@@ -313,10 +246,6 @@ impl<'a> OcrBuilder<'a> {
|
|||||||
|
|
||||||
Ok(tensor)
|
Ok(tensor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
impl<'a> OcrBuilder<'a> {
|
impl<'a> OcrBuilder<'a> {
|
||||||
pub fn get_valid_indices(&self) -> HashSet<usize> {
|
pub fn get_valid_indices(&self) -> HashSet<usize> {
|
||||||
|
|||||||
@@ -165,26 +165,17 @@ pub trait ColorFilter {
|
|||||||
}
|
}
|
||||||
/// 【新扩展的架构方法】将自身安全的合并到已有的普通容器中,并完成去重和排序
|
/// 【新扩展的架构方法】将自身安全的合并到已有的普通容器中,并完成去重和排序
|
||||||
/// 完美的责任分离:Builder 不再需要关心怎么分配内存、怎么排序去重
|
/// 完美的责任分离:Builder 不再需要关心怎么分配内存、怎么排序去重
|
||||||
fn merge_to_vec(&self, mut existing: Option<Vec<HsvRange>>) -> Result<Option<Vec<HsvRange>>, String> {
|
fn collect_to_vec(&self) -> Result<Option<Vec<HsvRange>>, String> {
|
||||||
// 1. 触发自检
|
// 1. 触发自检
|
||||||
self.validate_self()?;
|
self.validate_self()?;
|
||||||
|
|
||||||
let total_capacity = self.estimated_count();
|
let total_capacity = self.estimated_count();
|
||||||
if total_capacity == 0 {
|
if total_capacity == 0 {
|
||||||
return Ok(existing);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut v = match existing {
|
// 2. 永远一击必中分配精准内存,不需要再考虑追加和扩容!
|
||||||
None => {
|
let mut v = Vec::with_capacity(total_capacity.max(16));
|
||||||
// 情况 A:第一次配置,精准分配
|
|
||||||
Vec::with_capacity(total_capacity)
|
|
||||||
}
|
|
||||||
Some(mut existing_vec) => {
|
|
||||||
// 情况 B:追加配置,精准扩容
|
|
||||||
existing_vec.reserve(total_capacity);
|
|
||||||
existing_vec
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 倒入数据
|
// 2. 倒入数据
|
||||||
self.append_ranges(&mut v);
|
self.append_ranges(&mut v);
|
||||||
|
|||||||
Reference in New Issue
Block a user