diff --git a/Cargo.toml b/Cargo.toml index fc83e21..587929d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,29 @@ [workspace] resolver = "2" members = [ - "ddddocr-core", + "ddddocr-core", "ddddocr-ort", "ddddocr-tract", ] [workspace.package] -version = "0.1.0" +version = "0.2.0" edition = "2024" license = "MIT OR Apache-2.0" [workspace.dependencies] -tract-onnx = { version = "0.21.10" } -anyhow = "1.0.102" +tract-onnx = "0.23.4" +tract-linalg = { version = "0.23.4",features = ["multithread-mm"]} +ort = "2.0.0-rc.12" +ndarray = "0.17.2" + image = "0.25.10" base64 = "0.22.1" imageproc = { version = "0.26.2", default-features = true } + serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.150" -ndarray = "0.16.1" + +anyhow = "1.0.102" thiserror = "1.0" # 刚好可以开始接入你需要的标准库错误处理 -tracing = "0.1.44" # 埋入日志打点(后续需要继续优化,现在只是简单尝试) \ No newline at end of file +tracing = "0.1.44" # 埋入日志打点(后续需要继续优化,现在只是简单尝试) diff --git a/ddddocr-core/Cargo.toml b/ddddocr-core/Cargo.toml index 318b703..8b5f74e 100644 --- a/ddddocr-core/Cargo.toml +++ b/ddddocr-core/Cargo.toml @@ -5,12 +5,12 @@ edition = { workspace = true } license = { workspace = true } [dependencies] -image = "0.25.10" -base64 = "0.22.1" -imageproc = { version = "0.26.2", default-features = true } -serde = { workspace = true } -serde_json = "1.0.150" ndarray = { workspace = true } # 继承自工作空间 + +base64 = { workspace = true } + +image = { workspace = true } +imageproc = { workspace = true } + thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 -tracing={workspace = true} -#serde = { workspace = true, features = ["derive"] } \ No newline at end of file +tracing={workspace = true} \ No newline at end of file diff --git a/ddddocr-core/src/lib.rs b/ddddocr-core/src/lib.rs index 1ae5ea6..a103779 100644 --- a/ddddocr-core/src/lib.rs +++ b/ddddocr-core/src/lib.rs @@ -1,16 +1,16 @@ -pub mod det; +mod det; pub mod error; -pub mod ocr; +mod ocr; mod slide; pub mod utils; -use crate::error::{Result, TensorError}; +use error::{Result, TensorError}; +use std::path::Path; -pub use crate::slide::{SlideResult, Slider}; pub use crate::det::{DetBuilder, DetectionResult, Detector}; -pub use crate::ocr::{Ocr, OcrBuilder, OcrResult}; -pub use crate::ocr::{ModelMetadata,Normalization}; -pub use ocr::Charset; +pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize}; +pub use crate::slide::{SlideResult, Slider}; + // DetSession pub enum OcrOutput { @@ -27,7 +27,7 @@ pub enum DetOutput { pub trait InferenceEngine { /// 关联类型:具体的 Session 需要声明自己到底产出什么枚举 type Output; - fn inference(&self, input_array: ndarray::Array4) -> Result; + fn inference(&self, input_array: ndarray::Array4) -> Result; } pub trait OcrEngine: InferenceEngine { @@ -35,3 +35,11 @@ pub trait OcrEngine: InferenceEngine { } pub trait DetEngine: InferenceEngine {} + +pub trait ModelBuilder { + type Session; + type Error; + fn model_for_path>(&self,model_path: P) -> Result; + + fn model_from_bytes(&self,model_bytes: &[u8]) -> Result; +} diff --git a/ddddocr-core/src/ocr/executor.rs b/ddddocr-core/src/ocr/executor.rs index bde2df3..f51a04f 100644 --- a/ddddocr-core/src/ocr/executor.rs +++ b/ddddocr-core/src/ocr/executor.rs @@ -5,7 +5,6 @@ use crate::ocr::color_filter::{HsvRange, apply_to_image}; use crate::utils::image_convert::png_rgba_white_preprocess; use crate::utils::image_processor::{convert_to_grayscale, resize_image}; use image::DynamicImage; -use serde::Serialize; use std::borrow::Cow; use std::fmt; // use tract_onnx::prelude::tract_ndarray::{ Ix2, s}; @@ -19,7 +18,7 @@ use ndarray::ArrayView2; use crate::error::{ImagePreprocessError, Result, TensorError}; use crate::{OcrEngine, OcrOutput}; use tracing::{ warn}; -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone)] pub enum OcrResult { /// 纯文本分支(对应 probability = false) Text(String), diff --git a/ddddocr-core/src/utils.rs b/ddddocr-core/src/utils.rs index bfe5fa4..40342e4 100644 --- a/ddddocr-core/src/utils.rs +++ b/ddddocr-core/src/utils.rs @@ -1,8 +1,8 @@ pub mod image_convert; +mod image_helper; pub mod image_processor; mod tensor_transform; -mod image_helper; // 对外统一暴露干净的 API 语义层 +pub use image_convert::ColorMode; pub use tensor_transform::normalize_ocr_logits; -pub use image_convert::{ColorMode}; diff --git a/ddddocr-ort/Cargo.toml b/ddddocr-ort/Cargo.toml new file mode 100644 index 0000000..fdcea1e --- /dev/null +++ b/ddddocr-ort/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ddddocr-ort" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ddddocr-core = { path = "../ddddocr-core" } # 引入兄弟库 +ort = { workspace = true ,features = ["cuda"]} +ndarray = { workspace = true } # 继承自工作空间 + +image = { workspace = true } + +serde = { workspace = true } +serde_json = { workspace = true } + +anyhow = { workspace = true } +thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 + +[features] +# 1. 声明 cuda feature +# 2. 当开启 ddddocr-ort 的 cuda feature 时,自动开启底层 ort 库的 cuda 支持(如果 ort 库支持的话) +cuda = ["ort/cuda"] \ No newline at end of file diff --git a/ddddocr-ort/src/det.rs b/ddddocr-ort/src/det.rs new file mode 100644 index 0000000..3e7b3f8 --- /dev/null +++ b/ddddocr-ort/src/det.rs @@ -0,0 +1 @@ +pub mod session; \ No newline at end of file diff --git a/ddddocr-ort/src/det/session.rs b/ddddocr-ort/src/det/session.rs new file mode 100644 index 0000000..575cc4d --- /dev/null +++ b/ddddocr-ort/src/det/session.rs @@ -0,0 +1,67 @@ +use crate::types::Session; +use ddddocr_core::error::{Result, TensorError}; +use ddddocr_core::{DetEngine, DetOutput, InferenceEngine}; +use ndarray::Ix3; +use ort::inputs; +use ort::value::TensorRef; +// use tract_onnx::prelude::{tvec, IntoTensor, Tensor}; + +#[derive(Debug)] +pub struct DetSession { + pub session: Session, +} + +impl DetSession { + pub fn new(session: Session) -> Self { + Self { session } + } + +} + +impl InferenceEngine for DetSession { + type Output = DetOutput; // 明确绑定 OCR 小枚举 + fn inference(&self, input_array: ndarray::Array4) -> Result { + // tract 的 run 会返回一个 Vec,我们通常只需要第一个输出 + // let result = self.ocr.run(tvec!(tensor.into()))?; + let mut session_guard = self + .session + .lock() + .map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?; + + + let result = session_guard + .run(inputs![TensorRef::from_array_view(&input_array).map_err( + |e| TensorError::Engine(format!("构建输入失败: {e}")) + )?]) + .map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?; + // .context("执行模型推理失败")?; + println!("模型输出原始数据: {:?}", result); + + // Ok(result.swap_remove(0).into_tensor()) + let raw_value = &result[0]; + + // raw_tensor.into_plain_array()? + let (shape_ref, slice) = raw_value.try_extract_tensor::().map_err(|_| { + TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string()) + })?; + // 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec) + let shape_vec: Vec = + shape_ref.to_vec().iter().map(|v| *v as usize).collect(); + let shape_vec_slice = shape_vec.as_slice(); + + let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice) + .map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?; + + let array3 = view.to_owned().into_dimensionality::().map_err(|_| { + TensorError::DimensionMismatch { + expected: "3D 检测矩阵 [Batch, Box_Count, Box_Attributes]".to_string(), + actual: shape_vec, // 优雅降维失败时动态捕获 + } + })?; + Ok(DetOutput::Detection(array3)) + + // 在引擎内部消化掉 DatumType 强耦合 + } +} + +impl DetEngine for DetSession {} diff --git a/ddddocr-ort/src/lib.rs b/ddddocr-ort/src/lib.rs new file mode 100644 index 0000000..4940d3f --- /dev/null +++ b/ddddocr-ort/src/lib.rs @@ -0,0 +1,9 @@ +mod det; +pub mod loader; +mod ocr; +mod types; + + +pub use ddddocr_core::{SlideResult, Slider,OcrBuilder}; +pub use det::session::DetSession; +pub use ocr::session::OcrSession; \ No newline at end of file diff --git a/ddddocr-ort/src/loader.rs b/ddddocr-ort/src/loader.rs new file mode 100644 index 0000000..ec95941 --- /dev/null +++ b/ddddocr-ort/src/loader.rs @@ -0,0 +1,7 @@ +mod error; +mod metadata; +mod model; + +pub use error::{Error, ParseError, Result}; +pub use metadata::{ModelMetadataDto, NormalizationDto, TractModelMetadata}; +pub use model::OrtModelLoader; \ No newline at end of file diff --git a/ddddocr-ort/src/loader/error.rs b/ddddocr-ort/src/loader/error.rs new file mode 100644 index 0000000..1e9fba2 --- /dev/null +++ b/ddddocr-ort/src/loader/error.rs @@ -0,0 +1,66 @@ +use ort::Error as OrtError; +pub type Result = std::result::Result; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("builder构建失败")] + Build(#[from] BuildError), + /// 解析 ONNX 模型/路径失败(如文件损坏、算子不支持、路径非法) + #[error("解析 ONNX 模型结构失败: {0}")] + ModelParse(#[from] ParseError), + + /// 模型计算图优化失败(如常量折叠、形状推导失败) + #[error("优化 Tract 模型图失败: {0}")] + OptimizationFailed(#[source] OrtError), + + /// 构建可执行 Session 失败(如输入输出 Tensor 类型/形状未确定) + #[error("构建可运行 Tract 实例失败: {0}")] + RunnableBuildFailed(#[source] OrtError), + + /// JSON 反序列化失败(自动透传 serde_json 报错) + #[error("模型 Metadata JSON 解析失败: {0}")] + JsonParse(#[from] serde_json::Error), + + /// 字节流非合法 UTF-8 编码(自动透传 Utf8Error) + #[error("Metadata 字节流不是合法的 UTF-8 编码: {0}")] + InvalidUtf8(#[from] std::str::Utf8Error), + + #[error("模型元数据解析失败: {0}")] + MetadataParse(String), + + /// 承载任何第三方扩展、解密、特定预处理插件在执行时产生的自定义错误 + #[error("{0}: {1}")] + Other(String, #[source] Box), +} + + + +impl Error { + /// 方便将任何第三方 Error 包装为 Error::Other + pub fn new(msg: impl Into, err: E) -> Self + where + E: Into>, + { + Self::Other(msg.into(), err.into()) + } +} +#[derive(thiserror::Error, Debug)] +pub enum ParseError { + /// 策略 A:从文件路径加载失败(附带路径上下文信息,方便排查是找不到文件还是格式不对) + #[error("从路径 '{0}' 加载 ONNX 模型失败: {1}")] + Path(String, #[source] OrtError), + + /// 策略 B:从内存字节流加载失败(如 include_bytes! 传入的字节流损坏) + #[error("从内存字节流解析 ONNX 模型失败: {0}")] + Bytes(#[source] OrtError), +} +#[derive(thiserror::Error, Debug)] +pub enum BuildError{ + #[error("builder构建失败")] + BuildFailed(#[from] OrtError), + #[error("builder构建失败")] + Threads(String), + #[error("builder构建失败")] + EnabledCudaFailed(String), + #[error("builder构建失败")] + NotEnabledCuda(String) +} \ No newline at end of file diff --git a/ddddocr-ort/src/loader/metadata.rs b/ddddocr-ort/src/loader/metadata.rs new file mode 100644 index 0000000..fb067dc --- /dev/null +++ b/ddddocr-ort/src/loader/metadata.rs @@ -0,0 +1,93 @@ +use crate::loader::error::{Error, Result}; + +use ddddocr_core::ModelMetadata; +use ddddocr_core::Resize; +use ddddocr_core::{Charset, Normalization}; +use serde::Deserialize; +use std::borrow::Cow; + +#[derive(Deserialize)] +#[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one" +pub enum NormalizationDto { + /// 映射到 [0.0, 1.0] -> pixel / 255.0 + ZeroToOne, + /// 映射到 [-1.0, 1.0] -> (pixel / 255.0 - 0.5) / 0.5 + MinusOneToOne, +} + +impl From for Normalization { + fn from(dto: NormalizationDto) -> Self { + match dto { + NormalizationDto::ZeroToOne => Normalization::ZeroToOne, + NormalizationDto::MinusOneToOne => Normalization::MinusOneToOne, + } + } +} + +/// 仅用于反序列化 JSON 的中间临时结构体(DTO) +#[derive(Deserialize)] +pub struct ModelMetadataDto { + charset: Vec, + word: bool, + #[serde(alias = "image")] + resize: Vec, + channel: u8, + /// 新增:允许在配置文件中指定归一化策略。 + /// 使用 serde(default) 可以在不配置时提供一个默认值(比如默认 ZeroToOne) + #[serde(default = "default_normalization")] + normalization: NormalizationDto, +} +fn default_normalization() -> NormalizationDto { + NormalizationDto::ZeroToOne +} + +/// Tract 专属扩展trait 或 工具函数 +pub trait TractModelMetadata: Sized { + fn from_json_str(json_str: &str) -> Result; + /// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载) + fn from_json_bytes(bytes: &[u8]) -> Result { + let json_str = std::str::from_utf8(bytes)?; + Self::from_json_str(json_str) + } +} +impl TractModelMetadata for ModelMetadata { + // --- 优雅的工厂模式构造器 --- + fn from_json_str(json_str: &str) -> Result { + let dto: ModelMetadataDto = serde_json::from_str(json_str)?; + + // 1. 将 DTO 的字符串数组转化为强类型的 Charset + let tokens: Vec> = + dto.charset.into_iter().map(|s| Cow::Owned(s)).collect(); + let charset = Charset::new(tokens); + + // 2. 解析 resize 策略(重现 Python 的复杂条件判断) + if dto.resize.len() != 2 { + return Err(Error::MetadataParse( + "'resize (or image)' 字段必须是包含两个元素的数组,例如 [-1, 64]".to_string(), + )); + } + let r0 = dto.resize[0]; + let r1 = dto.resize[1]; + + let resize = if r0 == -1 { + if dto.word { + // 如果 word 为 true,且包含 -1,Python 里是 resize 为 (r1, r1) 的正方形 + Resize::Square(r1 as u32) + } else { + // 如果 word 为 false,且包含 -1,Python 里是高度固定为 r1,宽度按原图比例缩放 + Resize::DynamicWidth(r1 as u32) + } + } else { + // 正常的固定宽高 + Resize::Fixed(r0 as u32, r1 as u32) + }; + + Ok(ModelMetadata::new( + charset, + dto.word, + resize, + dto.channel, + dto.normalization.into(), + )) + } +} diff --git a/ddddocr-ort/src/loader/model.rs b/ddddocr-ort/src/loader/model.rs new file mode 100644 index 0000000..291f4a6 --- /dev/null +++ b/ddddocr-ort/src/loader/model.rs @@ -0,0 +1,114 @@ +use crate::loader::Error; +use crate::loader::error::{BuildError, ParseError, Result}; +use crate::types::Session; +use ddddocr_core::ModelBuilder; +use ort::session::Session as OrtSession; +use ort::session::builder::SessionBuilder; +use std::sync::{Arc, Mutex}; + +pub struct OrtModelLoader; + +impl OrtModelLoader { + /// 获取针对 ORT 后端的链式构建器 + pub fn builder() -> OrtModelBuilder { + OrtModelBuilder::default() + } +} +/// ORT 专用的链式构建器 +#[derive(Debug, Clone)] +pub struct OrtModelBuilder { + use_gpu: bool, + device_id: i32, + intra_threads: Option, +} +impl Default for OrtModelBuilder { + fn default() -> Self { + Self { + use_gpu: false, + device_id: 0, + intra_threads: None, + } + } +} +impl OrtModelBuilder { + /// 开启或关闭 GPU 加速 + pub fn use_gpu(mut self, enable: bool) -> Self { + self.use_gpu = enable; + self + } + + /// 指定 GPU 设备 ID + pub fn device_id(mut self, id: i32) -> Self { + self.device_id = id; + self + } + pub fn num_threads(mut self, threads: usize) -> Self { + self.intra_threads = Some(threads); + self + } + + /// 内部辅助方法:根据当前的配置构建 ORT 底层的 SessionBuilder + fn create_session_builder(&self) -> Result { + let mut builder = OrtSession::builder().map_err(|e| BuildError::BuildFailed(e))?; + // 如果用户显式设置了线程数,则配置给 ORT + if let Some(threads) = self.intra_threads { + builder = builder + .with_intra_threads(threads) + .map_err(|e| BuildError::Threads(format!("设置线程数失败: {e}")))?; + } + + if self.use_gpu { + // 根据 ort 库版本配置 CUDA 执行提供者 (Execution Provider) + #[cfg(feature = "cuda")] + { + use ort::ep::CUDAExecutionProvider; + let cuda_ep = CUDAExecutionProvider::default().with_device_id(self.device_id); + builder = builder + .with_execution_providers([cuda_ep.build()]) + .map_err(|e| { + BuildError::EnabledCudaFailed(format!("配置 CUDA 硬件加速失败: {e}")) + })? + } + #[cfg(not(feature = "cuda"))] + { + // 如果用户明确开启了 GPU,但 Feature 没编译进去,明确抛错提醒 + return Err(BuildError::NotEnabledCuda( + "未启用 CUDA 支持:请在 Cargo.toml 中为 ddddocr-ort 开启 `cuda` feature" + .to_string(), + ))?; + } + } + + Ok(builder) + } +} + +impl ModelBuilder for OrtModelBuilder { + type Session = Session; + type Error = Error; + + fn model_for_path

