Spaces:
Sleeping
Sleeping
| """Heuristic captcha type detector. | |
| This is intentionally simple: it looks at image dimensions, presence of | |
| many equal-sized tiles, color distribution, etc. For audio it returns | |
| "audio" if audio was provided. For images it picks the most likely | |
| captcha type. The user-provided `type` field always wins. | |
| """ | |
| from __future__ import annotations | |
| from typing import Optional | |
| import numpy as np | |
| from PIL import Image | |
| from captcha_solver.utils.image import decode_base64_image, image_to_pil | |
| def detect_type(image_b64: Optional[str], audio_b64: Optional[str]) -> str: | |
| """Best-guess captcha type from inputs. Returns one of: | |
| 'math', 'text_ocr', 'image_grid', 'audio'. | |
| 'math' is the default for any non-grid text-like image. When in doubt | |
| we still pick a solver rather than return None. | |
| """ | |
| if audio_b64: | |
| return "audio" | |
| if not image_b64: | |
| return "math" | |
| try: | |
| data = decode_base64_image(image_b64) | |
| img = image_to_pil(data) | |
| except Exception: | |
| return "math" | |
| w, h = img.size | |
| aspect = w / max(h, 1) | |
| arr = np.asarray(img.convert("L")) | |
| h_std = float(arr.std()) | |
| edges = _edge_density(arr) | |
| if 0.7 <= aspect <= 1.4 and h_std < 60 and edges < 0.08 and w < 400 and h < 200: | |
| return "math" | |
| if 0.4 <= aspect <= 0.7 and edges > 0.12 and _has_tile_grid(arr): | |
| return "image_grid" | |
| if h_std > 40 and (w >= 200 or h >= 60): | |
| return "text_ocr" | |
| return "math" | |
| def _edge_density(arr: np.ndarray) -> float: | |
| """Fraction of pixels that are 'edges' (Sobel-lite).""" | |
| gx = np.abs(np.diff(arr.astype(np.int16), axis=1)) | |
| gy = np.abs(np.diff(arr.astype(np.int16), axis=0)) | |
| e = (gx[:-1, :] > 30).sum() + (gy[:, :-1] > 30).sum() | |
| return e / max(arr.size, 1) | |
| def _has_tile_grid(arr: np.ndarray) -> bool: | |
| """Check for a 3x3 grid pattern (9 tiles) by looking for vertical/horizontal dark seams.""" | |
| h, w = arr.shape | |
| if h < 90 or w < 90: | |
| return False | |
| rows = [h // 3, 2 * h // 3] | |
| cols = [w // 3, 2 * w // 3] | |
| seam_strengths = [] | |
| for r in rows: | |
| seam_strengths.append(arr[r - 2 : r + 3, :].mean()) | |
| for c in cols: | |
| seam_strengths.append(arr[:, c - 2 : c + 3].mean()) | |
| return min(seam_strengths) < 100 | |