pokemon_grading.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. # -*- coding: utf-8 -*-
  2. """
  3. pokemon_grading.py - best.pt 在 images_pokemon 上跑: 裁评级标签条 + 标注图 + 预测记录
  4. 输出:
  5. {OUT}/crops/<folder>_<name>.jpg 评级标签条裁剪图
  6. {OUT}/predict/<folder>/<name>.jpg best.pt 标注图(带框+标签+置信度)
  7. {OUT}/crop_records.json 裁剪记录(box, conf)
  8. {OUT}/test_predictions.json best.pt 预测(true, pred, conf, file)
  9. {OUT}/grading_summary.csv 汇总(filename, true_label, detected_company, is_graded, conf, box)
  10. 运行环境: pytorch (~/miniconda3/envs/pytorch/bin/python)
  11. """
  12. import os
  13. import sys
  14. import json
  15. import csv
  16. import re
  17. import time
  18. from pathlib import Path
  19. import numpy as np
  20. import cv2
  21. sys.stdout.reconfigure(encoding="utf-8")
  22. ROOT = "/home/martin/顾工交接/wzj/images_pokemon"
  23. OUT = "/home/martin/顾工交接/wzj/images_pokemon_out"
  24. BEST = "/home/martin/顾工交接/wzj/ultralytics/runs/detect/card_v1/weights/best.pt"
  25. FOLDERS = ["BGS", "CGC", "PSA", "SGC"]
  26. CONF = 0.25
  27. os.makedirs(os.path.join(OUT, "crops"), exist_ok=True)
  28. os.makedirs(os.path.join(OUT, "predict"), exist_ok=True)
  29. for f in FOLDERS:
  30. os.makedirs(os.path.join(OUT, "predict", f), exist_ok=True)
  31. print("[STEP] 加载 best.pt ...")
  32. sys.path.insert(0, "/home/martin/顾工交接/wzj")
  33. from modules.ultralytics_compat import import_yolo
  34. YOLO = import_yolo()
  35. _t_load0 = time.perf_counter()
  36. model = YOLO(BEST, task="detect")
  37. t_load = time.perf_counter() - _t_load0
  38. print(f"[STEP] 加载完成 (耗时 {t_load:.2f}s)\n")
  39. crop_records = []
  40. pred_records = []
  41. csv_rows = []
  42. # 计时累加器
  43. T = {"infer": 0.0, "imread": 0.0, "crop_write": 0.0, "annot_write": 0.0}
  44. n_img = 0
  45. _t_all0 = time.perf_counter()
  46. for fld in FOLDERS:
  47. imgs = sorted(Path(os.path.join(ROOT, fld)).glob("*.jpg"))
  48. print(f"[STEP] {fld}: {len(imgs)} 张", flush=True)
  49. for imgp in imgs:
  50. name = imgp.name
  51. n_img += 1
  52. _t = time.perf_counter()
  53. results = model.predict(str(imgp), conf=CONF, imgsz=640, verbose=False)
  54. T["infer"] += time.perf_counter() - _t
  55. box = None
  56. conf_val = 0.0
  57. pred_label = "(无检测)"
  58. if results and results[0].boxes is not None and len(results[0].boxes) > 0:
  59. confs = results[0].boxes.conf.cpu().numpy()
  60. clses = results[0].boxes.cls.cpu().numpy().astype(int)
  61. xyxy = results[0].boxes.xyxy.cpu().numpy()
  62. best_i = int(np.argmax(confs))
  63. conf_val = float(confs[best_i])
  64. box = tuple(float(v) for v in xyxy[best_i])
  65. pred_label = model.names[clses[best_i]]
  66. # 公司前缀(从 label 取,如 BGS-AUTHENTIC -> BGS)
  67. detected_company = pred_label.split("-", 1)[0] if pred_label != "(无检测)" else ""
  68. is_graded = 1 if detected_company else 0
  69. # 裁剪评级标签条
  70. _t = time.perf_counter()
  71. img = cv2.imread(str(imgp))
  72. T["imread"] += time.perf_counter() - _t
  73. crop = None
  74. if box:
  75. x1, y1, x2, y2 = box
  76. h, w = img.shape[:2]
  77. x1i, y1i = max(0, int(x1)), max(0, int(y1))
  78. x2i, y2i = min(w, int(x2)), min(h, int(y2))
  79. if x2i > x1i and y2i > y1i:
  80. crop = img[y1i:y2i, x1i:x2i]
  81. if crop is None:
  82. crop = img # 无检测则用原图(后续 OCR 兜底)
  83. crop_path = os.path.join(OUT, "crops", f"{fld}_{name}")
  84. _t = time.perf_counter()
  85. cv2.imwrite(crop_path, crop)
  86. T["crop_write"] += time.perf_counter() - _t
  87. # 标注图(带框+标签+置信度)
  88. _t = time.perf_counter()
  89. if results and results[0].boxes is not None and len(results[0].boxes) > 0:
  90. annotated = results[0].plot() # ultralytics 自带标注
  91. else:
  92. annotated = img
  93. cv2.imwrite(os.path.join(OUT, "predict", fld, name), annotated)
  94. T["annot_write"] += time.perf_counter() - _t
  95. crop_records.append({
  96. "file": name, "true_label": fld, "box": box,
  97. "conf": round(conf_val, 4) if conf_val else None,
  98. "crop_path": crop_path,
  99. })
  100. pred_records.append({
  101. "true": fld, "pred": pred_label,
  102. "conf": round(conf_val, 4) if conf_val else 0.0, "file": name,
  103. })
  104. csv_rows.append({
  105. "filename": name, "true_label": fld,
  106. "detected_company": detected_company,
  107. "is_graded": is_graded,
  108. "conf": round(conf_val, 4) if conf_val else "",
  109. "box": str(box) if box else "",
  110. })
  111. print("\n[STEP] 保存记录 ...")
  112. with open(os.path.join(OUT, "crop_records.json"), "w", encoding="utf-8") as f:
  113. json.dump(crop_records, f, ensure_ascii=False, indent=2)
  114. with open(os.path.join(OUT, "test_predictions.json"), "w", encoding="utf-8") as f:
  115. json.dump({"records": pred_records}, f, ensure_ascii=False, indent=2)
  116. with open(os.path.join(OUT, "grading_summary.csv"), "w", encoding="utf-8-sig", newline="") as f:
  117. w = csv.DictWriter(f, fieldnames=["filename", "true_label", "detected_company", "is_graded", "conf", "box"])
  118. w.writeheader()
  119. w.writerows(csv_rows)
  120. # 准确率统计(仅评级卡)
  121. graded = [r for r in csv_rows if r["is_graded"] == 1]
  122. correct = sum(1 for r in graded if r["detected_company"] == r["true_label"])
  123. print(f"\n[RESULT] 评级卡 {len(graded)} 张, 非评级卡 {len(csv_rows)-len(graded)} 张")
  124. print(f"[RESULT] 公司识别准确率(仅评级卡): {correct}/{len(graded)} = {correct/len(graded)*100:.2f}%")
  125. # ===== 计时报告 =====
  126. t_all = time.perf_counter() - _t_all0
  127. rep = []
  128. rep.append("=" * 50)
  129. rep.append("⏱ 计时报告 (pokemon_grading.py)")
  130. rep.append("=" * 50)
  131. rep.append(f" 模型加载(best.pt) : {t_load:7.2f}s")
  132. rep.append(f" best.pt 推理(累计) : {T['infer']:7.2f}s | 均 {T['infer']/n_img*1000:6.1f} ms/张")
  133. rep.append(f" 读图 cv2.imread(累计) : {T['imread']:7.2f}s | 均 {T['imread']/n_img*1000:6.1f} ms/张")
  134. rep.append(f" 写裁剪图(累计) : {T['crop_write']:7.2f}s | 均 {T['crop_write']/n_img*1000:6.1f} ms/张")
  135. rep.append(f" 写标注图(累计) : {T['annot_write']:7.2f}s | 均 {T['annot_write']/n_img*1000:6.1f} ms/张")
  136. rep.append(f" {'-'*46}")
  137. rep.append(f" 处理总耗时({n_img}张) : {t_all:7.2f}s | 均 {t_all/n_img*1000:6.1f} ms/张 | 吞吐 {n_img/t_all:.1f} 张/s")
  138. rep.append("=" * 50)
  139. print("\n" + "\n".join(rep))
  140. # 写 timing.log (追加, 带时间戳)
  141. import datetime
  142. with open(os.path.join(OUT, "timing.log"), "a", encoding="utf-8") as lf:
  143. lf.write(f"\n[{datetime.datetime.now():%Y-%m-%d %H:%M:%S}] === 步骤① pokemon_grading.py (评级检测 best.pt) ===\n")
  144. lf.write(f" 样本数: {n_img} 张\n")
  145. lf.write(f" 模型加载 best.pt : {t_load:.2f}s\n")
  146. lf.write(f" best.pt 推理累计 : {T['infer']:.2f}s (均 {T['infer']/n_img*1000:.1f} ms/张)\n")
  147. lf.write(f" 读图 cv2.imread 累计 : {T['imread']:.2f}s (均 {T['imread']/n_img*1000:.1f} ms/张)\n")
  148. lf.write(f" 写裁剪图累计 : {T['crop_write']:.2f}s (均 {T['crop_write']/n_img*1000:.1f} ms/张)\n")
  149. lf.write(f" 写标注图累计 : {T['annot_write']:.2f}s (均 {T['annot_write']/n_img*1000:.1f} ms/张)\n")
  150. lf.write(f" 处理总耗时 : {t_all:.2f}s (均 {t_all/n_img*1000:.1f} ms/张, 吞吐 {n_img/t_all:.1f} 张/s)\n")
  151. print(f"\n✓ 完成. 输出目录: {OUT}")
  152. print(f"✓ 计时已写入: {os.path.join(OUT, 'timing.log')}")