build_dual_gallery_88384.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. # -*- coding: utf-8 -*-
  2. """build_dual_gallery_88384.py - 新卡池(88,384张)双区特征库构建
  3. 与 pokemon/build_dual_gallery.py 完全同源的预处理流程(YOLO裁卡 → letterbox392 →
  4. split_half → DualCardFeatureExtractor),**唯一差异**是图片定位方式:
  5. - 原脚本: card_master_all.csv 的 img_url → md5(url).jpg
  6. - 本脚本: train_meta_88407.json 的 fname(= img_url 尾段原始文件名)
  7. 原脚本一字未改,本文件为新增,互不影响。
  8. 输出(写到独立目录,不覆盖任何现有文件):
  9. data/gallery_v819/gallery_upper_features.npy
  10. data/gallery_v819/gallery_lower_features.npy
  11. data/gallery_v819/gallery_dual_meta.json
  12. 用法(pytorch 环境, GPU):
  13. python build_dual_gallery_88384.py # 全量
  14. python build_dual_gallery_88384.py --limit 500 # 冒烟
  15. """
  16. import os
  17. import sys
  18. import json
  19. import time
  20. sys.stdout.reconfigure(encoding="utf-8")
  21. _THIS = os.path.dirname(os.path.abspath(__file__))
  22. _ROOT = _THIS if os.path.exists(os.path.join(_THIS, "config.py")) else os.path.dirname(_THIS)
  23. sys.path.insert(0, _ROOT)
  24. import argparse
  25. import numpy as np
  26. from PIL import Image
  27. import config
  28. from modules.yolo_detector import CardDetector
  29. from modules.feature_extractor_dual import (
  30. DualCardFeatureExtractor, letterbox392, split_half,
  31. )
  32. META_IN = os.path.join(_ROOT, "data", "train_meta_88407.json")
  33. GI = config.GALLERY_IMG_DIR
  34. OUT_DIR = os.path.join(_ROOT, "data", "gallery_v819")
  35. OUT_U = os.path.join(OUT_DIR, "gallery_upper_features.npy")
  36. OUT_L = os.path.join(OUT_DIR, "gallery_lower_features.npy")
  37. OUT_M = os.path.join(OUT_DIR, "gallery_dual_meta.json")
  38. BATCH = 64
  39. def main():
  40. ap = argparse.ArgumentParser()
  41. ap.add_argument("--limit", type=int, default=0)
  42. ap.add_argument("--batch", type=int, default=BATCH)
  43. args = ap.parse_args()
  44. os.makedirs(OUT_DIR, exist_ok=True)
  45. print(f"[INIT] META={META_IN}", flush=True)
  46. print(f"[INIT] GALLERY_IMG_DIR={GI}", flush=True)
  47. print(f"[INIT] UPPER_MODEL={config.UPPER_MODEL_PATH}", flush=True)
  48. print(f"[INIT] LOWER_MODEL={config.LOWER_MODEL_PATH}", flush=True)
  49. print(f"[INIT] OUT={OUT_DIR}", flush=True)
  50. detector = CardDetector(config.YOLO_MODEL_PATH)
  51. extractor = DualCardFeatureExtractor(
  52. config.UPPER_MODEL_PATH, config.LOWER_MODEL_PATH, batch_size=args.batch,
  53. )
  54. cards = json.load(open(META_IN, encoding="utf-8"))["records"]
  55. if args.limit:
  56. cards = cards[: args.limit]
  57. print(f"[INIT] META: {len(cards)} 条待处理\n", flush=True)
  58. feats_u, feats_l, metas = [], [], []
  59. buf_u, buf_l, buf_meta = [], [], []
  60. n_done = n_noimg = n_fail = 0
  61. t0 = time.time()
  62. def flush():
  63. if not buf_u:
  64. return
  65. fu = extractor.extract_upper(buf_u)
  66. fl = extractor.extract_lower(buf_l)
  67. for i, m in enumerate(buf_meta):
  68. if np.isnan(fu[i]).any() or np.isnan(fl[i]).any():
  69. continue
  70. feats_u.append(fu[i])
  71. feats_l.append(fl[i])
  72. metas.append(m)
  73. buf_u.clear()
  74. buf_l.clear()
  75. buf_meta.clear()
  76. for card in cards:
  77. n_done += 1
  78. img_path = os.path.join(GI, card["fname"])
  79. if not os.path.exists(img_path):
  80. n_noimg += 1
  81. continue
  82. try:
  83. arr = np.array(Image.open(img_path).convert("RGB"))
  84. crop = detector.detect_and_crop(arr)
  85. if crop is None:
  86. crop = arr
  87. lb = letterbox392(Image.fromarray(crop))
  88. up, lo = split_half(lb)
  89. except Exception as e:
  90. n_fail += 1
  91. if n_fail <= 10:
  92. print(f" [FAIL] {card['fname']}: {e}", flush=True)
  93. continue
  94. buf_u.append(up)
  95. buf_l.append(lo)
  96. buf_meta.append({
  97. "card_id": card.get("card_id", ""),
  98. "card_name_ch": card.get("card_name_ch", ""),
  99. "language": card.get("language", ""),
  100. "year": card.get("year", ""),
  101. "card_no": card.get("card_no", ""),
  102. "pg_label": card.get("pg_label", ""),
  103. })
  104. if len(buf_u) >= args.batch:
  105. flush()
  106. if n_done % 1000 == 0:
  107. flush()
  108. print(f" [{n_done}/{len(cards)}] 有效{len(feats_u)} 无图{n_noimg} 失败{n_fail} "
  109. f"({(time.time()-t0)/60:.1f}min)", flush=True)
  110. flush()
  111. feats_u_arr = np.array(feats_u, dtype=np.float32)
  112. feats_l_arr = np.array(feats_l, dtype=np.float32)
  113. np.save(OUT_U, feats_u_arr)
  114. np.save(OUT_L, feats_l_arr)
  115. with open(OUT_M, "w", encoding="utf-8") as f:
  116. json.dump({"card_ids": [m["card_id"] for m in metas], "metas": metas},
  117. f, ensure_ascii=False)
  118. print(f"\n[DONE] upper={feats_u_arr.shape} lower={feats_l_arr.shape} "
  119. f"meta={len(metas)} 无图{n_noimg} 失败{n_fail} "
  120. f"耗时{(time.time()-t0)/60:.1f}min", flush=True)
  121. print(f"[DONE] -> {OUT_DIR}", flush=True)
  122. if __name__ == "__main__":
  123. main()