| """ |
| ํ๊ตญ ๋ฒํธํ OCR - KLPR_v1 (Model v5) |
| Hugging Face Gradio App |
| """ |
|
|
| import gradio as gr |
| import torch |
| import torch.nn as nn |
| from PIL import Image |
| import torchvision.transforms as transforms |
| import numpy as np |
|
|
| |
| |
| |
| class CRNN(nn.Module): |
| def __init__(self, img_height, num_chars, rnn_hidden=256): |
| super(CRNN, self).__init__() |
|
|
| |
| self.cnn = nn.Sequential( |
| nn.Conv2d(1, 64, kernel_size=3, padding=1), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d((2, 2)), |
|
|
| nn.Conv2d(64, 128, kernel_size=3, padding=1), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d((2, 2)), |
|
|
| nn.Conv2d(128, 256, kernel_size=3, padding=1), |
| nn.BatchNorm2d(256), |
| nn.ReLU(inplace=True), |
|
|
| nn.Conv2d(256, 256, kernel_size=3, padding=1), |
| nn.BatchNorm2d(256), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d((2, 1)), |
|
|
| nn.Conv2d(256, 512, kernel_size=3, padding=1), |
| nn.BatchNorm2d(512), |
| nn.ReLU(inplace=True), |
|
|
| nn.Conv2d(512, 512, kernel_size=3, padding=1), |
| nn.BatchNorm2d(512), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d((2, 1)), |
|
|
| nn.Conv2d(512, 512, kernel_size=3, padding=1), |
| nn.BatchNorm2d(512), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d((2, 1)) |
| ) |
|
|
| self.rnn = nn.LSTM(512, rnn_hidden, bidirectional=True, num_layers=2, batch_first=True) |
| self.fc = nn.Linear(rnn_hidden * 2, num_chars) |
|
|
| def forward(self, x): |
| conv = self.cnn(x) |
| b, c, h, w = conv.size() |
| conv = conv.squeeze(2).permute(0, 2, 1) |
| rnn_out, _ = self.rnn(conv) |
| output = self.fc(rnn_out) |
| return output |
|
|
| |
| |
| |
| def decode_predictions(outputs, itos, blank_idx=0): |
| """CTC ๋์ฝ๋ฉ""" |
| preds = outputs.argmax(2).detach().cpu().numpy() |
|
|
| decoded = [] |
| for pred in preds: |
| char_list = [] |
| prev_idx = blank_idx |
| for idx in pred: |
| if idx != blank_idx and idx != prev_idx: |
| char_list.append(itos[int(idx)]) |
| prev_idx = idx |
| decoded.append(''.join(char_list)) |
| return decoded |
|
|
| |
| |
| |
| def preprocess_image(image, img_height=32, max_width=200): |
| """๋ฒํธํ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ""" |
| |
| if not isinstance(image, Image.Image): |
| if isinstance(image, np.ndarray): |
| image = Image.fromarray(image.astype('uint8')) |
|
|
| image = image.convert('L') |
|
|
| |
| w, h = image.size |
| new_w = min(int(img_height * w / h), max_width) |
| image = image.resize((new_w, img_height), Image.LANCZOS) |
|
|
| |
| new_img = Image.new('L', (max_width, img_height), 255) |
| new_img.paste(image, (0, 0)) |
|
|
| |
| transform = transforms.Compose([ |
| transforms.ToTensor(), |
| transforms.Normalize((0.5,), (0.5,)) |
| ]) |
|
|
| return transform(new_img).unsqueeze(0) |
|
|
| |
| |
| |
| print("๋ชจ๋ธ ๋ก๋ฉ ์ค...") |
| checkpoint_path = 'best_ocr_one_line.pth' |
| checkpoint = torch.load(checkpoint_path, map_location='cpu') |
|
|
| img_h = checkpoint.get('img_h', 32) |
| max_w = checkpoint.get('max_w', 200) |
| itos = checkpoint['itos'] |
| num_chars = len(itos) |
|
|
| device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
| model = CRNN(img_h, num_chars, rnn_hidden=256).to(device) |
| model.load_state_dict(checkpoint['model_state']) |
| model.eval() |
|
|
| print(f"โ ๋ชจ๋ธ ๋ก๋ ์๋ฃ (Device: {device})") |
| print(f" - Epoch: {checkpoint.get('epoch', '?')}") |
| print(f" - Val Acc: {checkpoint.get('val_acc', '?'):.2%}") |
|
|
| |
| |
| |
| def predict_license_plate(image): |
| """๋ฒํธํ ์ด๋ฏธ์ง์์ ํ
์คํธ ์์ธก""" |
| if image is None: |
| return "์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํด์ฃผ์ธ์." |
|
|
| try: |
| |
| image_tensor = preprocess_image(image, img_h, max_w).to(device) |
|
|
| |
| with torch.no_grad(): |
| outputs = model(image_tensor).log_softmax(2) |
| predictions = decode_predictions(outputs, itos) |
|
|
| result = predictions[0] |
| return result if result else "(์ธ์ ๊ฒฐ๊ณผ ์์)" |
|
|
| except Exception as e: |
| return f"์ค๋ฅ ๋ฐ์: {str(e)}" |
|
|
| |
| |
| |
| demo = gr.Interface( |
| fn=predict_license_plate, |
| inputs=gr.Image(type="pil", label="๋ฒํธํ ์ด๋ฏธ์ง"), |
| outputs=gr.Textbox(label="์ธ์ ๊ฒฐ๊ณผ"), |
| title="๐ ํ๊ตญ ๋ฒํธํ OCR - KLPR v2", |
| description=""" |
| ํ๊ตญ ์๋์ฐจ ๋ฒํธํ์ ์ธ์ํ๋ OCR ๋ชจ๋ธ์
๋๋ค. |
| |
| **๋ชจ๋ธ ์ ๋ณด:** |
| - Model: CRNN (CNN + Bidirectional LSTM + CTC) |
| - Validation Accuracy: 91.23% |
| - Epoch: 18 |
| - ์ง์ ๋ฌธ์: 77๊ฐ (ํ๊ธ + ์ซ์ + ์ถ๊ฐ ํน์ ์ง์ญ๋ช
) |
| |
| **์ฌ์ฉ ๋ฐฉ๋ฒ:** |
| 1. ๋ฒํธํ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ธ์ |
| 2. ์๋์ผ๋ก ๋ฒํธํ ๋ฒํธ๊ฐ ์ธ์๋ฉ๋๋ค |
| """, |
| api_name="predict" |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|