flat_card_no_detect.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. """
  2. flat_card_no_detect.py - 步骤①:卡牌分割+编号检测+裁剪(pytorch环境)
  3. 输入:IN_DIR(平铺原图)
  4. 输出:OUT_DIR/card_no_crops/ + OUT_DIR/card_no_crop_records.json
  5. 用法:
  6. export IN_DIR=/path/to/images
  7. export OUT_DIR=/path/to/output
  8. export CUDA_VISIBLE_DEVICES=db470791-15e0-5fc2-3408-64ca8e9881dc
  9. ~/miniconda3/envs/pytorch/bin/python flat_card_no_detect.py
  10. """
  11. import os, sys, json, time, datetime
  12. from pathlib import Path
  13. sys.stdout.reconfigure(encoding="utf-8")
  14. IN_DIR = os.environ.get("IN_DIR", "/home/martin/顾工交接/wzj/_infer_in")
  15. OUT_DIR = os.environ.get("OUT_DIR", "/home/martin/顾工交接/wzj/_infer_out")
  16. MARGIN = float(os.environ.get("MARGIN", "0.05")) # v6_medium_rec 在紧裁剪下已验证优于 v5+放宽margin
  17. CROP_DIR = os.path.join(OUT_DIR, "card_no_crops")
  18. os.makedirs(CROP_DIR, exist_ok=True)
  19. sys.path.insert(0, "/home/martin/顾工交接/wzj")
  20. import cv2
  21. import config
  22. from modules.yolo_detector import CardDetector
  23. from modules.rectifier import CardRectifier
  24. from modules.ultralytics_compat import import_yolo
  25. # 模型路径
  26. SEG_MODEL = "/home/martin/顾工交接/wzj/ultralytics/runs/segment/card_seg_pn_v2/weights/best.pt"
  27. CARD_NO_MODEL = "/home/martin/顾工交接/wzj/ultralytics/runs/detect/yolo26s_card_no/weights/best.pt"
  28. print("[INIT] 加载 card_seg_v2 + yolo26s_card_no ...")
  29. _t = time.perf_counter()
  30. seg_detector = CardDetector(SEG_MODEL, conf=0.25)
  31. rectifier = CardRectifier(use_perspective=True)
  32. YOLO = import_yolo()
  33. card_no_model = YOLO(CARD_NO_MODEL, task="detect")
  34. t_load = time.perf_counter() - _t
  35. print(f"[INIT] 模型加载完成 ({t_load:.2f}s)\n")
  36. # 读原图
  37. imgs = []
  38. for ext in ("*.jpg", "*.jpeg", "*.png", "*.JPG", "*.JPEG", "*.PNG"):
  39. imgs += list(Path(IN_DIR).glob(ext))
  40. imgs = sorted(set(imgs))
  41. print(f"[RUN] {len(imgs)} 张原图\n")
  42. crop_records = []
  43. t_detect = 0.0
  44. _t_all = time.perf_counter()
  45. for i, imgp in enumerate(imgs, 1):
  46. name = imgp.name
  47. try:
  48. img_bgr = cv2.imread(str(imgp))
  49. if img_bgr is None:
  50. continue
  51. img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
  52. # card_seg_v2 分割+warp
  53. box, mask, orig_rgb = seg_detector.detect_box_mask(img_rgb)
  54. if box is None:
  55. crop_rgb = seg_detector.detect_and_crop(img_rgb)
  56. else:
  57. crop_rgb = rectifier.rectify(orig_rgb, mask=mask, box=box, angle=0)
  58. if crop_rgb is None or crop_rgb.size == 0:
  59. continue
  60. crop_bgr = cv2.cvtColor(crop_rgb, cv2.COLOR_RGB2BGR)
  61. h, w = crop_bgr.shape[:2]
  62. # yolo26s_card_no 检测编号区域
  63. # conf=0.3;多框时下方 argmax 取最高置信度
  64. res = card_no_model.predict(crop_bgr, conf=0.3, imgsz=640, verbose=False)[0]
  65. boxes = res.boxes
  66. if boxes is None or len(boxes) == 0:
  67. continue
  68. # 取置信度最高的框
  69. xyxy = boxes.xyxy.cpu().numpy()
  70. confs = boxes.conf.cpu().numpy()
  71. idx = int(confs.argmax())
  72. x1, y1, x2, y2 = xyxy[idx]
  73. conf = float(confs[idx])
  74. # 裁剪编号区域(margin 为检测框外扩比例,太紧会吃掉斜杠/截断右半)
  75. margin = MARGIN
  76. bw = x2 - x1
  77. bh = y2 - y1
  78. x1 = max(0, int(x1 - bw * margin))
  79. y1 = max(0, int(y1 - bh * margin))
  80. x2 = min(w, int(x2 + bw * margin))
  81. y2 = min(h, int(y2 + bh * margin))
  82. crop_num = crop_bgr[y1:y2, x1:x2]
  83. if crop_num.size == 0 or crop_num.shape[0] == 0 or crop_num.shape[1] == 0:
  84. continue
  85. # 保存裁剪图(文件名带 conf 前缀,便于按名称排序挑低置信度坏例)
  86. crop_path = os.path.join(CROP_DIR, f"{conf:.2f}__{name}")
  87. cv2.imwrite(crop_path, crop_num)
  88. crop_records.append({"file": name, "crop_path": crop_path, "conf": conf,
  89. "box": [int(x1), int(y1), int(x2), int(y2)],
  90. "crop_wh": [crop_num.shape[1], crop_num.shape[0]]})
  91. if i % 200 == 0:
  92. print(f" [{i}/{len(imgs)}] 完成", flush=True)
  93. except Exception as e:
  94. print(f" [ERR] {name}: {e}")
  95. t_detect = time.perf_counter() - _t_all
  96. print(f"\n[RESULT] {len(crop_records)} 张检测成功")
  97. print("=" * 50)
  98. print(f"⏱ flat_card_no_detect (分割+检测+裁剪) | {len(crop_records)} 张")
  99. print(f" 模型加载 {t_load:.2f}s")
  100. print(f" 检测+裁剪 {t_detect:.2f}s (均{t_detect/max(len(crop_records),1)*1000:.1f}ms)")
  101. print("=" * 50)
  102. with open(os.path.join(OUT_DIR, "timing.log"), "a", encoding="utf-8") as lf:
  103. lf.write(f"\n[{datetime.datetime.now():%Y-%m-%d %H:%M:%S}] === flat_card_no_detect.py (分割+检测+裁剪) ===\n")
  104. lf.write(f" 样本 {len(crop_records)} 张\n")
  105. lf.write(f" 模型加载 {t_load:.2f}s\n")
  106. lf.write(f" 检测+裁剪 {t_detect:.2f}s (均 {t_detect/max(len(crop_records),1)*1000:.1f} ms/张)\n")
  107. print(f"✓ 裁剪图: {CROP_DIR} | 记录: {OUT_DIR}/card_no_crop_records.json | 计时→ timing.log")
  108. # 保存裁剪记录
  109. with open(os.path.join(OUT_DIR, "card_no_crop_records.json"), "w", encoding="utf-8") as f:
  110. json.dump(crop_records, f, ensure_ascii=False, indent=2)