Files
OLmatter 905b6e6651 v23.3: upgrade OCR to PP-OCRv6_tiny + fix click-no-captcha 15s wait
Backend:
- Default OCR model PP-OCRv5_server_rec -> PP-OCRv6_tiny_rec (~14x faster,
  83ms/img vs 1189ms on 379 real captchas, accuracy still 100%)
- Configurable via config.json ocr_model or env CNCAPTCHA_CPU_OCR_MODEL/GLM_OCR_MODEL
- GUI OCR model choices updated to v6 (tiny/medium) + v5 server fallback
- worker.py: split 3 crops into independent OCR tasks for parallel recognition
- ppocr_worker.py: batch forward (predict_batch_*) + single-crop path
- server.py: partial results aggregation + OCR_MODEL env passthrough
- requirements: paddleocr>=3.7.0 / paddlex>=3.7.0 (v6 requires)

Userscript v23.3:
- Fix: auto-click subscribe then wait 15s when click didn't trigger captcha.
  Synthetic click sometimes gets swallowed by Zhipu frontend (button DOM ready
  but component state machine not ready), no captcha pops up, main loop stuck in
  WAITING until MODAL_WAIT=15000 timeout.
- Fix: use 'iframe got new prompt+bg image' as the signal. iframe increments GM
  counter glm_captcha_seen_seq each time it sees a new captcha (prompt or bg
  changed); main loop records baseline at click time, compares in WAITING -
  counter increased = captcha popped, wait patiently; no increase after 1.5s =
  click didn't trigger captcha, immediately retry subscribe. Counter is
  monotonic, no residue, no timestamp race.
2026-06-23 00:07:51 +08:00

118 lines
3.5 KiB
Python

"""
YOLO Worker - 极致流水线版
- OMP=1 独占物理核,消灭 GIL 争抢
- 裁剪切片通过 queue 直接传递,无共享内存
"""
import os
import sys
import io
import time
from pathlib import Path
import psutil
from PIL import Image
from ultralytics import YOLO
if getattr(sys, 'frozen', False):
ROOT = Path(sys._MEIPASS)
else:
ROOT = Path(__file__).resolve().parent.parent
DETECTOR = ROOT / "models" / "weights" / "yolo-captcha-detector.pt"
YOLO_IMGSZ = 448
# YOLO 单线程!多进程并行靠进程数
for _key in ("OMP_NUM_THREADS", "MKL_NUM_THREADS", "OPENBLAS_NUM_THREADS", "NUMEXPR_NUM_THREADS"):
os.environ[_key] = "1"
from backend.evaluate import select_fixed3
def run_yolo_worker(core_id: int, req_queue, ocr_queue, ready_queue):
# 绑定物理核
try:
p = psutil.Process()
p.cpu_affinity([core_id])
except Exception:
pass
detector = YOLO(str(DETECTOR))
_ = detector.predict(
Image.new("RGB", (YOLO_IMGSZ, YOLO_IMGSZ)),
imgsz=YOLO_IMGSZ,
conf=0.15,
iou=0.5,
max_det=1,
verbose=False,
)
print(f"[yolo] Core {core_id} ready")
ready_queue.put("yolo_ready")
while True:
payload = req_queue.get()
if payload is None:
break
req_id = payload["req_id"]
chars = payload["chars"]
try:
image = Image.open(io.BytesIO(payload["img_bytes"])).convert("RGB")
t0 = time.perf_counter()
result = detector.predict(
source=image,
imgsz=YOLO_IMGSZ,
conf=0.15,
iou=0.5,
max_det=10,
verbose=False,
)[0]
yolo_ms = (time.perf_counter() - t0) * 1000
raw_boxes, raw_confs = [], []
if result.boxes is not None:
for b in result.boxes:
raw_boxes.append(tuple(float(x) for x in b.xyxy[0].tolist()))
raw_confs.append(float(b.conf[0].item()))
boxes, confs, reason = select_fixed3(raw_boxes, raw_confs, image.size)
selected = sorted(zip(boxes, confs), key=lambda x: x[0][0])
boxes = [x[0] for x in selected]
# 纯内存裁剪
crop_bytes_list = []
for box in boxes:
x1, y1, x2, y2 = [int(round(v)) for v in box]
crop = image.crop(
(
max(0, x1),
max(0, y1),
min(image.width, x2),
min(image.height, y2),
)
)
buf = io.BytesIO()
crop.save(buf, format="PNG")
crop_bytes_list.append(buf.getvalue())
# 每个 crop 独立投递,让多个 OCR worker 并行识别同一张验证码。
total = len(crop_bytes_list)
for crop_index, crop_bytes in enumerate(crop_bytes_list):
ocr_queue.put({
"req_id": req_id,
"crop_index": crop_index,
"crop_total": total,
"yolo_ms": yolo_ms,
"boxes": boxes,
"chars": chars,
"image_size": list(image.size),
"reason": reason,
"crop_bytes": crop_bytes,
})
except Exception as e:
import traceback
traceback.print_exc()
ocr_queue.put({"req_id": req_id, "error": str(e)})