| | import base64 |
| | import cv2 |
| | import numpy as np |
| | import os |
| | from fastapi import FastAPI |
| | 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 save_base64_image_cv(base64_str, output_path="final.png"): |
| | img_data = base64.b64decode(base64_str) |
| | nparr = np.frombuffer(img_data, np.uint8) |
| | img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) |
| |
|
| | if 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) |
| |
|
| | cv2.imwrite(output_path, img) |
| |
|
| | def extract_icon_positions(image_path): |
| | img = cv2.imread(image_path) |
| | 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): |
| | hashes = [img_hash(i) for i in icon_features] |
| | groups = defaultdict(list) |
| |
|
| | for i, h in enumerate(hashes): |
| | for g in groups.values(): |
| | if np.sum(h != hashes[g[0]]) < 3: |
| | g.append(i) |
| | break |
| | else: |
| | groups[len(groups)] = [i] |
| |
|
| | idx = min(groups.values(), key=len)[0] |
| | return positions[idx] |
| |
|
| | @app.post("/solve") |
| | def solve(data: Input): |
| | try: |
| | save_base64_image_cv(data.image_base64) |
| | icons, pos = extract_icon_positions("final.png") |
| | x, y = find_rarest(icons, pos) |
| | return {"x": x, "y": y} |
| | finally: |
| | if os.path.exists("final.png"): |
| | os.remove("final.png") |