File size: 12,363 Bytes
f02eb0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import os
import time
import cv2  # OpenCV for image handling
import easyocr  # For OCR
import torch  # PyTorch (dependency for YOLO/EasyOCR)
import numpy as np  # For numerical operations
from ultralytics import YOLO  # YOLO model from ultralytics
from flask import Flask, request, render_template, redirect, url_for, flash
from werkzeug.utils import secure_filename
import traceback # To print full error tracebacks
import re # Import regular expressions for post-processing

# --- Configuration ---
UPLOAD_FOLDER = os.path.join('static', 'uploads')
RESULT_FOLDER = os.path.join('static', 'results')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

weights_path = 'best.pt' # Assumes best.pt is in the same folder as app.py
PLATE_CLASS_NAME = 'license_plate'


app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['RESULT_FOLDER'] = RESULT_FOLDER
app.secret_key = 'super secret key' # Needed for flashing messages

# --- Create Folders if they don't exist ---
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(RESULT_FOLDER, exist_ok=True)

# --- Load Models (Load only once when the app starts) ---
reader = None
model = None
model_class_names = {} # Initialize empty

print("Loading EasyOCR Reader...")
try:
    # Use gpu=True if you have a CUDA-enabled GPU and compatible PyTorch/CUDA
    model_dir = os.path.join('.', 'easyocr_models')
    os.makedirs(model_dir, exist_ok=True)
    reader = easyocr.Reader(['en'], gpu=torch.cuda.is_available(), model_storage_directory=model_dir)
    print("EasyOCR Reader loaded successfully.")
except Exception as e:
    print(f"Error loading EasyOCR Reader: {e}. OCR will not work.")
    print(traceback.format_exc()) # Print full traceback

print("Loading YOLO Model...")
try:
    if not os.path.exists(weights_path):
        raise FileNotFoundError(f"YOLO weights file not found at {weights_path}. Please make sure 'best.pt' is in the same directory as app.py or update the path.")
    model = YOLO(weights_path)
    print("YOLO Model loaded successfully.")
    # Store class names if model loaded
    if model:
        model_class_names = model.names
        print("Class names:", model_class_names) # Print class names {index: 'name'}
except Exception as e:
    print(f"Error loading YOLO model from {weights_path}: {e}. Detection will not work.")
    print(traceback.format_exc()) # Print full traceback

