Spaces:
Sleeping
Sleeping
File size: 8,661 Bytes
3a3f6c6 c8c67ab 3a3f6c6 c8c67ab 3a3f6c6 c8c67ab 3a3f6c6 c8c67ab 3a3f6c6 c8c67ab 3a3f6c6 c2e5172 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | """
Gradio app for testing CAPTCHA model.
Allows uploading CAPTCHA images and getting predictions with preprocessing.
"""
import gradio as gr
import torch
from torchvision import transforms
from PIL import Image
import string
from pathlib import Path
import numpy as np
import cv2
from src.model import CTCCaptchaModel
# Setup
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
CHARACTERS = string.digits + string.ascii_lowercase + string.ascii_uppercase
MODEL_PATH = Path("models/captcha_model_v4.pth")
# Load model
model = CTCCaptchaModel(num_classes=len(CHARACTERS), use_attention=True)
# Load checkpoint
checkpoint = torch.load(MODEL_PATH, map_location=DEVICE)
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
model.load_state_dict(checkpoint['model_state_dict'])
else:
model.load_state_dict(checkpoint)
model.to(DEVICE)
model.eval()
# Image preprocessing transforms
transform = transforms.Compose([
transforms.Resize((60, 160)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])
])
def preprocess_image(image):
"""
Preprocess image: grayscale, denoising, and thresholding.
Args:
image: PIL Image
Returns:
Preprocessed PIL Image
"""
# Convert to grayscale numpy array
img_array = np.array(image.convert('L'))
# If background is dark (mean < 127), invert so we get dark text on light background
if img_array.mean() < 127:
img_array = 255 - img_array
# Apply Otsu's thresholding
_, binary = cv2.threshold(img_array, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Morphological closing to remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
processed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# Convert back to PIL Image
return Image.fromarray(processed)
def predict_captcha(image, ground_truth=""):
"""
Predict CAPTCHA text from image with preprocessing.
Args:
image: PIL Image or numpy array
ground_truth: Optional ground truth text for comparison
Returns:
Tuple of (prediction result, preprocessed image)
"""
try:
# Convert to PIL Image if numpy array
if isinstance(image, np.ndarray):
image = Image.fromarray(image)
# Resize image if not standard dimensions (60x160)
if image.size != (160, 60):
image = image.resize((160, 60), Image.LANCZOS)
# Preprocess image
processed_image = preprocess_image(image)
# Convert to tensor and predict
image_tensor = transform(processed_image).unsqueeze(0).to(DEVICE)
# Predict
with torch.no_grad():
decoded = model.predict(image_tensor)
# Decode first (and only) batch element
pred_indices = decoded[0] if decoded else []
predicted_text = ''.join([
CHARACTERS[idx] for idx in pred_indices
if 0 <= idx < len(CHARACTERS)
])
# Format output with styling
result = f"### π― Prediction Result\n\n"
result += f"# **{predicted_text}**\n\n"
result += f"*Length: {len(predicted_text)} characters*\n\n"
if ground_truth.strip():
ground_truth = ground_truth # Keep case sensitive
is_correct = predicted_text == ground_truth
result += f"**Expected:** {ground_truth}\n\n"
if is_correct:
result += "## β
**CORRECT!**"
else:
result += f"## β **INCORRECT**"
return result, processed_image
except Exception as e:
return f"β **Error:** {str(e)}", None
def extract_from_filename(filename):
"""Extract text from CAPTCHA filename (format: TEXT_INDEX.png)."""
if filename and hasattr(filename, 'name'):
stem = Path(filename.name).stem
text = stem.split('_')[0]
return text
return ""
# Create Gradio interface
with gr.Blocks(title="π CAPTCHA Breaker", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
<div style="text-align: center; padding: 20px;">
# π CAPTCHA Breaker
### Advanced AI-Powered CAPTCHA Recognition
Powered by **CNN + LSTM + Self-Attention** neural network
</div>
""")
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("#### πΈ Upload Your CAPTCHA")
image_input = gr.Image(
type="pil",
label="Drop CAPTCHA image here",
image_mode="L"
)
with gr.Row():
ground_truth_input = gr.Textbox(
label="Expected Answer (optional)",
placeholder="Type here to verify accuracy",
lines=1,
scale=3
)
predict_button = gr.Button(
"π Decode",
variant="primary",
scale=1
)
with gr.Column(scale=2):
gr.Markdown("#### π― Results")
output = gr.Markdown(
"<div style='text-align: center; padding: 40px; color: #888;'>Upload an image to get started</div>"
)
with gr.Row():
with gr.Column():
gr.Markdown("#### π¬ Preprocessing Steps Applied:")
gr.Markdown("""
- β Auto-resize to 60Γ160 (if needed)
- β Grayscale conversion
- β Otsu's thresholding
- β Morphological closing (denoising)
- β Tensor normalization
- β Variable length support (3-7 chars)
- β Lowercase + Uppercase + Digits
""")
with gr.Column():
gr.Markdown("#### π Character Set:")
gr.Markdown("""
- **Digits:** 0-9
- **Lowercase:** a-z
- **Uppercase:** A-Z
- **Total:** 62 characters
""")
with gr.Column():
gr.Markdown("#### πΌοΈ Processed Image:")
preprocessed_image = gr.Image(
label="Input After Preprocessing",
type="pil"
)
# Info section
with gr.Accordion("βΉοΈ Model Architecture & Performance", open=False):
gr.Markdown("""
### ποΈ Architecture
```
Input Image (1, 60, 160) [Auto-resized if needed]
β
CNN: 4 Convolutional Blocks
β’ Progressive feature extraction
β’ 1β32β64β128β256 channels
β
Bidirectional LSTM: 2 layers
β’ 256 hidden units each direction
β’ Learns sequential dependencies
β
Self-Attention: 4 heads
β’ Refines character representations
β’ Improves focus on important features
β
CTC Loss: Automatic Alignment
β’ No bounding boxes needed!
β’ Learns character positions automatically
β
Output: Variable-length prediction (3-7 characters)
```
### π Model Capabilities (v3)
| Feature | Details |
|---------|---------|
| **Model Version** | v3 (Latest) |
| **Text Length** | 3-7 characters (variable) |
| **Character Set** | 0-9, a-z, A-Z (62 total) |
| **Architecture** | CNN + LSTM + Attention |
| **Training Data** | 10,000 synthetic CAPTCHAs |
| **Image Resize** | Automatic (any size β 60Γ160) |
### β οΈ Known Limitations
- 0 vs O confusion (visual similarity)
- i vs l vs 1 confusion (very similar shapes)
- Limited performance on decorative/stylized fonts
- Sensitive to extreme image distortions
""")
# Connect buttons to prediction function
predict_button.click(
fn=predict_captcha,
inputs=[image_input, ground_truth_input],
outputs=[output, preprocessed_image]
)
# Auto-predict on image upload
image_input.change(
fn=lambda img: predict_captcha(img, ""),
inputs=image_input,
outputs=[output, preprocessed_image]
)
# Footer
gr.Markdown("""
---
<div style="text-align: center; color: #999; padding: 20px;">
Built with PyTorch | Device: {device} | GitHub: vedchamp07/captcha-breaker
</div>
""".format(device=DEVICE))
if __name__ == "__main__":
demo.launch(share=True)
|