Sompote commited on
Commit
7ced5c0
·
verified ·
1 Parent(s): 22280fb

Upload 6 files

Browse files
Files changed (6) hide show
  1. README.md +78 -6
  2. app.py +260 -0
  3. config/data.yaml +105 -0
  4. models/best.pt +3 -0
  5. models/read_char.pt +3 -0
  6. requirements.txt +10 -0
README.md CHANGED
@@ -1,13 +1,85 @@
 
1
  ---
2
- title: License Plate V 2
3
- emoji: 🚀
4
- colorFrom: gray
5
- colorTo: purple
6
  sdk: streamlit
7
- sdk_version: 1.42.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: License plate detection
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
  ---
3
+ title: Thai License Plate Detection V1.2
4
+ emoji: 🚗
5
+ colorFrom: blue
6
+ colorTo: green
7
  sdk: streamlit
8
+ sdk_version: "1.29.0"
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+
15
+ # Thai License Plate Recognition App
16
+
17
+ This Streamlit application performs Thai license plate detection and recognition using deep learning models.
18
+
19
+ ## Features
20
+
21
+ - License plate detection using YOLO
22
+ - Character recognition for Thai license plates
23
+ - Province name detection and recognition
24
+ - Real-time processing with visual feedback
25
+ - User-friendly web interface
26
+
27
+ ## Setup
28
+
29
+ 1. Install the required dependencies:
30
+ ```bash
31
+ pip install -r requirements.txt
32
+ ```
33
+
34
+ 2. Download the required model files and place them in the `models` directory:
35
+ - `best.pt` - YOLO model for license plate detection
36
+ - `read_char.pt` - YOLO model for character recognition
37
+
38
+ 3. Ensure the configuration file `config/data.yaml` is present with character mappings.
39
+
40
+ ## Running the Application
41
+
42
+ To run the application:
43
+
44
+ ```bash
45
+ streamlit run app.py
46
+ ```
47
+
48
+ The application will be available at `http://localhost:8501` by default.
49
+
50
+ ## Usage
51
+
52
+ 1. Open the application in your web browser
53
+ 2. Click the "Choose an image..." button to upload an image containing a Thai license plate
54
+ 3. Wait for the processing to complete
55
+ 4. View the results:
56
+ - Detected license plate number
57
+ - Detected province name
58
+ - Visual detection results
59
+
60
+ ## Models
61
+
62
+ The application uses three main models:
63
+ 1. YOLO model for license plate detection
64
+ 2. YOLO model for character recognition
65
+ 3. TrOCR model for province text recognition (automatically downloaded)
66
+
67
+ ## Directory Structure
68
+
69
+ ```
70
+ streamlitapp/
71
+ ├── app.py
72
+ ├── requirements.txt
73
+ ├── README.md
74
+ ├── models/
75
+ │ ├── best.pt
76
+ │ └── read_char.pt
77
+ └── config/
78
+ └── data.yaml
79
+ ```
80
+
81
+ ## Notes
82
+
83
+ - The application requires an internet connection for the first run to download the TrOCR model
84
+ - Supported image formats: JPG, JPEG, PNG
85
+ - For optimal results, ensure the license plate is clearly visible in the image
app.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ import cv2
4
+ import numpy as np
5
+ from transformers import TrOCRProcessor, VisionEncoderDecoderModel
6
+ from ultralytics import YOLO
7
+ import Levenshtein
8
+ import yaml
9
+ import os
10
+ import io
11
+ import tempfile
12
+ import torchvision
13
+
14
+
15
+ class LicensePlateProcessor:
16
+ def __init__(self):
17
+ # Load models for plate detection
18
+ self.yolo_detector = YOLO('models/best.pt') # For plate detection
19
+ self.char_reader = YOLO('models/read_char.pt') # For character reading
20
+
21
+ # Load TrOCR for province detection
22
+ self.processor_plate = TrOCRProcessor.from_pretrained('openthaigpt/thai-trocr')
23
+ self.model_plate = VisionEncoderDecoderModel.from_pretrained('openthaigpt/thai-trocr')
24
+
25
+ # Load character mapping from yaml
26
+ with open('config/data.yaml', 'r', encoding='utf-8') as f:
27
+ data_config = yaml.safe_load(f)
28
+ self.char_mapping = data_config.get('char_mapping', {})
29
+ self.names = data_config['names']
30
+
31
+ # Load province list
32
+ self.thai_provinces = [
33
+ "กรุงเทพมหานคร", "กระบี่", "กาญจนบุรี", "กาฬสินธุ์", "กำแพงเพชร", "ขอนแก่น",
34
+ "จันทบุรี", "ฉะเชิงเทรา", "ชลบุรี", "ชัยนาท", "ชัยภูมิ", "ชุมพร", "เชียงราย",
35
+ "เชียงใหม่", "ตรัง", "ตราด", "ตาก", "นครนายก", "นครปฐม", "นครพนม", "นครราชสีมา",
36
+ "นครศรีธรรมราช", "นครสวรรค์", "นราธิวาส", "น่าน", "บึงกาฬ", "บุรีรัมย์", "ปทุมธานี",
37
+ "ประจวบคีรีขันธ์", "ปราจีนบุรี", "ปัตตานี", "พะเยา", "พังงา", "พัทลุง", "พิจิตร",
38
+ "พิษณุโลก", "เพชรบูรณ์", "เพชรบุรี", "แพร่", "ภูเก็ต", "มหาสารคาม", "มุกดาหาร",
39
+ "แม่ฮ่องสอน", "ยโสธร", "ยะลา", "ร้อยเอ็ด", "ระนอง", "ระยอง", "ราชบุรี", "ลพบุรี",
40
+ "ลำปาง", "ลำพูน", "เลย", "ศรีสะเกษ", "สกลนคร", "สงขลา", "สมุทรปราการ", "สมุทรสงคราม",
41
+ "สมุทรสาคร", "สระแก้ว", "สระบุรี", "สิงห์บุรี", "สุโขทัย", "สุพรรณบุรี", "สุราษฎร์ธานี",
42
+ "สุรินทร์", "หนองคาย", "หนองบัวลำภู", "อำนาจเจริญ", "อุดรธานี", "อุทัยธานี",
43
+ "อุบลราชธานี", "อ่างทอง"
44
+ ]
45
+
46
+ self.CONF_THRESHOLD = 0.3
47
+
48
+ def _map_class_to_char(self, class_name):
49
+ """Map class to character using yaml mapping"""
50
+ if str(class_name) in self.char_mapping:
51
+ return self.char_mapping[str(class_name)]
52
+ return str(class_name)
53
+
54
+ def get_closest_province(self, input_text):
55
+ """Find closest matching province"""
56
+ min_distance = float('inf')
57
+ closest_province = None
58
+
59
+ for province in self.thai_provinces:
60
+ distance = Levenshtein.distance(input_text, province)
61
+ if distance < min_distance:
62
+ min_distance = distance
63
+ closest_province = province
64
+
65
+ return closest_province, min_distance
66
+
67
+ def read_plate_characters(self, plate_image):
68
+ """Read characters from plate image"""
69
+ results = self.char_reader.predict(plate_image, conf=0.3)
70
+
71
+ detections = []
72
+ for r in results:
73
+ boxes = r.boxes
74
+ for box in boxes:
75
+ x1, y1, x2, y2 = map(int, box.xyxy[0])
76
+ confidence = float(box.conf[0])
77
+ class_id = int(box.cls[0])
78
+
79
+ mapped_char = self._map_class_to_char(self.names[class_id])
80
+
81
+ detections.append({
82
+ 'char': mapped_char,
83
+ 'confidence': confidence,
84
+ 'bbox': (x1, y1, x2, y2)
85
+ })
86
+
87
+ # Sort detections left to right
88
+ detections.sort(key=lambda x: x['bbox'][0])
89
+
90
+ # Combine characters
91
+ plate_text = ''.join(det['char'] for det in detections)
92
+ return plate_text
93
+
94
+ def process_image(self, image_path: str):
95
+ try:
96
+ # Read image
97
+ image = cv2.imread(image_path)
98
+ if image is None:
99
+ print(f"Error: Could not read image from {image_path}")
100
+ return None
101
+
102
+ # Detect license plate location
103
+ results = self.yolo_detector(image)
104
+
105
+ data = {"plate_number": "", "province": "", "raw_province": ""}
106
+
107
+ # Save visualization
108
+ output_image = image.copy()
109
+
110
+ for result in results:
111
+ for box in result.boxes:
112
+ confidence = float(box.conf)
113
+ if confidence < self.CONF_THRESHOLD:
114
+ continue
115
+
116
+ x1, y1, x2, y2 = map(int, box.xyxy.flatten())
117
+ cropped_image = image[y1:y2, x1:x2]
118
+
119
+ # Draw rectangle on output image
120
+ color = (0, 255, 0) if int(box.cls.item()) == 0 else (255, 0, 0)
121
+ cv2.rectangle(output_image, (x1, y1), (x2, y2), color, 2)
122
+
123
+ if int(box.cls.item()) == 0: # License plate number
124
+ # Read characters using YOLO character reader
125
+ data["plate_number"] = self.read_plate_characters(cropped_image)
126
+
127
+ elif int(box.cls.item()) == 1: # Province
128
+ # Process province using TrOCR
129
+ cropped_image_gray = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY)
130
+ equalized_image = cv2.equalizeHist(cropped_image_gray)
131
+ _, thresh_image = cv2.threshold(equalized_image, 65, 255, cv2.THRESH_BINARY_INV)
132
+ cropped_image_3d = cv2.cvtColor(thresh_image, cv2.COLOR_GRAY2RGB)
133
+ resized_image = cv2.resize(cropped_image_3d, (128, 32))
134
+
135
+ pixel_values = self.processor_plate(resized_image, return_tensors="pt").pixel_values
136
+ generated_ids = self.model_plate.generate(pixel_values)
137
+ generated_text = self.processor_plate.batch_decode(generated_ids, skip_special_tokens=True)[0]
138
+
139
+ generated_province, _ = self.get_closest_province(generated_text)
140
+ data["raw_province"] = generated_text
141
+ data["province"] = generated_province
142
+
143
+ # Save the output image
144
+ cv2.imwrite('output_detection.jpg', output_image)
145
+ return data
146
+
147
+ except Exception as e:
148
+ print(f"Error processing image: {str(e)}")
149
+ return None
150
+
151
+ def main():
152
+ st.set_page_config(
153
+ page_title="Thai License Plate Recognition",
154
+ layout="wide"
155
+ )
156
+
157
+ st.title("Thai License Plate Recognition")
158
+ st.write("Upload an image to detect and read Thai license plates")
159
+
160
+ # Initialize processor
161
+ @st.cache_resource
162
+ def load_processor():
163
+ return LicensePlateProcessor()
164
+
165
+ processor = load_processor()
166
+
167
+ # File uploader
168
+ uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
169
+
170
+ if uploaded_file is not None:
171
+ # Create columns for side-by-side display
172
+ col1, col2 = st.columns(2)
173
+
174
+ # Display original image
175
+ with col1:
176
+ st.subheader("Original Image")
177
+ image = Image.open(uploaded_file)
178
+ st.image(image, use_column_width=True)
179
+
180
+ # Convert PIL Image to OpenCV format for processing
181
+ image_array = np.array(image)
182
+ if len(image_array.shape) == 3 and image_array.shape[2] == 4:
183
+ # Convert RGBA to RGB if needed
184
+ image_array = cv2.cvtColor(image_array, cv2.COLOR_RGBA2RGB)
185
+ image_cv = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
186
+
187
+ # Process image
188
+ with st.spinner("Processing image..."):
189
+ try:
190
+ # Save the OpenCV image for processing
191
+ temp_path = 'temp_input.jpg'
192
+ cv2.imwrite(temp_path, image_cv)
193
+
194
+ # Process the image using the processor
195
+ results = processor.process_image(temp_path)
196
+
197
+ # Clean up temporary input file
198
+ os.remove(temp_path)
199
+
200
+ if results:
201
+ # Display results
202
+ st.subheader("Detection Results")
203
+
204
+ # Create a styled container for results
205
+ results_container = st.container()
206
+ with results_container:
207
+ st.markdown(f"""
208
+ <div style='background-color: #f0f2f6; padding: 20px; border-radius: 10px;'>
209
+ <h3>License Plate: {results['plate_number']}</h3>
210
+ <h3>Province: {results['province']}</h3>
211
+ <p>Raw Province Text: {results['raw_province']}</p>
212
+ </div>
213
+ """, unsafe_allow_html=True)
214
+
215
+ # Display detection visualization
216
+ with col2:
217
+ st.subheader("Detection Visualization")
218
+ if os.path.exists('output_detection.jpg'):
219
+ # Read and convert the output image from BGR to RGB
220
+ output_image = cv2.imread('output_detection.jpg')
221
+ output_image_rgb = cv2.cvtColor(output_image, cv2.COLOR_BGR2RGB)
222
+ st.image(output_image_rgb, use_column_width=True)
223
+ # Clean up output image
224
+ os.remove('output_detection.jpg')
225
+ else:
226
+ st.error("No license plate detected in the image.")
227
+
228
+ except Exception as e:
229
+ st.error(f"Error processing image: {str(e)}")
230
+ # Clean up any temporary files in case of error
231
+ if os.path.exists('temp_input.jpg'):
232
+ os.remove('temp_input.jpg')
233
+ if os.path.exists('output_detection.jpg'):
234
+ os.remove('output_detection.jpg')
235
+
236
+ # Add information about the application
237
+ with st.expander("About This Application"):
238
+ st.markdown("""
239
+ ### Thai License Plate Recognition System
240
+
241
+ This application uses advanced computer vision and deep learning to:
242
+ - Detect license plates in images using YOLO
243
+ - Read Thai license plate numbers using character recognition
244
+ - Identify province names using TrOCR
245
+ - Provide visual detection results
246
+
247
+ #### How to Use:
248
+ 1. Click the 'Browse files' button above
249
+ 2. Select an image containing a Thai license plate
250
+ 3. Wait for the processing to complete
251
+ 4. View the results and detection visualization
252
+
253
+ #### Technologies Used:
254
+ - YOLO for license plate detection
255
+ - Custom YOLO model for character recognition
256
+ - TrOCR for province text recognition
257
+ """)
258
+
259
+ if __name__ == "__main__":
260
+ main()
config/data.yaml ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ names:
2
+ 0: '0'
3
+ 1: '1'
4
+ 2: '2'
5
+ 3: '3'
6
+ 4: '4'
7
+ 5: '5'
8
+ 6: '6'
9
+ 7: '7'
10
+ 8: '8'
11
+ 9: '9'
12
+ 10: 'ก'
13
+ 11: 'ข'
14
+ 12: 'ค'
15
+ 13: 'ง'
16
+ 14: 'จ'
17
+ 15: 'ฉ'
18
+ 16: 'ช'
19
+ 17: 'ซ'
20
+ 18: 'ฌ'
21
+ 19: 'ญ'
22
+ 20: 'ฎ'
23
+ 21: 'ฏ'
24
+ 22: 'ฐ'
25
+ 23: 'ฑ'
26
+ 24: 'ฒ'
27
+ 25: 'ณ'
28
+ 26: 'ด'
29
+ 27: 'ต'
30
+ 28: 'ถ'
31
+ 29: 'ท'
32
+ 30: 'ธ'
33
+ 31: 'น'
34
+ 32: 'บ'
35
+ 33: 'ป'
36
+ 34: 'ผ'
37
+ 35: 'ฝ'
38
+ 36: 'พ'
39
+ 37: 'ฟ'
40
+ 38: 'ภ'
41
+ 39: 'ม'
42
+ 40: 'ย'
43
+ 41: 'ร'
44
+ 42: 'ล'
45
+ 43: 'ว'
46
+ 44: 'ศ'
47
+ 45: 'ษ'
48
+ 46: 'ส'
49
+ 47: 'ห'
50
+ 48: 'ฬ'
51
+ 49: 'อ'
52
+ 50: 'ฮ'
53
+
54
+ char_mapping:
55
+ '0': '0'
56
+ '1': '1'
57
+ '2': '2'
58
+ '3': '3'
59
+ '4': '4'
60
+ '5': '5'
61
+ '6': '6'
62
+ '7': '7'
63
+ '8': '8'
64
+ '9': '9'
65
+ '10': 'ก'
66
+ '11': 'ข'
67
+ '12': 'ค'
68
+ '13': 'ง'
69
+ '14': 'จ'
70
+ '15': 'ฉ'
71
+ '16': 'ช'
72
+ '17': 'ซ'
73
+ '18': 'ฌ'
74
+ '19': 'ญ'
75
+ '20': 'ฎ'
76
+ '21': 'ฏ'
77
+ '22': 'ฐ'
78
+ '23': 'ฑ'
79
+ '24': 'ฒ'
80
+ '25': 'ณ'
81
+ '26': 'ด'
82
+ '27': 'ต'
83
+ '28': 'ถ'
84
+ '29': 'ท'
85
+ '30': 'ธ'
86
+ '31': 'น'
87
+ '32': 'บ'
88
+ '33': 'ป'
89
+ '34': 'ผ'
90
+ '35': 'ฝ'
91
+ '36': 'พ'
92
+ '37': 'ฟ'
93
+ '38': 'ภ'
94
+ '39': 'ม'
95
+ '40': 'ย'
96
+ '41': 'ร'
97
+ '42': 'ล'
98
+ '43': 'ว'
99
+ '44': 'ศ'
100
+ '45': 'ษ'
101
+ '46': 'ส'
102
+ '47': 'ห'
103
+ '48': 'ฬ'
104
+ '49': 'อ'
105
+ '50': 'ฮ'
models/best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3b1da8d9362a1005aa5b060b0ac53b4622677e753eded2893da10b6a69bc9fb7
3
+ size 5468691
models/read_char.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:871501b08ee035447680cfef41a764da1dc9ed67fabdac1bcb3b67543a2678e1
3
+ size 19224275
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.31.1
2
+ Pillow==10.0.0
3
+ opencv-python-headless==4.8.0.74
4
+ numpy==1.24.3
5
+ transformers==4.36.2
6
+ ultralytics==8.0.196
7
+ python-Levenshtein==0.23.0
8
+ PyYAML==6.0.1
9
+ torch==2.1.0
10
+ torchvision==0.16.0