| import base64 |
| import cv2 |
| import numpy as np |
| import uvicorn |
| from fastapi import FastAPI, HTTPException |
| from pydantic import BaseModel |
| from collections import defaultdict |
|
|
| app = FastAPI() |
|
|
| @app.get("/") |
| def root(): |
| return { |
| "status": "ok", |
| "service": "iconCaptcha solver", |
| "endpoint": "/solve" |
| } |
|
|
| class Input(BaseModel): |
| image_base64: str |
|
|
| def preprocess_image_memory(base64_str): |
| try: |
| img_data = base64.b64decode(base64_str) |
| nparr = np.frombuffer(img_data, np.uint8) |
| img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) |
| |
| if img is None: |
| raise ValueError("Invalid/corrupt image") |
|
|
| if len(img.shape) == 3 and img.shape[2] == 4: |
| alpha = img[:, :, 3] / 255.0 |
| rgb = img[:, :, :3] |
| white_bg = np.ones_like(rgb, dtype=np.uint8) * 255 |
| img = (rgb * alpha[:, :, None] + white_bg * (1 - alpha[:, :, None])).astype(np.uint8) |
| |
| return img |
| except Exception as e: |
| raise ValueError(f"Error decoding image: {str(e)}") |
|
|
| def extract_icon_positions(img): |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| _, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV) |
| contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| |
| icons, pos = [], [] |
| for c in contours: |
| x, y, w, h = cv2.boundingRect(c) |
| if w > 10 and h > 10: |
| roi = cv2.resize(thresh[y:y+h, x:x+w], (50, 50)) |
| icons.append(roi) |
| pos.append((x, y)) |
| return icons, pos |
|
|
| def img_hash(img): |
| img = cv2.resize(img, (8, 8)) |
| return (img > img.mean()).astype(np.uint8).flatten() |
|
|
| def find_rarest(icon_features, positions): |
| if not icon_features: |
| return None, None |
| |
| hashes = [img_hash(i) for i in icon_features] |
| groups = defaultdict(list) |
| |
| for i, h in enumerate(hashes): |
| found = False |
| for label, group in groups.items(): |
| if np.sum(h != hashes[group[0]]) < 3: |
| group.append(i) |
| found = True |
| break |
| if not found: |
| groups[len(groups)] = [i] |
| |
| if not groups: |
| return None, None |
|
|
| idx = min(groups.values(), key=len)[0] |
| return positions[idx] |
|
|
| @app.post("/solve") |
| def solve(data: Input): |
| try: |
| img = preprocess_image_memory(data.image_base64) |
|
|
| icons, pos = extract_icon_positions(img) |
| |
| if not icons: |
| return {"error": "No icons found", "x": 0, "y": 0} |
|
|
| x, y = find_rarest(icons, pos) |
|
|
| return {"x": int(x), "y": int(y)} |
| |
| except Exception as e: |
| return {"error": str(e)} |
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |