| import os, json |
| import numpy as np |
| from pathlib import Path |
| from typing import Iterable, Optional, Union |
| from ultralytics import YOLO |
| import re |
|
|
|
|
| promotion_keywords = [ |
| r"领", r"申请", r"立即",r"马上",r"即刻",r"立即",r"下载", |
| r"一定",r"现发", |
| r"大牌美食", |
| ] |
| extra_keywords = [ |
| r"立即领取",r"点击领取",r"申请我的额度",r"免费观看", |
| r"火热选购", |
| r"立即前往",r"立即下载",r"立即投保",r"立即参与",r"立即解救",r"立即领取",r"立即抢购",r"立即购买",r"立即签到", |
| r"测一测",r"一定要买",r"现摘现发", |
| r"登录",r"上滑", |
|
|
| ] |
| compiled_patterns = [re.compile(p, flags=re.IGNORECASE) for p in (promotion_keywords + extra_keywords)] |
| compiled_extra_patterns = [re.compile(p, flags=re.IGNORECASE) for p in extra_keywords] |
|
|
| def extract_texts(ocr_result) -> str: |
| |
| texts = [] |
| texts = ocr_result[0]['rec_texts'] |
| scores = ocr_result[0]['rec_scores'] |
| coordss = ocr_result[0]['rec_polys'] |
| ocr_result = [ |
| (pts, (txt, conf)) |
| for pts, txt, conf in zip(coordss, texts, scores) |
| ] |
|
|
| for res in ocr_result: |
| coords, (text, confidence) = res |
| if confidence > 0.5: |
| texts.append(text) |
|
|
|
|
| merged = " ".join([t for t in texts if t]).strip() |
| merged = re.sub(r"\s+", "", merged) |
| print(merged) |
| return merged |
|
|
| def merge_by_rows(ocr_result, y_threshold=40): |
| blocks = [] |
| texts = ocr_result[0]['rec_texts'] |
| scores = ocr_result[0]['rec_scores'] |
| coordss = ocr_result[0]['rec_polys'] |
| ocr_result = [ |
| (pts, (txt, conf)) |
| for pts, txt, conf in zip(coordss, texts, scores) |
| ] |
|
|
| for pts, (txt, conf) in ocr_result: |
| if conf < 0.5 or not txt.strip(): |
| continue |
| ys = [p[1] for p in pts] |
| xs = [p[0] for p in pts] |
| blocks.append({ |
| "text": txt.strip(), |
| "x_min": min(xs), |
| "y_max": max(ys) |
| }) |
| if not blocks: |
| return "" |
| blocks.sort(key=lambda b: b["y_max"]) |
| lines, cur = [], [blocks[0]] |
| for blk in blocks[1:]: |
| if abs(blk["y_max"] - cur[0]["y_max"]) <= y_threshold: |
| cur.append(blk) |
| else: |
| lines.append(cur) |
| cur = [blk] |
| lines.append(cur) |
| merged = [] |
| for line in lines: |
| line.sort(key=lambda b: b["x_min"]) |
| merged.append("".join(b["text"] for b in line)) |
| return " ".join(merged) |
|
|
| |
| def is_guide_button_with_yolo_image( |
| image_np: np.ndarray, |
| model: Union[str, YOLO], |
| ocr, |
| out_dir: Union[str, Path] = "yolo_outputs", |
| name: str = "predict", |
| imgsz: int = 1024, |
| device: Union[int, str] = 1, |
| iou: float = 0.45, |
| max_det: int = 300, |
| half: bool = False, |
| verbose: bool = True, |
| idx: Optional[Union[int, str]] = None |
| ) -> bool: |
| """ |
| 用 YOLO 对单张 numpy 图像进行检测,并保存可视化与 JSON。 |
| 返回:是否包含促销元素(True/False) |
| """ |
| class_thresholds = { |
| 0: 1.0, |
| 1: 0.75, |
| 2: 1.0, |
| 3: 1.0, |
| 4: 0.95, |
| 5: 0.95, |
| 6: 0.95, |
| 7: 0.95, |
| 8: 0.95, |
| 9: 0.95, |
| 10: 0.35, |
| 11: 0.35, |
| } |
|
|
| |
| if isinstance(model, str): |
| model = YOLO(model) |
|
|
| out_dir = Path(out_dir) |
| out_dir.mkdir(parents=True, exist_ok=True) |
|
|
| results = model.predict( |
| source=[image_np], |
| imgsz=imgsz, |
| conf=0.25, |
| iou=iou, |
| device=device, |
| max_det=max_det, |
| half=half, |
| save=True, |
| project=str(out_dir), |
| name=name, |
| exist_ok=True, |
| verbose=verbose |
| ) |
|
|
| promo_found = False |
| det_list = [] |
| names_dict = getattr(model, "names", None) |
|
|
| for result in results: |
| save_dir = Path(result.save_dir) |
| if result.boxes is None: |
| continue |
| boxes = result.boxes.xyxy.cpu().numpy() |
| classes = result.boxes.cls.cpu().numpy().astype(int) |
| scores = result.boxes.conf.cpu().numpy() |
|
|
| if verbose: |
| print("—— detections ——") |
| |
| h, w = image_np.shape[:2] |
| total_img_area = h * w |
|
|
| for (x1, y1, x2, y2), cid, score in zip(boxes, classes, scores): |
| cname = names_dict.get(int(cid), str(int(cid))) if isinstance(names_dict, dict) else str(int(cid)) |
| if verbose: |
| print(f"cls={cname}({cid}) conf={score:.3f}") |
| det_list.append([cname]) |
| det_list.append([float(x1), float(y1), float(x2), float(y2), int(cid), float(score)]) |
|
|
| th = class_thresholds.get(cid, None) |
| if th is None: |
| continue |
|
|
| |
| |
| |
| if cid not in (10, 11): |
| if cid == 1: |
| |
| box_area = (x2 - x1) * (y2 - y1) |
| |
| ratio = box_area / total_img_area if total_img_area > 0 else 0 |
| |
| |
| if score >= th and ratio < 0.01: |
| if verbose: |
| print(f" [Match] Class 1 (Area Ratio: {ratio:.4f} < 0.01)") |
| promo_found = True |
| else: |
| |
| if score >= th: |
| promo_found = True |
| continue |
| |
| |
|
|
| |
| if cid in (10,11) and score >= 0.875: |
| promo_found = True |
| continue |
|
|
| if score >= th: |
| if ocr is not None: |
| |
| xi1, yi1 = max(0, int(x1)), max(0, int(y1)) |
| xi2, yi2 = min(w - 1, int(x2)), min(h - 1, int(y2)) |
| if xi2 > xi1 and yi2 > yi1: |
| crop = image_np[yi1:yi2, xi1:xi2] |
| try: |
| ocr_result = ocr.ocr(crop) |
| |
| text_merged = merge_by_rows(ocr_result) |
| print(text_merged) |
| |
| if any(p.search(text_merged) for p in compiled_patterns): |
| if verbose: |
| print(f"[OCR-MATCH] {cname} 命中促销关键词") |
| promo_found = True |
| continue |
| except Exception as e: |
| if verbose: |
| print(f"[OCR-ERROR] {e}") |
| |
|
|
| |
| if idx is not None: |
| default_path = save_dir / "image0.jpg" |
| target_path = save_dir / f"{idx}.jpg" |
| if default_path.exists(): |
| default_path.rename(target_path) |
| if verbose: |
| print(f"[保存] 可视化检测图: {target_path}") |
|
|
| |
| json_path = save_dir / f"detections_{idx if idx is not None else '0'}.json" |
| with open(json_path, "w", encoding="utf-8") as f: |
| json.dump(det_list, f, ensure_ascii=False, indent=2) |
| if verbose: |
| print(f"[保存] 检测数组: {json_path}") |
|
|
| |
| if ocr is not None and not promo_found: |
| try: |
| global_ocr_result = ocr.ocr(image_np) |
| global_text = merge_by_rows(global_ocr_result) |
| print("[全局OCR]", global_text) |
| if any(p.search(global_text) for p in compiled_extra_patterns): |
| if verbose: |
| print("[全局OCR-MATCH] 命中extra_keywords促销关键词") |
| return True |
| except Exception as e: |
| if verbose: |
| print(f"[全局OCR-ERROR] {e}") |
|
|
| return promo_found |