Spaces:
Sleeping
Sleeping
Download backend/app/ocr_engine.py from haisyam/PlatDetect: direct link, hf CLI and curl.
- Browser
- Download file 5.24 kB
-
https://huggingface.co/spaces/haisyam/PlatDetect/resolve/main/backend/app/ocr_engine.py
- Command line
-
hf download hf://spaces/haisyam/PlatDetect/backend/app/ocr_engine.py
-
curl -L -o ocr_engine.py https://huggingface.co/spaces/haisyam/PlatDetect/resolve/main/backend/app/ocr_engine.py
5.24 kB
| """ | |
| ocr_engine.py β Multi-preprocessing OCR engine menggunakan EasyOCR. | |
| Menjalankan OCR pada beberapa versi preprocessing dan memilih hasil terbaik. | |
| """ | |
| import cv2 | |
| import numpy as np | |
| from app.plate_formatter import clean_text, format_plate_candidate, score_plate_text | |
| # ββ Singleton EasyOCR Reader ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| _reader = None | |
| def load_reader(): | |
| """Load EasyOCR reader sekali saja (singleton). GPU=False untuk CPU basic.""" | |
| global _reader | |
| if _reader is None: | |
| import easyocr | |
| _reader = easyocr.Reader(['en'], gpu=False) | |
| return _reader | |
| # ββ Preprocessing Functions βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def crop_main_plate_line(plate_crop): | |
| """ | |
| Ambil bagian atas plat saja (sekitar 58% tinggi crop). | |
| Ini untuk menghindari terbacanya masa berlaku (misal '05 26') | |
| yang ada di baris bawah plat Indonesia. | |
| """ | |
| if plate_crop is None or plate_crop.size == 0: | |
| return plate_crop | |
| h, w = plate_crop.shape[:2] | |
| main_line = plate_crop[0:int(h * 0.58), 0:w] | |
| # Pastikan crop tidak terlalu kecil | |
| if main_line.shape[0] < 10 or main_line.shape[1] < 10: | |
| return plate_crop | |
| return main_line | |
| def make_ocr_versions(plate_crop) -> dict: | |
| """ | |
| Membuat beberapa versi preprocessing dari crop plat untuk OCR. | |
| Setiap versi mengoptimalkan aspek berbeda dari gambar plat. | |
| Versi yang dihasilkan: | |
| - gray: Grayscale standar | |
| - clahe: Contrast-Limited Adaptive Histogram Equalization | |
| - blur: Gaussian blur untuk mengurangi noise | |
| - sharp: Sharpening untuk memperjelas tepi karakter | |
| - otsu: Otsu thresholding untuk binarisasi | |
| - inverted: Versi terbalik dari otsu | |
| """ | |
| versions = {} | |
| # Konversi ke grayscale jika belum | |
| if len(plate_crop.shape) == 3: | |
| gray = cv2.cvtColor(plate_crop, cv2.COLOR_BGR2GRAY) | |
| else: | |
| gray = plate_crop.copy() | |
| # 1. Gray β baseline | |
| versions["gray"] = gray | |
| # 2. CLAHE β adaptive contrast enhancement | |
| clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) | |
| versions["clahe"] = clahe.apply(gray) | |
| # 3. Blur β gaussian blur untuk kurangi noise | |
| versions["blur"] = cv2.GaussianBlur(gray, (3, 3), 0) | |
| # 4. Sharp β kernel sharpening | |
| kernel_sharp = np.array([[-1, -1, -1], | |
| [-1, 9, -1], | |
| [-1, -1, -1]]) | |
| versions["sharp"] = cv2.filter2D(gray, -1, kernel_sharp) | |
| # 5. Otsu β binary thresholding otomatis | |
| blurred_for_otsu = cv2.GaussianBlur(gray, (5, 5), 0) | |
| _, otsu = cv2.threshold(blurred_for_otsu, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
| versions["otsu"] = otsu | |
| # 6. Inverted β kebalikan dari otsu | |
| versions["inverted"] = cv2.bitwise_not(otsu) | |
| return versions | |
| # ββ Multi-OCR Reader ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def read_plate_with_multi_ocr(plate_crop) -> dict: | |
| """ | |
| Menjalankan OCR pada semua versi preprocessing dan memilih hasil terbaik | |
| berdasarkan skor format plat Indonesia. | |
| Args: | |
| plate_crop: Gambar crop area plat (numpy array BGR atau grayscale) | |
| Returns: | |
| dict: { | |
| "raw": "H2148BL", | |
| "formatted": "H 2148 BL", | |
| "score": 120, | |
| "version": "clahe" | |
| } | |
| """ | |
| reader = load_reader() | |
| # Ambil baris utama plat (tanpa masa berlaku) | |
| main_crop = crop_main_plate_line(plate_crop) | |
| # Buat semua versi preprocessing | |
| versions = make_ocr_versions(main_crop) | |
| best_result = { | |
| "raw": "", | |
| "formatted": "", | |
| "score": 0, | |
| "version": "none", | |
| } | |
| allowlist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | |
| for version_name, version_img in versions.items(): | |
| try: | |
| # Jalankan OCR | |
| results = reader.readtext( | |
| version_img, | |
| allowlist=allowlist, | |
| detail=1, | |
| paragraph=False, | |
| ) | |
| # Gabungkan semua teks yang terdeteksi | |
| raw_texts = [] | |
| for detection in results: | |
| text = detection[1] if len(detection) > 1 else "" | |
| text = clean_text(text) | |
| if text: | |
| raw_texts.append(text) | |
| combined_raw = "".join(raw_texts) | |
| if not combined_raw: | |
| continue | |
| # Format dan skor | |
| formatted = format_plate_candidate(combined_raw) | |
| score = score_plate_text(combined_raw) | |
| # Update best jika skor lebih tinggi | |
| if score > best_result["score"]: | |
| best_result = { | |
| "raw": combined_raw, | |
| "formatted": formatted, | |
| "score": score, | |
| "version": version_name, | |
| } | |
| except Exception: | |
| continue | |
| return best_result | |