flat_card_no_ocr.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. """
  2. flat_card_no_ocr.py - 步骤②:编号区域 OCR + 单次宽容 + 低分不合规置空
  3. 链路:
  4. card_no 裁剪图 → PP-OCRv6_medium_rec → apply_leniency(单次)
  5. → rec_score≤0.9 且不合规格式 → card_no 置空(blur_unreadable)
  6. 空结果区分:
  7. no_detection — 上层无检测框(卡本身无编号),本脚本不产生
  8. blur_unreadable — 有框但模糊不可读
  9. rec_empty — 有框但 rec 空文本
  10. 用法:
  11. export OUT_DIR=/path/to/output
  12. ~/miniconda3/envs/paddleocr37/bin/python flat_card_no_ocr.py
  13. """
  14. import os, sys, json, time, datetime
  15. sys.stdout.reconfigure(encoding="utf-8")
  16. _HERE = os.path.dirname(os.path.abspath(__file__))
  17. for root in (
  18. "/home/martin/顾工交接/wzj",
  19. os.path.expanduser("~/顾工交接/wzj"),
  20. os.path.abspath(os.path.join(_HERE, "..", "..", "..")),
  21. ):
  22. if os.path.isdir(os.path.join(root, "modules")):
  23. sys.path.insert(0, root)
  24. break
  25. from modules.card_no_leniency import finalize_card_no
  26. OUT_DIR = os.environ.get("OUT_DIR", "/home/martin/顾工交接/wzj/_infer_out")
  27. REC_MODEL = os.environ.get("REC_MODEL", "PP-OCRv6_medium_rec")
  28. RECORDS = os.path.join(OUT_DIR, "card_no_crop_records.json")
  29. SCORE_THR = float(os.environ.get("REC_SCORE_THR", "0.9"))
  30. REC_KWARGS = {"model_name": REC_MODEL}
  31. if REC_MODEL.startswith("PP-OCRv6"):
  32. REC_KWARGS.update(engine="transformers", device="cpu")
  33. print(f"[OCR] 初始化 TextRecognition({REC_MODEL}) ...")
  34. from paddleocr import TextRecognition
  35. _t = time.perf_counter()
  36. rec = TextRecognition(**REC_KWARGS)
  37. t_load = time.perf_counter() - _t
  38. print(f"[OCR] 加载完成 ({t_load:.2f}s)\n")
  39. records = json.load(open(RECORDS, encoding="utf-8"))
  40. paths = [r["crop_path"] for r in records]
  41. print(f"[RUN] {len(paths)} 张裁剪图 (rec-only + 单次宽容 + 低分置空 thr={SCORE_THR})\n")
  42. _t = time.perf_counter()
  43. outs = []
  44. for r in rec.predict(paths):
  45. outs.append((r.get("rec_text", "") or "", float(r.get("rec_score", 0.0) or 0.0)))
  46. t_ocr = time.perf_counter() - _t
  47. ocr_results = {}
  48. n_empty_rec = n_blur = n_lenient = n_keep = 0
  49. for meta, (text, score) in zip(records, outs):
  50. fin = finalize_card_no(text, score, score_threshold=SCORE_THR)
  51. if fin["leniency_applied"]:
  52. n_lenient += 1
  53. er = fin["empty_reason"]
  54. if er == "rec_empty":
  55. n_empty_rec += 1
  56. elif er == "blur_unreadable":
  57. n_blur += 1
  58. else:
  59. n_keep += 1
  60. ocr_results[meta["file"]] = {
  61. "rec_text": (text or "").strip(),
  62. "card_no": fin["card_no"],
  63. "leniency_applied": fin["leniency_applied"],
  64. "empty_reason": er, # None / rec_empty / blur_unreadable
  65. "rec_score": score,
  66. "det_conf": meta["conf"],
  67. "crop_wh": meta.get("crop_wh"),
  68. "status": "detected", # 本文件只处理有检测框的样本
  69. }
  70. n = max(len(paths), 1)
  71. avg = sum(s for _, s in outs) / n if n else 0.0
  72. print(f"\n[RESULT] {n} 张(有检测框)")
  73. print(f" 保留编号 {n_keep} | 宽容命中 {n_lenient}")
  74. print(f" rec空 {n_empty_rec} | 模糊置空(blur_unreadable) {n_blur}")
  75. print(f" 平均 rec_score {avg:.3f}")
  76. print("=" * 50)
  77. print(f"⏱ flat_card_no_ocr | {n} 张 | 加载 {t_load:.2f}s | rec {t_ocr:.2f}s ({n/t_ocr:.1f}/s)")
  78. print("=" * 50)
  79. with open(os.path.join(OUT_DIR, "timing.log"), "a", encoding="utf-8") as lf:
  80. lf.write(f"\n[{datetime.datetime.now():%Y-%m-%d %H:%M:%S}] === flat_card_no_ocr.py ===\n")
  81. lf.write(f" 有检测框 {n}, 保留 {n_keep}, 宽容 {n_lenient}, rec空 {n_empty_rec}, 模糊置空 {n_blur}\n")
  82. lf.write(f" 加载 {t_load:.2f}s, rec {t_ocr:.2f}s ({n/t_ocr:.1f}/s)\n")
  83. json.dump(ocr_results, open(os.path.join(OUT_DIR, "card_no_ocr.json"), "w", encoding="utf-8"),
  84. ensure_ascii=False, indent=2)
  85. print(f"✓ {OUT_DIR}/card_no_ocr.json")