Upload CRNN.py
Browse files
CRNN.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
import shutil
|
| 6 |
+
import cv2
|
| 7 |
+
|
| 8 |
+
CRNN_loc = 'best_crnn.pth'
|
| 9 |
+
|
| 10 |
+
class CRNN(nn.Module):
|
| 11 |
+
def __init__(self, imgH, nc, nclass, nh):
|
| 12 |
+
super(CRNN, self).__init__()
|
| 13 |
+
self.cnn = nn.Sequential(
|
| 14 |
+
nn.Conv2d(nc,64,3,1,1), nn.ReLU(True), nn.MaxPool2d(2,2),
|
| 15 |
+
nn.Conv2d(64,128,3,1,1), nn.ReLU(True), nn.MaxPool2d(2,2),
|
| 16 |
+
nn.Conv2d(128,256,3,1,1), nn.ReLU(True),
|
| 17 |
+
nn.Conv2d(256,256,3,1,1), nn.ReLU(True), nn.MaxPool2d((2,2),(2,1),(0,1)),
|
| 18 |
+
nn.Conv2d(256,512,3,1,1), nn.BatchNorm2d(512), nn.ReLU(True),
|
| 19 |
+
nn.Conv2d(512,512,3,1,1), nn.BatchNorm2d(512), nn.ReLU(True), nn.MaxPool2d((2,2),(2,1),(0,1)),
|
| 20 |
+
nn.Conv2d(512,512,2,1,0), nn.ReLU(True)
|
| 21 |
+
)
|
| 22 |
+
self.rnn1 = nn.LSTM(512, nh, bidirectional=True, dropout=0.3)
|
| 23 |
+
self.rnn2 = nn.LSTM(nh*2, nh, bidirectional=True, dropout=0.3)
|
| 24 |
+
self.fc = nn.Linear(nh*2, nclass)
|
| 25 |
+
|
| 26 |
+
def forward(self, x):
|
| 27 |
+
conv = self.cnn(x)
|
| 28 |
+
b, c, h, w = conv.size()
|
| 29 |
+
conv = conv.squeeze(2).permute(2, 0, 1)
|
| 30 |
+
recurrent, _ = self.rnn1(conv)
|
| 31 |
+
recurrent, _ = self.rnn2(recurrent)
|
| 32 |
+
output = self.fc(recurrent)
|
| 33 |
+
return output.permute(1, 0, 2)
|
| 34 |
+
|
| 35 |
+
# Device
|
| 36 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 37 |
+
|
| 38 |
+
# Recreate model with same hyperparameters
|
| 39 |
+
CRNN_model = CRNN(imgH=32, nc=1, nclass= 303, nh=256).to(DEVICE)
|
| 40 |
+
|
| 41 |
+
# Load weights
|
| 42 |
+
checkpoint = torch.load(CRNN_loc, map_location=DEVICE)
|
| 43 |
+
CRNN_model.load_state_dict(checkpoint)
|
| 44 |
+
|
| 45 |
+
CRNN_model.eval()
|
| 46 |
+
print("Model loaded and ready!")
|
| 47 |
+
|
| 48 |
+
def preprocess_image(img_path):
|
| 49 |
+
img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
|
| 50 |
+
print(img.shape)
|
| 51 |
+
h, w= img.shape
|
| 52 |
+
new_w = max(int(32 * (w / h)), 32)
|
| 53 |
+
img = cv2.resize(img, (new_w, 32))
|
| 54 |
+
img = (img / 255.0).astype(np.float32)
|
| 55 |
+
img = (img - 0.5) / 0.5 # normalize
|
| 56 |
+
img = torch.from_numpy(img).unsqueeze(0).unsqueeze(0) # [B,C,H,W]
|
| 57 |
+
return img.to(DEVICE)
|
| 58 |
+
|
| 59 |
+
BLANK = 302
|
| 60 |
+
def ctc_greedy_decoder(output, idx_to_char, blank=BLANK):
|
| 61 |
+
# output: [B, T, nclass]
|
| 62 |
+
preds = output.softmax(2).argmax(2) # [B, T]
|
| 63 |
+
preds = preds[0].cpu().numpy().tolist()
|
| 64 |
+
|
| 65 |
+
decoded = []
|
| 66 |
+
prev = -1
|
| 67 |
+
for p in preds:
|
| 68 |
+
if p != prev and p != blank: # collapse repeats, ignore blanks
|
| 69 |
+
decoded.append(idx_to_char.get(p, "�"))
|
| 70 |
+
prev = p
|
| 71 |
+
return "".join(decoded)
|
| 72 |
+
|
| 73 |
+
amharic_mapping = {
|
| 74 |
+
# Basic consonants + vowels
|
| 75 |
+
0: 'ሀ', 1: 'ሁ', 2: 'ሂ', 3: 'ሃ', 4: 'ሄ', 5: 'ህ', 6: 'ሆ',
|
| 76 |
+
7: 'ለ', 8: 'ሉ', 9: 'ሊ', 10: 'ላ', 11: 'ሌ', 12: 'ል', 13: 'ሎ', 14: 'ሏ',
|
| 77 |
+
15: 'ሐ', 16: 'ሑ', 17: 'ሒ', 18: 'ሓ', 19: 'ሔ', 20: 'ሕ', 21: 'ሖ', 22: 'ሗ',
|
| 78 |
+
23: 'መ', 24: 'ሙ', 25: 'ሚ', 26: 'ማ', 27: 'ሜ', 28: 'ም', 29: 'ሞ', 30: 'ሟ',
|
| 79 |
+
31: 'ሠ', 32: 'ሡ', 33: 'ሢ', 34: 'ሣ', 35: 'ሤ', 36: 'ሥ', 37: 'ሦ', 38: 'ሧ',
|
| 80 |
+
39: 'ረ', 40: 'ሩ', 41: 'ሪ', 42: 'ራ', 43: 'ሬ', 44: 'ር', 45: 'ሮ', 46: 'ሯ',
|
| 81 |
+
47: 'ሰ', 48: 'ሱ', 49: 'ሲ', 50: 'ሳ', 51: 'ሴ', 52: 'ስ', 53: 'ሶ', 54: 'ሷ',
|
| 82 |
+
55: 'ሸ', 56: 'ሹ', 57: 'ሺ', 58: 'ሻ', 59: 'ሼ', 60: 'ሽ', 61: 'ሾ', 62: 'ሿ',
|
| 83 |
+
63: 'ቀ', 64: 'ቁ', 65: 'ቂ', 66: 'ቃ', 67: 'ቄ', 68: 'ቅ', 69: 'ቆ', 70: 'ቋ',
|
| 84 |
+
71: 'በ', 72: 'ቡ', 73: 'ቢ', 74: 'ባ', 75: 'ቤ', 76: 'ብ', 77: 'ቦ', 78: 'ቧ',
|
| 85 |
+
79: 'ቨ', 80: 'ቩ', 81: 'ቪ', 82: 'ቫ', 83: 'ቬ', 84: 'ቭ', 85: 'ቮ', 86: 'ቯ',
|
| 86 |
+
87: 'ተ', 88: 'ቱ', 89: 'ቲ', 90: 'ታ', 91: 'ቴ', 92: 'ት', 93: 'ቶ', 94: 'ቷ',
|
| 87 |
+
95: 'ቸ', 96: 'ቹ', 97: 'ቺ', 98: 'ቻ', 99: 'ቼ', 100: 'ች', 101: 'ቾ', 102: 'ቿ',
|
| 88 |
+
103: 'ኀ', 104: 'ኁ', 105: 'ኂ', 106: 'ኃ', 107: 'ኄ', 108: 'ኅ', 109: 'ኆ',
|
| 89 |
+
110: 'ነ', 111: 'ኑ', 112: 'ኒ', 113: 'ና', 114: 'ኔ', 115: 'ን', 116: 'ኖ', 117: 'ኗ',
|
| 90 |
+
118: 'ኘ', 119: 'ኙ', 120: 'ኚ', 121: 'ኛ', 122: 'ኜ', 123: 'ኝ', 124: 'ኞ', 125: 'ኟ',
|
| 91 |
+
126: 'አ', 127: 'ኡ', 128: 'ኢ', 129: 'ኣ', 130: 'ኤ', 131: 'እ', 132: 'ኦ', 133: 'ኧ',
|
| 92 |
+
134: 'ከ', 135: 'ኩ', 136: 'ኪ', 137: 'ካ', 138: 'ኬ', 139: 'ክ', 140: 'ኮ', 141: 'ኯ',
|
| 93 |
+
142: 'ኸ', 143: 'ኹ', 144: 'ኺ', 145: 'ኻ', 146: 'ኼ', 147: 'ኽ', 148: 'ኾ', 149: 'ዀ', 150: 'ዃ',
|
| 94 |
+
151: 'ወ', 152: 'ዉ', 153: 'ዊ', 154: 'ዋ', 155: 'ዌ', 156: 'ው', 157: 'ዎ', 158: 'ዏ',
|
| 95 |
+
159: 'ዐ', 160: 'ዑ', 161: 'ዒ', 162: 'ዓ', 163: 'ዔ', 164: 'ዕ', 165: 'ዖ',
|
| 96 |
+
166: 'ዘ', 167: 'ዙ', 168: 'ዚ', 169: 'ዛ', 170: 'ዜ', 171: 'ዝ', 172: 'ዞ', 173: 'ዟ',
|
| 97 |
+
174: 'ዠ', 175: 'ዡ', 176: 'ዢ', 177: 'ዣ', 178: 'ዤ', 179: 'ዥ', 180: 'ዦ', 181: 'ዧ',
|
| 98 |
+
182: 'የ', 183: 'ዩ', 184: 'ዪ', 185: 'ያ', 186: 'ዬ', 187: 'ይ', 188: 'ዮ',
|
| 99 |
+
189: 'ደ', 190: 'ዱ', 191: 'ዲ', 192: 'ዳ', 193: 'ዴ', 194: 'ድ', 195: 'ዶ', 196: 'ዷ',
|
| 100 |
+
197: 'ጀ', 198: 'ጁ', 199: 'ጂ', 200: 'ጃ', 201: 'ጄ', 202: 'ጅ', 203: 'ጆ', 204: 'ጇ',
|
| 101 |
+
205: 'ገ', 206: 'ጉ', 207: 'ጊ', 208: 'ጋ', 209: 'ጌ', 210: 'ግ', 211: 'ጎ', 212: 'ጏ',
|
| 102 |
+
213: 'ጠ', 214: 'ጡ', 215: 'ጢ', 216: 'ጣ', 217: 'ጤ', 218: 'ጥ', 219: 'ጦ', 220: 'ጧ',
|
| 103 |
+
221: 'ጨ', 222: 'ጩ', 223: 'ጪ', 224: 'ጫ', 225: 'ጬ', 226: 'ጭ', 227: 'ጮ', 228: 'ጯ',
|
| 104 |
+
229: 'ጰ', 230: 'ጱ', 231: 'ጲ', 232: 'ጳ', 233: 'ጴ', 234: 'ጵ', 235: 'ጶ', 236: 'ጷ',
|
| 105 |
+
237: 'ጸ', 238: 'ጹ', 239: 'ጺ', 240: 'ጻ', 241: 'ጼ', 242: 'ጽ', 243: 'ጾ', 244: 'ጿ',
|
| 106 |
+
245: 'ፀ', 246: 'ፁ', 247: 'ፂ', 248: 'ፃ', 249: 'ፄ', 250: 'ፅ', 251: 'ፆ', 252: 'ፇ',
|
| 107 |
+
253: 'ፈ', 254: 'ፉ', 255: 'ፊ', 256: 'ፋ', 257: 'ፌ', 258: 'ፍ', 259: 'ፎ', 260: 'ፏ',
|
| 108 |
+
261: 'ፐ', 262: 'ፑ', 263: 'ፒ', 264: 'ፓ', 265: 'ፔ', 266: 'ፕ', 267: 'ፖ', 268: 'ፗ',
|
| 109 |
+
|
| 110 |
+
# Special characters / punctuation / numbers
|
| 111 |
+
269: "!", 270: ":-", 271: "<", 272: "(", 273: "«", 274: "፥", 275: "%", 276: "»", 277: ")",
|
| 112 |
+
278: ">", 279: ".", 280: "+", 281: "፣", 282: "-", 283: "።", 284: "/",
|
| 113 |
+
285: "0", 286: "1", 287: "2", 288: "3", 289: "4", 290: "5", 291: "6", 292: "7", 293: "8", 294: "9",
|
| 114 |
+
295: "፡", 296: "፤", 297: "...", 298: "*", 299: "#", 300: "?"
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
def CNNR_Interface(img_path):
|
| 118 |
+
img = preprocess_image(img_path)
|
| 119 |
+
with torch.no_grad():
|
| 120 |
+
output = CRNN_model(img) # [B, T, nclass]
|
| 121 |
+
|
| 122 |
+
predicted_text = ctc_greedy_decoder(output, amharic_mapping, blank=BLANK)
|
| 123 |
+
print("Predicted:", predicted_text)
|
| 124 |
+
return predicted_text
|
| 125 |
+
|
| 126 |
+
def clear_folder(folder_path):
|
| 127 |
+
# Create folder if it does not exist
|
| 128 |
+
if not os.path.exists(folder_path):
|
| 129 |
+
os.makedirs(folder_path)
|
| 130 |
+
print(f"📁 Created folder '{folder_path}'")
|
| 131 |
+
return
|
| 132 |
+
|
| 133 |
+
# Clear existing contents
|
| 134 |
+
for filename in os.listdir(folder_path):
|
| 135 |
+
file_path = os.path.join(folder_path, filename)
|
| 136 |
+
try:
|
| 137 |
+
if os.path.isfile(file_path) or os.path.islink(file_path):
|
| 138 |
+
os.remove(file_path) # Remove file or symlink
|
| 139 |
+
elif os.path.isdir(file_path):
|
| 140 |
+
shutil.rmtree(file_path) # Remove subdirectory
|
| 141 |
+
except Exception as e:
|
| 142 |
+
print(f"⚠️ Failed to delete {file_path}. Reason: {e}")
|
| 143 |
+
print(f"✅ Cleared all contents in '{folder_path}'")
|
| 144 |
+
|
| 145 |
+
if __name__ == "__main__":
|
| 146 |
+
path = 'row6_word5.jpg'
|
| 147 |
+
print(CNNR_Interface(path))
|