flat_card_no.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """
  2. 集成脚本:card_seg_v2 分割+warp → yolo26s_card_no 检测编号 → 输出可视化+位置
  3. 环境:pytorch
  4. 输入:IN_DIR(平铺目录,默认 ~/顾工交接/wzj/_infer_in)
  5. 输出:OUT_DIR/vis_num(可视化图)+ OUT_DIR/labels_num(YOLO格式,含conf)
  6. 用法:
  7. export IN_DIR=/path/to/images
  8. export OUT_DIR=/path/to/output
  9. CUDA_VISIBLE_DEVICES=$UUID ~/miniconda3/envs/pytorch/bin/python flat_card_no.py
  10. """
  11. import os, sys, json, time, datetime
  12. from pathlib import Path
  13. sys.stdout.reconfigure(encoding="utf-8")
  14. sys.path.insert(0, "/home/martin/顾工交接/wzj")
  15. import cv2
  16. import numpy as np
  17. import config
  18. from modules.yolo_detector import CardDetector
  19. from modules.rectifier import CardRectifier
  20. IN_DIR = os.environ.get("IN_DIR", "/home/martin/顾工交接/wzj/_infer_in")
  21. OUT_DIR = os.environ.get("OUT_DIR", "/home/martin/顾工交接/wzj/_infer_out")
  22. VIS_DIR = os.path.join(OUT_DIR, "vis_num")
  23. LABEL_DIR = os.path.join(OUT_DIR, "labels_num")
  24. os.makedirs(VIS_DIR, exist_ok=True)
  25. os.makedirs(LABEL_DIR, exist_ok=True)
  26. # 模型路径(绝对路径)
  27. SEG_MODEL = "/home/martin/顾工交接/wzj/ultralytics/runs/segment/card_seg_pn_v2/weights/best.pt"
  28. CARD_NO_MODEL = "/home/martin/顾工交接/wzj/ultralytics/runs/detect/yolo26s_card_no/weights/best.pt"
  29. print("[INIT] 加载 card_seg_v2 分割模型 ...")
  30. _t = time.perf_counter()
  31. seg_detector = CardDetector(SEG_MODEL, conf=0.25)
  32. rectifier = CardRectifier(use_perspective=True)
  33. t_seg = time.perf_counter() - _t
  34. print(f"[INIT] 加载 card_no 检测模型 ...")
  35. from modules.ultralytics_compat import import_yolo
  36. YOLO = import_yolo()
  37. _t = time.perf_counter()
  38. card_no_model = YOLO(CARD_NO_MODEL, task="detect")
  39. t_load = time.perf_counter() - _t
  40. print(f"[INIT] card_seg_v2 {t_seg:.2f}s, card_no {t_load:.2f}s\n")
  41. imgs = []
  42. for ext in ("*.jpg", "*.jpeg", "*.png", "*.JPG", "*.JPEG", "*.PNG"):
  43. imgs += list(Path(IN_DIR).glob(ext))
  44. imgs = sorted(set(imgs))
  45. print(f"[RUN] {len(imgs)} 张图片\n")
  46. n_seg_ok, n_seg_fail, n_det_hit, n_det_multi, n_det_none = 0, 0, 0, 0, 0
  47. t_all = time.perf_counter()
  48. for i, imgp in enumerate(imgs, 1):
  49. name = imgp.name
  50. try:
  51. img_bgr = cv2.imread(str(imgp))
  52. if img_bgr is None:
  53. n_seg_fail += 1
  54. continue
  55. img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
  56. # ① card_seg_v2 分割 + mask4点 warp 摆正
  57. box, mask, orig_rgb = seg_detector.detect_box_mask(img_rgb)
  58. if box is None:
  59. crop_rgb = seg_detector.detect_and_crop(img_rgb)
  60. if crop_rgb is None:
  61. n_seg_fail += 1
  62. continue
  63. else:
  64. crop_rgb = rectifier.rectify(orig_rgb, mask=mask, box=box, angle=0)
  65. if crop_rgb is None or crop_rgb.size == 0:
  66. n_seg_fail += 1
  67. continue
  68. n_seg_ok += 1
  69. crop_bgr = cv2.cvtColor(crop_rgb, cv2.COLOR_RGB2BGR)
  70. # ② yolo26s_card_no 检测编号
  71. res = card_no_model.predict(crop_bgr, conf=0.25, imgsz=640, verbose=False)[0]
  72. h, w = crop_bgr.shape[:2]
  73. boxes = res.boxes
  74. n = 0 if boxes is None else len(boxes)
  75. if n == 0:
  76. n_det_none += 1
  77. elif n == 1:
  78. n_det_hit += 1
  79. else:
  80. n_det_multi += 1
  81. # ③ 可视化 + YOLO标签
  82. vis = crop_bgr.copy()
  83. label_lines = []
  84. if boxes is not None:
  85. xyxy = boxes.xyxy.cpu().numpy()
  86. confs = boxes.conf.cpu().numpy()
  87. clss = boxes.cls.cpu().numpy()
  88. for (x1, y1, x2, y2), conf, cls in zip(xyxy, confs, clss):
  89. cv2.rectangle(vis, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
  90. cv2.putText(vis, f"card_no {conf:.2f}", (int(x1), max(0, int(y1) - 6)),
  91. cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)
  92. xc = (x1 + x2) / 2 / w
  93. yc = (y1 + y2) / 2 / h
  94. bw = (x2 - x1) / w
  95. bh = (y2 - y1) / h
  96. label_lines.append(f"{int(cls)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f} {conf:.4f}")
  97. cv2.imwrite(os.path.join(VIS_DIR, name), vis)
  98. with open(os.path.join(LABEL_DIR, f"{os.path.splitext(name)[0]}.txt"), "w") as f:
  99. f.write("\n".join(label_lines))
  100. except Exception as e:
  101. n_seg_fail += 1
  102. print(f" [ERR] {name}: {e}")
  103. if i % 200 == 0 or i == len(imgs):
  104. el = time.perf_counter() - t_all
  105. rate = i / el if el > 0 else 0
  106. eta = (len(imgs) - i) / rate if rate > 0 else 0
  107. print(f" [{i}/{len(imgs)}] 分割ok={n_seg_ok} fail={n_seg_fail} "
  108. f"检出1框={n_det_hit} 多框={n_det_multi} 无={n_det_none} "
  109. f"速率={rate:.1f}/s 剩余≈{eta/60:.1f}min", flush=True)
  110. n = max(len(imgs), 1); t_all = time.perf_counter() - t_all
  111. print(f"\n[RESULT] {n} 张")
  112. print(f" 分割成功: {n_seg_ok}, 分割失败: {n_seg_fail}")
  113. print(f" card_no 检出1框: {n_det_hit}, 多框: {n_det_multi}, 无检出: {n_det_none}")
  114. print("=" * 50)
  115. print(f"⏱ flat_card_no (card_seg_v2 + yolo26s_card_no) | {n} 张")
  116. print(f" 加载 card_seg_v2 {t_seg:.2f}s, card_no {t_load:.2f}s")
  117. print(f" 总耗时 {t_all:.2f}s ({n/t_all:.1f}张/s)")
  118. print("=" * 50)
  119. with open(os.path.join(OUT_DIR, "timing.log"), "a", encoding="utf-8") as lf:
  120. lf.write(f"\n[{datetime.datetime.now():%Y-%m-%d %H:%M:%S}] === flat_card_no.py (card_seg_v2 + yolo26s_card_no) ===\n")
  121. lf.write(f" 样本 {n} 张\n")
  122. lf.write(f" 分割成功 {n_seg_ok}, 分割失败 {n_seg_fail}\n")
  123. lf.write(f" card_no 检出1框 {n_det_hit}, 多框 {n_det_multi}, 无检出 {n_det_none}\n")
  124. lf.write(f" 加载 card_seg_v2 {t_seg:.2f}s, card_no {t_load:.2f}s\n")
  125. lf.write(f" 总耗时 {t_all:.2f}s (均 {t_all/n*1000:.1f} ms/张, 吞吐 {n/t_all:.1f} 张/s)\n")
  126. print(f"✓ 可视化: {VIS_DIR} | 标签: {LABEL_DIR} | 计时→ timing.log")