feat(tcaptcha): 完成腾讯天御点选纯协议全自动化客户端与雨云业务适配器
This commit is contained in:
+17
-12
@@ -7,11 +7,12 @@
|
||||
4. 全局匹配: 全局最大后验概率分配 -> 输出毫秒级精准有序点击坐标 [(x1, y1), (x2, y2), (x3, y3)]
|
||||
"""
|
||||
|
||||
import io
|
||||
import itertools
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
@@ -75,11 +76,11 @@ class CaptchaSolver:
|
||||
bin_t = cv2.resize(((arr < bg - 20).astype(np.uint8) * 255), (48, 48), interpolation=cv2.INTER_AREA)
|
||||
self.templates.append((cid, bin_t))
|
||||
|
||||
def parse_prompt_targets(self, prompt_img_path: Path) -> List[int]:
|
||||
def parse_prompt_targets(self, prompt_img: str | Path | bytes | Image.Image) -> List[int]:
|
||||
"""
|
||||
自动识别并解析提示图中 3 个目标图标的真实类别 ID (0~11)
|
||||
"""
|
||||
crops = slice_prompt_image(prompt_img_path)
|
||||
crops = slice_prompt_image(prompt_img)
|
||||
target_classes = []
|
||||
for crop_img, _ in crops[:3]:
|
||||
arr = np.array(crop_img.convert("L"))
|
||||
@@ -92,12 +93,19 @@ class CaptchaSolver:
|
||||
|
||||
return target_classes
|
||||
|
||||
def _extract_candidates(self, bg_img_path: Path) -> List[Tuple[int, int, Image.Image]]:
|
||||
def _extract_candidates(self, bg_img: str | Path | bytes | Image.Image) -> List[Tuple[int, int, Image.Image]]:
|
||||
"""从背景图中提取所有去重后的候选图标 (cx, cy, patch)"""
|
||||
results = self.detector(str(bg_img_path), conf=0.03, verbose=False)
|
||||
if isinstance(bg_img, Image.Image):
|
||||
bg_pil = bg_img.convert("RGB")
|
||||
elif isinstance(bg_img, bytes):
|
||||
bg_pil = Image.open(io.BytesIO(bg_img)).convert("RGB")
|
||||
else:
|
||||
bg_pil = Image.open(bg_img).convert("RGB")
|
||||
|
||||
# Ultralytics detector natively accepts PIL.Image
|
||||
results = self.detector(bg_pil, conf=0.03, verbose=False)
|
||||
boxes = results[0].boxes.xyxy.cpu().numpy()
|
||||
|
||||
bg_pil = Image.open(bg_img_path).convert("RGB")
|
||||
w_img, h_img = bg_pil.size
|
||||
raw_cands = []
|
||||
|
||||
@@ -120,22 +128,19 @@ class CaptchaSolver:
|
||||
|
||||
return candidates
|
||||
|
||||
def solve(self, bg_img_path: str | Path, prompt_img_path: str | Path) -> List[Tuple[int, int]]:
|
||||
def solve(self, bg_img: str | Path | bytes | Image.Image, prompt_img: str | Path | bytes | Image.Image) -> List[Tuple[int, int]]:
|
||||
"""
|
||||
端到端求解验证码点选坐标
|
||||
返回 3 个目标的像素坐标: [(x1, y1), (x2, y2), (x3, y3)]
|
||||
"""
|
||||
bg_path = Path(bg_img_path)
|
||||
prompt_path = Path(prompt_img_path)
|
||||
|
||||
# 1. 确定提示图的目标序列 [T1, T2, T3]
|
||||
target_classes = self.parse_prompt_targets(prompt_path)
|
||||
target_classes = self.parse_prompt_targets(prompt_img)
|
||||
if len(target_classes) < 3:
|
||||
logger.warning("提示图未成功切出 3 个目标")
|
||||
return []
|
||||
|
||||
# 2. 定位背景图所有候选图标
|
||||
candidates = self._extract_candidates(bg_path)
|
||||
candidates = self._extract_candidates(bg_img)
|
||||
if len(candidates) < 3:
|
||||
logger.warning(f"背景图检出候选图标数量不足: {len(candidates)}")
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user