(&self, model_path: P) -> Result + where + P: AsRef, + { + let path_ref = model_path.as_ref(); + let mut builder = self.create_session_builder()?; + // Session::builder() 会返回 Result + + let session = builder + .commit_from_file(path_ref) + .map_err(|e| ParseError::Path(path_ref.display().to_string(), e))?; + + Ok(Arc::new(Mutex::new(session))) // 这里的session需要包装下 + } + /// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用) + fn model_from_bytes(&self, model_bytes: &[u8]) -> Result { + let mut builder = self.create_session_builder()?; + + let session = builder + .commit_from_memory(model_bytes) + .map_err(ParseError::Bytes)?; + + Ok(Arc::new(Mutex::new(session))) + } +} diff --git a/ddddocr-ort/src/ocr.rs b/ddddocr-ort/src/ocr.rs new file mode 100644 index 0000000..f52f1c4 --- /dev/null +++ b/ddddocr-ort/src/ocr.rs @@ -0,0 +1 @@ +pub mod session; diff --git a/ddddocr-ort/src/ocr/session.rs b/ddddocr-ort/src/ocr/session.rs new file mode 100644 index 0000000..204e580 --- /dev/null +++ b/ddddocr-ort/src/ocr/session.rs @@ -0,0 +1,225 @@ +use crate::types::Session; +use ddddocr_core::ModelMetadata; +use ddddocr_core::error::{DdddError, Result, TensorError}; +use ddddocr_core::utils::normalize_ocr_logits; +use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput}; +use ort::inputs; +use ort::value::{TensorElementType, TensorRef}; +use std::sync::Mutex; +// 引入核心层的统一错误类型 +/// 明确命名为 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 格式化输出,彻底融化套娃外壳,保证日志干净漂亮 +impl std::fmt::Debug for AxisDim { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AxisDim::Static(size) => write!(f, "{}", size), + AxisDim::Dynamic(expr) => write!(f, "Dynamic(\"{}\")", expr), + } + } +} +/// 模拟 Python 的 input_info 和 output_info 结构 +#[derive(Debug, Clone)] +pub struct TensorInfo { + pub name: String, + pub shape: Vec, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号 + pub data_type: TensorElementType, // 对应 Python 的 type +} + +/// 最终返回的模型完整信息 +#[derive(Debug, Clone)] +pub struct ModelInfo { + pub inputs: Vec, + pub outputs: Vec, + /// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎) + pub providers: Option>, +} +pub struct OcrSession { + pub session: Session, + pub model_metadata: ModelMetadata, +} +impl OcrSession { + pub fn new(session: Session, model_metadata: ModelMetadata) -> Self { + Self { + session, + model_metadata, + } + } +} +impl OcrEngine for OcrSession { + fn metadata(&self) -> &ModelMetadata { + &self.model_metadata + } +} +impl InferenceEngine for OcrSession { + type Output = OcrOutput; + /// 对应 Python 的 _inference + fn inference(&self, input_array: ndarray::Array4) -> Result { + // tract 的 run 会返回一个 Vec,我们通常只需要第一个输出 + // let result = self.ocr.run(tvec!(tensor.into()))?; + // let tensor = Tensor::from(input_array); + let mut session_guard = self + .session + .lock() + .map_err(|_| TensorError::Engine("获取 Session 锁失败 (Poisoned)".to_string()))?; + + // // 2. 获取输入节点名称 + // let input_name = session_guard + // .inputs() + // .first() + // .map(|i| i.name()) + // .unwrap_or("input"); + // + // // 3. 在 session_guard (&mut Session) 上调用 run + // let outputs = session_guard + // .run(inputs![TensorRef::from_array_view(&input_array).map_err(|e| TensorError::Engine(format!("构建输入失败: {e}")))? ) + // .map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?; + + let result = session_guard + .run(inputs![TensorRef::from_array_view(&input_array).map_err( + |e| TensorError::Engine(format!("构建输入失败: {e}")) + )?]) + .map_err(|e| TensorError::Engine(format!("执行模型推理失败: {e}")))?; + // .context("执行模型推理失败")?; + println!("模型输出原始数据: {:?}", result); + // Ok(result.swap_remove(0).into_tensor()) + let raw_value = &result[0]; + // let dtype = raw_tensor + // .dtype(); + // .map_err(|e| TensorError::Engine(format!("无法读取输出数据类型: {e}")))?; + // 在引擎内部消化掉 DatumType 强耦合 + match raw_value.dtype().tensor_type().unwrap() { + TensorElementType::Int64 => { + let (array_d, slice) = raw_value + .try_extract_tensor::() + .map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?; + // .context("Tract 无法获取 i64 内存视图")?; + + // 🌟 提前提取真实维度 + let actual_shape = array_d + .to_vec() + .iter() + .map(|v| *v as usize) + .collect::>(); + + let view = ndarray::ArrayViewD::from_shape(actual_shape.as_slice(), slice) + .map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?; + // 转成标准的 Array1 传给 core + let array1 = view + .to_owned() + .into_dimensionality::() + .map_err(|_| TensorError::DimensionMismatch { + expected: "1D 字符索引静态矩阵".to_string(), + actual: actual_shape, + })?; + Ok(OcrOutput::Indices(array1)) + } + TensorElementType::Float32 => { + let shape = raw_value.shape(); + println!("模型输出shape数据: {:?}", shape); + // raw_tensor.to_plain_array_view() + let (shape_ref, slice) = raw_value + .try_extract_tensor::() + .map_err(|_| TensorError::Engine("Tract 无法获取 f32 内存视图".to_string()))?; + // 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗 + let shape_vec: Vec = + shape_ref.to_vec().iter().map(|v| *v as usize).collect(); + let shape_vec_slice = shape_vec.as_slice(); + + let view = ndarray::ArrayViewD::from_shape(shape_vec_slice, slice) + .map_err(|_| TensorError::Engine("构建 ndarray ArrayViewD 失败".to_string()))?; + + normalize_ocr_logits(view, shape_vec_slice) + } + _ => Err( + // anyhow::anyhow!("不支持的模型输出数据类型: {:?}",raw_tensor.datum_type()) + TensorError::UnknownOutputFormat, + ), + } + } +} +// impl OcrSession { +// /// 获取模型输入的节点信息列表 +// pub fn input_info(&self) -> Result> { +// let model = self.session.model(); +// let outlets = model.input_outlets().map_err(DdddError::new)?; +// self.resolve_tensors(model, outlets) +// } +// +// /// 获取模型输出的节点信息列表 +// pub fn output_info(&self) -> Result> { +// let model = self.session.model(); +// let outlets = model.output_outlets().map_err(DdddError::new)?; +// self.resolve_tensors(model, outlets) +// } +// +// /// 获取模型详细元数据信息(对标 Python ddddocr 的 get_model_info) +// /// 完美包容 [1, 1, 64, image_width] 这样的变长图像模型 +// /// 获取模型详细元数据信息(代码更紧凑、优雅) +// pub fn model_info(&self) -> Result { +// Ok(ModelInfo { +// inputs: self.input_info()?, +// outputs: self.output_info()?, +// providers: None, +// }) +// } +// +// /// 提取出来的公共转换逻辑:将一组 OutletId 解析为 TensorInfo 列表 +// fn resolve_tensors(&self, model: &TypedModel, outlets: &[OutletId]) -> Result> { +// outlets +// .iter() +// .map(|&outlet_id| { +// let fact = model.outlet_fact(outlet_id).map_err(DdddError::new)?; +// // .map_err(|e| { +// // DdddError::InternalError(format!("解析节点 Fact 失败: {:?}", e)) +// // })?; +// +// let shape = self.resolve_shape(&fact.shape); +// let node_name = model.node(outlet_id.node).name.clone(); +// +// Ok(TensorInfo { +// name: node_name, +// shape, +// data_type: fact.datum_type, +// }) +// }) +// .collect() // 函数式声明:自动传播第一处发生的错误 +// } +// +// /// 安全还原 Tract 维度至 Vec +// fn resolve_shape(&self, shape_fact: &ShapeFact) -> Vec { +// let tract_shape = shape_fact.to_tvec(); +// +// let resolved = tract_shape +// .iter() +// .map(|dim| { +// // 防御性编程:必须同时满足能够转换为 i64 且 大于等于 0 +// if let Ok(size) = dim.to_i64() { +// if size >= 0 { +// AxisDim::Static(size as usize) +// } else { +// // 如果 ONNX 导出时某些动态维度被标记为了 -1,安全地作为动态符号捕获 +// AxisDim::Dynamic(dim.to_string()) +// } +// } else { +// AxisDim::Dynamic(dim.to_string()) +// } +// }) +// .collect(); +// +// resolved +// } +// } diff --git a/ddddocr-ort/src/types.rs b/ddddocr-ort/src/types.rs new file mode 100644 index 0000000..f22a5f0 --- /dev/null +++ b/ddddocr-ort/src/types.rs @@ -0,0 +1,4 @@ +use ort::session::Session as OrtSession; +use std::sync::{Arc, Mutex}; + +pub type Session = Arc>; diff --git a/ddddocr-ort/tests/char_slice.rs b/ddddocr-ort/tests/char_slice.rs new file mode 100644 index 0000000..cec0056 --- /dev/null +++ b/ddddocr-ort/tests/char_slice.rs @@ -0,0 +1,600 @@ +use std::borrow::Cow; +use std::fs::File; +use std::path::Path; +use anyhow::anyhow; +use ddddocr_core::Charset; +use ddddocr_core::{Normalization, Resize}; + +pub const CHARSET_BETA: &[&str] = &[ + "", "笤", "谴", "膀", "荔", "佰", "电", "臁", "矍", "同", "奇", "芄", "吠", "6", "曛", "荇", + "砥", "蹅", "晃", "厄", "殣", "c", "辱", "钋", "杻", "價", "眙", "鴿", "⒄", "裙", "训", "涛", + "酉", "挞", "忙", "怍", "︰", "镍", "檐", "眯", "茓", "辖", "淩", "啟", "蜀", "芟", "裟", "楝", + "彘", "嶪", "费", "亞", "滁", "榉", "朝", "f", "倻", "裎", "谧", "崂", "卑", "助", "触", "氐", + "锟", "铢", "膪", "脐", "渲", "荫", "佾", "琯", "钣", "珰", "翦", "膻", "娥", "浥", "淄", "猸", + "内", "消", "粞", "反", "苪", "冽", "酵", "玩", "父", "存", "屯", "殷", "俐", "篱", "俛", "塮", + "苕", "耲", "输", "壖", "溶", "琤", "氏", "真", "黩", "瑄", "阶", "茔", "眩", "浙", "痄", "噔", + "烤", "楯", "²", "铆", "裈", "偬", "盏", "祐", "伯", "庙", "獯", "榀", "裒", "综", "蒸", "架", + "蜱", "鹖", "涴", "肌", "廖", "祾", "蔗", "破", "!", "鑫", "瓷", "H", "宛", "倪", "贝", "酝", + "倞", "榼", "菊", "帕", "胍", "淌", "抨", "倕", "味", "独", "à", "庐", "蹲", "肸", "洹", "騳", + "绖", "觉", "蝙", "铜", "選", "郚", "奄", "手", "篦", "忮", "潺", "歁", "湖", "貔", "缚", "癫", + "捣", "翎", "勇", "徒", "杪", "捃", "纴", "郞", "蛱", "浓", "講", "薇", "汊", "彻", "琖", "觇", + "驩", "野", "闺", "彩", "膊", "簸", "瑭", "龏", "栓", "攸", "堕", "鹿", "檠", "锽", "晟", "煊", + "衖", "p", "L", "侞", "吹", "岵", "捡", "邃", "曩", "泼", "娌", "磙", "鞮", "號", "苤", "骁", + "感", "氙", "榜", "菟", "蠲", "∶", "焌", "漯", "胪", "以", "剜", "=", "衰", "剔", "疏", "韩", + "邋", "探", "搌", "握", "舵", "腰", "咐", "郎", "鈺", "赴", "斩", "铥", "棂", "褫", "秾", "城", + "葬", "缺", "甓", "沙", "鴨", "恧", "吩", "膈", "俗", "引", "濑", "坛", "蛘", "谍", "飘", "鎚", + "貋", "袅", "圃", "肽", "祖", "瑢", "鄣", "卡", "恸", "饷", "撖", "阖", "碌", "墉", "⇋", "抗", + "僇", "撩", "狷", "静", "荽", "憯", "虻", "滹", "簧", "Y", "汾", "嫁", "蚴", "岈", "榔", "邶", + "挛", "火", "w", "旌", "線", "3", "跏", "F", "楸", "瞬", "證", "現", "符", "鲀", "窸", "朗", + "m", "劉", "襄", "鸻", "敖", "憩", "濛", "胶", "雏", "禽", "缜", "鐀", "澄", "泉", "懈", "鹟", + "牙", "叟", "镖", "膨", "硷", "钏", "嵌", "冲", "橪", "厨", "溘", "妻", "贺", "耀", "潲", "瞳", + "惺", "涑", "鄄", "舡", "战", "钹", "盍", "窀", "凳", "锋", "(", "绤", "翻", "》", "嶷", "戛", + "照", "设", "两", "霹", "風", "格", "栖", "椹", "№", "蔌", "达", "悠", "旒", "函", "抔", "逆", + "疙", "玫", "箢", "恩", "樘", "远", "考", "荭", "殒", "靡", "蝾", "舅", "䀲", "偾", "灿", "埴", + "瀍", "特", "诸", "搦", "恒", "妊", "课", "劂", "殊", "艋", "柚", "硕", "捅", "钍", "芘", "脖", + "襁", "募", "卅", "疆", "嫖", "黹", "臀", "豇", "瘗", "憎", "嗯", "Θ", "跺", "喧", "捺", "爪", + "鲠", "縠", "屋", "撑", "者", "娶", "喝", "墼", "丶", "茚", "髅", "瓢", "农", "橼", "攰", "折", + "诋", "镒", "赂", "捲", "耑", "沐", "窜", "亰", "煌", "阕", "羯", "纤", "滇", "值", "琥", "渴", + "祗", "鳌", "蛞", "庄", "鹇", "訄", "犷", "弋", "琐", "佗", "氛", "揄", "旺", "聱", "榨", "湿", + "蟥", "湎", "敕", "轷", "耿", "三", "運", "瑶", "困", "勁", "蚱", "泸", "螫", "斜", "蟠", "轨", + "镞", "霆", "嶙", "烷", "瘩", "敦", "塾", "僚", "澙", "關", "酩", "殴", "—", "例", "筷", "乘", + "颗", "核", "孬", "舣", "糇", "劾", "黧", "镌", "罅", "X", "仅", "哐", "蟊", "呻", "呕", "粟", + "配", "伊", "槊", "昌", "宰", "盘", "肫", "鳥", "圾", "恬", "辘", "绿", "時", "丐", "扃", "敓", + "摄", "陕", "滿", "鹆", "嗬", "龅", "渣", "釣", "萦", "督", "孑", "∧", "疥", "噱", "蝎", "君", + "笮", "泌", "镔", "称", "柘", "鬣", "罨", "潍", "垣", "顔", "褶", "礓", "△", "骐", "湍", "獾", + "羖", "戾", "预", "祭", "鹨", "凤", "茕", "珏", "蛊", "毛", "枰", "鄭", "娲", "丕", "蜜", "纟", + "蛹", "粮", "嚰", "嚓", "螨", "裰", "G", "集", "删", "郜", "舻", "嵛", "鵰", "腊", "峄", "脸", + "鲺", "坢", "寞", "撰", "顸", "枋", "荠", "夡", "豆", "馆", "赭", "傎", "淼", "镪", "許", "礌", + "带", "訂", "饧", "锜", "及", "漾", "編", "究", "仍", "糜", "喳", "嗝", "醦", "堅", "企", "烔", + "图", "垲", "枥", "畿", "踱", "槟", "◆", "酽", "溢", "酥", "谚", "缨", "死", "镑", "干", "用", + "紊", "坑", "副", "枭", "琮", "鸨", "獻", "弈", "伺", "醍", "氓", "宁", "臻", "贾", "啐", "玉", + "咀", "孀", "烆", "嘱", "频", "蜥", "楡", "瞩", "委", "锺", "赀", "睬", "旅", "刿", "韪", "抹", + "鞘", "x", "钝", "倌", "奖", "蟭", "灌", "肼", "曰", "啊", "属", "唐", "彦", "煦", "鄘", "坦", + "鬻", "告", "单", "菽", "匏", "浚", "仺", "怜", "拦", "鸡", "拄", "乍", "燠", "暧", "竭", "⒂", + "济", "Ⓡ", "趑", "舴", "体", "拒", "罂", "说", "猎", "闫", "鄗", "妲", "鑙", "葶", "匍", "等", + "略", "盲", "唔", "钰", "渠", "镦", "葫", "蹒", "姘", "婵", "夯", "实", "何", "株", "锌", "礁", + "桺", "捉", "鮕", "莓", "轶", "辉", "溋", "视", "嗒", "猞", "猁", "杓", "怡", "咴", "巷", "仂", + "婉", "睛", "?", "淡", "/", "郛", "绨", "较", "毁", "沓", "瞎", "馏", "蕙", "戏", "i", "董", + "臣", "鹫", "栳", "锹", "裕", "蜷", "唣", "9", "缂", "螬", "笥", "惶", "蚰", "徨", "忭", "传", + "绛", "离", "锑", "候", "拓", "德", "损", "附", "紡", "徕", "錾", "蕻", "⑧", "構", "镊", "脘", + "靸", "涒", "镡", "光", "廒", "尴", "荥", "佳", "弼", "暮", "榆", "鼑", "辅", "钆", "湝", "佤", + "瘌", "炽", "筰", "嗈", "電", "飙", "坶", "椿", "俾", "灊", "泵", "像", "咅", "樓", "苒", "烨", + "溺", "棽", "戒", "箅", "愔", "缢", "楞", "庤", "塑", "湮", "沽", "蝌", "赌", "薨", "锾", "圜", + "骥", "秉", "瞌", "惇", "诊", "圣", "睢", "我", "廰", "苠", "襻", "鲚", "酮", "厮", "评", "沈", + "愀", "垞", "习", "敷", "比", "欢", "尚", "钪", "卣", "她", ",", "伴", "赃", "蚬", "喇", "醇", + "嚅", "T", "姝", "鍊", "悯", "N", "抚", "颡", "獐", "趟", "洑", "缝", "喁", "帷", "憷", "获", + "阉", "镫", "臨", "炮", "奴", "揩", "叩", "恺", "粱", "胁", "憬", "痿", "´", "沘", "彀", "饩", + "滋", "竑", "嗑", "鸟", "T", "璠", "快", "銀", "舶", "羞", "桞", "飛", "茹", "師", "偌", "節", + "冁", "叡", "臑", "踔", "酔", "養", "溦", "岐", "豉", "杌", "胩", "仳", "沾", "窑", "曳", "闷", + "垢", "垆", "磨", "髹", "態", "丽", "見", "洃", "遣", "场", "铭", "溉", "衬", "橦", "详", "馊", + "濂", "瑾", "鲫", "贞", "搴", "胺", " ", "景", "执", "袴", "炖", "箭", "楂", "婚", "镧", "厩", + "宣", "缣", "跃", "痘", "亓", "宦", "豪", "侣", "郧", "與", "痳", "挚", "殻", "嘢", "洽", "蹉", + "孽", "森", "俚", "八", "砹", "凹", "訇", "屹", "啰", "宴", "廨", "沇", "麇", "泅", "绡", "辛", + "鹲", "鋆", "旮", "婀", "幔", "赞", ":", "永", "活", "萸", "霉", "←", "媲", "阚", "鲮", "佝", + "獒", "圹", "隋", "征", "蚶", "龊", "搀", "嫫", "鬏", "幞", "學", "然", "沧", "萑", "襜", "撻", + "苯", "狳", "鞠", "咋", "壹", "栅", "款", "镘", "阳", "蚜", "荬", "糊", "疳", "糕", "镵", "寐", + "褴", "v", "醋", "诀", "汰", "啸", "备", "娘", "氚", "镓", "室", "簟", "硝", "嘌", "釐", "争", + "男", "疬", "鹄", "艳", "倒", "忸", "庶", "葒", "岖", "涨", "羝", "诼", "纨", "纰", "扉", "酎", + "藉", "疮", "枞", "黜", "戮", "芽", "鳑", "末", "蒿", "茈", "透", "渊", "秷", "〈", "试", "络", + "羼", "滪", "奋", "虏", "脁", "沫", "蓰", "襆", "披", "鲌", "艚", "逹", "炳", "泔", "叹", "轳", + "锵", "嫜", "佥", "严", "迅", "筠", "逵", "铿", "钇", "拇", "诏", "绯", "吊", "纠", "蟾", "c", + "涝", "汩", "盐", "跋", "拤", "邹", "镨", "羚", "龆", "脊", "攉", "傕", "短", "團", "蹬", "嘤", + "奎", "熨", "芪", "鸢", "濉", "''", "莴", "義", "赜", "踺", "皂", "努", "偏", "狡", "遭", "吞", + "嘿", "婊", "媸", "增", "殿", "刮", "燘", "劫", "娜", "瞄", "寡", "优", "捋", "佴", "菰", "蓠", + "笙", "镃", "樇", "瘫", "B", "橇", "逯", "堍", "О", "磐", "腼", "て", "送", "狭", "皓", "亡", + "嗉", "菠", "顺", "連", "嶶", "瑪", "辟", "婷", "牛", "笫", "窅", "萁", "戟", "覃", "馍", "建", + "謇", "旘", "镣", "燏", "葉", "轺", "倏", "堪", "见", "葛", "钕", "键", "押", "僊", "槐", "戎", + "窨", "洙", "鲢", "鞒", "慒", "雁", "圭", "D", "陌", "肱", "蜿", "洧", "惑", "祛", "樟", "矧", + "呵", "峻", "凝", "蕨", "拯", "珮", "塥", "展", "贻", "囐", "弱", "庳", "嫔", "緣", "呈", "策", + "漉", "瑗", "鲂", "鹂", "吾", "灶", "并", "挲", "重", "奭", "皙", "侪", "埗", "烬", "纾", "椒", + "技", "ɔ", "擀", "恍", "遨", "订", "雨", "卵", "锏", "猗", "癸", ")", "谡", "稷", "枨", "蹽", + "荑", "沅", "稽", "間", "冉", "颇", "酺", "份", "瞾", "毯", "藥", "蕞", "狲", "吡", "慷", "卯", + "摽", "肿", "嗛", "悒", "丨", "横", "鳡", "仫", "狎", "砗", "聿", "腥", "酡", "飱", "柳", "遽", + "汇", "湔", "麋", "垃", "粽", "坷", "鳗", "迫", "丢", "\"", "⒀", "嗲", "肐", "結", "署", "飨", + "蠡", "涩", "挈", "浿", "鐾", "姞", "隧", "铘", "呜", "蜕", "鷄", "逼", "哌", "病", "係", "偿", + "Ⅲ", "埋", "妤", "赘", "悉", "陷", "沸", "呲", "誓", "舆", "髀", "挫", "羑", "据", "顿", "淝", + "抟", "珧", "郑", "仗", "怛", "掠", "稳", "尥", "祙", "找", "郐", "锔", "轹", "钓", "黙", "饸", + "谌", "斐", "龙", "噫", "駆", "浼", "峒", "育", "纣", "溠", "铊", "亨", "杮", "呓", "钌", "业", + "繻", "溪", "戌", "蓿", "椱", "悱", "仉", "阮", "芈", "濋", "搔", "纽", "琛", "趄", "双", "镯", + "☆", "敉", "啬", "讦", "娱", "爾", "遶", "漱", "郗", "锪", "颃", "靰", "醊", "驊", "呢", "術", + "妙", "蚣", "溽", "酇", "巾", "舐", "却", "废", "邾", "砣", "乙", "鲜", "蒤", "囍", "璈", "稔", + "蘘", "匐", "業", "碟", "渺", "贤", "绋", "畑", "颞", "侥", "盟", "鼍", "阊", "蔽", "标", "吮", + "淬", "鏾", "圗", "夜", "乕", "娇", "瞿", "循", "讲", "懒", "熘", "禚", "观", "钷", "万", "n", + "未", "藝", "愿", "圈", "浩", "伦", "扛", "暄", "饶", "梧", "欣", "咿", "檔", "吼", "妮", "覆", + "辰", "誤", "允", "危", "硗", "惫", "瘪", "李", "焱", "沣", "坯", "穄", "归", "画", "营", ":", + "色", "哔", "矢", "巯", "祆", "傍", "享", "悻", "取", "凫", "铒", "唅", "眈", "疹", "败", "晴", + "顼", "绶", "剃", "斗", "禾", "4", "誜", "俨", "2", "患", "结", "可", "帇", "抍", "筝", "衢", + "鹛", "跸", "颢", "钾", "渡", "棒", "丛", "皱", "梓", "将", "压", "#", "岛", "?", "砺", "过", + "党", "挣", "瞋", "谶", "妯", "羡", "化", "淫", "歪", "鼗", "阄", "蔓", "烩", "餘", "猊", ".", + "畯", "祧", "狒", "碁", "咛", "鲈", "叨", "哞", "5", "娈", "半", "免", "拿", "畎", "媾", "棚", + "丈", "周", "匋", "酯", "奚", "爇", "摇", "搭", "蓇", "陽", "岢", "禤", "藠", "雅", "哲", "弹", + "按", "↑", "蹀", "察", "螾", "渎", "褂", "觳", "耍", "皲", "骗", "箫", "蕺", "亚", "保", "棵", + "放", "踪", "了", "熣", "亦", "痛", "币", "馐", "夢", "诱", "梱", "鲰", "郕", "璜", "祯", "颦", + "走", "踣", "嫚", "旯", "雲", "湟", "墨", "笃", "肇", "撝", "腦", "账", "舞", "⑨", "噻", "幂", + "僵", "崦", "’", "牢", "号", "嫡", "囱", "肥", "代", "锶", "掏", "随", "棓", "殉", "嘅", "掰", + "功", "垛", "踶", "娠", "霜", "碣", "鲼", "伉", "凄", "骋", "鹞", "洺", "乌", "赧", "瑛", "黎", + "曚", "鲴", "髫", "瘴", "藏", "雍", "畐", "蔻", "爼", "蹴", "巨", "贱", "汜", "胡", "虬", "椎", + "逸", "魇", "亶", "Φ", "忆", "赉", "塞", "潢", "垌", "简", "鼹", "發", "枢", "麝", "虹", "惭", + "唛", "春", "瑟", "郰", "桡", "捩", "堙", "嗨", "驳", "F", "荪", "忑", "贪", "躅", "步", "揜", + "闪", "垟", "晶", "分", "韭", "戴", "泪", "啧", "機", "峙", "和", "鸱", "绎", "屠", "阋", "黍", + "淸", "萩", "汉", "吐", "匙", "铗", "蠔", "簠", "鲵", "须", "蛣", "躏", "完", "咻", "釜", "馼", + "崤", "欻", "珐", "于", "郅", "焓", "轴", "递", "堰", "嗷", "儇", "壕", "嘟", "酸", "庾", "龂", + "妍", "锅", "雳", "桦", "抬", "谄", "气", "六", "诎", "绀", "张", "復", "客", "荞", "鳚", "衔", + "亁", "昂", "漤", "鞚", "筘", "绫", "彝", "枪", "苊", "榟", "饺", "苦", "顶", "衷", "聚", "寮", + "揆", "轪", "栋", "臂", "葖", "颋", "镐", "愕", "贸", "Q", "琼", "糥", "世", "莪", "龁", "禁", + "绲", "陶", "弑", "黢", "铵", "睐", "沄", "紬", "防", "癣", "曾", "钉", "纶", "膘", "句", "莸", + "踝", "躐", "酤", "腑", "雄", "堤", "喀", "姣", "孢", "阡", "褐", "胂", "髙", ";", "骖", "膺", + "糙", "辢", "⒃", "险", "砻", "缫", "骎", "低", "蚵", "箐", "苞", "劭", "峪", "工", "盈", "腹", + "袄", "祉", "癔", "笨", "R", "乚", "畏", "虍", "臾", "泛", "噙", "杷", "麗", "蹋", "逍", "迓", + "摅", "页", "戥", "胞", "艄", "壅", "啶", "趼", "牟", "翙", "蓝", "府", "轿", "砼", "荜", "杆", + "惊", "起", "瘅", "墈", "氖", "匀", "麃", "阘", "虮", "蘇", "蚤", "汗", "鳞", "籁", "缲", "畈", + "亟", "劬", "課", "蓄", "缅", "楮", "湜", "珩", "斋", "塬", "殁", "魃", "脞", "H", "澼", "钚", + "饕", "缕", "Ⅱ", "攮", "卿", "莅", "镆", "熹", "藩", "汁", "順", "趿", "拆", "蟹", "砒", "惴", + "㎡", "忖", "寝", "戕", "螭", "酿", "™", "柬", "枧", "凶", "蚁", "島", "殄", "鲊", "忠", "肉", + "辕", "叫", "徙", "漆", "缞", "夀", "楦", "佪", "兴", "粉", "裳", "蘧", "國", "旬", "看", "Ⅰ", + "剑", "痭", "襟", "恐", "遹", "◎", "窃", "穰", "澎", "敬", "旱", "燚", "坩", "彤", "尜", "猃", + "夏", "穈", "媒", "柑", "駡", "孛", "脉", "车", "零", "菩", "痊", "卉", "桔", "距", "吧", "漦", + "启", "仁", "滬", "馋", "帅", "鳈", "鄌", "超", "芡", "窘", "刽", "掌", "氤", "梽", "拎", "踏", + "勋", "甍", "玑", "稱", "鞍", "浍", "翅", "饟", "鼎", "罩", "加", "虚", "蕰", "簉", "堇", "巢", + "疲", "蟑", "狝", "瓮", "潋", "行", "饥", "散", "糌", "牵", "貢", "偉", "咄", "痕", "沃", "苓", + "锂", "狻", "褿", "畸", "姿", "煎", "胜", "觅", "烊", "質", "疵", "擢", "椤", "米", "累", "诳", + "斡", "K", "恻", "匦", "烫", "湾", "鹎", "吟", "摘", "涞", "恿", "嫉", "炎", "婧", "朽", "铑", + "ㆍ", "讧", "曜", "挑", "〇", "搅", "鹐", "丁", "彼", "棠", "饪", "箬", "祎", "魄", "囿", "犬", + "市", "髃", "勚", "桶", "辎", "瓞", "财", "缄", "園", "睒", "护", "尿", "融", "围", "水", "糍", + "虢", "呦", "越", "棺", "砮", "邓", "鹦", "稣", "呃", "柙", "鎌", "转", "袋", "湉", "亘", "俩", + "腆", "谣", "飔", "撂", "鄳", "爲", "盦", "谳", "卸", "W", "嚆", "婕", "卫", "拚", "呀", "汽", + "洣", "冻", "鳝", "录", "毋", "閥", "熬", "谜", "齐", "匳", "慧", "猴", "撬", "妳", "諾", "蠼", + "瘟", "伐", "颤", "奶", "陧", "麾", "岌", "浇", "邸", "「", "不", "哼", "热", "旳", "慙", "&", + "苔", "郿", "钗", "氡", "纹", "侬", "霓", "靈", "扁", "聢", "疼", "岣", "甥", "恭", "喷", "芫", + "骂", "肪", "熥", "揠", "鲷", "遁", "霎", "娆", "圩", "爬", "傲", "贽", "紫", "觑", "琇", "蟆", + "怙", "玙", "庼", "筮", "慗", "层", "娓", "蚨", "糟", "璩", "隼", "锧", "疱", "铎", "祠", "绁", + "速", "湛", "蝮", "立", "媵", "禇", "撸", "禳", ">", "恋", "⑥", "鹡", "蓑", "樱", "奸", "蝣", + "埭", "聪", "慭", "睑", "肢", "焗", "骃", "毵", "潼", "塘", "烧", "劓", "栾", "牯", "〉", "毓", + "釉", "庞", "宕", "磚", "夺", "畜", "俏", "筼", "虱", "釆", "计", "陀", "诧", "臱", "牌", "固", + "鹉", "凰", "擞", "■", "㛃", "词", "店", "當", "许", "妣", "耋", "硼", "根", "焅", "砸", "霈", + "锃", "巳", "誉", "咕", "锊", "P", "云", "乞", "为", "姆", "桕", "丞", "鳤", "楷", "醒", "趔", + "怔", "砉", "潏", "肝", "拷", "也", "璐", "厘", "致", "昇", "绥", "抃", "佩", "斥", "⑶", "断", + "纵", "翁", "耄", "沭", "洄", "别", "吻", "渍", "r", "愦", "替", "骓", "攻", "旦", "来", "案", + "坫", "辞", "班", "锝", "拜", "掎", "穸", "笛", "痂", "笈", "鲶", "僻", "依", "碎", "蕤", "炕", + "寥", "拔", "髑", "慨", "眺", "⑿", "珞", "歧", "湲", "錦", "拮", "哭", "爰", "验", "寸", "%", + "口", "阔", "积", "篝", "狴", "殂", "痧", "绽", "搪", "祸", "缘", "遯", "祜", "种", "缓", "冒", + "庵", "窥", "颂", "哕", "谘", "蜗", "掖", "驮", "郎", "跂", "蝓", "贲", "馔", "琰", "大", "芊", + "粝", "啻", "蒉", "谒", "羔", "蘑", "菑", "布", "徂", "l", "炬", "蘩", "弭", "嵎", "補", "岑", + "佯", "棍", "没", "志", "裣", "咸", "权", "豺", "韦", "優", "紧", "嚚", "牴", "酚", "沤", "睥", + "葆", "乓", "划", "犒", "惚", "埕", "锴", "就", "滓", "汕", "冏", "缵", "囟", "旸", "麂", "接", + "餮", "瓤", "偕", "灏", "溻", "揶", "钯", "荸", "宪", "泠", "槠", "抺", "威", "燴", "井", "骣", + "耜", "磔", "懜", "淇", "瘘", "蹼", "觏", "圬", "纔", "恤", "铝", "薏", "鞫", "衉", "E", "球", + "姽", "毹", "嵘", "睦", "蛉", "伽", "橥", "痤", "卞", "警", "则", "芬", "磉", "悢", "逾", "吆", + "朿", "锒", "卧", "E", "焉", "纥", "髦", "鞯", "牞", "蹿", "琪", "洌", "幛", "淳", "菲", "涔", + "噗", "勺", "势", "哓", "毪", "刖", "雜", "浪", "懵", "棁", "秋", "◇", "玡", "埯", "谥", "芑", + "拌", "Z", "埂", "寻", "虼", "豫", "蛄", "蜞", "椟", "〔", "哩", "絷", "咱", "郈", "湫", "抡", + "矮", "庖", "锰", "鎖", "葸", "棣", "砑", "题", "棋", "晕", "兠", "蓣", "貂", "裤", "昊", "扳", + "讴", "弩", "蚀", "尓", "暗", "莘", "黏", "寖", "蛔", "И", "痪", "饳", "妈", "Z", "秏", "矽", + "蘭", "艴", "菖", "寿", "撅", "秤", "颓", "廪", "醪", "耢", "螣", "椰", "麈", "听", "敞", "铠", + "润", "穑", "涟", "澹", "茨", "昨", "嚯", "钟", "護", "榇", "曦", "蜎", "呷", "邦", "瘵", "鲬", + "谖", "概", "九", "飞", "潇", "高", "锫", "钐", "徘", "诟", "缧", "剞", "【", "簋", "噍", "痋", + "关", "糁", "鹊", "潤", "邬", "赔", "序", "盤", "拙", "盾", "鸫", "迟", "氇", "塄", "庀", "噩", + "臃", "绔", "岔", "珠", "癀", "骡", "隶", "堋", "笆", "影", "昶", "飗", "α", "薄", "碜", "瞅", + "奠", "鲽", "牂", "裆", "刀", "遑", "隙", "沔", "訾", "旇", "s", "诮", "炱", "榚", "些", "縯", + "提", "甚", "埏", "峣", "意", "出", "郡", "摊", "哦", "轼", "嚒", "杈", "痢", "裥", "袖", "陔", + "溷", "裀", "迄", "枇", "筑", "貊", "鲃", "鸪", "V", "鞭", "粳", "菥", "盒", "毙", "风", "壽", + "胝", "章", "泄", "浜", "菇", "釘", "揖", "蝰", "榧", "掊", "瘿", "亍", "欤", "髻", "歃", "朱", + "撇", "铁", "兜", "喋", "趵", "瘼", "芤", "邳", "除", "滌", "么", "践", "矜", "曆", "炒", "咔", + "促", "姐", "酦", "货", "箔", "锡", "吝", "医", "汀", "精", "馘", "骶", "碉", "啷", "缉", "褪", + "阙", "滕", "蓐", "搏", "俅", "癌", "股", "圪", "孕", "鳕", "话", "鳳", "妩", "馬", "鞣", "璁", + "测", "螠", "圆", "颥", "卒", "钲", "䨱", "骢", "批", "陨", "婶", "熵", "鸣", "梁", "怅", "滑", + "驵", "帑", "鑽", "痈", "鲑", "犍", "茑", "糧", "谫", "咒", "绘", "塆", "耖", "铮", "殳", "角", + "齮", "挻", "捎", "2", "З", "廋", "泻", "犊", "儋", "椋", "聍", "鼬", "鳏", "收", "跞", "盥", + "信", "嘴", "眉", "黠", "纡", "Ω", "愆", "桹", "昽", "讠", "蚧", "龀", "鳓", "麓", "括", "蘊", + "鈇", "写", "焕", "哒", "忌", "咳", "蜮", "衄", "氰", "邘", "缇", "缮", "表", "续", "篇", "旄", + "闻", "門", "偃", "茳", "晗", "鋪", "讶", "衙", "闭", "管", "傒", "藔", "鳯", "萼", "泥", "汔", + "浆", "姚", "几", "含", "沆", "繇", "灾", "嗦", "饬", "系", "熠", "漶", "潭", "牲", "湘", "稁", + "宄", "亳", "杏", "豳", "監", "印", "旁", "辙", "播", "让", "乳", "镚", "腒", "衅", "铅", "殛", + "形", "颚", "茸", "绚", "铲", "贬", "溆", "寘", "傫", "解", "事", "旻", "差", "竣", "篃", "渥", + "嵩", "@", "鹀", "哈", "痍", "绪", "囔", "渗", "帐", "叙", "⑦", "跖", "貝", "盅", "碶", "哗", + "裼", "窝", "滙", "郸", "延", "炼", "芹", "簃", "谭", "莱", "佞", "猝", "褥", "陆", "王", "碴", + "粑", "怃", "倭", "貉", "硈", "裴", "溍", "坂", "勐", "踬", "敌", "改", "膠", "罘", "罚", "繁", + "幢", "煸", "扩", "黑", "氮", "孓", "饔", "姹", "蠖", "倬", "慝", "沩", "嚏", "雌", "琏", "莹", + "觊", "颟", "坤", "筅", "桉", "肈", "幻", "”", "烽", "蓅", "噌", "桎", "矻", "指", "瀚", "羊", + "邒", "筒", "匣", "旨", "葑", "团", "偻", "镛", "忉", "帱", "往", "刎", "害", "母", "串", "酢", + "舟", "栝", "娣", "⒉", "装", "笊", "篑", "垄", "镳", "牝", "煲", "氵", "遂", "骜", "钜", "屡", + "宇", "z", "惨", "潆", "鹃", "豌", "浞", "幄", "麺", "暽", "弁", "菤", "酲", "虑", "样", "访", + "睁", "黼", "悞", "禧", "涠", "舂", "駕", "屈", "噀", "邽", "炅", "欸", "隆", "描", "绦", "派", + "溲", "颙", "忐", "鹺", "只", "诈", "腭", "亵", "这", "炀", "趣", "喙", "跨", "場", "全", "搢", + "舨", "踢", "巿", "糸", "乐", "澈", "羰", "匯", "D", "濒", "莛", "癯", "娴", "姒", "祥", "渖", + "庭", "渑", "挹", "狉", "萍", "鹚", "焻", "自", "舸", "岫", "飑", "戍", "淙", "愠", "导", "賣", + "进", "赳", "鱿", "硂", "僤", "O", "拟", "钨", "笳", "汴", "挤", "稞", "柏", "阐", "弥", "艽", + "爻", "魭", "俶", "聂", "鲎", "齌", "菀", "僜", "煅", "满", "兀", "辔", "舊", "胥", "卢", "额", + "膝", "嵬", "昼", "唏", "璨", "苷", "舫", "倨", "耵", "碳", "鄺", "橘", "裔", "裸", "宋", "窋", + "悚", "昱", "错", "咣", "檄", "硁", "莆", "肃", "曼", "垍", "绵", "役", "內", "館", "规", "犋", + "毅", "锨", "瓘", "丘", "疝", "杀", "袗", "屏", "阎", "衹", "嚣", "冼", "铪", "熏", "t", "储", + "傧", "髁", "邙", "窴", "泾", "止", "晾", "涵", "庸", "庠", "靿", "睚", "减", "衲", "思", "簖", + "耘", "之", "斑", "扬", "袼", "缗", "菏", "滍", "研", "垚", "艘", "黇", "蜘", "汨", "侦", "疃", + "再", "沂", "榛", "励", "嚼", "拴", "钴", "珑", "撕", "鸯", "共", "醐", "揿", "醴", "凭", "菹", + "鹬", "捷", "掼", "芝", "缪", "咏", "挺", "蒗", "疖", "伸", "索", "黉", "髋", "習", "姜", "蔺", + "扶", "忽", "锉", "戚", "珲", "摸", "黛", "跌", "螈", "冚", "洱", "鼯", "庡", "痒", "哆", "品", + "歆", "登", "呔", "追", "鉫", "碾", "祚", "总", "帚", "薹", "趴", "容", "滘", "❋", "迷", "拾", + "炯", "析", "佼", "嗾", "-", "针", "滩", "禅", "讯", "織", "饱", "哟", "蹓", "侧", "蹯", "踽", + "趙", "漫", "卷", "朴", "卺", "狈", "绉", "浉", "玳", "榕", "畛", "蛾", "帔", "欠", "劙", "矾", + "進", "蚺", "骨", "捂", "疾", "璱", "圠", "坚", "弯", "秩", "逧", "烟", "佛", "嫱", "鳢", "裹", + "耥", "潮", "巽", "嶝", "廷", "鳆", "五", "西", "壤", "慌", "鴈", "爡", "‧", "儆", "萘", "愁", + "昔", "咩", "侃", "炫", "迳", "构", "缶", "皴", "捱", "菜", "秀", "粢", "畵", "辊", "伟", "榄", + "嗪", "已", "聖", "枕", "恢", "厍", "涅", "鄢", "饨", "鲨", "谪", "礤", "搁", "疔", "雎", "羿", + "犁", "硐", "盹", "若", "操", "昀", "亩", "凑", "睃", "贰", "穷", "荆", "葹", "嘃", "廓", "憔", + "制", "胧", "染", "麿", "镴", "砀", "组", "茎", "畫", "哑", "坨", "罱", "刳", "嘲", "瞰", "缏", + "侩", "谝", "谮", "还", "惋", "佈", "谏", "酗", "怩", "i", "秽", "抻", "蟪", "狠", "辑", "羟", + "娉", "肯", "毕", "枸", "啮", "鳙", "要", "娑", "蜈", "忍", "侗", "Р", "凛", "巉", "拽", "该", + "俎", "惠", "薅", "猬", "忲", "●", "哏", "柔", "沉", "磁", "脾", "龃", "鲩", "钞", "厂", "鱽", + "濞", "群", "唱", "攝", "刃", "蹻", "剕", "朊", "啜", "语", "蜻", "遠", "呖", "都", "嫄", "戢", + "丫", "酞", "修", "户", "膏", "侈", "浣", "捍", "馒", "谱", "點", "滏", "跟", "鹁", "书", "愉", + "扭", "票", "耒", "燁", "魚", "拨", "鞑", "匆", "最", "憨", "晨", "癍", "邻", "醅", "骘", "骇", + "蔫", "。", "珪", "安", "歉", "邑", "淦", "胱", "漏", "咂", "叮", "瘛", "钺", "聘", "玎", "荻", + "涰", "淏", "烈", "囚", "㙟", "矫", "嗵", "澶", "磛", "肖", "蹑", "支", "哪", "久", "嗅", "稍", + "抠", "具", "裨", "韨", "虿", "漪", "外", "埸", "ä", "葡", "鲇", "廛", "蝥", "广", "兒", "涧", + "阌", "谊", "嵴", "拂", "悸", "哳", "4", "佘", "篁", "基", "赪", "掬", "演", "谋", "酣", "植", + "波", "恅", "杼", "胀", "纯", "缩", "盆", "芷", "绺", "施", "礞", "莳", "稻", "狐", "馌", "椑", + "恃", "镩", "骼", "孱", "硭", "曈", "焼", "秕", "觥", "茛", "射", "肣", "蟞", "′", "彰", "罔", + "麻", "逋", "谓", "革", "藤", "堆", "忿", "茂", "屦", "炟", "碹", "啤", "垤", "文", "佐", "叼", + "蹭", "踉", "眍", "龟", "黾", "崋", "氘", "尼", "瞢", "悦", "罢", "瑀", "睇", "贴", "涿", "拍", + "庥", "粪", "陳", "闌", "蓍", "元", "磊", "忻", "葴", "买", "诺", "㐂", "沦", "苏", "岂", "浊", + "脍", "誠", "嚬", "蔬", "黄", "銘", "钩", "劃", "嫘", "嵖", "笄", "鸰", "熳", "堑", "河", "遮", + "尝", "灭", "愬", "娅", "鸽", "锢", "狂", "弓", "侨", "滚", "释", "晏", "徵", "苡", "佚", "切", + "惰", "降", "槎", "脑", "磺", "谟", "裉", "弘", "醺", "眇", "顒", "兰", "忧", "础", "-", "茫", + "袒", "喤", "慶", "話", "跽", "堵", "能", "武", "壳", "汤", "橐", "圧", "嗡", "簌", "斧", "林", + "楊", "净", "嫌", "猛", "炸", "冠", "弦", "洵", "°", "創", "葜", "苁", "顏", "薛", "平", "舜", + "崧", "撼", "哮", "窣", "殖", "象", "飒", "瞥", "玥", "斝", "胭", "葱", "擎", "踰", "盛", "稇", + "斫", "滴", "硫", "胬", "唁", "拐", "殍", "冱", "陪", "鋈", "烦", "辋", "妇", "轸", "潴", "荤", + "碡", "徭", "膜", "秧", "淀", "缤", "琶", "^", "肋", "违", "轭", "$", "箕", "墩", "罗", "摆", + "槛", "笠", "塃", "畚", "嘛", "秸", "≥", "剪", "羅", "帜", "庇", "骙", "酾", "康", "陬", "蝶", + "闇", "圏", "琴", "鬈", "苶", "黡", "瓠", "酋", "琎", "奏", "科", "搒", "億", "翡", "熊", "性", + "痞", "篾", "邴", "篥", "饵", "揸", "狙", "鳂", "否", "煺", "畦", "凖", "鹧", "宵", "庛", "鬯", + "袂", "昴", "徽", "奧", "鸥", "锸", "庚", "彪", "槿", "趱", "铡", "讹", "莎", "約", "璞", "┌", + "铫", "鸈", "瞀", "鼙", "廙", "估", "屺", "锘", "渝", "汶", "囵", "跻", "酏", "扎", "孜", "腔", + "吗", "块", "潽", "“", "焚", "倘", "靥", "默", "笺", "遍", "剅", "桠", "极", "夷", "眸", "泳", + "谢", "懦", ")", "泜", "物", "开", "淮", "邵", "卟", "徜", "轧", "苍", "酶", "捌", "耨", "膄", + "琦", "痦", "涪", "舰", "迥", "涘", "季", "悫", "初", "贩", "碍", "沼", "腮", "勻", "恣", "他", + "院", "7", "蜒", "叉", "昉", "瞽", "疢", "蓼", "相", "臜", "ü", "秘", "蚌", "摉", "莞", "番", + "盼", "冀", "缠", "学", "馗", "搞", "苛", "羹", "潟", "嬴", "杶", "妖", "↓", "銭", "斄", "猢", + "搂", "芦", "埶", "癜", "铴", "從", "留", "硎", "簇", "臬", "侮", "鳎", "頉", "所", "邝", "哂", + "弛", "垂", "暑", "仿", "療", "轾", "熜", "悃", "勘", "鋫", "泞", "閃", "+", "士", "虐", "盡", + "溯", "劣", "1", "锇", "蜇", "碛", "婴", "8", "捶", "藻", "嫰", "跹", "芋", "淠", "疯", "Й", + "诨", "醢", "痨", "栌", "摧", "噤", "灸", "馉", "蒇", "洲", "孪", "孵", "亿", "枝", "涕", "孟", + "缎", "攒", "湧", "褛", "铺", "廣", "诃", "蕈", "维", "珣", "逖", "瓯", "苘", "榫", "寰", "供", + "铽", "暲", "义", "兖", "喆", "祇", "蝽", "膛", "髭", "尹", "乒", "柈", "抓", "镋", "脏", "乩", + "挎", "酬", "靳", "躔", "霞", "離", "桌", "o", " ", "滨", "懷", "萭", "望", "仆", "怂", "麸", + "槁", "洨", "俬", "孚", "匕", "栩", "t", "被", "邗", "纺", "狄", "国", "苋", "眢", "秣", "畤", + "∵", "创", "嵫", "胖", "垮", "镁", "壑", "A", "趾", "荟", "汝", "嗥", "驰", "向", "磴", "讪", + "溅", "箍", "驾", "讙", "蛐", "眨", "醯", "筣", "鼫", "叠", "沥", "恕", "埽", "甩", "煜", "端", + "蜃", "帏", "丙", "荏", "圻", "夥", "暖", "抛", "岸", "头", "恝", "船", "鞲", "の", "七", "怪", + "诅", "茧", "北", "搓", "皖", "綉", "坪", "簪", "贵", "阍", "刑", "偶", "雷", "體", "髡", "岚", + "剂", "妾", "茬", "鹣", "鲳", "蜊", "假", "认", "嗍", "傈", "焘", "驷", "賀", "氆", "炝", "聋", + "嘡", "悺", "﹐", "僭", "证", "侉", "屐", "吃", "十", "〕", "瀦", "骤", "俳", "舁", "驸", "竿", + "坍", "|", "普", "軒", "瘃", "现", "醾", "铈", "榭", "蓁", "唷", "睫", "绰", "叶", "頤", "锤", + "浸", "杂", "汹", "璟", "蓖", "迦", "截", "錎", "年", "達", "劁", "码", "乃", "捯", "髪", "埚", + "呯", "頭", "镈", "汆", "β", "舷", "菂", "纸", "造", "晁", "卦", "蛎", "枉", "脯", "诛", "生", + "令", "桄", "恶", "鬟", "怯", "囡", "充", "省", "控", "螂", "笕", "邺", "扅", "斲", "骏", "鹋", + "队", "套", "镢", "牖", "I", "援", "封", "泚", "艿", "沮", "铙", "揳", "抄", "帨", "近", "唾", + "珥", "销", "非", "啱", "驼", "瓅", "遢", "筱", "奥", "善", "氩", "盉", "帖", "杜", "鈉", "蕉", + "鳄", "江", "鄂", "矩", "排", "诠", "准", "菼", "刻", "足", "峦", "灰", "僳", "蝢", "孫", "據", + "璆", "孳", "徹", "妆", "坭", "渌", "喏", "钦", "朔", "机", "徼", "掾", "其", "愧", "刹", "迹", + "惹", "闿", "鹘", "窗", "贳", "箓", "奡", "鸮", "翟", "滠", "辄", "夸", "袱", "】", "阿", "摁", + "觽", "垠", "棼", "杉", "甏", "旖", "阜", "猩", "埤", "醛", "馥", "哫", "囝", "晖", "骸", "椭", + "军", "哺", "束", "犄", "遛", "涡", "罄", "赇", "幌", "曙", "契", "饹", "圳", "仝", "爝", "示", + "敝", "漓", "马", "铐", "掳", "鲱", "蟀", "阵", "栒", "b", "轮", "俯", "嬉", "漩", "佟", "捆", + "迩", "液", "炊", "抒", "迭", "换", "菘", "请", "柩", "腓", "妥", "怖", "穆", "骝", "胛", "幺", + "燥", "棹", "孖", "洁", "边", "彥", "睨", "桁", "淆", "醤", "奘", "伙", "刘", "o", "過", "鏖", + "怄", "镗", "蘖", "卩", "龈", "枣", "联", "圊", "贫", "郭", "梗", "赡", "輸", "拼", "冰", "Ë", + "姓", "办", "拉", "富", "对", "肩", "缬", "逻", "瞠", "な", "荚", "宝", "緃", "峋", "鞁", "坉", + "译", "银", "猹", "榞", "借", "楠", "殽", "片", "谸", "雩", "琨", "颏", "晷", ";", "莠", "楽", + "嬗", "啉", "爍", "視", "岽", "荙", "冗", "『", "鯭", "觌", "喹", "药", "腈", "蹰", "–", "泷", + "榷", "卮", "缥", "各", "瘙", "凡", "悍", "劢", "6", "镰", "四", "摈", "驹", "陵", "沟", "麟", + "骊", "椀", "★", "滦", "菌", "彊", "疽", "辫", "璎", "郁", "资", "粼", "萄", "鳁", "鸤", "漁", + "痃", "绌", "蒴", "尊", "哙", "守", "坠", "胯", "泽", "持", "隅", "耧", "逛", "鄯", "瑜", "召", + "眷", "逄", "垦", "窄", "板", "赵", "源", "绹", "總", "适", "轰", "钛", "停", "唝", "燎", "栽", + "燃", "搬", "夬", "掐", "簦", "旃", "更", "蹊", "熙", "埃", "偲", "蒙", "舍", "杭", "朋", "霍", + "谙", "蓥", "赫", "鲏", "純", "缃", "蓓", "è", "萧", "锆", "诉", "恚", "u", "旷", "⑵", "褲", + "鲟", "互", "峂", "绝", "陒", "议", "脽", "态", "岙", "僮", "突", "濯", "犹", "阆", "溧", "宜", + "霣", "呆", "鄉", "觖", "濠", "耐", "橱", "敛", "踮", "怦", "鸠", "授", "滫", "叭", "镤", "仵", + "圖", "救", "变", "寕", "洋", "泃", "晥", "檎", "缦", "鏠", "悬", "彧", "韓", "籽", "磷", "歙", + "÷", "杨", "闯", "罴", "鼠", "陋", "瀣", "揾", "匪", "晚", "丧", "彷", "梏", "诽", "尻", "蹢", + "拊", "氕", "芳", "凸", "懊", "型", "媄", "憝", "骆", "纭", "刺", "悼", "藍", "欧", "葳", "艺", + "±", "紗", "钡", "钭", "首", "栈", "闩", "勃", "喑", "栉", "栟", "焊", "哀", "竖", "肘", "悲", + "鳜", "迨", "怿", "裘", "綮", "蕹", "冕", "无", "鼢", "戽", "鲣", "蜍", "插", "扯", "俣", "袪", + "级", "理", "茵", "矣", "障", "禊", "惯", "䄂", "宏", "韫", "栗", "编", "座", "跫", "、", "捞", + "谠", "車", "袆", "舖", "暝", "9", "混", "蓊", "韂", "拳", "j", "伶", "啦", "0", "诰", "缑", + "郴", "蕲", "谛", "皋", "兢", "件", "隈", "溊", "窈", "檀", "洪", "揭", "油", "颐", "剎", "侠", + "或", "腻", "迪", "舔", "牺", "倦", "夭", "蠋", "鹑", "倜", "喱", "豨", "谨", "翥", "馄", "蔚", + "类", "鳍", "泯", "岷", "張", "紅", "情", "婪", "睡", "诒", "旋", "舱", "列", "求", "姥", "翯", + "锲", "適", "蒡", "㭗", "韵", "撄", "律", "湊", "鲪", "碓", "馓", "汛", "尤", "℃", "泫", "竹", + "溃", "诵", "乡", "楹", "肷", "戋", "獭", "撴", "晤", "嘎", "裢", "轱", "歘", "鼱", "舀", "潵", + "腩", "挽", "至", "骞", "佻", "蕊", "轫", "岘", "斌", "《", "雇", "枘", "贶", "履", "亭", "绍", + "空", "泡", "擒", "蠓", "桼", "舢", "踵", "柢", "躺", "罥", "蔁", "幪", "蒯", "≤", "齉", "況", + "锕", "徇", "儿", "帽", "谕", "抽", "獍", "揪", "膳", "酴", "姊", "作", "弍", "郦", "翮", "髌", + "勝", "孔", "颁", "泩", "衾", "翊", "噉", "∩", "先", "的", "財", "绢", "崇", "饲", "賓", "绾", + "陡", "砾", "纷", "酱", "窎", "浕", "琫", "补", "嗽", "呛", "蚓", "伪", "艨", "搡", "粗", "阱", + "商", "郫", "喵", "缯", "炻", "州", "珽", "芒", "脶", "蕖", "拘", "\\", "蕃", "伏", "蛏", "璘", + "缡", "伞", "吸", "煞", "嗫", "餪", "━", "8", "漠", "衮", "峭", "筌", "阀", "乇", "杞", "镕", + "鹈", "觚", "疋", "猥", "肚", "阃", "地", "鸾", "玚", "镀", "诬", "凇", "惬", "劝", "堠", "睪", + "瞪", "涓", "專", "琢", "蠹", "裂", "芎", "轲", "谯", "畋", "长", "攘", "缁", "吵", "汈", "稂", + "咤", "甸", "瀑", "讣", "翠", "綦", "笑", "掺", "宫", "唆", "旗", "帘", "遵", "唪", "连", "必", + "姬", "枵", "渫", "神", "凱", "虞", "闵", "爭", "迢", "飓", "桴", "冑", "招", "鬶", "赊", "朦", + "薜", "逗", "铩", "焖", "狱", "昕", "犀", "捏", "侓", "爆", "箧", "怠", "缐", "苄", "踯", "暹", + "絮", "裁", "嘶", "诙", "攥", "¥", "痫", "摹", "朕", "薰", "闼", "樨", "需", "蕾", "扔", "服", + "蚩", "漳", "瞻", "陟", "诐", "爽", "耩", "坏", "寶", "瞧", "蘅", "爺", "仇", "瞈", "揉", "隰", + "阏", "皤", "鉏", "郇", "啵", "僧", "渐", "骠", "厾", "谦", "榱", "蓮", "乂", "伷", "觐", "遫", + "畀", "污", "眭", "蝗", "亢", "腾", "遇", "欺", "狸", "籼", "锯", "赚", "槔", "域", "老", "掩", + "锎", "邪", "沬", "袯", "映", "肤", "舒", "箦", "涎", "缔", "孃", "萎", "推", "峧", "嫒", "嵐", + "怒", "闾", "阅", "扢", "絡", "剩", "腢", "晌", "铟", "铚", "燔", "鄜", "墠", "擤", "芸", "鼩", + "食", "朰", "啥", "贿", "券", "垩", "典", "逭", "成", "帧", "侯", "妒", "顷", "晩", "浦", "汳", + "㾄", "坊", "②", "蝼", "倩", "挦", "騠", "蓂", "兹", "渤", "茴", "矶", "煤", "伛", "逃", "堞", + "漭", "闽", "埠", "妗", "烙", "傀", "滢", "堂", "骅", "崮", "兿", "螟", "榴", "蒈", "蟛", "员", + "嗟", "毗", "鹭", "毖", "籍", "傣", "偈", "澴", "耆", "黔", "薷", "俘", "箸", "蒺", "记", "铣", + "拥", "瑰", "抉", "史", "急", "什", "川", "曪", "剖", "罪", "扥", "鱼", "羌", "复", "昵", "朓", + "氪", "矅", "ò", "摒", "催", "{", "懂", "莉", "屃", "症", "遒", "璋", "鄒", "玄", "氦", "舳", + "納", "锷", "鲥", "羸", "怵", "嬖", "飚", "倔", "辨", "篌", "娄", "洎", "弟", "悝", "婥", "待", + "梃", "棕", "撞", "肊", "婺", "桥", "慕", "5", "尖", "监", "囊", "腎", "瘁", "⑴", "伢", "邈", + "炷", "籴", "月", "终", "龠", "蹇", "柯", "噜", "多", "玮", "踹", "泣", "圮", "辈", "鄙", "赆", + "蠃", "珈", "鬃", "识", "甑", "藓", "宥", "韧", "隗", "跚", "峁", "晒", "戳", "環", "葓", "筐", + "瑚", "稆", "巩", "葵", "网", "铨", "仡", "呶", "休", "村", "傩", "浡", "祝", "幅", "晳", "岿", + "[", "尃", "响", "咽", "讵", "淹", "糠", "驴", "烹", "甪", "焯", "整", "瘆", "茀", "瑞", "嘁", + "赅", "芼", "震", "怼", "膑", "蒟", "钊", "缷", "獨", "俄", "噪", "亏", "芍", "鲲", "黟", "酃", + "铻", "→", "鲡", "仑", "有", "扌", "治", "聆", "腙", "冬", "剥", "诓", "祃", "棘", "孥", "濆", + "鞋", "赣", "卜", "谆", "投", "琊", "塍", "洗", "津", "肴", "奕", "掴", "螗", "胸", "惦", "赠", + "耶", "蛸", "曷", "名", "栏", "崖", "始", "孤", "篆", "椴", "模", "儙", "袁", "颌", "卤", "寂", + "呸", "蓺", "噶", "钔", "粹", "潸", "郾", "娡", "啖", "芜", "䧳", "缟", "鞅", "哇", "嚄", "熛", + "私", "住", "鉄", "U", "失", "蜾", "㺃", "莨", "擂", "沕", "铄", "耰", "炔", "藐", "锈", "苇", + "芯", "檇", "龍", "珀", "筻", "褚", "缀", "棬", "仲", "杧", "铛", "挖", "缸", "嵇", "剟", "觋", + "鬼", "天", "鹪", "豭", "腴", "驿", "粵", "馈", "迸", "夕", "蜚", "禺", "迎", "狞", "嶽", "躯", + "暅", "蹾", "甾", "柆", "e", "欃", "無", "羽", "蛛", "僖", "碲", "遴", "厝", "滂", "眦", "鹅", + "咖", "潜", "媽", "啴", "寨", "琬", "判", "塌", "与", "剧", "掮", "读", "庆", "愚", "氲", "梾", + "稗", "很", "饯", "淘", "灞", "A", "!", "飾", "醚", "壁", "芮", "蔼", "知", "郯", "瞒", "术", + "法", "匠", "┅", "尕", "脂", "琅", "孝", "埙", "濟", "袷", "方", "芾", "驽", "线", "咡", "睿", + "滥", "鞨", "駹", "馎", "蛲", "申", "涣", "牗", "跄", "鲐", "泐", "铍", "肓", "é", "司", "辣", + "慑", "崞", "魏", "髯", "涯", "滃", "薪", "~", "鐵", "町", "芥", "匿", "癞", "嘚", "□", "啪", + "俇", "橛", "巅", "鄞", "痱", "回", "着", "魂", "瘀", "埒", "措", "蚪", "锁", "巴", "芏", "另", + "忏", "洇", "⑾", "箱", "劳", "榈", "B", "毒", "泰", "巍", "寇", "斛", "赖", "酒", "密", "左", + "莜", "茯", "脆", "饴", "洳", "舉", "酪", "鼒", "職", "桫", "螋", "砌", "戬", "前", "豁", "膲", + "鲯", "憋", "珺", "蹂", "亥", "冷", "蜔", "俦", "碇", "慰", "扒", "亮", "弒", "胫", "肠", "弇", + "拣", "丸", "涫", "勉", "帝", "玠", "应", "砜", "鬓", "泮", "鳖", "峇", "辗", "吴", "僡", "玊", + "钘", "惘", "奁", "翰", "爨", "罽", "莶", "纻", "菪", "興", "钧", "鄹", "缌", "嘉", "玲", "蛋", + "倚", "镬", "彳", "氯", "谐", "疗", "魉", "扈", "鏡", "涤", "佑", "勾", "饽", "R", "局", "醉", + "燫", "驱", "崽", "榮", "锄", "脎", "枓", "敲", "俑", "镶", "当", "逦", "寅", "捽", "宗", "茼", + "稿", "躇", "晋", "东", "渭", "闶", "潦", "慊", "蔟", "疑", "蜡", "嬛", "荒", "扪", "拈", "受", + "沱", "螽", "嗣", "瘰", "颖", "百", "楱", "崛", "窿", "蓬", "禨", "螵", "捕", "鳀", "霰", "瘸", + "圄", "陂", "敢", "矗", "炉", "篪", "注", "凉", "摔", "衿", "g", "遘", "鳟", "腚", "泱", "荣", + "池", "杰", "阻", "里", "峥", "葺", "翔", "柁", "飐", "载", "刨", "瘊", "溏", "嚭", "穝", "站", + "笞", "厌", "门", "汞", "雠", "靖", "怨", "葙", "厅", "凉", "荨", "底", "坼", "海", "轩", "秃", + "醭", "莒", "坎", "驺", "瀛", "嵝", "溥", "嚋", "寤", "k", "築", "耔", "跎", "小", "弊", "疭", + "√", "洞", "利", "次", "迕", "鸿", "刁", "媳", "酹", "磋", "勒", "伎", "浔", "斤", "珊", "鸳", + "颊", "塗", "刈", "龛", "縻", "楚", "脓", "南", "伧", "桨", "尸", "退", "亊", "侔", "蓟", "削", + "诲", "瑯", "挝", "绩", "嶂", "耷", "太", "著", "讥", "烂", "哥", "菔", "唳", "筫", "嚷", "斯", + "蔡", "碧", "慵", "搐", "琵", "逶", "菡", "篙", "驭", "莲", "毫", "犯", "坌", "啕", "疴", "傻", + "牧", "肺", "既", "悟", "讳", "练", "钠", "胨", "G", "迁", "帙", "嫦", "哿", "柝", "迤", "庑", + "踟", "粤", "乜", "悭", "霁", "在", "虔", "蓦", "暾", "鲅", "様", "徐", "诫", "料", "蒂", "揎", + "溜", "崎", "玦", "隐", "绳", "幼", "钼", "腧", "槌", "镅", "锿", "蹁", "猱", "狁", "蹈", "S", + "傢", "}", "珉", "浴", "龚", "吇", "肆", "⑩", "臌", "鏢", "―", "定", "哧", "螳", "爹", "摞", + "榍", "苩", "丑", "犴", "璀", "偎", "燹", "洯", "啁", "鱾", "計", "镂", "字", "琉", "壴", "霖", + "蟜", "做", "松", "扣", "徊", "叱", "桑", "≠", "寓", "崔", "茌", "`", "殓", "猖", "慎", "坻", + "皮", "喔", "耕", "拧", "织", "桂", "垝", "约", "飯", "皿", "移", "微", "桅", "钬", "赑", "宓", + "颉", "掂", "希", "沁", "翳", "澝", "崭", "聃", "蔸", "磕", "崚", "奉", "槍", "颠", "任", "飕", + "蛴", "歼", "蛭", "¥", "鉵", "刷", "泓", "頫", "诤", "阈", "诖", "漂", "冯", "柠", "本", "狼", + "芗", "丹", "柒", "饫", "衫", "摩", "桃", "啼", "譄", "醌", "帡", "少", "螃", "祷", "幸", "喘", + "噼", "甲", "刭", "泊", "那", "舄", "畲", "遊", "記", "绠", "蝈", "忒", "耙", "鹌", "搽", "绕", + "镠", "凼", "扫", "鸵", "匡", "缈", "铼", "挢", "踊", "噭", "苴", "貌", "啾", "蒜", "茱", "⒅", + "邛", "棉", "怀", "筵", "笾", "邢", "濱", "蒲", "嫠", "儡", "益", "萊", "歩", "日", "婢", "楔", + "嚎", "髂", "禄", "巫", "懿", "砍", "惆", "袭", "~", "欲", "鳅", "書", "撷", "库", "绻", "⒁", + "蒹", "辜", "屉", "奂", "龇", "诡", "醑", "儀", "讷", "聒", "盖", "浅", "長", "蛑", "念", "辒", + "蚊", "勖", "揣", "岬", "糅", "刊", "蛆", "硌", "柜", "惗", "激", "颈", "閣", "唬", "捧", "蒽", + "爸", "侏", "氧", "蛤", "牡", "瘦", "铉", "撒", "屿", "谔", "钀", "瓦", "恂", "钻", "声", "馨", + "贷", "藁", "闰", "人", "瀹", "间", "炜", "煨", "畴", "鞡", "悔", "量", "原", "屁", "杯", "趋", + "愎", "瓶", "烘", "韬", "姮", "勰", "眛", "饮", "瘐", "决", "版", "際", "俸", "此", "辽", "愫", + "爷", "隘", "螯", "腕", "闟", "厣", "齁", "矿", "女", "扇", "郄", "诚", "焜", "<", "檵", "赙", + "郢", "酌", "恳", "侵", "劍", "徳", "脔", "岍", "倍", "隹", "镲", "僬", "○", "啭", "坳", "谰", + "璪", "昆", "⒆", "惧", "虺", "篡", "锐", "邮", "嵊", "恹", "茄", "黯", "對", "魑", "禀", "添", + "汎", "廉", "戈", "∈", "陈", "肟", "螅", "缰", "啃", "瘾", "辁", "茜", "曝", "C", "餍", "蜴", + "咦", "臺", "扺", "隽", "崴", "遐", "胃", "铕", "楗", "墀", "魈", "淑", "邰", "飧", "勤", "糨", + "你", "铂", "蛰", "锻", "塈", "予", "怏", "鷪", "暂", "诞", "會", "鼐", "辏", "矬", "筜", "蟒", + "柫", "靴", "垒", "樵", "狨", "魋", "查", "掸", "'", "每", "浮", "绞", "疍", "於", "饦", "辐", + "墟", "窳", "养", "墦", "鲔", "黝", "迈", "砟", "伥", "藜", "墅", "喬", "卬", "赶", "眼", "龌", + "主", "嗞", "転", "襕", ",", "杲", "儒", "政", "蹩", "付", "Я", "厢", "点", "如", "茗", "奍", + "嗌", "蕴", "问", "膚", "硒", "佃", "居", "赍", "鯨", "槜", "衡", "廐", "鲾", "慈", "莫", "篮", + "铱", "园", "范", "靠", "叻", "绸", "众", "抑", "浃", "極", "辀", "千", "蔯", "r", "碱", "應", + "夼", "祺", "绣", "澉", "愛", "譬", "刚", "扼", "檫", "时", "荼", "部", "耽", "稀", "琳", "鎮", + "鳃", "疌", "挠", "京", "筴", "胳", "橡", "裱", "栊", "蛩", "流", "猄", "葭", "邯", "伤", "秦", + "孺", "郓", "筏", "茉", "谈", "黥", "鳇", "魅", "秫", "敫", "肄", "俪", "尺", "度", "廊", "昏", + "怎", "钎", "愤", "腿", "Λ", "碘", "囤", "婆", "煋", "桀", "阪", "焒", "砘", "澡", "阒", "臼", + "茭", "纛", "唻", "胗", "歌", "吱", "铸", "種", "礳", "舾", "砰", "即", "慥", "缆", "躞", "卖", + "眆", "搎", "鲁", "稠", "惝", "毶", "耎", "挡", "异", "咨", "歔", "垫", "素", "鳊", "打", "镏", + "耗", "锭", "剀", "讓", "鍋", "歇", "拗", "齿", "聊", "惕", "塔", "况", "汐", "采", "灣", "菁", + "峰", "呋", "阴", "衽", "鹏", "挂", "鲞", "第", "浑", "秭", "铯", "胎", "滔", "雕", "渔", "骍", + "鴻", "眚", "易", "枫", "麥", "罹", "劈", "苣", "蹄", "是", "锛", "饼", "铳", "器", "秒", "開", + "舛", "纳", "庒", "饰", "发", "虎", "乏", "届", "浯", "目", "霪", "氢", "华", "澧", "陴", "誊", + "欷", "瀌", "梵", "嗖", "惩", "妞", "螓", "溇", "嬃", "笼", "嬢", "獠", "辩", "骛", "挪", "礅", + "銎", "趺", "狍", "阑", "擦", "咙", "+", "鼻", "戆", "宿", "霏", "牁", "耦", "枳", "鲸", "阽", + "峯", "瘭", "祁", "镜", "绮", "道", "摭", "﹒", "耠", "限", "轻", "莽", "帛", "唯", "墒", "W", + "荦", "矸", "崃", "洫", "杳", "舌", "涌", "朵", "苑", "瓻", "辂", "凬", "畺", "莩", "奔", "荐", + "撵", "嶲", "憧", "沖", "邱", "瞑", "瘢", "腺", "嵯", "诗", "經", "胚", "谎", "央", "稼", "3", + "冈", "痣", "故", "皎", "珅", "踅", "硇", "通", "鹱", "草", "粕", "而", "梨", "萜", "搛", "鸺", + "托", "倡", "匾", "骷", "桐", "阇", "璃", "抖", "显", "镝", "I", "锣", "辆", "茝", "虫", "谀", + "萤", "帼", "滈", "鮼", "乔", "会", "产", "縢", "纱", "跆", "铤", "圯", "旚", "墓", "棰", "苜", + "聩", "蚍", "筢", "殇", "槚", "阂", "豊", "翚", "犰", "疚", "浠", "礴", "界", "蛙", "吁", "忪", + "讫", "辇", "汧", "澥", "纮", "糺", "拭", "镉", "鄠", "讼", "鎏", "逐", "烝", "扊", "筹", "仪", + "段", "杵", "卲", "钤", "曹", "潘", "浏", "陑", "答", "期", "橋", "位", "梅", "榻", "芨", "汭", + "贇", "杅", "伋", "馁", "皈", "躁", "鲦", "良", "幤", "曺", "榖", "便", "氍", "窍", "槃", "設", + "襦", "菓", "猡", "槲", "觱", "邠", "岩", "骈", "鬲", "睽", "残", "碗", "凯", "览", "伾", "吣", + "峡", "箨", "忡", "岁", "醵", "霸", "焐", "谁", "疟", "樾", "竺", "果", "阝", "花", "•", "耱", + "嗜", "骰", "萋", "甄", "侍", "厦", "辌", "给", "健", "垧", "娩", "校", "又", "临", "揽", "犟", + "蠢", "包", "檬", "穹", "燮", "札", "翏", "领", "深", "诶", "窒", "鸷", "统", "肾", "澍", "畹", + "职", "蝟", "眬", "旎", "朘", "眠", "篚", "贼", "貴", "#", "茆", "磬", "①", "祀", "坞", "盯", + "苹", "痼", "耪", "狺", "妓", "湨", "動", "渚", "售", "鸶", "霄", "某", "×", "铀", "桓", "脲", + "心", "节", "[", "蜩", "剐", "猜", "盂", "穿", "洮", "坜", "掷", "坐", "烜", "冶", "袍", "穴", + "偷", "槽", "面", "媛", "撙", "孩", "询", "苈", "橙", "恙", "吉", "燈", "暇", "杋", "後", "璇", + "喟", "妪", "湄", "贡", "兮", "楫", "锗", "肮", "圉", "参", "烃", "葩", "骀", "锖", "犿", "恼", + "麯", "牮", "溱", "俵", "祢", "割", "羲", "中", "博", "膦", "7", "钙", "耸", "蚋", "嘬", "逝", + "瓿", "且", "苗", "屑", "華", "糯", "吓", "陇", ".", "魆", "d", "拶", "箜", "澳", "蘼", "垸", + "钅", "樯", "侑", "娼", "赛", "恪", "击", "濮", "隔", "似", "惜", "滤", "培", "谩", "坝", "煙", + "馕", "茡", "颼", "啡", "强", "巡", "糗", "芙", "骒", "常", "鳔", "楣", "嫩", "师", "攀", "旭", + "茠", "吏", "剌", "露", "把", "携", "忘", "软", "荩", "弧", "锩", "僦", "掘", "仓", "嵚", "沲", + "傅", "蝴", "爵", "纪", "薮", "參", "荀", "珙", "逞", "蹙", "徉", "佶", "庹", "弢", "锞", "想", + "窕", "躬", "蚡", "闸", "难", "妃", "唶", "V", "蚂", "酐", "]", "羁", "土", "敏", "蝻", "赎", + "闹", "铷", "脬", "攫", "椐", "橹", "(", "暨", "剁", "灵", "咫", "苻", "赈", "萝", "鹮", "颛", + "勿", "臆", "福", "蝠", "烺", "跤", "垓", "庋", "鏊", "彭", "碚", "膂", "鐘", "鸹", "择", "疫", + "暌", "胙", "翘", "仞", "實", "猫", "嘹", "坟", "泺", "靽", "仟", "鲋", "畅", "箪", "箩", "星", + "辍", "餐", "伍", "狃", "蹐", "芴", "龋", "仨", "狩", "诌", "媚", "琚", "❤", "鉰", "仔", "忝", + "垅", "嗤", "氅", "填", "詹", "诿", "皑", "椅", "鲭", "繪", "碑", "选", "廠", "嶓", "迮", "唠", + "赏", "│", "泍", "姑", "晦", "直", "艾", "邂", "裝", "郊", "疡", "」", "鸲", "兕", "县", "邡", + "麤", "旧", "壶", "礼", "梭", "铔", "玕", "硬", "后", "馅", "窠", "俊", "犾", "槭", "姨", "妨", + "幡", "沿", "帮", "爿", "褊", "淅", "册", "诔", "使", "唢", "族", "逮", "蟋", "掣", "红", "颔", + "闲", "咧", "衍", "矛", "蹜", "抱", "雀", "箴", "雙", "茁", "聲", "芰", "瘤", "落", "峨", "秆", + "献", "霭", "胰", "墁", "柽", "跛", "阼", "菝", "谗", "蠊", "懋", "蔷", "拃", "岱", "戡", "逡", + "背", "克", "棱", "栲", "乖", "橄", "炭", "萱", "胲", "摺", "殆", "魍", "宾", "拢", "姁", "祲", + "·", "歹", "胆", "爱", "旰", "抢", "匜", "硚", "愈", "跳", "▲", "跪", "網", "糈", "維", "痹", + "娃", "疸", "痔", "褙", "芭", "鹜", "猷", "檩", "吨", "淖", "竟", "呱", "瘠", "奢", "呐", "扞", + "灼", "家", "饿", "玻", "倾", "锚", "遗", "赝", "去", "跣", "纬", "玢", "梆", "陉", "屎", "上", + "酆", "搤", "併", "腌", "避", "禹", "绅", "帆", "英", "区", "交", "▪", "巧", "豹", "游", "清", + "荧", "娀", "訓", "尾", "觫", "梢", "均", "忤", "继", "變", "喽", "尉", "骚", "趸", "馃", "石", + "置", "毡", "竴", "龄", "掭", "骟", "堡", "确", "街", "沌", "恨", "壮", "弄", "鞴", "剿", "貅", + "路", "杠", "豚", "澜", "靼", "擐", "租", "媪", "彖", "逢", "撺", "盗", "呑", "锥", "栎", "唧", + "翩", "倥", "虽", "郏", "钒", "雉", "捒", "官", "酷", "樽", "房", "屣", "堌", "蒍", "悌", "④", + "蛳", "俱", "慢", "筇", "框", "椽", "债", "吭", "柱", "夤", "嘈", "骺", "趹", "刓", "豕", "噢", + "J", "论", "俞", "仙", "碰", "经", "铋", "⑤", "顽", "曲", "得", "汪", "浒", "际", "嘏", "扮", + "祊", "嵋", "‘", "牒", "禮", "粒", "籀", "闱", "刍", "邕", "美", "苫", "靺", "铹", "莰", "蔑", + "敵", "逑", "锬", "宸", "鞥", "匝", "褀", "菅", "逊", "脱", "晔", "罾", "郝", "恰", "丟", "芩", + "枷", "鹩", "a", "夙", "述", "钢", "瀵", "铧", "翱", "尧", "醮", "&", "鳐", "懲", "濩", "树", + "劵", "虾", "砚", "腄", "罟", "硖", "崩", "埘", "╱", "血", "觯", "巂", "栀", "萏", "雪", "眵", + "白", "新", "靶", "猪", "亻", "窬", "Ⅳ", "麽", "遄", "沏", "茅", "笋", "甬", "潔", "悛", "幕", + "鹤", "悴", "耻", "觜", "滞", "己", "鏂", "肛", "婤", "嘞", "蘸", "胴", "柿", "穗", "咯", "嵕", + "蜣", "僰", "玃", "细", "▏", "戗", "尘", "仄", "子", "历", "翌", "烀", "晞", "桷", "藕", "啄", + "腐", "瘥", "诣", "彗", "捭", "懑", "陛", "傥", "沚", "贯", "﹑", "雒", "航", "跬", "晓", "赢", + "鸭", "臊", "闳", "稲", "阁", "鼋", "‰", "个", "農", "匮", "辚", "滗", "桢", "詈", "绗", "馇", + "蜓", "专", "余", "鳉", "稚", "午", "珍", "癃", "钵", "档", "颍", "厕", "岭", "祓", "诂", "襙", + "炙", "枯", "锦", "袤", "桊", "嗓", "䘵", "嫂", "丰", "苎", "梦", "早", "菱", "钱", "灯", "钮", + "锓", "豐", "蝤", "协", "h", "鼓", "燕", "覺", "涮", "呗", "専", "篓", "玷", "褓", "从", "鲍", + "羧", "俭", "萌", "樣", "颜", "谷", "夹", "囫", "』", "缙", "鹰", "条", "状", "沨", "因", "莺", + "楼", "扰", "禢", "砧", "埔", "纩", "臭", "茶", "妹", "莼", "境", "山", "诘", "蒌", "唤", "仃", + "眄", "田", "畼", "缱", "祘", "痴", "揍", "猾", "甙", "躲", "返", "砝", "叽", "苟", "肭", "悖", + "嫣", "凋", "踞", "勢", "弃", "般", "赤", "佣", "嘘", "智", "寒", "旆", "咎", "戊", "温", "昙", + "蝄", "掉", "冤", "凍", "一", "鋼", "茏", "兼", "昃", "峤", "挟", "0", "顾", "颅", "*", "遆", + "硅", "烯", "眊", "鳘", "下", "雾", "癖", "贮", "缴", "升", "劲", "婞", "掇", "梯", "壬", "撮", + "绑", "氣", "薤", "兔", "赟", "漕", "签", "顇", "砭", "鸼", "明", "佧", "鹗", "哉", "席", "趁", + "砖", "澌", "撤", "萨", "熔", "蚝", "狮", "夔", "介", "青", "喈", "郂", "埝", "狗", "哚", "咪", + "材", "跑", "炗", "龉", "髓", "衣", "觀", "淋", "寄", "羮", "抵", "恁", "藿", "蜢", "甜", "兽", + "洒", "眶", "薯", "绷", "颀", "骑", "鲻", "|", "q", "繄", "丝", "焙", "瑕", "童", "豸", "讨", + "钿", "艮", "踧", "幽", "焰", "宽", "址", "鸩", "堉", "音", "擅", "铞", "埌", "宙", "_", "钥", + "承", "率", "噬", "凌", "程", "蚕", "嘣", "饭", "橉", "径", "1", "鄏", "袜", "孙", "樊", "窦", + "牦", "歡", "罕", "蓉", "诩", "㧎", "责", "豢", "逅", "甘", "礶", "镥", "穂", "到", "檑", "皇", + "泖", "腱", "郤", "竞", "竽", "溴", "木", "蛀", "蛟", "燧", "鹳", "谵", "钳", "魔", "链", "咆", + "荡", "婳", "冢", "鲧", "昝", "※", "倫", "才", "簿", "醫", "吕", "姗", "L", "俺", "隳", "鮮", + "衩", "鲆", "崟", "鲹", "岗", "镇", "嗄", "硪", "盎", "氟", "笪", "今", "公", "尽", "佬", "骧", + "驻", "翼", "咚", "盱", "镎", "浈", "谅", "洟", "嘭", "跶", "蔹", "咭", "妁", "殚", "砷", "购", + "犸", "哄", "笸", "喂", "赋", "倓", "项", "臧", "梳", "笏", "鹝", "昭", "罿", "镄", "П", "湯", + "U", "绐", "绂", "鸬", "毐", "台", "鈴", "憾", "痰", "缭", "绒", "赁", "效", "哝", "─", "偆", + "泗", "谇", "貘", "搜", "阗", "個", "艟", "嗔", "正", "筛", "鼷", "摛", "馀", "坡", "卓", "厥", + "睾", "雯", "篷", "狰", "掀", "夫", "靛", "伻", "茇", "顧", "猕", "占", "俟", "兄", "垡", "兆", + "铃", "屙", "轵", "銮", "磲", "桤", "跷", "缳", "粘", "袢", "尬", "獘", "社", "彬", "哨", "煳", + "莊", "教", "鸸", "窖", "晰", "捻", "环", "廿", "驶", "椠", "熟", "赓", "挥", "兵", "洚", "桧", + "埼", "嘀", "哎", "柞", "悄", "调", "洼", "蛇", "铬", "蝇", "倆", "谂", "姻", "怕", "瑙", "算", + "惮", "遥", "惟", "甫", "玺", "袈", "税", "玟", "械", "擗", "淤", "溟", "揞", "纲", "谤", "條", + "翕", "荛", "忾", "馿", "糖", "檗", "垱", "┐", "躜", "缒", "您", "蹶", "合", "数", "斟", "粲", + "玖", "淞", "孰", "觎", "M", "嫪", "毽", "桩", "蹦", "亹", "胾", "亲", "痉", "莙", "捐", "烁", + "瓣", "邀", "但", "误", "饻", "妄", "镱", "民", "嗳", "竦", "柰", "脒", "斓", "瞟", "拱", "霾", + "粜", "腽", "咬", "唿", "礻", "y", "褒", "郪", "熰", "梻", "甭", "处", "拖", "蜉", "鄱", "絲", + "宠", "瞵", "荷", "靓", "喻", "阢", "伫", "担", "朐", "墙", "儣", "胄", "簏", "皆", "踌", "萃", + "命", "滟", "睹", "叁", "蟮", "東", "﹖", "式", "營", "疠", "醳", "尔", "購", "吒", "柴", "途", + "力", "疰", "寳", "萬", "挨", "碥", "牍", "嘻", "柃", "剡", "徝", "杖", "螺", "祈", "産", "煽", + "讽", "瞭", "由", "牀", "涉", "暴", "枚", "仰", "它", "胤", "驯", "港", "寵", "熄", "蛮", "务", + "殃", "亸", "涢", "妫", "粥", "扦", "寺", "抿", "湃", "C", "脚", "涸", "动", "務", "镭", "检", + "釂", "耳", "榦", "怆", "氨", "瘳", "钽", "牚", "负", "纂", "獬", "息", "婄", "襞", "囹", "酰", + "扑", "臛", "价", "褡", "篼", "媢", "酊", "腋", "愣", "垯", "喃", "牾", "犨", "蜂", "诹", "迂", + "槑", "好", "褰", "咝", "③", "劐", "骄", "腉", "仕", "叵", "磅", "金", "皁", "勍", "煮", "鍪", + "筚", "吲", "艰", "床", "叛", "﹔", "喜", "齑", "昧", "乎", "谲", "匈", "麒", "身", "唑", "鲤", + "栂", "垭", "殪", "沪", "苌", "濡", "蒎", "遏", "®", "质", "纫", "罐", "瞍", "柄", "呼", "锱", + "盔", "搋", "烛", "喉", "奈", "赦", "轘", "鉴", "瓒", "铰", "恽", "腠", "韶", "胼", "侄", "珂", + "鹕", "䝉", "呤", "椁", "岳", "汲", "疤", "绊", "涂", "乾", "噎", "兑", "絜", "嘧", "蚯", "们", + "蝉", "玛", "魁", "黻", "振", "冥", "报", "窟", "笱", "喊", "瓴", "喾", "説", "雹", "恫", "a", + "颧", "給", "宅", "鞧", "嬷", "鼾", "笔", "鲉", "匹", "蠕", "愍", "氽", "赐", "畔", "絪", "洴", + "审", "窂", "褔", "狯", "谑", "毳", "右", "疣", "鲙", "堃", "瓜", "…", "崆", "钫", "樭", "鲛", + "砂", "筋", "犏", "乱", "入", "浐", "膙", "厚", "獗", "覌", "焦", "罃", "宬", "隍", "荃", "醜", + "厉", "艇", "娟", "樂", "筲", "洛", "唼", "诜", "馑", "香", "缛", "运", "祟", "踩", "陲", "叔", + "潞", "二", "搠", "祼", "瀘", "蒋", "樗", "麦", "鸦", "棻", "古", "鹠", "怊", "裾", "够", "璧", + "晡", "擘", "毂", "御", "葚", "忱", "觞", "瑁", "唇", "罡", "剽", "殡", "沛", "帻", "举", "瞓", + "谬", "溝", "言", "哽", "婿", "猿", "跗", "獴", "俜", "呙", "弗", "凿", "窭", "铌", "友", "唉", + "怫", "荘", +]; + +pub const CHARSET_OLD: &[&str] = &["", "笤", "谴", "膀", "荔"]; + + + + +// pub fn from_builtin_old() -> Self { +// Self::from_static_slice( +// CHARSET_OLD, +// false, +// Resize::DynamicWidth(64), +// 1, +// Normalization::ZeroToOne, +// ) +// } +// +// /// 从预设的 Beta 版字符集创建 +// pub fn from_builtin_beta() -> Self { +// Self::from_static_slice( +// CHARSET_BETA, +// false, +// Resize::DynamicWidth(64), +// 1, +// Normalization::MinusOneToOne, +// ) +// } + + +// /// 从外部外部 JSON 文件动态加载字符集(在后续优化中移除) +// pub fn from_json_file>(path: P) -> anyhow::Result { +// let path = path.as_ref(); +// if !path.exists() { +// return Err(anyhow!("模型元数据配置文件不存在: {:?}", path)); +// } +// +// let mut file = File::open(path)?; +// let mut content = String::new(); +// file.read_to_string(&mut content)?; +// +// let dto: ModelMetadataDto = serde_json::from_str(&content) +// .map_err(|e| anyhow!("JSON 反序列化失败,请检查字段是否完整: {}", e))?; +// +// // 1. 将 DTO 的字符串数组转化为强类型的 Charset +// let tokens: Vec> = +// dto.charset.into_iter().map(|s| Cow::Owned(s)).collect(); +// let charset = Charset::new(tokens); +// +// // 2. 解析 resize 策略(重现 Python 的复杂条件判断) +// if dto.resize.len() != 2 { +// return Err(anyhow!( +// "'resize (or image)' 字段必须是包含两个元素的数组,例如 [-1, 64]" +// )); +// } +// let r0 = dto.resize[0]; +// let r1 = dto.resize[1]; +// +// let resize = if r0 == -1 { +// if dto.word { +// // 如果 word 为 true,且包含 -1,Python 里是 resize 为 (r1, r1) 的正方形 +// Resize::Square(r1 as u32) +// } else { +// // 如果 word 为 false,且包含 -1,Python 里是高度固定为 r1,宽度按原图比例缩放 +// Resize::DynamicWidth(r1 as u32) +// } +// } else { +// // 正常的固定宽高 +// Resize::Fixed(r0 as u32, r1 as u32) +// }; +// +// Ok(Self { +// charset, +// word: dto.word, +// resize, +// channel: dto.channel, +// normalization: dto.normalization, +// }) +// } \ No newline at end of file diff --git a/ddddocr-ort/tests/ocr_test.rs b/ddddocr-ort/tests/ocr_test.rs new file mode 100644 index 0000000..8eff838 --- /dev/null +++ b/ddddocr-ort/tests/ocr_test.rs @@ -0,0 +1,248 @@ +use anyhow::Context; +use ddddocr_core::{DetectionResult, ModelBuilder}; +use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider}; +// 假设你的包名是这个 +use ddddocr_ort::{DetSession, OcrBuilder, OcrSession}; +use image::{DynamicImage, ImageBuffer, Luma, Rgb}; +use std::fs; +use std::path::Path; + +mod char_slice; +use char_slice::CHARSET_BETA; +use ddddocr_core::Resize; + +use ddddocr_ort::loader::OrtModelLoader; + +fn load_image>(path: P) -> anyhow::Result { + // 1. 先将泛型转为具体的 &Path 引用 + let path_ref = path.as_ref(); + + // 2. 调用 open 时传入引用(utils::open 支持 AsRef) + image::open(path_ref).map_err(|e| { + // 3. 此时 path_ref 依然有效,可以安全地在闭包中使用 + anyhow::anyhow!("无法加载图片 {:?}: {}", path_ref, e) + }) +} +/// 将检测结果绘制在图像上并保存 +fn save_debug_image( + dynamic_img: &DynamicImage, // 【优化点 1】直接传入解码好的引用,拒绝重复解码 + bboxes: &[DetectionResult], // 【修改点 1】类型改为自定义结构体切片 + output_path: &str, +) -> anyhow::Result<()> { + // 删除了原本的 let dynamic_img = image::load_from_memory(image_bytes)?; + let mut img = dynamic_img.to_rgb8(); + let (width, height) = img.dimensions(); + let red = Rgb([255u8, 0, 0]); + + for bbox in bboxes { + // 【修改点 2】将原来的索引 bbox[0].. 改为结构体字段访问 .x1, .y1 .. + let x1 = bbox.x1.max(0).min(width as i32 - 1) as u32; + let y1 = bbox.y1.max(0).min(height as i32 - 1) as u32; + let x2 = bbox.x2.max(0).min(width as i32 - 1) as u32; + let y2 = bbox.y2.max(0).min(height as i32 - 1) as u32; + + // 绘制横向线条 + for x in x1..=x2 { + img.put_pixel(x, y1, red); + img.put_pixel(x, y2, red); + if y1 + 1 < height { + img.put_pixel(x, y1 + 1, red); + } + if y2.saturating_sub(1) > 0 { + img.put_pixel(x, y2 - 1, red); + } + } + // 绘制纵向线条 + for y in y1..=y2 { + img.put_pixel(x1, y, red); + img.put_pixel(x2, y, red); + if x1 + 1 < width { + img.put_pixel(x1 + 1, y, red); + } + if x2.saturating_sub(1) > 0 { + img.put_pixel(x2 - 1, y, red); + } + } + } + + img.save(output_path)?; + Ok(()) +} +#[allow(dead_code)] +fn save_rust_result(result: &ImageBuffer, Vec>, filename: &str) { + let (width, height) = result.dimensions(); + + // 1. 寻找最值进行归一化 + let mut max_val = f32::MIN; + let mut min_val = f32::MAX; + for p in result.pixels() { + if p.0[0] > max_val { + max_val = p.0[0]; + } + if p.0[0] < min_val { + min_val = p.0[0]; + } + } + + // 2. 创建 8 位灰度图 + let mut out_buf = ImageBuffer::new(width, height); + for y in 0..height { + for x in 0..width { + let val = result.get_pixel(x, y).0[0]; + let normalized = if max_val > min_val { + ((val - min_val) / (max_val - min_val) * 255.0) as u8 + } else { + 0u8 + }; + out_buf.put_pixel(x, y, Luma([normalized])); + } + } + + // 3. 保存 + DynamicImage::ImageLuma8(out_buf).save(filename).unwrap(); + println!("Rust 结果热力图已保存至: {}", filename); +} +#[test] +fn test_full_classification() { + let model = OrtModelLoader::builder().use_gpu(true) + .model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx") + .expect("模型加载失败"); + let metadata = ModelMetadata::from_static_slice( + CHARSET_BETA, + false, + Resize::DynamicWidth(64), + 1, + Normalization::MinusOneToOne, + ); + // 1. 初始化模型 + let ocr = OcrSession::new(model, metadata); + // 2. 加载测试图片 + let img = + image::open("D:/CNWei/CNW/Rust/ddddocr-rs/samples/code2.png").expect("测试图片不存在"); + + // 3. 执行识别 + // let result = Ocr::new(&ocr) + // .predict(&img) + // .expect("识别过程出错") + // .into_text(); + let result = OcrBuilder::new() + .build(&ocr) + .predict(&img) + .expect("识别过程出错") + .into_text(); + + println!("识别结果: {}", result); + assert!(!result.is_empty()); +} +#[test] +fn test_det_load() -> anyhow::Result<()> { + let det_model = OrtModelLoader::builder() + .model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx") + .expect("模型加载失败"); + let det = DetSession::new(det_model); + 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))?; + + println!("图片读取成功,字节大小: {}", image_bytes.len()); + + // 【修改点 1】将字节流解码为统一的 DynamicImage + let img = image::load_from_memory(&image_bytes) + .map_err(|e| anyhow::anyhow!("图片解码失败: {}", e))?; + + // 【修改点 2】传入统一的 &DynamicImage 引用 + let bboxes = Detector::new(&det).predict(&img)?; + // println!("{:?}", det); + println!("检测到的目标数量: {}", bboxes.len()); + + if bboxes.is_empty() { + println!("未检测到任何目标。"); + } else { + // 如果 save_debug_image 报错,记得去把它的入参类型和内部访问也改为 DetectionResult + save_debug_image( + &img, + &bboxes, + "D:/CNWei/CNW/Rust/ddddocr-rs/samples/result.jpg", + )?; + + for (i, bbox) in bboxes.iter().enumerate() { + // 【修改点 3】将原来的 bbox[0].. 索引访问改为结构体字段访问 + println!("目标 [{}]: {}", i, bbox); + } + } + Ok(()) +} + +#[test] +fn test_real_slide_match() { + let engine = Slider::new(); + + // 1. 加载你准备好的测试图 + // 假设图片放在项目根目录下的 assets 文件夹 + let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/hua.png") + .expect("请确保 samples/hua.png 存在"); + let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/huatu.png") + .expect("请确保 samples/huatu.png 存在"); + + // 2. 执行匹配 + // 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false + let start = std::time::Instant::now(); + let result = engine + .slide_match(&target_img, &bg_img, false) + .expect("Slide match 执行失败"); + let duration = start.elapsed(); + + // 3. 打印结果 + println!("-------------------------------------------"); + println!("{}", result); + println!("耗时: {:?}", duration); + println!("-------------------------------------------"); + + // 验证基本逻辑:坐标不应为 0 (除非匹配失败) + assert_eq!(result.target_x, 237); + assert_eq!(result.target_y, 77); + assert!(result.confidence > 0.0); +} + +#[test] +fn test_real_slide_comparison() { + let engine = Slider::new(); + + // 1. 加载你准备好的测试图 + // 假设图片放在项目根目录下的 assets 文件夹 + let target_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/ken.jpg") + .expect("请确保 samples/ken.jpg 存在"); + let bg_img = load_image("D:/CNWei/CNW/Rust/ddddocr-rs/samples/kenyuan.jpg") + .expect("请确保 samples/kenyuan.jpg 存在"); + + // 2. 执行匹配 + // 如果是那种带有明显阴影边缘的复杂滑块,建议 simple_target 传 false + let start = std::time::Instant::now(); + let result = engine + .slide_comparison(&target_img, &bg_img) + .expect("Slide match 执行失败"); + let duration = start.elapsed(); + + // 3. 打印结果 + println!("-------------------------------------------"); + println!("滑块匹配测试结果:"); + println!("检测坐标: [x: {}, y: {}]", result.target_x, result.target_y); + println!("置信度: {:.4}", result.confidence); + println!("耗时: {:?}", duration); + println!("-------------------------------------------"); + + // 验证基本逻辑:坐标不应为 0 (除非匹配失败) + assert_eq!(result.target_x, 171); + assert_eq!(result.target_y, 90); + assert!(result.confidence > 0.0); +} +#[test] +fn test_resolve_shape_logic_direct() { + // 创建一个哑 ModelLoader 实例(session 用不上,因为我们直接测私有方法) + let loader = OrtModelLoader::builder() + .model_for_path( + // "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx", + "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_huashi666_i64.onnx", + ) + .expect("建立测试模型图失败"); +} diff --git a/ddddocr-tract/Cargo.toml b/ddddocr-tract/Cargo.toml index 80dbb68..7f9a569 100644 --- a/ddddocr-tract/Cargo.toml +++ b/ddddocr-tract/Cargo.toml @@ -7,14 +7,16 @@ license = { workspace = true } [dependencies] ddddocr-core = { path = "../ddddocr-core" } # 引入兄弟库 -tract-onnx = "0.21.10" -anyhow = "1.0.102" -image = { workspace = true } -base64 = "0.22.1" -imageproc = { version = "0.26.2", default-features = true } -serde = { workspace = true } -serde_json = "1.0.150" +tract-onnx = { workspace = true } +tract-linalg = {workspace = true } ndarray = { workspace = true } # 继承自工作空间 + +image = { workspace = true } + +serde = { workspace = true } +serde_json = { workspace = true } + +anyhow = { workspace = true } thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 diff --git a/ddddocr-tract/src/det/session.rs b/ddddocr-tract/src/det/session.rs index cbfecc2..0c7404d 100644 --- a/ddddocr-tract/src/det/session.rs +++ b/ddddocr-tract/src/det/session.rs @@ -2,7 +2,8 @@ use crate::types::Session; use ddddocr_core::error::{Result, TensorError}; use ddddocr_core::{DetEngine, DetOutput, InferenceEngine}; use ndarray::Ix3; -use tract_onnx::prelude::{tvec, IntoTensor, Tensor}; +// use tract_onnx::prelude::{tvec, IntoTensor, Tensor}; +use tract_onnx::prelude::*; #[derive(Debug)] pub struct DetSession { pub session: Session, @@ -29,7 +30,8 @@ impl InferenceEngine for DetSession { println!("模型输出原始数据: {:?}", result); // Ok(result.swap_remove(0).into_tensor()) let raw_tensor = result.swap_remove(0).into_tensor(); - let array_d = raw_tensor.into_array::().map_err(|_| { + // raw_tensor.into_plain_array()? + let array_d = raw_tensor.into_plain_array::().map_err(|_| { TensorError::Engine("Tract 实体张量无法转换为 ndarray::ArrayD".to_string()) })?; // 提前利用克隆(Clone)备份好当前未转维度前的真实 shape (Vec) diff --git a/ddddocr-tract/src/lib.rs b/ddddocr-tract/src/lib.rs index 27fcca1..c67f953 100644 --- a/ddddocr-tract/src/lib.rs +++ b/ddddocr-tract/src/lib.rs @@ -4,7 +4,6 @@ mod ocr; mod types; mod error; -pub use ddddocr_core::ocr::OcrBuilder; -pub use ddddocr_core::{SlideResult, Slider}; +pub use ddddocr_core::{SlideResult, Slider,OcrBuilder}; pub use det::session::DetSession; pub use ocr::session::OcrSession; diff --git a/ddddocr-tract/src/loader.rs b/ddddocr-tract/src/loader.rs index 1e4294b..8690dea 100644 --- a/ddddocr-tract/src/loader.rs +++ b/ddddocr-tract/src/loader.rs @@ -3,5 +3,5 @@ mod metadata; mod model; pub use error::{Error, ParseError, Result}; -pub use metadata::ModelMetadata; -pub use model::ModelLoader; +pub use metadata::{ModelMetadataDto, NormalizationDto, TractModelMetadata}; +pub use model::TractModelLoader; diff --git a/ddddocr-tract/src/loader/metadata.rs b/ddddocr-tract/src/loader/metadata.rs index 7388fa3..fb067dc 100644 --- a/ddddocr-tract/src/loader/metadata.rs +++ b/ddddocr-tract/src/loader/metadata.rs @@ -1,14 +1,14 @@ use crate::loader::error::{Error, Result}; -pub use ddddocr_core::ModelMetadata; -use ddddocr_core::ocr::Resize; +use ddddocr_core::ModelMetadata; +use ddddocr_core::Resize; use ddddocr_core::{Charset, Normalization}; use serde::Deserialize; use std::borrow::Cow; #[derive(Deserialize)] #[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one" -enum NormalizationDto { +pub enum NormalizationDto { /// 映射到 [0.0, 1.0] -> pixel / 255.0 ZeroToOne, /// 映射到 [-1.0, 1.0] -> (pixel / 255.0 - 0.5) / 0.5 @@ -26,7 +26,7 @@ impl From for Normalization { /// 仅用于反序列化 JSON 的中间临时结构体(DTO) #[derive(Deserialize)] -struct ModelMetadataDto { +pub struct ModelMetadataDto { charset: Vec, word: bool, #[serde(alias = "image")] diff --git a/ddddocr-tract/src/loader/model.rs b/ddddocr-tract/src/loader/model.rs index fcfc6ac..c94f74c 100644 --- a/ddddocr-tract/src/loader/model.rs +++ b/ddddocr-tract/src/loader/model.rs @@ -1,17 +1,54 @@ -use crate::loader::error; use crate::loader::error::{Error, ParseError, Result}; use crate::types::Session; +use ddddocr_core::ModelBuilder; use std::io::Cursor; +use tract_linalg::multithread::{Executor, set_default_executor}; use tract_onnx::onnx; use tract_onnx::prelude::*; -pub struct ModelLoader; +pub struct TractModelLoader; +impl TractModelLoader { + /// 获取针对 Tract 后端的链式构建器 + pub fn builder() -> TractModelBuilder { + TractModelBuilder::default() + } +} -impl ModelLoader { - pub fn model_for_path

(model_path: P) -> Result +/// Tract 专用的链式构建器 +#[derive(Debug, Clone, Default)] +pub struct TractModelBuilder { + num_threads: Option, +} + +impl TractModelBuilder { + /// 可选扩展:设置 CPU 线程数(不提供任何 GPU 相关的 API) + pub fn num_threads(mut self, threads: usize) -> Self { + self.num_threads = Some(threads); + self + } + fn setup_tract_threads(&self) { + // 💡 1. 如果设置了线程数,可以通过 tract 的 multithread 配置应用给 model + if let Some(threads) = self.num_threads { + // 在 Tract 中可以通过 set_num_threads 或设置底层环境控制并发 + // (注:Tract 0.20+ 版本支持全局/局部线程控制) + let executor = if threads <= 1 { + Executor::SingleThread + } else { + Executor::multithread(threads) + }; + set_default_executor(executor); + } + } +} + +impl ModelBuilder for TractModelBuilder { + type Session = Session; + type Error = Error; + fn model_for_path

(&self, model_path: P) -> Result where P: AsRef, { + self.setup_tract_threads(); let path_ref = model_path.as_ref(); let session = onnx() @@ -27,7 +64,8 @@ impl ModelLoader { Ok(session) } /// 策略 B:从内存字节流加载模型(配合 include_bytes! 使用) - pub fn model_from_bytes(model_bytes: &[u8]) -> Result { + fn model_from_bytes(&self, model_bytes: &[u8]) -> Result { + self.setup_tract_threads(); // 使用 std::io::Cursor 将 &[u8] 包装为可读的流(实现 std::io::Read) let mut cursor = Cursor::new(model_bytes); @@ -52,7 +90,7 @@ mod tests { /// 辅助函数:动态构建一个简单的 ONNX/Tract 内存模型图用于测试 fn create_test_model() -> std::result::Result { - let mut rect = tract_onnx::prelude::Graph::default(); + let mut rect = TypedModel::default(); // 0.21.10 最稳妥的静态 Fact 构建 let input_fact = TypedFact::dt_shape(DatumType::F32, &[1, 3, 224, 224]); @@ -63,8 +101,6 @@ mod tests { rect.set_input_outlets(&[input_node.into()]) .map_err(|e| anyhow::anyhow!("{:?}", e))?; - rect.set_output_outlets(&[input_node.into()]) - .map_err(|e| anyhow::anyhow!("{:?}", e))?; let typed = rect .into_optimized() diff --git a/ddddocr-tract/src/ocr/session.rs b/ddddocr-tract/src/ocr/session.rs index 247b5bb..4155985 100644 --- a/ddddocr-tract/src/ocr/session.rs +++ b/ddddocr-tract/src/ocr/session.rs @@ -1,5 +1,5 @@ -use crate::loader::ModelMetadata; use crate::types::Session; +use ddddocr_core::ModelMetadata; use ddddocr_core::error::{DdddError, Result, TensorError}; use ddddocr_core::utils::normalize_ocr_logits; use ddddocr_core::{InferenceEngine, OcrEngine, OcrOutput}; @@ -83,7 +83,7 @@ impl InferenceEngine for OcrSession { match raw_tensor.datum_type() { DatumType::I64 => { let array_d = raw_tensor - .into_array::() + .into_plain_array::() .map_err(|_| TensorError::Engine("Tract 无法获取 i64 内存视图".to_string()))?; // .context("Tract 无法获取 i64 内存视图")?; // 🌟 提前提取真实维度 @@ -101,8 +101,9 @@ impl InferenceEngine for OcrSession { DatumType::F32 => { let shape = raw_tensor.shape(); println!("模型输出shape数据: {:?}", shape); + // raw_tensor.to_plain_array_view() let view = raw_tensor - .to_array_view::() + .to_plain_array_view::() .map_err(|_| TensorError::Engine("Tract 无法获取 f32 内存视图".to_string()))?; // 1. 极其纯粹的、无拷贝的多维 Shape 压扁清洗 normalize_ocr_logits(view, shape) diff --git a/ddddocr-tract/src/types.rs b/ddddocr-tract/src/types.rs index 9c1fbb6..dcf55ef 100644 --- a/ddddocr-tract/src/types.rs +++ b/ddddocr-tract/src/types.rs @@ -1,3 +1,4 @@ -use tract_onnx::prelude::{Graph, RunnableModel, TypedFact, TypedOp}; +use std::sync::Arc; +use tract_onnx::prelude::TypedRunnableModel; -pub type Session = RunnableModel, Graph>>; \ No newline at end of file +pub type Session = Arc; diff --git a/ddddocr-tract/tests/char_slice.rs b/ddddocr-tract/tests/char_slice.rs index 6c41dd3..cec0056 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::ocr::Charset; -use ddddocr_core::ocr::{Normalization, Resize}; +use ddddocr_core::Charset; +use ddddocr_core::{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 61354d5..2f7eb5c 100644 --- a/ddddocr-tract/tests/ocr_test.rs +++ b/ddddocr-tract/tests/ocr_test.rs @@ -1,18 +1,17 @@ use anyhow::Context; -use ddddocr_core::det::DetectionResult; -use ddddocr_core::{Detector, ModelMetadata, Normalization, Ocr, Slider}; +use ddddocr_core::{DetectionResult, ModelBuilder}; +use ddddocr_core::{Detector, ModelMetadata, Normalization, Slider}; // 假设你的包名是这个 -use ddddocr_tract::{DetSession, OcrSession,OcrBuilder}; +use ddddocr_tract::{DetSession, OcrBuilder, OcrSession}; use image::{DynamicImage, ImageBuffer, Luma, Rgb}; use std::fs; use std::path::Path; -use tract_onnx::model; mod char_slice; use char_slice::CHARSET_BETA; -use ddddocr_core::ocr::Resize; +use ddddocr_core::Resize; -use ddddocr_tract::loader::ModelLoader; +use ddddocr_tract::loader::{ TractModelLoader}; fn load_image>(path: P) -> anyhow::Result { // 1. 先将泛型转为具体的 &Path 引用 @@ -105,10 +104,9 @@ fn save_rust_result(result: &ImageBuffer, Vec>, filename: &str) { } #[test] fn test_full_classification() { - let model = ModelLoader::model_for_path( - "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx", - ) - .expect("模型加载失败"); + let model = TractModelLoader::builder() + .model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx") + .expect("模型加载失败"); let metadata = ModelMetadata::from_static_slice( CHARSET_BETA, false, @@ -127,7 +125,8 @@ fn test_full_classification() { // .predict(&img) // .expect("识别过程出错") // .into_text(); - let result = OcrBuilder::new().build(&ocr) + let result = OcrBuilder::new() + .build(&ocr) .predict(&img) .expect("识别过程出错") .into_text(); @@ -138,7 +137,8 @@ fn test_full_classification() { #[test] fn test_det_load() -> anyhow::Result<()> { let det_model = - ModelLoader::model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx") + TractModelLoader::builder() + .model_for_path("D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_det.onnx") .expect("模型加载失败"); let det = DetSession::new(det_model); let image_path = "D:/CNWei/CNW/Rust/ddddocr-rs/samples/det1.png"; @@ -240,9 +240,11 @@ fn test_real_slide_comparison() { #[test] fn test_resolve_shape_logic_direct() { // 创建一个哑 ModelLoader 实例(session 用不上,因为我们直接测私有方法) - let loader = ModelLoader::model_for_path( + let loader = TractModelLoader::builder() + .model_for_path( // "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_sml2h3_f32.onnx", "D:\\CNWei\\CNW\\Rust\\ddddocr-rs\\models\\common_huashi666_i64.onnx", ) .expect("建立测试模型图失败"); + println!("{:?}", loader.model().inputs); }