mirror of
https://github.com/opendatalab/MinerU.git
synced 2026-08-30 17:12:39 +08:00
feat: integrate PP-OCRv6 model components and update configurations for detection and recognition
This commit is contained in:
@@ -162,8 +162,9 @@ class PytorchPaddleOCR(TextSystem):
|
||||
|
||||
device = get_device()
|
||||
if device == 'cpu':
|
||||
if self.lang in ['ch', 'ch_server', 'japan', 'chinese_cht']:
|
||||
# logger.warning("The current device in use is CPU. To ensure the speed of parsing, the language is automatically switched to ch_lite.")
|
||||
# 显式指定 ch_server 时保留服务模型配置,避免 v6 medium rec 在 CPU 环境被静默降级。
|
||||
if self.lang in ['ch', 'japan', 'chinese_cht']:
|
||||
# logger.warning("CPU device switches this language to ch_lite for parsing speed.")
|
||||
self.lang = 'ch_lite'
|
||||
elif self.lang in ['seal']:
|
||||
self.lang = 'seal_lite'
|
||||
@@ -292,7 +293,7 @@ class PytorchPaddleOCR(TextSystem):
|
||||
tqdm_progress_bar=None,
|
||||
):
|
||||
assert isinstance(img, (np.ndarray, list, str, bytes))
|
||||
if isinstance(img, list) and det == True:
|
||||
if isinstance(img, list) and det:
|
||||
logger.error('When input a list of images, det must be false')
|
||||
exit(0)
|
||||
img = check_img(img)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Copyright (c) Opendatalab. All rights reserved.
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
|
||||
from .modeling.architectures.base_model import BaseModel
|
||||
|
||||
class BaseOCRV20:
|
||||
@@ -13,13 +16,47 @@ class BaseOCRV20:
|
||||
def build_net(self, **kwargs):
|
||||
self.net = BaseModel(self.config, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _is_safetensors_path(weights_path):
|
||||
"""判断权重文件是否为 safetensors 格式。"""
|
||||
return Path(weights_path).suffix == ".safetensors"
|
||||
|
||||
@staticmethod
|
||||
def _load_weight_file(weights_path):
|
||||
"""根据文件后缀选择 safetensors 或 torch 原生加载方式。"""
|
||||
if BaseOCRV20._is_safetensors_path(weights_path):
|
||||
from safetensors.torch import load_file
|
||||
|
||||
return load_file(str(weights_path), device="cpu")
|
||||
try:
|
||||
return torch.load(weights_path, map_location="cpu", weights_only=True)
|
||||
except TypeError:
|
||||
return torch.load(weights_path, map_location="cpu")
|
||||
|
||||
@staticmethod
|
||||
def _normalize_ppocrv6_state_dict(weights, weights_path):
|
||||
"""归一化 HF OCR safetensors 的外层 `model.` 前缀。"""
|
||||
if not BaseOCRV20._is_safetensors_path(weights_path):
|
||||
return weights
|
||||
if not any(key.startswith("model.") for key in weights.keys()):
|
||||
return weights
|
||||
return {
|
||||
key.removeprefix("model."): value
|
||||
for key, value in weights.items()
|
||||
}
|
||||
|
||||
def read_pytorch_weights(self, weights_path):
|
||||
"""读取 PyTorch OCR 权重,并兼容 PP-OCRv6 safetensors。"""
|
||||
if not os.path.exists(weights_path):
|
||||
raise FileNotFoundError('{} is not existed.'.format(weights_path))
|
||||
weights = torch.load(weights_path)
|
||||
return weights
|
||||
weights = self._load_weight_file(weights_path)
|
||||
return self._normalize_ppocrv6_state_dict(weights, weights_path)
|
||||
|
||||
def get_out_channels(self, weights):
|
||||
"""从权重结构推断识别输出通道数。"""
|
||||
if "head.head.weight" in weights:
|
||||
# PP-OCRv6 safetensors 的识别分类层固定命名为 head.head。
|
||||
return weights["head.head.weight"].shape[0]
|
||||
if list(weights.keys())[-1].endswith('.weight') and len(list(weights.values())[-1].shape) == 2:
|
||||
out_channels = list(weights.values())[-1].numpy().shape[1]
|
||||
else:
|
||||
@@ -31,7 +68,8 @@ class BaseOCRV20:
|
||||
# print('weights is loaded.')
|
||||
|
||||
def load_pytorch_weights(self, weights_path):
|
||||
self.net.load_state_dict(torch.load(weights_path, weights_only=True))
|
||||
"""加载 PyTorch OCR 权重,按后缀兼容 safetensors。"""
|
||||
self.net.load_state_dict(self.read_pytorch_weights(weights_path))
|
||||
# print('model is loaded: {}'.format(weights_path))
|
||||
|
||||
def inference(self, inputs):
|
||||
|
||||
@@ -20,39 +20,34 @@ def build_backbone(config, model_type):
|
||||
from .det_mobilenet_v3 import MobileNetV3
|
||||
from .rec_hgnet import PPHGNet_small
|
||||
from .rec_lcnetv3 import PPLCNetV3
|
||||
from .rec_lcnetv4 import PPLCNetV4
|
||||
from .rec_pphgnetv2 import PPHGNetV2_B4
|
||||
|
||||
support_dict = [
|
||||
"MobileNetV3",
|
||||
"ResNet",
|
||||
"ResNet_vd",
|
||||
"ResNet_SAST",
|
||||
"PPLCNetV3",
|
||||
"PPHGNet_small",
|
||||
'PPHGNetV2_B4',
|
||||
]
|
||||
support_dict = {
|
||||
"MobileNetV3": MobileNetV3,
|
||||
"PPLCNetV3": PPLCNetV3,
|
||||
"PPLCNetV4": PPLCNetV4,
|
||||
"PPHGNet_small": PPHGNet_small,
|
||||
"PPHGNetV2_B4": PPHGNetV2_B4,
|
||||
}
|
||||
elif model_type == "rec" or model_type == "cls":
|
||||
from .rec_hgnet import PPHGNet_small
|
||||
from .rec_lcnetv3 import PPLCNetV3
|
||||
from .rec_lcnetv4 import PPLCNetV4
|
||||
from .rec_mobilenet_v3 import MobileNetV3
|
||||
from .rec_svtrnet import SVTRNet
|
||||
from .rec_mv1_enhance import MobileNetV1Enhance
|
||||
from .rec_pphgnetv2 import PPHGNetV2_B4, PPHGNetV2_B6_Formula
|
||||
support_dict = [
|
||||
"MobileNetV1Enhance",
|
||||
"MobileNetV3",
|
||||
"ResNet",
|
||||
"ResNetFPN",
|
||||
"MTB",
|
||||
"ResNet31",
|
||||
"SVTRNet",
|
||||
"ViTSTR",
|
||||
"DenseNet",
|
||||
"PPLCNetV3",
|
||||
"PPHGNet_small",
|
||||
"PPHGNetV2_B4",
|
||||
"PPHGNetV2_B6_Formula"
|
||||
]
|
||||
support_dict = {
|
||||
"MobileNetV1Enhance": MobileNetV1Enhance,
|
||||
"MobileNetV3": MobileNetV3,
|
||||
"SVTRNet": SVTRNet,
|
||||
"PPLCNetV3": PPLCNetV3,
|
||||
"PPLCNetV4": PPLCNetV4,
|
||||
"PPHGNet_small": PPHGNet_small,
|
||||
"PPHGNetV2_B4": PPHGNetV2_B4,
|
||||
"PPHGNetV2_B6_Formula": PPHGNetV2_B6_Formula,
|
||||
}
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -62,5 +57,5 @@ def build_backbone(config, model_type):
|
||||
model_type, support_dict
|
||||
)
|
||||
)
|
||||
module_class = eval(module_name)(**config)
|
||||
module_class = support_dict[module_name](**config)
|
||||
return module_class
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
# Copyright (c) Opendatalab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
|
||||
NET_CONFIG_DET = {
|
||||
"small": {
|
||||
"stem_channels": [3, 24, 48],
|
||||
"block_configs": [
|
||||
[[3, 48, 48, 1, True], [3, 48, 48, 1, False]],
|
||||
[[3, 48, 96, 2, False], [3, 96, 96, 1, True], [3, 96, 96, 1, False]],
|
||||
[
|
||||
[3, 96, 192, 2, False],
|
||||
[3, 192, 192, 1, True],
|
||||
[3, 192, 192, 1, False],
|
||||
[3, 192, 192, 1, True],
|
||||
[3, 192, 192, 1, False],
|
||||
],
|
||||
[[3, 192, 384, 2, False], [3, 384, 384, 1, True], [3, 384, 384, 1, False]],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
NET_CONFIG_REC = {
|
||||
"small": {
|
||||
"stem_channels": [3, 48, 96],
|
||||
"block_configs": [
|
||||
[[3, 96, 96, 1, True]],
|
||||
[[3, 96, 96, 1, False], [3, 96, 96, 1, False]],
|
||||
[
|
||||
[3, 96, 192, (2, 1), False],
|
||||
[3, 192, 192, 1, True],
|
||||
[3, 192, 192, 1, False],
|
||||
[3, 192, 192, 1, True],
|
||||
[3, 192, 192, 1, False],
|
||||
[3, 192, 192, 1, True],
|
||||
[3, 192, 192, 1, False],
|
||||
],
|
||||
[[3, 192, 384, (2, 1), False], [3, 384, 384, 1, True], [3, 384, 384, 1, False]],
|
||||
],
|
||||
},
|
||||
"medium": {
|
||||
"stem_channels": [3, 64, 128],
|
||||
"block_configs": [
|
||||
[[3, 128, 128, 1, True]],
|
||||
[[3, 128, 256, 1, False], [3, 256, 256, 1, False], [3, 256, 256, 1, True]],
|
||||
[
|
||||
[3, 256, 512, (2, 1), False],
|
||||
[3, 512, 512, 1, True],
|
||||
[3, 512, 512, 1, False],
|
||||
[3, 512, 512, 1, True],
|
||||
[3, 512, 512, 1, False],
|
||||
[3, 512, 512, 1, True],
|
||||
[3, 512, 512, 1, False],
|
||||
],
|
||||
[[3, 512, 768, (2, 1), False], [3, 768, 768, 1, True], [3, 768, 768, 1, False]],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _build_activation(name):
|
||||
"""按 PP-OCRv6 配置创建无参数激活层,便于和 safetensors 权重命名保持解耦。"""
|
||||
if name is None:
|
||||
return nn.Identity()
|
||||
if name == "relu":
|
||||
return nn.ReLU()
|
||||
if name == "gelu":
|
||||
return nn.GELU()
|
||||
if name in {"silu", "swish"}:
|
||||
return nn.SiLU()
|
||||
if name == "hardsigmoid":
|
||||
return nn.Hardsigmoid()
|
||||
raise ValueError(f"Unsupported activation: {name}")
|
||||
|
||||
|
||||
def _to_stride(stride):
|
||||
"""把 Paddle/Transformers 配置里的 stride 统一成 PyTorch 可接受的格式。"""
|
||||
if isinstance(stride, list):
|
||||
return tuple(stride)
|
||||
return stride
|
||||
|
||||
|
||||
class PPLCNetV4ConvLayer(nn.Module):
|
||||
"""PP-LCNetV4 的 Conv-BN-Act 基础层,属性名对齐 HF 权重。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
groups=1,
|
||||
activation="relu",
|
||||
):
|
||||
"""初始化卷积、归一化和激活层。"""
|
||||
super().__init__()
|
||||
self.convolution = nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=_to_stride(stride),
|
||||
groups=groups,
|
||||
padding=(kernel_size - 1) // 2,
|
||||
bias=False,
|
||||
)
|
||||
self.normalization = nn.BatchNorm2d(out_channels)
|
||||
self.activation = _build_activation(activation)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 Conv-BN-Act 前向计算。"""
|
||||
hidden_states = self.convolution(hidden_states)
|
||||
hidden_states = self.normalization(hidden_states)
|
||||
hidden_states = self.activation(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class PPLCNetV4SqueezeExcitationModule(nn.Module):
|
||||
"""PP-LCNetV4 的 SE 模块,保留 `convolutions.0/2` 权重命名。"""
|
||||
|
||||
def __init__(self, channel, reduction=4):
|
||||
"""初始化全局池化和两层 1x1 卷积。"""
|
||||
super().__init__()
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
||||
self.convolutions = nn.ModuleList(
|
||||
[
|
||||
nn.Conv2d(channel, channel // reduction, kernel_size=1, stride=1, padding=0, bias=True),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(channel // reduction, channel, kernel_size=1, stride=1, padding=0, bias=True),
|
||||
nn.Hardsigmoid(),
|
||||
]
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""根据通道注意力缩放输入特征。"""
|
||||
residual = hidden_states
|
||||
hidden_states = self.avg_pool(hidden_states)
|
||||
for layer in self.convolutions:
|
||||
hidden_states = layer(hidden_states)
|
||||
return residual * hidden_states
|
||||
|
||||
|
||||
class PPLCNetV4LargeStem(nn.Module):
|
||||
"""PP-LCNetV4 branch stem,属性名对齐 `encoder.convolution.stem*`。"""
|
||||
|
||||
def __init__(self, stem_channels):
|
||||
"""初始化 v6 small/medium 使用的分支 stem。"""
|
||||
super().__init__()
|
||||
self.stem1 = PPLCNetV4ConvLayer(stem_channels[0], stem_channels[1], kernel_size=3, stride=2)
|
||||
self.stem2a = PPLCNetV4ConvLayer(stem_channels[1], stem_channels[1] // 2, kernel_size=2, stride=1)
|
||||
self.stem2b = PPLCNetV4ConvLayer(stem_channels[1] // 2, stem_channels[1], kernel_size=2, stride=1)
|
||||
self.stem3 = PPLCNetV4ConvLayer(stem_channels[1] * 2, stem_channels[1], kernel_size=3, stride=2)
|
||||
self.stem4 = PPLCNetV4ConvLayer(stem_channels[1], stem_channels[2], kernel_size=1, stride=1)
|
||||
self.pool = nn.MaxPool2d(kernel_size=2, stride=1, ceil_mode=True)
|
||||
|
||||
def forward(self, pixel_values):
|
||||
"""执行分支 stem 的 pad、pool 和 concat 流程。"""
|
||||
embedding = self.stem1(pixel_values)
|
||||
embedding = F.pad(embedding, (0, 1, 0, 1))
|
||||
emb_stem_2a = self.stem2a(embedding)
|
||||
emb_stem_2a = F.pad(emb_stem_2a, (0, 1, 0, 1))
|
||||
emb_stem_2a = self.stem2b(emb_stem_2a)
|
||||
pooled_emb = self.pool(embedding)
|
||||
embedding = torch.cat([pooled_emb, emb_stem_2a], dim=1)
|
||||
embedding = self.stem3(embedding)
|
||||
embedding = self.stem4(embedding)
|
||||
return embedding
|
||||
|
||||
|
||||
class PPLCNetV4DepthwiseSeparableConvLayer(nn.Module):
|
||||
"""PP-LCNetV4 block 中的 token mixer 和 channel mixer。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
stride,
|
||||
kernel_size,
|
||||
use_squeeze_excitation,
|
||||
reduction=4,
|
||||
):
|
||||
"""按 v6 配置初始化深度卷积、SE 和两层 point-wise 卷积。"""
|
||||
super().__init__()
|
||||
self.has_residual = in_channels == out_channels and stride == 1
|
||||
self.use_rep_dw = stride == 1 and in_channels == out_channels
|
||||
if self.use_rep_dw:
|
||||
self.token_conv = nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=kernel_size // 2,
|
||||
groups=in_channels,
|
||||
bias=True,
|
||||
)
|
||||
else:
|
||||
self.token_conv = PPLCNetV4ConvLayer(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
groups=in_channels,
|
||||
activation=None,
|
||||
)
|
||||
self.token_squeeze_excitation = (
|
||||
PPLCNetV4SqueezeExcitationModule(in_channels, reduction) if use_squeeze_excitation else nn.Identity()
|
||||
)
|
||||
self.channel_conv1 = PPLCNetV4ConvLayer(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels * 2,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
activation=None,
|
||||
)
|
||||
self.channel_act_fn = nn.GELU()
|
||||
self.channel_conv2 = PPLCNetV4ConvLayer(
|
||||
in_channels=in_channels * 2,
|
||||
out_channels=out_channels,
|
||||
kernel_size=1,
|
||||
stride=1,
|
||||
activation=None,
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 token mixing、channel mixing 和可选残差连接。"""
|
||||
hidden_states = self.token_conv(hidden_states)
|
||||
hidden_states = self.token_squeeze_excitation(hidden_states)
|
||||
residual = hidden_states
|
||||
hidden_states = self.channel_conv1(hidden_states)
|
||||
hidden_states = self.channel_act_fn(hidden_states)
|
||||
hidden_states = self.channel_conv2(hidden_states)
|
||||
if self.has_residual:
|
||||
hidden_states = residual + hidden_states
|
||||
return hidden_states
|
||||
|
||||
|
||||
class PPLCNetV4Block(nn.Module):
|
||||
"""PP-LCNetV4 的一个 stage,内部包含多个 depthwise separable block。"""
|
||||
|
||||
def __init__(self, block_configs):
|
||||
"""根据 stage 配置创建 block 列表。"""
|
||||
super().__init__()
|
||||
self.blocks = nn.ModuleList()
|
||||
for kernel_size, in_channels, out_channels, stride, use_squeeze_excitation in block_configs:
|
||||
self.blocks.append(
|
||||
PPLCNetV4DepthwiseSeparableConvLayer(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=_to_stride(stride),
|
||||
use_squeeze_excitation=use_squeeze_excitation,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""顺序执行当前 stage 的所有 block。"""
|
||||
for block in self.blocks:
|
||||
hidden_states = block(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class PPLCNetV4Encoder(nn.Module):
|
||||
"""PP-LCNetV4 编码器,保留 `encoder.convolution` 和 `encoder.blocks` 命名。"""
|
||||
|
||||
def __init__(self, stem_channels, block_configs):
|
||||
"""初始化 stem 和四个 stage。"""
|
||||
super().__init__()
|
||||
self.convolution = PPLCNetV4LargeStem(stem_channels)
|
||||
self.blocks = nn.ModuleList([PPLCNetV4Block(stage_configs) for stage_configs in block_configs])
|
||||
|
||||
def forward(self, pixel_values):
|
||||
"""返回四个 stage 的输出特征,供 det/rec 上层按需使用。"""
|
||||
hidden_states = self.convolution(pixel_values)
|
||||
feature_maps = []
|
||||
for block in self.blocks:
|
||||
hidden_states = block(hidden_states)
|
||||
feature_maps.append(hidden_states)
|
||||
return feature_maps
|
||||
|
||||
|
||||
class PPLCNetV4(nn.Module):
|
||||
"""PP-OCRv6 使用的 PPLCNetV4 backbone,支持 det small 和 rec small/medium。"""
|
||||
|
||||
def __init__(self, det=False, model_size="small", in_channels=3, **kwargs):
|
||||
"""按 det/rec 模式选择 v6 的固定网络配置。"""
|
||||
super().__init__()
|
||||
self.det = det
|
||||
if in_channels != 3:
|
||||
raise ValueError(f"PPLCNetV4 only supports 3 input channels, got {in_channels}.")
|
||||
config_dict = NET_CONFIG_DET if det else NET_CONFIG_REC
|
||||
if model_size not in config_dict:
|
||||
mode = "det" if det else "rec"
|
||||
raise ValueError(f"PPLCNetV4 {mode} model_size must be one of {list(config_dict)}, got {model_size}.")
|
||||
config = config_dict[model_size]
|
||||
self.encoder = PPLCNetV4Encoder(config["stem_channels"], config["block_configs"])
|
||||
stage_out_channels = [stage[-1][2] for stage in config["block_configs"]]
|
||||
self.out_channels = stage_out_channels if det else stage_out_channels[-1]
|
||||
|
||||
def forward(self, x):
|
||||
"""det 返回四级特征列表,rec 返回高度池化后的识别特征。"""
|
||||
feature_maps = self.encoder(x)
|
||||
if self.det:
|
||||
return feature_maps
|
||||
x = feature_maps[-1]
|
||||
if self.training:
|
||||
return F.adaptive_avg_pool2d(x, [1, 40])
|
||||
if x.shape[2] < 3:
|
||||
raise ValueError(f"Feature height {x.shape[2]} < pool kernel 3.")
|
||||
return F.avg_pool2d(x, [3, 2])
|
||||
@@ -49,6 +49,49 @@ class Head(nn.Module):
|
||||
return x
|
||||
|
||||
|
||||
class PPOCRV6DBConvBatchnormLayer(nn.Module):
|
||||
"""PP-OCRv6 DBHead 使用的 Conv-BN-Act 基础层,命名对齐 safetensors。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=1,
|
||||
activation="relu",
|
||||
bias=False,
|
||||
convolution_transpose=False,
|
||||
):
|
||||
"""初始化普通卷积或反卷积、BN 和激活层。"""
|
||||
super().__init__()
|
||||
if convolution_transpose:
|
||||
self.convolution = nn.ConvTranspose2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
)
|
||||
else:
|
||||
self.convolution = nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
bias=bias,
|
||||
)
|
||||
self.norm = nn.BatchNorm2d(out_channels)
|
||||
self.act_fn = nn.ReLU() if activation == "relu" else nn.Identity()
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 DBHead v6 分支的卷积、BN 和激活。"""
|
||||
hidden_states = self.convolution(hidden_states)
|
||||
hidden_states = self.norm(hidden_states)
|
||||
hidden_states = self.act_fn(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class DBHead(nn.Module):
|
||||
"""
|
||||
Differentiable Binarization (DB) for text detection:
|
||||
@@ -57,24 +100,51 @@ class DBHead(nn.Module):
|
||||
params(dict): super parameters for build DB network
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, k=50, **kwargs):
|
||||
def __init__(self, in_channels, k=50, mode=None, kernel_list=None, fix_nan=False, **kwargs):
|
||||
"""初始化 DBHead;v6 模式使用 safetensors 对齐的三层上采样 head。"""
|
||||
super(DBHead, self).__init__()
|
||||
self.k = k
|
||||
binarize_name_list = [
|
||||
'conv2d_56', 'batch_norm_47', 'conv2d_transpose_0', 'batch_norm_48',
|
||||
'conv2d_transpose_1', 'binarize'
|
||||
]
|
||||
thresh_name_list = [
|
||||
'conv2d_57', 'batch_norm_49', 'conv2d_transpose_2', 'batch_norm_50',
|
||||
'conv2d_transpose_3', 'thresh'
|
||||
]
|
||||
self.binarize = Head(in_channels, **kwargs)# binarize_name_list)
|
||||
self.thresh = Head(in_channels, **kwargs)#thresh_name_list)
|
||||
self.mode = mode
|
||||
self.fix_nan = fix_nan
|
||||
if mode == "ppocrv6":
|
||||
kernel_list = kernel_list or [3, 2, 2]
|
||||
self.conv_down = PPOCRV6DBConvBatchnormLayer(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels // 4,
|
||||
kernel_size=kernel_list[0],
|
||||
padding=int(kernel_list[0] // 2),
|
||||
)
|
||||
self.conv_up = PPOCRV6DBConvBatchnormLayer(
|
||||
in_channels=in_channels // 4,
|
||||
out_channels=in_channels // 4,
|
||||
kernel_size=kernel_list[1],
|
||||
stride=2,
|
||||
convolution_transpose=True,
|
||||
)
|
||||
self.conv_final = nn.ConvTranspose2d(
|
||||
in_channels=in_channels // 4,
|
||||
out_channels=1,
|
||||
kernel_size=kernel_list[2],
|
||||
stride=2,
|
||||
)
|
||||
return
|
||||
self.binarize = Head(in_channels, **kwargs)
|
||||
self.thresh = Head(in_channels, **kwargs)
|
||||
|
||||
def step_function(self, x, y):
|
||||
"""计算 DB 二值化近似阶跃函数。"""
|
||||
return torch.reciprocal(1 + torch.exp(-self.k * (x - y)))
|
||||
|
||||
def forward(self, x):
|
||||
"""推理时返回统一的 `maps` 字段,兼容现有 OCR-det 后处理。"""
|
||||
if self.mode == "ppocrv6":
|
||||
shrink_maps = self.conv_down(x)
|
||||
shrink_maps = self.conv_up(shrink_maps)
|
||||
shrink_maps = self.conv_final(shrink_maps)
|
||||
shrink_maps = torch.sigmoid(shrink_maps)
|
||||
if self.fix_nan:
|
||||
shrink_maps = torch.nan_to_num(shrink_maps)
|
||||
return {'maps': shrink_maps}
|
||||
shrink_maps = self.binarize(x)
|
||||
return {'maps': shrink_maps}
|
||||
|
||||
@@ -107,4 +177,4 @@ class PFHeadLocal(DBHead):
|
||||
base_maps = shrink_maps
|
||||
cbn_maps = self.cbn_layer(self.up_conv(f), shrink_maps, None)
|
||||
cbn_maps = F.sigmoid(cbn_maps)
|
||||
return {'maps': 0.5 * (base_maps + cbn_maps), 'cbn_maps': cbn_maps}
|
||||
return {'maps': 0.5 * (base_maps + cbn_maps), 'cbn_maps': cbn_maps}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Copyright (c) Opendatalab. All rights reserved.
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from ..necks.rnn import Im2Seq, SequenceEncoder
|
||||
from ..necks.rnn import EncoderWithLightSVTR, Im2Seq, SequenceEncoder
|
||||
from .rec_ctc_head import CTCHead
|
||||
|
||||
|
||||
@@ -21,8 +22,10 @@ class FCTranspose(nn.Module):
|
||||
|
||||
class MultiHead(nn.Module):
|
||||
def __init__(self, in_channels, out_channels_list, **kwargs):
|
||||
"""初始化多头识别 Head,v6 LightSVTR 分支使用 HF safetensors 命名。"""
|
||||
super().__init__()
|
||||
self.head_list = kwargs.pop("head_list")
|
||||
self.use_light_svtr_head = False
|
||||
|
||||
self.gtc_head = "sar"
|
||||
assert len(self.head_list) >= 2
|
||||
@@ -38,22 +41,37 @@ class MultiHead(nn.Module):
|
||||
self.encoder_reshape = Im2Seq(in_channels)
|
||||
neck_args = self.head_list[idx][name]["Neck"]
|
||||
encoder_type = neck_args.pop("name")
|
||||
self.ctc_encoder = SequenceEncoder(
|
||||
in_channels=in_channels, encoder_type=encoder_type, **neck_args
|
||||
)
|
||||
# ctc head
|
||||
head_args = self.head_list[idx][name].get("Head", {})
|
||||
if head_args is None:
|
||||
head_args = {}
|
||||
if encoder_type == "lightsvtr":
|
||||
# v6 safetensors 中 CTC 分支直接命名为 head.encoder/head.head。
|
||||
self.encoder = EncoderWithLightSVTR(in_channels=in_channels, **neck_args)
|
||||
self.head = nn.Linear(self.encoder.out_channels, out_channels_list["CTCLabelDecode"], bias=True)
|
||||
self.out_channels = out_channels_list["CTCLabelDecode"]
|
||||
self.use_light_svtr_head = True
|
||||
else:
|
||||
self.ctc_encoder = SequenceEncoder(
|
||||
in_channels=in_channels, encoder_type=encoder_type, **neck_args
|
||||
)
|
||||
# ctc head
|
||||
head_args = self.head_list[idx][name].get("Head", {})
|
||||
if head_args is None:
|
||||
head_args = {}
|
||||
|
||||
self.ctc_head = CTCHead(
|
||||
in_channels=self.ctc_encoder.out_channels,
|
||||
out_channels=out_channels_list["CTCLabelDecode"],
|
||||
**head_args,
|
||||
)
|
||||
self.ctc_head = CTCHead(
|
||||
in_channels=self.ctc_encoder.out_channels,
|
||||
out_channels=out_channels_list["CTCLabelDecode"],
|
||||
**head_args,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(f"{name} is not supported in MultiHead yet")
|
||||
|
||||
def forward(self, x, data=None):
|
||||
"""根据配置执行 v6 LightSVTR 或历史 CTC 分支。"""
|
||||
if self.use_light_svtr_head:
|
||||
ctc_encoder = self.encoder(x)
|
||||
ctc_encoder = ctc_encoder.squeeze(dim=2).permute(0, 2, 1)
|
||||
predicts = self.head(ctc_encoder)
|
||||
if not self.training:
|
||||
predicts = F.softmax(predicts, dim=2)
|
||||
return predicts
|
||||
ctc_encoder = self.ctc_encoder(x)
|
||||
return self.ctc_head(ctc_encoder)
|
||||
|
||||
@@ -16,14 +16,20 @@ __all__ = ["build_neck"]
|
||||
|
||||
|
||||
def build_neck(config):
|
||||
from .db_fpn import DBFPN, LKPAN, RSEFPN
|
||||
from .db_fpn import DBFPN, LKPAN, RSEFPN, RepLKFPN
|
||||
from .rnn import SequenceEncoder
|
||||
|
||||
support_dict = ["DBFPN", "SequenceEncoder", "RSEFPN", "LKPAN"]
|
||||
support_dict = {
|
||||
"DBFPN": DBFPN,
|
||||
"SequenceEncoder": SequenceEncoder,
|
||||
"RSEFPN": RSEFPN,
|
||||
"LKPAN": LKPAN,
|
||||
"RepLKFPN": RepLKFPN,
|
||||
}
|
||||
|
||||
module_name = config.pop("name")
|
||||
assert module_name in support_dict, Exception(
|
||||
"neck only support {}".format(support_dict)
|
||||
)
|
||||
module_class = eval(module_name)(**config)
|
||||
module_class = support_dict[module_name](**config)
|
||||
return module_class
|
||||
|
||||
@@ -25,7 +25,7 @@ class DSConv(nn.Module):
|
||||
**kwargs
|
||||
):
|
||||
super(DSConv, self).__init__()
|
||||
if groups == None:
|
||||
if groups is None:
|
||||
groups = in_channels
|
||||
self.if_act = if_act
|
||||
self.act = act
|
||||
@@ -285,6 +285,136 @@ class RSEFPN(nn.Module):
|
||||
return fuse
|
||||
|
||||
|
||||
class RepLKFPNSqueezeExcitationModule(nn.Module):
|
||||
"""PP-OCRv6 RepLKFPN 使用的轻量 SE 模块,命名对齐 safetensors。"""
|
||||
|
||||
def __init__(self, in_channels, reduction, activation="relu"):
|
||||
"""初始化通道压缩与恢复卷积。"""
|
||||
super().__init__()
|
||||
self.avg_pool = nn.AdaptiveAvgPool2d(output_size=1)
|
||||
self.conv1 = nn.Conv2d(in_channels, in_channels // reduction, kernel_size=1, stride=1, padding=0)
|
||||
self.conv2 = nn.Conv2d(in_channels // reduction, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
if activation == "relu":
|
||||
self.act_fn = nn.ReLU()
|
||||
else:
|
||||
raise ValueError(f"Unsupported RepLKFPN SE activation: {activation}")
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""计算 SE 权重并缩放输入特征。"""
|
||||
residual = hidden_states
|
||||
hidden_states = self.avg_pool(hidden_states)
|
||||
hidden_states = self.conv2(self.act_fn(self.conv1(hidden_states)))
|
||||
hidden_states = torch.clamp(0.2 * hidden_states + 0.5, min=0.0, max=1.0)
|
||||
return residual * hidden_states
|
||||
|
||||
|
||||
class RepLKFPNDepthwiseSeparableConvLayer(nn.Module):
|
||||
"""RepLKFPN 的大核深度卷积 + point-wise 压缩分支。"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size, reduction):
|
||||
"""初始化 depthwise、pointwise 和 SE 子层。"""
|
||||
super().__init__()
|
||||
self.depthwise_convolution = nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=kernel_size // 2,
|
||||
groups=in_channels,
|
||||
bias=True,
|
||||
)
|
||||
self.squeeze_excitation_module = RepLKFPNSqueezeExcitationModule(out_channels // 4, reduction)
|
||||
self.pointwise_convolution = nn.Conv2d(
|
||||
in_channels=out_channels,
|
||||
out_channels=out_channels // 4,
|
||||
kernel_size=1,
|
||||
bias=False,
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行大核 DW 卷积、PW 压缩和 SE 残差增强。"""
|
||||
hidden_states = self.depthwise_convolution(hidden_states)
|
||||
hidden_states = self.pointwise_convolution(hidden_states)
|
||||
hidden_states = hidden_states + self.squeeze_excitation_module(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class RepLKFPNResidualSqueezeExcitationLayer(nn.Module):
|
||||
"""RepLKFPN 的输入投影层,属性名对齐 `insert_conv.*` 权重。"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size, reduction, shortcut=True):
|
||||
"""初始化输入投影和 SE 分支。"""
|
||||
super().__init__()
|
||||
self.in_conv = nn.Conv2d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
padding=int(kernel_size // 2),
|
||||
bias=False,
|
||||
)
|
||||
self.squeeze_excitation_block = RepLKFPNSqueezeExcitationModule(out_channels, reduction)
|
||||
self.shortcut = shortcut
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 1x1 投影并按配置叠加 SE 输出。"""
|
||||
hidden_states = self.in_conv(hidden_states)
|
||||
if self.shortcut:
|
||||
return hidden_states + self.squeeze_excitation_block(hidden_states)
|
||||
return self.squeeze_excitation_block(hidden_states)
|
||||
|
||||
|
||||
class RepLKFPN(nn.Module):
|
||||
"""PP-OCRv6 small det 使用的 RepLKFPN neck。"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, shortcut=True, dilated_kernel_size=7, reduction=4, **kwargs):
|
||||
"""按四级 backbone 通道创建 v6 FPN 投影和大核融合层。"""
|
||||
super().__init__()
|
||||
self.out_channels = out_channels
|
||||
self.interpolate_mode = kwargs.get("interpolate_mode", "nearest")
|
||||
self.insert_conv = nn.ModuleList()
|
||||
self.input_conv = nn.ModuleList()
|
||||
for channels in in_channels:
|
||||
self.insert_conv.append(
|
||||
RepLKFPNResidualSqueezeExcitationLayer(
|
||||
in_channels=channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=1,
|
||||
reduction=reduction,
|
||||
shortcut=shortcut,
|
||||
)
|
||||
)
|
||||
self.input_conv.append(
|
||||
RepLKFPNDepthwiseSeparableConvLayer(
|
||||
in_channels=out_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=dilated_kernel_size,
|
||||
reduction=reduction,
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, feature_maps):
|
||||
"""融合四级特征并返回 DBHead 需要的单张特征图。"""
|
||||
fused = []
|
||||
for conv, feature in zip(self.insert_conv, feature_maps):
|
||||
fused.append(conv(feature))
|
||||
|
||||
for idx in range(2, -1, -1):
|
||||
fused[idx] = fused[idx] + F.interpolate(
|
||||
fused[idx + 1],
|
||||
scale_factor=2,
|
||||
mode=self.interpolate_mode,
|
||||
)
|
||||
|
||||
features = [conv(feat) for conv, feat in zip(self.input_conv, fused)]
|
||||
processed = []
|
||||
for feat, scale in zip(features, [1, 2, 4, 8]):
|
||||
if scale == 1:
|
||||
processed.append(feat)
|
||||
else:
|
||||
processed.append(F.interpolate(feat, scale_factor=scale, mode=self.interpolate_mode))
|
||||
return torch.cat(processed[::-1], dim=1)
|
||||
|
||||
|
||||
class LKPAN(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, mode="large", **kwargs):
|
||||
super(LKPAN, self).__init__()
|
||||
|
||||
@@ -200,6 +200,185 @@ class EncoderWithSVTR(nn.Module):
|
||||
return z
|
||||
|
||||
|
||||
class LightSVTRConvLayer(nn.Module):
|
||||
"""PP-OCRv6 LightSVTR 使用的 Conv-BN-SiLU 基础层。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=(3, 3),
|
||||
activation="silu",
|
||||
groups=1,
|
||||
):
|
||||
"""初始化与 safetensors key 对齐的卷积、归一化和激活层。"""
|
||||
super().__init__()
|
||||
if isinstance(kernel_size, int):
|
||||
kernel_size = (kernel_size, kernel_size)
|
||||
self.convolution = nn.Conv2d(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=(kernel_size[0] // 2, kernel_size[1] // 2),
|
||||
bias=False,
|
||||
groups=groups,
|
||||
)
|
||||
self.normalization = nn.BatchNorm2d(out_channels)
|
||||
self.activation = nn.SiLU() if activation in {"silu", "swish"} else nn.Identity()
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行卷积、BN 和激活。"""
|
||||
hidden_states = self.convolution(hidden_states)
|
||||
hidden_states = self.normalization(hidden_states)
|
||||
hidden_states = self.activation(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class LightSVTRAttention(nn.Module):
|
||||
"""LightSVTR 的多头自注意力,属性名对齐 `self_attn.*` 权重。"""
|
||||
|
||||
def __init__(self, hidden_size, num_heads=8, qkv_bias=True, attention_dropout=0.1):
|
||||
"""初始化 qkv 投影和输出投影。"""
|
||||
super().__init__()
|
||||
if hidden_size % num_heads != 0:
|
||||
raise ValueError(f"hidden_size {hidden_size} must be divisible by num_heads {num_heads}.")
|
||||
self.hidden_size = hidden_size
|
||||
self.num_heads = num_heads
|
||||
self.head_dim = hidden_size // num_heads
|
||||
self.scale = self.head_dim**-0.5
|
||||
self.attention_dropout = attention_dropout
|
||||
self.qkv = nn.Linear(hidden_size, 3 * hidden_size, bias=qkv_bias)
|
||||
self.projection = nn.Linear(hidden_size, hidden_size)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""计算全局自注意力并返回投影后的序列特征。"""
|
||||
batch_size, seq_len, embed_dim = hidden_states.shape
|
||||
mixed_qkv = self.qkv(hidden_states)
|
||||
mixed_qkv = mixed_qkv.reshape(batch_size, seq_len, 3, self.num_heads, embed_dim // self.num_heads)
|
||||
mixed_qkv = mixed_qkv.permute(2, 0, 3, 1, 4)
|
||||
query_states, key_states, value_states = mixed_qkv[0], mixed_qkv[1], mixed_qkv[2]
|
||||
attn_weights = torch.matmul(query_states, key_states.transpose(-1, -2)) * self.scale
|
||||
attn_weights = nn.functional.softmax(attn_weights, dim=-1)
|
||||
attn_weights = nn.functional.dropout(attn_weights, p=self.attention_dropout, training=self.training)
|
||||
attn_output = torch.matmul(attn_weights, value_states)
|
||||
attn_output = attn_output.transpose(1, 2).reshape(batch_size, seq_len, embed_dim).contiguous()
|
||||
return self.projection(attn_output)
|
||||
|
||||
|
||||
class LightSVTRMLP(nn.Module):
|
||||
"""LightSVTR block 内的前馈网络,属性名对齐 `mlp.fc*` 权重。"""
|
||||
|
||||
def __init__(self, hidden_size, mlp_ratio=4.0, drop_rate=0.1):
|
||||
"""初始化两层线性层、SiLU 激活和 dropout。"""
|
||||
super().__init__()
|
||||
self.fc1 = nn.Linear(hidden_size, int(hidden_size * mlp_ratio))
|
||||
self.activation = nn.SiLU()
|
||||
self.fc2 = nn.Linear(int(hidden_size * mlp_ratio), hidden_size)
|
||||
self.drop = nn.Dropout(drop_rate)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 MLP 前向计算。"""
|
||||
hidden_states = self.fc1(hidden_states)
|
||||
hidden_states = self.activation(hidden_states)
|
||||
hidden_states = self.drop(hidden_states)
|
||||
hidden_states = self.fc2(hidden_states)
|
||||
hidden_states = self.drop(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class LightSVTRBlock(nn.Module):
|
||||
"""LightSVTR 的 Transformer block,属性名对齐 v6 safetensors。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size,
|
||||
num_heads=8,
|
||||
qkv_bias=True,
|
||||
mlp_ratio=4.0,
|
||||
drop_rate=0.1,
|
||||
attn_drop_rate=0.1,
|
||||
layer_norm_eps=1e-6,
|
||||
):
|
||||
"""初始化注意力、MLP 和两层 LayerNorm。"""
|
||||
super().__init__()
|
||||
self.self_attn = LightSVTRAttention(hidden_size, num_heads, qkv_bias, attn_drop_rate)
|
||||
self.layer_norm1 = nn.LayerNorm(hidden_size, eps=layer_norm_eps)
|
||||
self.mlp = LightSVTRMLP(hidden_size, mlp_ratio, drop_rate)
|
||||
self.layer_norm2 = nn.LayerNorm(hidden_size, eps=layer_norm_eps)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
"""执行 pre-norm attention 和 pre-norm MLP 残差结构。"""
|
||||
residual = hidden_states
|
||||
hidden_states = self.layer_norm1(hidden_states)
|
||||
hidden_states = residual + self.self_attn(hidden_states)
|
||||
residual = hidden_states
|
||||
hidden_states = self.layer_norm2(hidden_states)
|
||||
hidden_states = residual + self.mlp(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class EncoderWithLightSVTR(nn.Module):
|
||||
"""PP-OCRv6 使用的 LightSVTR neck,输出仍保持 4D 特征。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
dims=64,
|
||||
depth=1,
|
||||
num_heads=8,
|
||||
qkv_bias=True,
|
||||
mlp_ratio=4.0,
|
||||
drop_rate=0.1,
|
||||
attn_drop_rate=0.1,
|
||||
drop_path=0.0,
|
||||
qk_scale=None,
|
||||
local_kernel=7,
|
||||
use_guide=False,
|
||||
**kwargs,
|
||||
):
|
||||
"""初始化 skip/reduce/local conv、LightSVTR block 和归一化层。"""
|
||||
super().__init__()
|
||||
self.use_guide = use_guide
|
||||
self.conv_block = nn.ModuleList(
|
||||
[
|
||||
LightSVTRConvLayer(in_channels, dims, kernel_size=(1, 1), activation="silu"),
|
||||
LightSVTRConvLayer(in_channels, dims, kernel_size=(1, 1), activation="silu"),
|
||||
LightSVTRConvLayer(dims, dims, kernel_size=(1, local_kernel), activation="silu", groups=dims),
|
||||
]
|
||||
)
|
||||
self.svtr_block = nn.ModuleList(
|
||||
[
|
||||
LightSVTRBlock(
|
||||
hidden_size=dims,
|
||||
num_heads=num_heads,
|
||||
qkv_bias=qkv_bias,
|
||||
mlp_ratio=mlp_ratio,
|
||||
drop_rate=drop_rate,
|
||||
attn_drop_rate=attn_drop_rate,
|
||||
)
|
||||
for _ in range(depth)
|
||||
]
|
||||
)
|
||||
self.norm = nn.LayerNorm(dims, eps=1e-6)
|
||||
self.out_channels = dims
|
||||
|
||||
def forward(self, x):
|
||||
"""执行轻量局部卷积增强、全局注意力和 skip 残差融合。"""
|
||||
if self.use_guide:
|
||||
x = x.detach()
|
||||
residual = self.conv_block[0](x)
|
||||
hidden_states = self.conv_block[1](x)
|
||||
hidden_states = hidden_states + self.conv_block[2](hidden_states)
|
||||
batch_size, channels, height, width = hidden_states.shape
|
||||
hidden_states = hidden_states.flatten(2).permute(0, 2, 1)
|
||||
for block in self.svtr_block:
|
||||
hidden_states = block(hidden_states)
|
||||
hidden_states = self.norm(hidden_states)
|
||||
hidden_states = hidden_states.reshape(batch_size, height, width, channels).permute(0, 3, 1, 2)
|
||||
return hidden_states + residual
|
||||
|
||||
|
||||
class SequenceEncoder(nn.Module):
|
||||
def __init__(self, in_channels, encoder_type, hidden_size=48, **kwargs):
|
||||
super(SequenceEncoder, self).__init__()
|
||||
@@ -214,12 +393,13 @@ class SequenceEncoder(nn.Module):
|
||||
"fc": EncoderWithFC,
|
||||
"rnn": EncoderWithRNN,
|
||||
"svtr": EncoderWithSVTR,
|
||||
"lightsvtr": EncoderWithLightSVTR,
|
||||
}
|
||||
assert encoder_type in support_encoder_dict, "{} must in {}".format(
|
||||
encoder_type, support_encoder_dict.keys()
|
||||
)
|
||||
|
||||
if encoder_type == "svtr":
|
||||
if encoder_type in ("svtr", "lightsvtr"):
|
||||
self.encoder = support_encoder_dict[encoder_type](
|
||||
self.encoder_reshape.out_channels, **kwargs
|
||||
)
|
||||
@@ -231,7 +411,7 @@ class SequenceEncoder(nn.Module):
|
||||
self.only_reshape = False
|
||||
|
||||
def forward(self, x):
|
||||
if self.encoder_type != "svtr":
|
||||
if self.encoder_type not in ("svtr", "lightsvtr"):
|
||||
x = self.encoder_reshape(x)
|
||||
if not self.only_reshape:
|
||||
x = self.encoder(x)
|
||||
|
||||
@@ -92,6 +92,27 @@ ch_PP-OCRv5_det_server_infer:
|
||||
k: 50
|
||||
mode: "large"
|
||||
|
||||
ch_PP-OCRv6_small_det_infer:
|
||||
model_type: det
|
||||
algorithm: DB
|
||||
Transform: null
|
||||
Backbone:
|
||||
name: PPLCNetV4
|
||||
det: True
|
||||
model_size: small
|
||||
Neck:
|
||||
name: RepLKFPN
|
||||
out_channels: 96
|
||||
dilated_kernel_size: 7
|
||||
shortcut: True
|
||||
Head:
|
||||
name: DBHead
|
||||
k: 50
|
||||
mode: ppocrv6
|
||||
fix_nan: True
|
||||
aux_in_channels: 96
|
||||
kernel_list: [3, 2, 2]
|
||||
|
||||
ch_PP-OCRv4_det_server_infer:
|
||||
model_type: det
|
||||
algorithm: DB
|
||||
@@ -236,6 +257,57 @@ ch_PP-OCRv5_rec_infer:
|
||||
nrtr_dim: 384
|
||||
max_text_length: 25
|
||||
|
||||
ch_PP-OCRv6_small_rec_infer:
|
||||
model_type: rec
|
||||
algorithm: SVTR_LCNet
|
||||
Transform:
|
||||
Backbone:
|
||||
name: PPLCNetV4
|
||||
model_size: small
|
||||
Head:
|
||||
name: MultiHead
|
||||
out_channels_list:
|
||||
CTCLabelDecode: 18710
|
||||
head_list:
|
||||
- CTCHead:
|
||||
Neck:
|
||||
name: lightsvtr
|
||||
dims: 120
|
||||
depth: 2
|
||||
mlp_ratio: 2.0
|
||||
local_kernel: 7
|
||||
Head:
|
||||
fc_decay: 0.00001
|
||||
- NRTRHead:
|
||||
nrtr_dim: 384
|
||||
max_text_length: 25
|
||||
|
||||
ch_PP-OCRv6_medium_rec_infer:
|
||||
model_type: rec
|
||||
algorithm: SVTR_LCNet
|
||||
Transform:
|
||||
Backbone:
|
||||
name: PPLCNetV4
|
||||
model_size: medium
|
||||
Head:
|
||||
name: MultiHead
|
||||
out_channels_list:
|
||||
CTCLabelDecode: 18710
|
||||
head_list:
|
||||
- CTCHead:
|
||||
Neck:
|
||||
name: lightsvtr
|
||||
dims: 192
|
||||
depth: 2
|
||||
mlp_ratio: 4.0
|
||||
local_kernel: 7
|
||||
use_guide: False
|
||||
Head:
|
||||
fc_decay: 0.00001
|
||||
- NRTRHead:
|
||||
nrtr_dim: 512
|
||||
max_text_length: 25
|
||||
|
||||
ka_PP-OCRv3_rec_infer:
|
||||
model_type: rec
|
||||
algorithm: SVTR
|
||||
@@ -541,4 +613,4 @@ te_PP-OCRv5_rec_infer:
|
||||
fc_decay: 0.00001
|
||||
- NRTRHead:
|
||||
nrtr_dim: 384
|
||||
max_text_length: 25
|
||||
max_text_length: 25
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
lang:
|
||||
ch_lite:
|
||||
det: ch_PP-OCRv5_det_infer.pth
|
||||
rec: ch_PP-OCRv5_rec_infer.pth
|
||||
dict: ppocrv5_dict.txt
|
||||
det: ch_PP-OCRv6_small_det_infer.safetensors
|
||||
rec: ch_PP-OCRv6_small_rec_infer.safetensors
|
||||
dict: ppocrv6_dict.txt
|
||||
ch_server:
|
||||
det: ch_PP-OCRv5_det_infer.pth
|
||||
rec: ch_PP-OCRv5_rec_server_infer.pth
|
||||
dict: ppocrv5_dict.txt
|
||||
det: ch_PP-OCRv6_small_det_infer.safetensors
|
||||
rec: ch_PP-OCRv6_medium_rec_infer.safetensors
|
||||
dict: ppocrv6_dict.txt
|
||||
ch:
|
||||
det: ch_PP-OCRv5_det_infer.pth
|
||||
rec: ch_PP-OCRv4_rec_server_doc_infer.pth
|
||||
@@ -74,4 +74,4 @@ lang:
|
||||
seal_lite:
|
||||
det: seal_PP-OCRv4_det_infer.pth
|
||||
rec: ch_PP-OCRv4_rec_infer.pth
|
||||
dict: ppocr_keys_v1.txt
|
||||
dict: ppocr_keys_v1.txt
|
||||
|
||||
@@ -98,6 +98,7 @@ pipeline = [
|
||||
"torch>=2.6.0,<3",
|
||||
"torchvision",
|
||||
"transformers>=4.57.3,<5.0.0",
|
||||
"safetensors>=0.4.0,<1",
|
||||
"onnxruntime>1.17.0",
|
||||
]
|
||||
gradio = [
|
||||
|
||||
Reference in New Issue
Block a user