KK KTP arsip
This commit is contained in:
174
image_processor.py
Normal file
174
image_processor.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
KTP Image Processor - Enhanced Version
|
||||
Crop, resize, dan enhanced preprocessing untuk OCR yang lebih akurat
|
||||
|
||||
Standar e-KTP: 85.6mm x 53.98mm = 1011x638 px @300dpi
|
||||
|
||||
Improvements based on Context7 documentation:
|
||||
- Pillow ImageEnhance for contrast/sharpness
|
||||
- OpenCV CLAHE for adaptive histogram equalization
|
||||
- Denoising for cleaner text detection
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
from PIL import Image, ImageEnhance, ImageFilter
|
||||
|
||||
KTP_WIDTH = 1011
|
||||
KTP_HEIGHT = 638
|
||||
|
||||
|
||||
def enhance_image_pil(image_path: str, output_path: str = None) -> str:
|
||||
"""
|
||||
Enhance image using Pillow (from Context7 docs)
|
||||
- Contrast enhancement
|
||||
- Sharpness enhancement
|
||||
- Detail filter for text clarity
|
||||
|
||||
Args:
|
||||
image_path: Path to input image
|
||||
output_path: Optional path to save enhanced image
|
||||
|
||||
Returns:
|
||||
Path to enhanced image
|
||||
"""
|
||||
try:
|
||||
img = Image.open(image_path)
|
||||
|
||||
# Contrast enhancement (factor 1.3 from Context7)
|
||||
contrast = ImageEnhance.Contrast(img)
|
||||
img = contrast.enhance(1.3)
|
||||
|
||||
# Sharpness enhancement
|
||||
sharpness = ImageEnhance.Sharpness(img)
|
||||
img = sharpness.enhance(1.2)
|
||||
|
||||
# Apply detail filter for text clarity
|
||||
img = img.filter(ImageFilter.DETAIL)
|
||||
|
||||
# Save
|
||||
if output_path is None:
|
||||
base, ext = os.path.splitext(image_path)
|
||||
output_path = f"{base}_enhanced.jpg"
|
||||
|
||||
img.save(output_path, quality=95)
|
||||
print(f" [ENHANCE] Pillow enhanced: {output_path}")
|
||||
|
||||
return output_path
|
||||
|
||||
except Exception as e:
|
||||
print(f" [ENHANCE] Pillow error: {e}")
|
||||
return image_path # Return original if enhancement fails
|
||||
|
||||
|
||||
def enhance_image_cv(image: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Enhance image using OpenCV (from Context7 docs)
|
||||
- CLAHE for adaptive histogram equalization
|
||||
- Denoising
|
||||
- Sharpening using Laplacian kernel
|
||||
|
||||
Args:
|
||||
image: OpenCV image (BGR)
|
||||
|
||||
Returns:
|
||||
Enhanced image (BGR)
|
||||
"""
|
||||
try:
|
||||
# Convert to grayscale for processing
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Denoise (from Context7)
|
||||
denoised = cv2.fastNlMeansDenoising(gray, None, 10, 7, 21)
|
||||
|
||||
# Enhanced CLAHE for documents
|
||||
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
|
||||
enhanced = clahe.apply(denoised)
|
||||
|
||||
# Sharpen using kernel (from Context7)
|
||||
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], dtype=np.float32)
|
||||
sharpened = cv2.filter2D(enhanced, -1, kernel)
|
||||
|
||||
# Convert back to BGR
|
||||
return cv2.cvtColor(sharpened, cv2.COLOR_GRAY2BGR)
|
||||
|
||||
except Exception as e:
|
||||
print(f" [ENHANCE] OpenCV error: {e}")
|
||||
return image # Return original if enhancement fails
|
||||
|
||||
|
||||
def crop_by_ocr_bounds(image, ocr_results, padding=0.03):
|
||||
"""Crop image based on OCR bounding boxes"""
|
||||
if not ocr_results:
|
||||
return image
|
||||
|
||||
h, w = image.shape[:2]
|
||||
all_x = []
|
||||
all_y = []
|
||||
|
||||
for r in ocr_results:
|
||||
box = r.get('box', [])
|
||||
if len(box) >= 4:
|
||||
try:
|
||||
for point in box:
|
||||
if isinstance(point, (list, tuple)) and len(point) >= 2:
|
||||
all_x.append(float(point[0]))
|
||||
all_y.append(float(point[1]))
|
||||
except:
|
||||
continue
|
||||
|
||||
if not all_x or not all_y:
|
||||
return image
|
||||
|
||||
x1 = int(max(0, min(all_x) - w * padding))
|
||||
y1 = int(max(0, min(all_y) - h * padding))
|
||||
x2 = int(min(w, max(all_x) + w * padding))
|
||||
y2 = int(min(h, max(all_y) + h * padding))
|
||||
|
||||
return image[y1:y2, x1:x2]
|
||||
|
||||
|
||||
def normalize_ktp_image(image_path, output_path=None, ocr_results=None):
|
||||
"""
|
||||
Normalisasi gambar KTP:
|
||||
1. Crop berdasarkan OCR bounds
|
||||
2. Ensure landscape
|
||||
3. Resize ke ukuran standar
|
||||
"""
|
||||
try:
|
||||
image = cv2.imread(image_path)
|
||||
if image is None:
|
||||
return None, False, "Gagal membaca gambar"
|
||||
|
||||
h, w = image.shape[:2]
|
||||
print(f" [IMAGE] Original: {w}x{h}")
|
||||
|
||||
# Crop
|
||||
if ocr_results:
|
||||
image = crop_by_ocr_bounds(image, ocr_results)
|
||||
h, w = image.shape[:2]
|
||||
print(f" [IMAGE] Cropped: {w}x{h}")
|
||||
|
||||
# Landscape
|
||||
if h > w:
|
||||
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
|
||||
|
||||
# Resize
|
||||
resized = cv2.resize(image, (KTP_WIDTH, KTP_HEIGHT),
|
||||
interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
# Save
|
||||
if output_path is None:
|
||||
base, ext = os.path.splitext(image_path)
|
||||
output_path = f"{base}_normalized.jpg"
|
||||
|
||||
cv2.imwrite(output_path, resized, [cv2.IMWRITE_JPEG_QUALITY, 95])
|
||||
print(f" [IMAGE] Saved: {output_path}")
|
||||
|
||||
return output_path, True, f"Normalized to {KTP_WIDTH}x{KTP_HEIGHT}"
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None, False, f"Error: {str(e)}"
|
||||
Reference in New Issue
Block a user