# --- Helper Functions ---
def allowed_file(filename):
    """Checks if the filename has an allowed extension."""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def perform_ocr_on_image(img_crop, allowlist='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
    """Performs OCR on a given image crop (NumPy array)."""
    if reader is None:
        print("EasyOCR Reader not loaded. Cannot perform OCR.")
        return "OCR Error"

    try:
        # Convert to grayscale
        gray_img = cv2.cvtColor(img_crop, cv2.COLOR_BGR2GRAY)

        # Resize for consistent input size
        target_height = 64  # You can adjust this value
        scale_ratio = target_height / gray_img.shape[0]
        target_width = int(gray_img.shape[1] * scale_ratio)
        resized_img = cv2.resize(gray_img, (target_width, target_height))

        # Basic preprocessing
        resized_img = cv2.equalizeHist(resized_img)  # Enhance contrast
        
        # Perform OCR
        results = reader.readtext(resized_img, allowlist=allowlist)
        
        # Process results
        if not results:
            return "No text found"
            
        # Take the result with highest confidence
        text = results[0][1]  # Get the text part
        
        # Convert to uppercase and remove any non-alphanumeric characters
        text = text.upper()
        text = re.sub(r'[^A-Z0-9]', '', text)
        
        return text if text else "No text found"


        print(f"Final combined text after post-processing: '{plate_text}'")

        return plate_text if plate_text else "No text found"

    except Exception as e:
        print(f"Error during OCR processing in perform_ocr_on_image: {e}")
        print(traceback.format_exc())
        return "OCR Processing Error"

# ... (rest of run_detection_ocr, Flask routes, and __main__ remain the same) ...

def run_detection_ocr(image_path, result_filename):
    """Runs YOLO detection and EasyOCR on the image."""
    if model is None or reader is None:
        print("Models not loaded. Cannot process image.")
        return None, ["Model Loading Error"]

    detected_texts = []
    result_image_path = None

    try:
        # Read the image
        img = cv2.imread(image_path)
        if img is None:
            print(f"Error: Could not read image at {image_path}")
            return None, ["Image Reading Error"]

        img_copy = img.copy() # Make a copy for drawing

        # Run YOLO detection with original confidence threshold
        print(f"Running YOLO prediction on {image_path}...")
        results = model.predict(img, conf=0.25) # Restore original confidence for detection
        print("YOLO prediction complete.")

        # Process detections
        for result in results:
            boxes = result.boxes
            if boxes is None or len(boxes) == 0:
                print("No boxes detected in this result.")
                continue

            for box in boxes:
                try:
                    # Get class index and name using the loaded names
                    class_id = int(box.cls[0])
                    class_name = model_class_names.get(class_id, 'Unknown')
                    confidence = float(box.conf[0])
                    x1_temp, y1_temp, x2_temp, y2_temp = map(int, box.xyxy[0])
                    print(f"Detected '{class_name}' (Conf: {confidence:.2f}) at [{x1_temp}, {y1_temp}, {x2_temp}, {y2_temp}]")

                    # Check with the CORRECTED class name
                    if class_name.lower() == PLATE_CLASS_NAME.lower(): # Comparison should now work
                        x1, y1, x2, y2 = map(int, box.xyxy[0])
                        padding = 10
                        crop_y1 = max(0, y1 - padding)
                        crop_y2 = min(img.shape[0], y2 + padding)
                        crop_x1 = max(0, x1 - padding)
                        crop_x2 = min(img.shape[1], x2 + padding)

                        if crop_y2 > crop_y1 and crop_x2 > crop_x1:
                            plate_crop = img[crop_y1:crop_y2, crop_x1:crop_x2]

                            # Added print before calling OCR
                            print(f"--- Cropped {PLATE_CLASS_NAME} at [{crop_x1}, {crop_y1}, {crop_x2}, {crop_y2}], attempting OCR ---")
                            plate_text = perform_ocr_on_image(plate_crop) # Function updated with preprocessing/postprocessing
                            print(f"-> OCR Result returned for {PLATE_CLASS_NAME}: {plate_text}")
                            detected_texts.append(plate_text)

                            # Drawing logic
                            label = f"{class_name}: {plate_text}" # Label uses result from OCR call
                            cv2.rectangle(img_copy, (x1, y1), (x2, y2), (0, 255, 0), 2)
                            text_pos = (x1, y1 - 10) if y1 > 20 else (x1, y2 + 20)
                            cv2.putText(img_copy, label, text_pos,
                                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                        else:
                            print(f"Skipping invalid crop dimensions for {PLATE_CLASS_NAME}: y1={crop_y1}, y2={crop_y2}, x1={crop_x1}, x2={crop_x2}")
                    else:
                       # Draw blue boxes for other detected objects (optional)
                       cv2.rectangle(img_copy, (x1_temp, y1_temp), (x2_temp, y2_temp), (255, 0, 0), 1) # Blue box, thinner
                       cv2.putText(img_copy, f"{class_name} {confidence:.2f}", (x1_temp, y1_temp - 5),
                                   cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)


                except Exception as e_inner:
                    print(f"Error processing a specific detection: {e_inner}")
                    print(traceback.format_exc())
                    detected_texts.append("Detection Proc. Error")

        # Save the result image
        result_image_path = os.path.join(app.config['RESULT_FOLDER'], result_filename)
        cv2.imwrite(result_image_path, img_copy)
        print(f"Result image saved to: {result_image_path}")

        return result_image_path, detected_texts

    except Exception as e:
        print(f"Error during detection/OCR: {e}")
        print(traceback.format_exc())
        return None, ["Overall Processing Error"]

# --- Flask Routes ---
@app.route('/', methods=['GET', 'POST'])
def upload_file():
    error_message = None
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)

        if file and allowed_file(file.filename):
            try:
                original_filename = secure_filename(file.filename)
                timestamp = time.strftime("%Y%m%d-%H%M%S")
                unique_filename = f"{timestamp}_{original_filename}"
                upload_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
                file.save(upload_path)
                print(f"File saved to: {upload_path}")

                # --- Call Processing Logic ---
                if model is None or reader is None:
                    return render_template('result.html',
                                           original_image_url=url_for('static', filename=f'uploads/{unique_filename}'),
                                           result_image_url=None,
                                           detected_texts=None,
                                           error_message="AI models failed to load. Cannot process image.")

                result_filename = f"result_{unique_filename}"
                result_image_path, detected_texts = run_detection_ocr(upload_path, result_filename)

                # Generate URLs for the templates
                original_url = url_for('static', filename=f'uploads/{unique_filename}')
                result_url = url_for('static', filename=f'results/{result_filename}') if result_image_path else None

                # *** Filter out error messages before displaying ***
                display_texts = [text for text in detected_texts if "Error" not in text and text != "No text found" and text != "Crop too small" and text != "Invalid Crop"]
                if not display_texts and detected_texts: # If only errors or 'no text', show generic message
                    display_texts = ["No text recognized"]
                elif not detected_texts and not display_texts: # If detection worked but OCR returned nothing valid
                    display_texts = ["No text recognized"]
                elif not detected_texts: # Fallback if processing completely failed
                     display_texts = ["Processing Error"]


                return render_template('result.html',
                                       original_image_url=original_url,
                                       result_image_url=result_url,
                                       detected_texts=display_texts, # Use filtered list
                                       error_message=None if result_image_path else "An error occurred during image processing.")

            except Exception as e:
                error_message = f"An error occurred during file upload or processing: {e}"
                print(f"Error in upload_file route: {e}")
                print(traceback.format_exc())
                # Render index again, show the error
                return render_template('index.html', error_message=error_message)

        else:
            error_message = "Invalid file type. Please upload a PNG, JPG, or JPEG image."
            return render_template('index.html', error_message=error_message)

    # For GET requests
    return render_template('index.html', error_message=None)


# --- Run the App ---
if __name__ == "__main__":
    app.run(debug=True)