From ceb272f1bf994fc54e0ad9fa62116848553d18d8 Mon Sep 17 00:00:00 2001 From: songhyeonsoo Date: Thu, 21 May 2026 13:06:28 +0900 Subject: [PATCH] docs: rewrite HANDOFF with PGNet track conclusion and next-direction options PGNet baseline finalized at step1-20260520_0937 (epoch 8, f_score_e2e 0.144). Documented diagnostic history including misdiagnoses (cv2 viz misled "Korean failure", char-level polygon hypothesis disproved by code reading) and verified facts (single-line Korean works, two-line region names fail, official PGNet step1 is "still low" by design, no Korean PGNet pretrained publicly exists). Next direction shifted toward YOLO + PaddleOCR 2-stage given (a) no Korean LP PGNet validation cases in literature, (b) standard Korean LP pipeline uses 2-stage with reported 95%+ accuracy, (c) PGNet step2 effect unverifiable without real data. Co-Authored-By: Claude Sonnet 4.6 --- HANDOFF.md | 150 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 8d17ae3..55bf03b 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -6,20 +6,53 @@ ## 30초 요약 -- **목표**: PGNet 한 모델로 한국 LP 4종 (자가용/영업/전기/화물) 검출+인식 → ONNX export -- **현재 상태 (2026-05-18)**: Step1 학습 중 (run `step1-20260518_1010`, 200 epoch, from-scratch) -- **진단 히스토리**: - 1. 라벨이 판 전체 box → tight box 수정 → **검출 f_score 0.52→0.7 개선**, 그러나 인식 여전히 실패 - 2. eval 시각화로 확인: type1 plate가 한글 뒤 공백에서 검출 분리 ("37도1563"→"37도"+"1563"), 한글 전부 □ - 3. 코드 검증: CTC는 alignment-free라 char-level polygon 무의미. 진짜 원인은 한글 뒤 36px 공백이 PGNet TCL 연결을 끊는 것 + 한글 클래스 불균형 - 4. `gen_type1`의 `col += 60 + 36` → `col += 60` (공백 제거) 후 재학습 -- **확인 포인트**: epoch 5~10에서 `eval/hit_str_count` 상승 + 검출 미분리 여부 → 공백 제거 효과 판정 -- **남은 이슈**: 한글 클래스 불균형 (plate당 숫자~6 : 한글 1), augmentation 부재 — 공백 효과 확인 후 별도 대응 -- **다음 단계**: 학습 완료 후 → eval f_score_e2e 확인 → Step2 fine-tune (실차 데이터) +- **목표**: 한국 LP 4종(자가용/영업/전기/화물) 검출+인식 → ONNX export +- **현재 상태 (2026-05-21)**: **PGNet 트랙 결산 단계**. 최종 baseline은 `step1-20260520_0937` (epoch 8 best) +- **결과 수치**: f_score_e2e 0.144, hit_str_count 561/3669 (15.3%), seqerr 0.78, recall 0.71 +- **결론**: 합성 step1만으로는 한계 명확. 공식 가이드도 "step1 alone is still low"라고 명시. 한국 LP에 PGNet 사용 검증 사례 없음. +- **다음 방향 (미결정)**: (A) 실차 데이터 수집 → step2 fine-tune (B) **YOLO + PaddleOCR 2-stage로 전환 (권장)** (C) PGNet 한계로 종료 - **운영 원칙**: 로컬 Mac은 코드 작성·git만, 모든 실행은 외부 GPU 서버에서 --- +## PGNet 트랙 결산 (2026-05-21) + +### 시도한 것들과 결과 + +| 시도 | 변경 | 결과 | +|---|---|---| +| 초기 (라벨이 판 전체) | — | f_score_e2e ≈ 0.0007, hit_str_count 32/2866 | +| **tight box로 수정** | 라벨을 글자 행만 감싸도록 | f_score_e2e 0.0007→0.014 (단줄 검출 분리 해소) | +| **type1 공백 제거** | `col += 60 + 36` → `col += 60` (한글 뒤 36px 공백) | 단줄 검출 더 안정화 | +| **stratified 샘플링 + 두줄 비중 ↑** | 한글/region 균등화, type3/4 14%→45% | f_score_e2e **0.144** ← 현재 best | + +### 직접 확인된 사실 (`_0937` epoch 8, 10장 추론) + +- **단줄 type1 한글**: 부분적 정확 (5장 중 2장 정확, 3장은 한글 부분 누락) +- **두줄 region명** (서울/제주/광주 등 16개): **5/5 모두 실패** (서울→우, 제주→주) +- **두줄 아랫줄**: 검출은 됨 (이전 0/5 → 5/5) ✅ 하지만 인식이 "7777777" 같은 반복 출력 +- **숫자**: 가끔 over/under count (87→7, 12→112) + +### 진단 (정확한 원인) + +1. **CTC 글자 과분할**: 모델이 같은 글자를 여러 frame에 걸쳐 출력 ("4442", "44444444"). 합성 데이터가 너무 깔끔하고 augmentation 없음. +2. **두줄 region명 학습 부족**: stratified+비중↑으로 노출은 늘렸지만 30 epoch에 정체. 200 epoch 돌려도 epoch 8 best 못 넘김. +3. **train ≈ eval**: 과적합 아님. 학습 신호 자체가 부족한 게 본질. + +### 한계 확인 + +- **공식 PGNet 가이드**: "step1 alone is still low" — 우리 0.144는 step1 단독의 typical 영역 +- **PaddleOCR PGNet 공식 성능**: Total-Text e2e_f_score 60.03 (step2 real-data fine-tune 후) +- **한국 LP에 PGNet 사용한 공개 사례**: 없음. 모두 YOLO+PP-OCR 등 2-stage +- **한국어로 학습된 PGNet weight**: 공개된 것 없음 (HuggingFace, ModelScope, PaddleOCR 모두 확인) + +### 잘못 진단했다 정정한 것들 + +- 초반 "한글 7/7 □ = 한글 인식 실패" 결론은 **cv2 시각화 폰트 한계**였음. Unicode 디코드해보니 단줄 한글은 실제로 잘 읽고 있었음. **시각화만 보고 진단 금지** — 항상 텍스트 출력으로 확인. +- "char-level polygon이 필요하다" → PaddleOCR pg_process.py 코드 확인 결과 CTC는 alignment-free이고 char position 입력 안 받음. **무의미**. + +--- + ## 환경 ### 1. 로컬 Mac (코드 작성 전용) @@ -30,13 +63,14 @@ ### 2. 외부 GPU 서버 - **SSH**: `ssh cuuva@192.168.10.189` (사내망 전용) - **호스트**: Ubuntu 25.10, RTX 5090 32GB, NVIDIA driver 590.48.01, CUDA 13.1 +- **커널 주의 (2026-05-20 사고)**: unattended-upgrade가 `linux-image-6.17.0-23` 설치 후 재부팅 시 GRUB이 자동으로 -23 선택 → nvidia 모듈이 -23에 없어서 GPU 죽음. `sudo grub-reboot "Advanced options>Ubuntu, with Linux 6.17.0-22-generic" && sudo reboot`으로 -22 복귀. 영구 고정은 `/etc/default/grub` 수정 필요 (미적용). - **레이아웃**: ``` /home/cuuva/workspace/ ├── PaddleOCR/ # git clone release/2.7 ├── kr_lp_pgnet/ # 이 repo ├── train_data/ - │ ├── kr_lp_synth/ # Step1 학습용 (50k, tight box 라벨) + │ ├── kr_lp_synth/ # 50k 합성 (tight box, stratified, 두줄 45%) │ ├── region_check_y/ # 영업용 region 16종 검증 │ └── region_check_g/ # 친환경 region 16종 검증 └── wheels/ @@ -55,42 +89,34 @@ --- -## 저장소 +## 저장소 / wandb - **Git remote**: `http://192.168.10.110/TTA/kr_lp_pgnet.git` (사내 Gitea) -- **브랜치**: `main`만 사용 - -## wandb - -- **Entity**: `hssong_cuuva` / **Project**: `kr_lp_pgnet` -- **현재 run**: `step1-20260518_0216` -- **dashboard**: https://wandb.ai/hssong_cuuva/kr_lp_pgnet +- **wandb**: `hssong_cuuva/kr_lp_pgnet` — https://wandb.ai/hssong_cuuva/kr_lp_pgnet +- **PGNet baseline run**: `step1-20260520_0937` (epoch 8 best, f_score_e2e 0.144) --- -## 핵심 이슈 히스토리 - -### 라벨 박스 문제 (2026-05-18 수정) -기존 학습 데이터(`kr_lp_synth`)의 라벨 박스가 판 전체 영역이었음: -``` -수정 전: "points": [[0,0],[520,0],[520,110],[0,110]] ← 판 전체 -수정 후: "points": [[35,13],[467,96],...] ← 글자 행만 -``` -결과: 100 epoch 학습해도 f_score_e2e ≈ 0 (hit_str_count 32/2867). -원인: training은 라벨과 직접 비교하므로 loss가 내려갔지만, 추론 시 post-processing이 -판 전체 heat map을 받아 박스를 쪼개거나 이상하게 복원 → 인식 실패. -`generate_synthetic.py`의 tight polygon 생성 코드는 이미 수정되어 있었으나 -서버 데이터가 구버전으로 생성된 상태였음. 데이터 재생성 후 재학습. +## 핵심 트러블슈팅 메모 ### NVML stale (재발 가능) -컨테이너 장시간 실행 후 `nvidia-smi` 에러. `docker restart kr_lp_pgnet`으로 해결. +컨테이너 장시간 실행 후 `nvidia-smi` 에러 또는 추론 시 CUDAPlace 실패. `docker restart kr_lp_pgnet`으로 해결. -### OOM -batch 32 → OOM. **batch 16 확정**. +### infer_e2e.py 결과를 시각화로만 보면 안 됨 +cv2 폰트가 한글 못 그려서 "□"로 나옴. 실제 예측 텍스트는 `predicts.txt`에 Unicode escape로 저장됨. `수` 등 디코드해서 확인. + +### infer_e2e.py 단일 호출은 predicts.txt 덮어씀 +디렉토리 입력(`Global.infer_img=/tmp/dir`)으로 한 번에 여러 장 처리하면 모두 누적 기록됨. + +### CPU 추론 fallback +NVML 죽었을 때 `Global.use_gpu=False`로 추론 가능. 느림(~10초/장). ### cuDNN 9.17 강제 paddle sm_120 wheel은 cuDNN 9.17 빌드. `setup_server.sh`에서 `nvidia-cudnn-cu13>=9.17` 강제 설치. +### OOM +batch 32 → OOM. **batch 16 확정**. + --- ## 자주 쓰는 운영 명령 @@ -103,22 +129,26 @@ ssh cuuva@192.168.10.189 'docker exec kr_lp_pgnet sh -c "tail -5 /workspace/Padd ssh cuuva@192.168.10.189 'docker exec kr_lp_pgnet nvidia-smi --query-gpu=memory.used,utilization.gpu --format=csv' # 학습 중단 -ssh cuuva@192.168.10.189 'docker exec kr_lp_pgnet pkill -f tools/train.py' +ssh cuuva@192.168.10.189 'docker exec kr_lp_pgnet pkill -9 -f tools/train.py' # 컨테이너 재시작 (NVML 풀기) ssh cuuva@192.168.10.189 'docker restart kr_lp_pgnet' -# 재학습 (처음부터) -ssh cuuva@192.168.10.189 'docker exec -d kr_lp_pgnet bash /workspace/kr_lp_pgnet/scripts/run_step1.sh' +# 재학습 (처음부터, 짧게 30 epoch 검증) +ssh cuuva@192.168.10.189 'docker exec -d kr_lp_pgnet bash -c "EPOCHS=30 bash /workspace/kr_lp_pgnet/scripts/run_step1.sh"' + +# CPU 추론 (NVML 죽었을 때 또는 GPU 점유 시) +ssh cuuva@192.168.10.189 'docker exec kr_lp_pgnet sh -c "cd /workspace/PaddleOCR && python3.10 tools/infer_e2e.py -c configs/e2e/kr_lp_pgnet.yml -o Global.use_gpu=False Global.infer_img=/path Global.pretrained_model=./output/kr_lp_pgnet_20260520_0937/best_accuracy Global.load_static_weights=False"' ``` --- ## 데이터 관련 알려진 이슈 -- **자산이 만들 수 없는 글자**: `하`, `호`, `배` — Step2 실차 데이터로 보충 +- **자산이 만들 수 없는 글자**: `하`, `호`, `배` — 실차 데이터로 보충 필요 - **REGION_MAP 추정값**: `A='서울'`만 검증, 나머지는 `tools/region_check.py`로 확인 필요 - **세종 누락**: 자산 region 16개, 실제 광역지자체 17개 (세종 미포함) +- **합성 한계**: 실차에 있는 배경/조명/원근/오염 없음 → augmentation 또는 실차 fine-tune 없이는 일반화 어려움 --- @@ -126,16 +156,32 @@ ssh cuuva@192.168.10.189 'docker exec -d kr_lp_pgnet bash /workspace/kr_lp_pgnet ### ✅ 완료 1. 환경 셋업 (컨테이너, paddle sm_120, cuDNN 9.17, PaddleOCR release/2.7) -2. dict 67자 + config (pad_num=67, max_text_length=10, valid_set=partvgg) -3. 합성기 4 plate type + tight word-level polygon 라벨 -4. wandb 연동 -5. make_gt_mat.py (eval GT 생성) -6. run_step1.sh 타임스탬프 기반 output 관리 - -### 🔄 진행 중 -- Step1 학습 (run `step1-20260518_0216`, 200 epoch, 50k tight-box 데이터) - -### 📋 다음 단계 -1. Step1 완료 후 f_score_e2e 확인 (이전 0.0007 → 대폭 개선 기대) -2. Step2 fine-tune — 실차 한국 LP 사진 수집·라벨링 -3. ONNX export +2. dict 67자 + config +3. 합성기 4 plate type + tight polygon + stratified 샘플링 + 두줄 비중 45% +4. wandb 연동, make_gt_mat.py, run_step1.sh 타임스탬프 +5. **PGNet baseline 확정**: `_0937` epoch 8, f_score_e2e 0.144 + +### ⏸ PGNet 트랙 종료 (의사결정 대기) +PGNet에서 의미있는 추가 개선은 실차 step2 없이는 어려움. 그리고 step2 효과도 공식 자료에 정량 비교 없어 미지수. + +### 📋 다음 단계 옵션 +1. **(권장) YOLO + PaddleOCR 2-stage 새 트랙 시작** + - 새 디렉토리 `kr_lp_2stage/` 또는 새 repo + - 검출: YOLOv8 (실차 데이터 또는 pre-trained LP detector) + - 인식: PaddleOCR PP-OCRv3 Korean fine-tune (우리 50k 합성 plate 그대로 활용 가능) + - 시작 검증: PP-OCRv3 Korean을 우리 합성에 무학습 inference → 출발선 측정 +2. **(보조) 실차 데이터 수집 시작** + - 둘 다 결국 실차 데이터로 fine-tune 해야 production 정확도 나옴 + - 최소 detection 500장, recognition 5000장 권장 (PaddleOCR 공식) +3. **(폐기) PGNet step2 시도** + - 효과 미지수, 한국 LP에 PGNet 사용 검증 사례 없음 + +--- + +## 참고 링크 + +- [PaddleOCR PGNet Documentation](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7/doc/doc_en/algorithm_e2e_pgnet_en.md) — 2단계 학습 명시, step1 alone "still low" +- [PaddleOCR Fine-tuning Guide](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7/doc/doc_en/finetune_en.md) — 최소 데이터 기준 (det 500, rec 5000) +- [PaddleX License Plate Tutorial](https://paddlepaddle.github.io/PaddleX/main/en/practical_tutorials/ocr_det_license_tutorial.html) — 공식 LP 전용 튜토리얼 (Chinese 기준) +- [PGNet Paper AAAI 2021](https://www.aaai.org/AAAI21Papers/AAAI-2885.WangP.pdf) — Ablation 수치는 PDF 직접 확인 필요 +- [YOLOv11 + PaddleOCR LP 예제](https://github.com/XZhangTX/LPPNET-LicensePlateRecognition-YOLOv11-PaddleOCR)