| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 真实拍摄图多卡检测审计(249 上跑):对比"关闭合并 vs 开启合并"的检出差异,
- 校准 CARD_MERGE_DIST_PX 阈值。
- 数据源二选一:
- --minio N MinIO grading/capp_img_data/ 最新 N 张(C 端真实拍摄图,默认 50)
- --dir PATH 本地目录(如 data/query_images/)
- 每张图输出:关闭合并的原始检出数 → 开启合并后检出数、合并发生明细
- (谁吸收了谁、中心距、conf)。结尾汇总:合并触发的图占比、误杀风险(原始多实例
- 但合并后变少的例子需要人工过目)。
- 用法(pytorch env):
- python tools/audit_detect_all.py --minio 50
- python tools/audit_detect_all.py --dir data/query_images
- """
- import os
- import sys
- import argparse
- sys.stdout.reconfigure(encoding="utf-8")
- _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- sys.path.insert(0, _ROOT)
- import numpy as np
- from PIL import Image
- import config
- from modules.yolo_detector import CardDetector
- def _fetch_minio(limit, out_dir, prefix=None):
- """拉 grading 桶指定前缀最新 limit 张到 out_dir,返回本地图路径列表。"""
- import config as _cfg
- cfg = _cfg.CAPP_MINIO
- from minio import Minio
- client = Minio(cfg["endpoint"], access_key=cfg["access_key"],
- secret_key=cfg["secret_key"], secure=bool(cfg.get("secure")))
- objs = []
- for o in client.list_objects(cfg["bucket"], prefix=(prefix or cfg["prefix"]) + "/", recursive=True):
- if o.object_name.lower().endswith((".jpg", ".jpeg", ".png", ".webp")):
- objs.append(o)
- objs.sort(key=lambda o: o.last_modified or 0, reverse=True)
- os.makedirs(out_dir, exist_ok=True)
- paths = []
- for o in objs[:limit]:
- dst = os.path.join(out_dir, os.path.basename(o.object_name))
- if not os.path.exists(dst) or os.path.getsize(dst) == 0:
- try:
- client.fget_object(cfg["bucket"], o.object_name, dst)
- except Exception as e:
- print(f" [skip] {o.object_name}: {e}", flush=True)
- continue
- paths.append(dst)
- return paths
- def _audit_one(detector, path, thr):
- """对一张图跑两遍(关/开合并),返回 dict。"""
- try:
- arr = np.array(Image.open(path).convert("RGB"))
- except Exception as e:
- return {"path": path, "error": str(e)[:100]}
- detector.merge_dist_px = 0.0
- raw = detector.detect_and_crop_all(arr)
- detector.merge_dist_px = thr
- merged = detector.detect_and_crop_all(arr)
- return {
- "path": path,
- "w": arr.shape[1], "h": arr.shape[0],
- "raw_n": len(raw),
- "merged_n": len(merged),
- "raw_confs": [round(it["conf"], 3) for it in raw],
- "merged_confs": [round(it["conf"], 3) for it in merged],
- "merged_centers": [(round(c[0]), round(c[1])) for c in (it["center"] for it in merged)],
- "merged": len(merged) < len(raw),
- }
- def main():
- ap = argparse.ArgumentParser()
- ap.add_argument("--minio", type=int, default=0, help="拉取 grading 桶最新 N 张")
- ap.add_argument("--prefix", type=str, default=None,
- help="MinIO 对象前缀(默认 config.CAPP_MINIO.prefix=capp_img_data;"
- "拍摄图更多可用 raspi_img_data)")
- ap.add_argument("--dir", type=str, default=None, help="本地图片目录")
- ap.add_argument("--limit", type=int, default=50)
- args = ap.parse_args()
- if args.dir:
- paths = sorted(
- os.path.join(args.dir, f) for f in os.listdir(args.dir)
- if f.lower().endswith((".jpg", ".jpeg", ".png", ".webp", ".bmp")))[:args.limit]
- elif args.minio:
- paths = _fetch_minio(args.minio, os.path.join(config.DATA_DIR, "audit_imgs"), args.prefix)
- else:
- ap.error("需 --minio N 或 --dir PATH")
- detector = CardDetector(config.YOLO_MODEL_PATH)
- thr = float(getattr(detector, "merge_dist_px", 50.0))
- print(f"[Audit] 阈值 CARD_MERGE_DIST_PX={thr} 图片数={len(paths)}\n", flush=True)
- n_merged = n_multi = n_zero = n_err = 0
- for i, p in enumerate(paths, 1):
- r = _audit_one(detector, p, thr)
- if "error" in r:
- n_err += 1
- print(f"[{i}/{len(paths)}] {os.path.basename(p)} 读取失败: {r['error']}", flush=True)
- continue
- n_merged += int(r["merged"])
- n_multi += int(r["merged_n"] > 1)
- n_zero += int(r["merged_n"] == 0)
- flag = " ← 合并触发" if r["merged"] else ""
- print(f"[{i}/{len(paths)}] {os.path.basename(p)} {r['w']}×{r['h']} "
- f"raw={r['raw_n']}({r['raw_confs']}) → merged={r['merged_n']}"
- f"({r['merged_confs']}) centers={r['merged_centers']}{flag}", flush=True)
- n_ok = len(paths) - n_err
- print(f"\n[Summary] 有效 {n_ok} 张:合并触发 {n_merged}({n_merged / max(1, n_ok):.1%}) "
- f"多卡图 {n_multi} 未检出 {n_zero} 读取失败 {n_err}", flush=True)
- print("[Hint] 合并触发的样例请人工过目:若 raw 里本就是两张紧贴的不同卡被并成一张,"
- "属误杀 → 调小 CARD_MERGE_DIST_PX 或启用 CARD_MERGE_MIN_IOU。", flush=True)
- if __name__ == "__main__":
- main()
|