|
|
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) |