Liiesl commited on
Commit
ed31d8f
·
verified ·
1 Parent(s): b16a312

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +210 -1
README.md CHANGED
@@ -1,3 +1,212 @@
1
  ---
2
- license: mit
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ license: apache-2.0
3
+ tags:
4
+ - computer-vision
5
+ - text-styling
6
+ - ocr-attributes
7
+ - edgenext
8
+ - timm
9
+ - onnx
10
+ - multi-task
11
+ pipeline_tag: image-feature-extraction
12
+ library_name: timm
13
+ base_model:
14
+ - timm/edgenext_small.usi_in1k
15
  ---
16
+
17
+ # Text Styling Multi-Task Model (EdgeNeXt-Small)
18
+
19
+ This model performs **fine-grained text attribute and styling extraction** from cropped text line images. Built upon a lightweight [`edgenext_small`](https://github.com/huggingface/pytorch-image-models) backbone, the model processes an input image of fixed dimensions ($64 \times 160$) and simultaneously predicts typography styles, background modalities, and color attributes across 8 dedicated heads.
20
+
21
+ ---
22
+
23
+ ## 📋 Model Summary
24
+
25
+ - **Backbone**: `edgenext_small` (pretrained via `timm`)
26
+ - **Spatial Pooling**: Adaptive Average Pooling to $(2 \times 5)$ ($304 \times 2 \times 5 = 3040\text{d}$)
27
+ - **Bottleneck**: `Linear(3040, 256) -> BatchNorm1d -> Hardswish -> Dropout(0.2)`
28
+ - **Input Resolution**: $64 \times 160$ (Height $\times$ Width), 3 RGB Channels
29
+ - **Export Format**: PyTorch Checkpoints (`.pth.tar`) & ONNX (`opset 18`, dynamic batch size)
30
+
31
+ ---
32
+
33
+ ## 🎯 Prediction Heads & Output Specification
34
+
35
+ The model outputs **8 multi-task tensors**:
36
+
37
+ | Output Name | Shape | Range / Activation | Description |
38
+ | :--- | :--- | :--- | :--- |
39
+ | `flags` | `[B, 5]` | Raw Logits $\to \sigma(x) \in [0, 1]$ | Binary flags: `[is_bold, is_italic, has_stroke, has_shadow, has_glow]` |
40
+ | `bg_type` | `[B, 3]` | Raw Logits $\to \text{Softmax}$ | Classification: `0: Solid`, `1: Gradient`, `2: Artwork/Image` |
41
+ | `text_color` | `[B, 3]` | Clamped $[0.0, 1.0]$ | Primary text fill color (RGB) |
42
+ | `effect_color` | `[B, 3]` | Clamped $[0.0, 1.0]$ | Outer effect color (Stroke / Shadow / Glow RGB) *(Valid if effect flags $> 0.5$)* |
43
+ | `bg_color` | `[B, 3]` | Clamped $[0.0, 1.0]$ | Solid background color (RGB) *(Valid if `bg_type == 0`)* |
44
+ | `bg_color_a` | `[B, 3]` | Clamped $[0.0, 1.0]$ | Gradient start color (RGB) *(Valid if `bg_type == 1`)* |
45
+ | `bg_color_b` | `[B, 3]` | Clamped $[0.0, 1.0]$ | Gradient end color (RGB) *(Valid if `bg_type == 1`)* |
46
+ | `bg_direction` | `[B, 2]` | Unit Vector $[\sin \theta, \cos \theta]$ | Gradient angle direction *(Valid if `bg_type == 1`)* |
47
+
48
+ ---
49
+
50
+ ## 🚀 Quickstart Inference
51
+
52
+ ### 1. ONNX Runtime (Recommended for Deployment)
53
+
54
+ ```python
55
+ import numpy as np
56
+ import onnxruntime as ort
57
+ from PIL import Image
58
+
59
+ # Initialize Session
60
+ session = ort.InferenceSession("text_styling_model.onnx", providers=["CPUExecutionProvider"])
61
+
62
+ def preprocess(img_path):
63
+ img = Image.open(img_path).convert("RGB").resize((160, 64), Image.BICUBIC)
64
+ arr = np.array(img).astype(np.float32) / 255.0
65
+ # Normalize with ImageNet mean/std
66
+ mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
67
+ std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
68
+ arr = (arr - mean) / std
69
+ arr = np.transpose(arr, (2, 0, 1)) # HWC to CHW
70
+ return np.expand_dims(arr, axis=0) # Add batch dim [1, 3, 64, 160]
71
+
72
+ # Run Inference
73
+ input_tensor = preprocess("sample_text.png")
74
+ outputs = session.run(None, {"input": input_tensor})
75
+
76
+ (pred_flags, pred_bg_type, pred_text_c, pred_effect_c,
77
+ pred_bg_c, pred_bg_ca, pred_bg_cb, pred_bg_dir) = outputs
78
+
79
+ # Post-process Flags
80
+ sigmoid = lambda x: 1 / (1 + np.exp(-x))
81
+ flag_probs = sigmoid(pred_flags[0])
82
+ flag_names = ["Bold", "Italic", "Stroke", "Shadow", "Glow"]
83
+ detected_flags = {name: bool(prob > 0.5) for name, prob in zip(flag_names, flag_probs)}
84
+
85
+ # Post-process Background Type
86
+ bg_types = ["Solid", "Gradient", "Artwork"]
87
+ bg_type_idx = int(np.argmax(pred_bg_type[0]))
88
+
89
+ # Post-process Gradient Angle (if gradient)
90
+ sin_val, cos_val = pred_bg_dir[0]
91
+ angle_rad = np.arctan2(sin_val, cos_val)
92
+ angle_deg = (np.degrees(angle_rad) + 360) % 360
93
+
94
+ print(f"Flags: {detected_flags}")
95
+ print(f"Background Type: {bg_types[bg_type_idx]}")
96
+ print(f"Text Color (RGB [0-255]): {(pred_text_c[0] * 255).astype(int).tolist()}")
97
+
98
+ if any([detected_flags["Stroke"], detected_flags["Shadow"], detected_flags["Glow"]]):
99
+ print(f"Effect Color (RGB [0-255]): {(pred_effect_c[0] * 255).astype(int).tolist()}")
100
+
101
+ if bg_type_idx == 0:
102
+ print(f"Solid BG Color (RGB): {(pred_bg_c[0] * 255).astype(int).tolist()}")
103
+ elif bg_type_idx == 1:
104
+ print(f"Gradient Start Color (RGB): {(pred_bg_ca[0] * 255).astype(int).tolist()}")
105
+ print(f"Gradient End Color (RGB): {(pred_bg_cb[0] * 255).astype(int).tolist()}")
106
+ print(f"Gradient Angle: {angle_deg:.1f}°")
107
+ ```
108
+
109
+ ---
110
+
111
+ ### 2. PyTorch Definition & Inference
112
+
113
+ ```python
114
+ import torch
115
+ import torch.nn as nn
116
+ from timm.models import create_model
117
+ from timm.data.transforms_factory import transforms_imagenet_eval
118
+ from PIL import Image
119
+
120
+ class TextStylingModel(nn.Module):
121
+ def __init__(self):
122
+ super().__init__()
123
+ self.backbone = create_model('edgenext_small', pretrained=False, num_classes=0)
124
+ self.spatial_pool = nn.AdaptiveAvgPool2d((2, 5))
125
+ in_dim = 304 * 2 * 5
126
+
127
+ self.bottleneck = nn.Sequential(
128
+ nn.Linear(in_dim, 256),
129
+ nn.BatchNorm1d(256),
130
+ nn.Hardswish(),
131
+ nn.Dropout(0.2)
132
+ )
133
+
134
+ self.head_flags = nn.Linear(256, 5)
135
+ self.head_bg_type = nn.Linear(256, 3)
136
+ self.head_text_color = nn.Linear(256, 3)
137
+ self.head_effect_color = nn.Linear(256, 3)
138
+ self.head_bg_color = nn.Linear(256, 3)
139
+ self.head_bg_color_a = nn.Linear(256, 3)
140
+ self.head_bg_color_b = nn.Linear(256, 3)
141
+ self.head_bg_direction = nn.Linear(256, 2)
142
+
143
+ def forward(self, x):
144
+ x = self.backbone.forward_features(x)
145
+ x = self.spatial_pool(x)
146
+ x = torch.flatten(x, 1)
147
+ x = self.bottleneck(x)
148
+
149
+ flags = self.head_flags(x)
150
+ bg_type = self.head_bg_type(x)
151
+ text_color = torch.clamp(self.head_text_color(x), 0.0, 1.0)
152
+ effect_color = torch.clamp(self.head_effect_color(x), 0.0, 1.0)
153
+ bg_color = torch.clamp(self.head_bg_color(x), 0.0, 1.0)
154
+ bg_color_a = torch.clamp(self.head_bg_color_a(x), 0.0, 1.0)
155
+ bg_color_b = torch.clamp(self.head_bg_color_b(x), 0.0, 1.0)
156
+
157
+ bg_direction = self.head_bg_direction(x)
158
+ dnorm = bg_direction.norm(dim=-1, keepdim=True).clamp(min=1e-6)
159
+ bg_direction = bg_direction / dnorm
160
+
161
+ return flags, bg_type, text_color, effect_color, bg_color, bg_color_a, bg_color_b, bg_direction
162
+
163
+ # Load model weights
164
+ model = TextStylingModel()
165
+ checkpoint = torch.load("model_best.pth.tar", map_location="cpu")
166
+ # If EMA weights exist, prioritize them:
167
+ state_dict = checkpoint.get("state_dict_ema", checkpoint.get("state_dict"))
168
+ model.load_state_dict(state_dict)
169
+ model.eval()
170
+
171
+ # Transform
172
+ transform = transforms_imagenet_eval(img_size=(64, 160), crop_pct=1.0, interpolation='bicubic')
173
+ img = Image.open("sample_text.png").convert("RGB")
174
+ img_t = transform(img).unsqueeze(0)
175
+
176
+ with torch.inference_mode():
177
+ preds = model(img_t)
178
+ ```
179
+
180
+ ---
181
+
182
+ ## 🛠️ Training Details
183
+
184
+ - **Loss Function**: Multi-Task Loss combining:
185
+ - Binary Cross Entropy with Logits for typography flags.
186
+ - Cross Entropy for background category.
187
+ - Smooth L1 (Huber Loss, $\beta=0.1$) with sample masking for RGB continuous targets.
188
+ - Cosine Distance $(1 - \cos \theta)$ for periodic gradient angle direction.
189
+ - **Optimizer**: `AdamW` ($\text{lr}=10^{-3}$, weight decay $= 10^{-4}$) with gradient clipping (`max_norm=1.0`).
190
+ - **Scheduler**: Cosine Annealing with 1 warmup epoch.
191
+ - **Weight Averaging**: Step-count normalized Exponential Moving Average (`ModelEmaV2`).
192
+ - **Mixed Precision**: Distributed FP16 training with HuggingFace Accelerate across $2\times\text{NVIDIA T4}$.
193
+
194
+ ---
195
+
196
+ ## ⚠️ Intended Limitations & Edge Cases
197
+
198
+ 1. **Aspect Ratio & Resolution**: Optimized for horizontal text strip crops rendered or resized to $64 \times 160$. Vertical text arrangements or extreme aspect ratios may degrade spatial boundary perception.
199
+ 2. **Masked Heads Dependency**:
200
+ - `effect_color` predictions should only be evaluated when one or more of `[Stroke, Shadow, Glow]` flags are positive.
201
+ - `bg_color` is only valid when `bg_type == Solid (0)`.
202
+ - `bg_color_a`, `bg_color_b`, and `bg_direction` are only valid when `bg_type == Gradient (1)`.
203
+ - For `bg_type == Artwork (2)`, no background color regression is guaranteed.
204
+ ```
205
+
206
+ ---
207
+
208
+ ### Recommended Files to Upload to the Hugging Face Repository:
209
+ 1. `README.md` (The Model Card above)
210
+ 2. `text_styling_model.onnx` (The exported ONNX model)
211
+ 3. `model_best.pth.tar` (The PyTorch checkpoint containing weights and EMA states)
212
+ 4. *(Optional)* `sample_text.png` (A sample cropped image for testing)