| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- """
- flat_card_no_ocr.py - 步骤②:编号区域 OCR + 单次宽容 + 低分不合规置空
- 链路:
- card_no 裁剪图 → PP-OCRv6_medium_rec → apply_leniency(单次)
- → rec_score≤0.9 且不合规格式 → card_no 置空(blur_unreadable)
- 空结果区分:
- no_detection — 上层无检测框(卡本身无编号),本脚本不产生
- blur_unreadable — 有框但模糊不可读
- rec_empty — 有框但 rec 空文本
- 用法:
- export OUT_DIR=/path/to/output
- ~/miniconda3/envs/paddleocr37/bin/python flat_card_no_ocr.py
- """
- import os, sys, json, time, datetime
- sys.stdout.reconfigure(encoding="utf-8")
- _HERE = os.path.dirname(os.path.abspath(__file__))
- for root in (
- "/home/martin/顾工交接/wzj",
- os.path.expanduser("~/顾工交接/wzj"),
- os.path.abspath(os.path.join(_HERE, "..", "..", "..")),
- ):
- if os.path.isdir(os.path.join(root, "modules")):
- sys.path.insert(0, root)
- break
- from modules.card_no_leniency import finalize_card_no
- OUT_DIR = os.environ.get("OUT_DIR", "/home/martin/顾工交接/wzj/_infer_out")
- REC_MODEL = os.environ.get("REC_MODEL", "PP-OCRv6_medium_rec")
- RECORDS = os.path.join(OUT_DIR, "card_no_crop_records.json")
- SCORE_THR = float(os.environ.get("REC_SCORE_THR", "0.9"))
- REC_KWARGS = {"model_name": REC_MODEL}
- if REC_MODEL.startswith("PP-OCRv6"):
- REC_KWARGS.update(engine="transformers", device="cpu")
- print(f"[OCR] 初始化 TextRecognition({REC_MODEL}) ...")
- from paddleocr import TextRecognition
- _t = time.perf_counter()
- rec = TextRecognition(**REC_KWARGS)
- t_load = time.perf_counter() - _t
- print(f"[OCR] 加载完成 ({t_load:.2f}s)\n")
- records = json.load(open(RECORDS, encoding="utf-8"))
- paths = [r["crop_path"] for r in records]
- print(f"[RUN] {len(paths)} 张裁剪图 (rec-only + 单次宽容 + 低分置空 thr={SCORE_THR})\n")
- _t = time.perf_counter()
- outs = []
- for r in rec.predict(paths):
- outs.append((r.get("rec_text", "") or "", float(r.get("rec_score", 0.0) or 0.0)))
- t_ocr = time.perf_counter() - _t
- ocr_results = {}
- n_empty_rec = n_blur = n_lenient = n_keep = 0
- for meta, (text, score) in zip(records, outs):
- fin = finalize_card_no(text, score, score_threshold=SCORE_THR)
- if fin["leniency_applied"]:
- n_lenient += 1
- er = fin["empty_reason"]
- if er == "rec_empty":
- n_empty_rec += 1
- elif er == "blur_unreadable":
- n_blur += 1
- else:
- n_keep += 1
- ocr_results[meta["file"]] = {
- "rec_text": (text or "").strip(),
- "card_no": fin["card_no"],
- "leniency_applied": fin["leniency_applied"],
- "empty_reason": er, # None / rec_empty / blur_unreadable
- "rec_score": score,
- "det_conf": meta["conf"],
- "crop_wh": meta.get("crop_wh"),
- "status": "detected", # 本文件只处理有检测框的样本
- }
- n = max(len(paths), 1)
- avg = sum(s for _, s in outs) / n if n else 0.0
- print(f"\n[RESULT] {n} 张(有检测框)")
- print(f" 保留编号 {n_keep} | 宽容命中 {n_lenient}")
- print(f" rec空 {n_empty_rec} | 模糊置空(blur_unreadable) {n_blur}")
- print(f" 平均 rec_score {avg:.3f}")
- print("=" * 50)
- print(f"⏱ flat_card_no_ocr | {n} 张 | 加载 {t_load:.2f}s | rec {t_ocr:.2f}s ({n/t_ocr:.1f}/s)")
- print("=" * 50)
- with open(os.path.join(OUT_DIR, "timing.log"), "a", encoding="utf-8") as lf:
- lf.write(f"\n[{datetime.datetime.now():%Y-%m-%d %H:%M:%S}] === flat_card_no_ocr.py ===\n")
- lf.write(f" 有检测框 {n}, 保留 {n_keep}, 宽容 {n_lenient}, rec空 {n_empty_rec}, 模糊置空 {n_blur}\n")
- lf.write(f" 加载 {t_load:.2f}s, rec {t_ocr:.2f}s ({n/t_ocr:.1f}/s)\n")
- json.dump(ocr_results, open(os.path.join(OUT_DIR, "card_no_ocr.json"), "w", encoding="utf-8"),
- ensure_ascii=False, indent=2)
- print(f"✓ {OUT_DIR}/card_no_ocr.json")
|