Sanjayraju30 commited on
Commit
9ac49a2
·
verified ·
1 Parent(s): 753fcb8

Update ocr_engine.py

Browse files
Files changed (1) hide show
  1. ocr_engine.py +293 -157
ocr_engine.py CHANGED
@@ -5,13 +5,11 @@ import re
5
  import logging
6
  from datetime import datetime
7
  import os
8
- from PIL import Image, ImageEnhance
9
- import pytesseract
10
 
11
- # Set up logging for detailed debugging
12
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
13
 
14
- # Initialize EasyOCR (enable GPU if available)
15
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
16
 
17
  # Directory for debug images
@@ -26,188 +24,326 @@ def save_debug_image(img, filename_suffix, prefix=""):
26
  cv2.imwrite(filename, img)
27
  else: # Grayscale image
28
  cv2.imwrite(filename, img)
29
- logging.debug(f"Saved debug image: {filename}")
30
 
31
  def estimate_brightness(img):
32
- """Estimate image brightness to adjust processing"""
33
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
34
- brightness = np.mean(gray)
35
- logging.debug(f"Estimated brightness: {brightness}")
36
- return brightness
37
 
38
- def deblur_image(img):
39
- """Apply iterative sharpening to reduce blur"""
40
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
41
- # Multiple sharpening passes
42
- for _ in range(2):
43
- kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
44
- gray = cv2.filter2D(gray, -1, kernel)
45
- gray = np.clip(gray, 0, 255).astype(np.uint8)
46
- save_debug_image(gray, "00_deblurred")
47
- return gray
48
-
49
- def preprocess_image(img):
50
- """Enhance image for digit detection under adverse conditions"""
51
- # PIL enhancement
52
- pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
53
- pil_img = ImageEnhance.Contrast(pil_img).enhance(3.0) # Extreme contrast
54
- pil_img = ImageEnhance.Brightness(pil_img).enhance(1.8) # Strong brightness
55
- img_enhanced = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
56
- save_debug_image(img_enhanced, "00_preprocessed_pil")
57
-
58
- # Deblur
59
- deblurred = deblur_image(img_enhanced)
60
-
61
- # CLAHE for local contrast
62
- clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8, 8))
63
- enhanced = clahe.apply(deblurred)
64
- save_debug_image(enhanced, "00_clahe_enhanced")
65
-
66
- # Noise reduction
67
- filtered = cv2.bilateralFilter(enhanced, d=17, sigmaColor=200, sigmaSpace=200)
68
- save_debug_image(filtered, "00_bilateral_filtered")
69
-
70
- # Morphological cleaning
71
- kernel = np.ones((5, 5), np.uint8)
72
- filtered = cv2.morphologyEx(filtered, cv2.MORPH_OPEN, kernel, iterations=2)
73
- save_debug_image(filtered, "00_morph_cleaned")
74
- return filtered
75
-
76
- def normalize_image(img):
77
- """Resize image to ensure digits are detectable"""
78
- h, w = img.shape[:2]
79
- target_height = 1080 # High resolution for small digits
80
- aspect_ratio = w / h
81
- target_width = int(target_height * aspect_ratio)
82
- if target_width < 480:
83
- target_width = 480
84
- target_height = int(target_width / aspect_ratio)
85
- resized = cv2.resize(img, (target_width, target_height), interpolation=cv2.INTER_CUBIC)
86
- save_debug_image(resized, "00_normalized")
87
- logging.debug(f"Normalized image to {target_width}x{target_height}")
88
- return resized
89
-
90
- def tesseract_ocr(img):
91
- """Fallback OCR using Tesseract"""
92
  try:
93
- config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789.-'
94
- text = pytesseract.image_to_string(img, config=config).strip()
95
- logging.info(f"Tesseract OCR raw text: {text}")
96
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  except Exception as e:
98
- logging.error(f"Tesseract OCR failed: {str(e)}")
 
 
 
 
 
 
 
99
  return None
100
 
101
- def extract_weight_from_image(pil_img):
102
- """Extract the actual weight shown in the image"""
103
- try:
104
- img = np.array(pil_img)
105
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
106
- save_debug_image(img, "00_input_image")
 
 
 
107
 
108
- # Normalize image
109
- img = normalize_image(img)
110
- brightness = estimate_brightness(img)
111
- conf_threshold = 0.1 # Very low threshold for blurry images
 
 
 
 
 
 
 
112
 
113
- # Preprocess entire image (bypass ROI detection)
114
- processed_img = preprocess_image(img)
115
- save_debug_image(processed_img, "01_processed_full")
 
 
 
 
 
 
 
 
 
116
 
117
- # Try multiple thresholding approaches
118
- if brightness > 100:
119
- thresh = cv2.adaptiveThreshold(processed_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
120
- cv2.THRESH_BINARY_INV, 61, 11)
121
- save_debug_image(thresh, "02_adaptive_threshold")
122
- else:
123
- _, thresh = cv2.threshold(processed_img, 10, 255, cv2.THRESH_BINARY_INV)
124
- save_debug_image(thresh, "02_simple_threshold")
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- # Morphological operations
127
- kernel = np.ones((7, 7), np.uint8)
128
- thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
129
- save_debug_image(thresh, "02_morph_cleaned")
 
 
 
 
 
 
130
 
131
- # EasyOCR attempt
132
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
133
- contrast_ths=0.05, adjust_contrast=1.5,
134
- text_threshold=0.05, mag_ratio=10.0,
135
- allowlist='0123456789.-', y_ths=0.8)
136
 
137
  logging.info(f"EasyOCR results: {results}")
138
- recognized_text = ""
139
- if results:
140
- # Sort by x-coordinate for left-to-right reading
141
- sorted_results = sorted(results, key=lambda x: x[0][0][0])
142
- for _, text, conf in sorted_results:
143
- logging.info(f"EasyOCR detected: {text}, Confidence: {conf}")
144
- if conf > conf_threshold and any(c in '0123456789.-' for c in text):
145
- recognized_text += text
146
- else:
147
  logging.info("EasyOCR found no digits.")
 
148
 
149
- if not recognized_text:
150
- # Tesseract fallback
151
- tesseract_result = tesseract_ocr(thresh)
152
- if tesseract_result:
153
- recognized_text = tesseract_result
154
- logging.info(f"Using Tesseract result: {recognized_text}")
 
 
155
 
156
- logging.info(f"Raw recognized text: {recognized_text}")
157
- if not recognized_text:
158
- logging.info("No text detected by EasyOCR or Tesseract.")
159
- return "Not detected", 0.0
160
-
161
- # Minimal cleaning to preserve actual weight
162
- text = recognized_text.lower().strip()
163
- text = text.replace(",", ".").replace(";", ".").replace(":", ".").replace(" ", "")
164
- text = text.replace("o", "0").replace("O", "0").replace("q", "0").replace("Q", "0")
165
- text = text.replace("s", "5").replace("S", "5").replace("g", "9").replace("G", "6")
166
- text = text.replace("l", "1").replace("I", "1").replace("|", "1")
167
- text = text.replace("b", "8").replace("B", "8").replace("z", "2").replace("Z", "2")
168
- text = text.replace("a", "4").replace("A", "4").replace("e", "3").replace("t", "7")
169
- text = re.sub(r"(kgs|kg|k|lb|g|gr|pounds|lbs)\b", "", text)
170
- text = re.sub(r"[^\d\.\-]", "", text)
 
 
171
 
 
 
172
  if text.count('.') > 1:
173
- parts = text.split('.')
174
- text = parts[0] + '.' + ''.join(parts[1:])
175
- text = text.strip('.')
176
-
177
- if text.startswith('.'):
178
- text = "0" + text
179
- if text.endswith('.'):
180
- text = text.rstrip('.')
181
-
182
- logging.info(f"Cleaned text: {text}")
183
- if not text or text == '.' or text == '-':
184
- logging.warning("Cleaned text is invalid.")
185
- return "Not detected", 0.0
 
186
 
187
- try:
188
- weight = float(text)
189
- confidence = 80.0 if recognized_text else 50.0
190
- if weight < -1000 or weight > 2000:
191
- logging.warning(f"Weight {weight} outside typical range, reducing confidence.")
192
- confidence *= 0.5
193
- if "." in text:
194
- int_part, dec_part = text.split(".")
 
 
 
 
 
 
 
195
  int_part = int_part.lstrip("0") or "0"
196
  dec_part = dec_part.rstrip('0')
197
  if not dec_part and int_part != "0":
198
- text = int_part
199
  elif not dec_part and int_part == "0":
200
- text = "0"
201
  else:
202
- text = f"{int_part}.{dec_part}"
203
  else:
204
- text = text.lstrip('0') or "0"
205
- logging.info(f"Final detected weight: {text}, Confidence: {confidence}%")
206
- return text, confidence
207
- except ValueError:
208
- logging.warning(f"Could not convert '{text}' to float.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  return "Not detected", 0.0
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  except Exception as e:
212
  logging.error(f"Weight extraction failed unexpectedly: {str(e)}")
213
  return "Not detected", 0.0
 
5
  import logging
6
  from datetime import datetime
7
  import os
 
 
8
 
9
+ # Set up logging for debugging
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
 
12
+ # Initialize EasyOCR
13
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
14
 
15
  # Directory for debug images
 
24
  cv2.imwrite(filename, img)
25
  else: # Grayscale image
26
  cv2.imwrite(filename, img)
27
+ logging.info(f"Saved debug image: {filename}")
28
 
29
  def estimate_brightness(img):
30
+ """Estimate image brightness to detect illuminated displays"""
31
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
32
+ return np.mean(gray)
 
 
33
 
34
+ def detect_roi(img):
35
+ """Detect and crop the region of interest (likely the digital display)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  try:
37
+ save_debug_image(img, "01_original")
38
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
39
+ save_debug_image(gray, "02_grayscale")
40
+
41
+ # Use adaptive thresholding for better robustness
42
+ thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
43
+ cv2.THRESH_BINARY, 11, 2)
44
+ save_debug_image(thresh, "03_roi_adaptive_threshold")
45
+
46
+ kernel = np.ones((7, 7), np.uint8) # Smaller kernel
47
+ dilated = cv2.dilate(thresh, kernel, iterations=3) # Fewer iterations
48
+ save_debug_image(dilated, "04_roi_dilated")
49
+
50
+ contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
51
+
52
+ if contours:
53
+ img_area = img.shape[0] * img.shape[1]
54
+ valid_contours = []
55
+ for c in contours:
56
+ area = cv2.contourArea(c)
57
+ # Relaxed area and aspect ratio filters
58
+ if 500 < area < (img_area * 0.95):
59
+ x, y, w, h = cv2.boundingRect(c)
60
+ aspect_ratio = w / h
61
+ if 1.5 <= aspect_ratio <= 6.0 and w > 80 and h > 40:
62
+ valid_contours.append(c)
63
+
64
+ if valid_contours:
65
+ for contour in sorted(valid_contours, key=cv2.contourArea, reverse=True):
66
+ x, y, w, h = cv2.boundingRect(contour)
67
+ padding = 60 # Increased padding
68
+ x, y = max(0, x - padding), max(0, y - padding)
69
+ w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
70
+ roi_img = img[y:y+h, x:x+w]
71
+ save_debug_image(roi_img, "05_detected_roi")
72
+ logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h})")
73
+ return roi_img, (x, y, w, h)
74
+
75
+ logging.info("No suitable ROI found, returning original image.")
76
+ save_debug_image(img, "05_no_roi_original_fallback")
77
+ return img, None
78
  except Exception as e:
79
+ logging.error(f"ROI detection failed: {str(e)}")
80
+ save_debug_image(img, "05_roi_detection_error_fallback")
81
+ return img, None
82
+
83
+ def detect_segments(digit_img):
84
+ """Detect seven-segment patterns in a digit image"""
85
+ h, w = digit_img.shape
86
+ if h < 15 or w < 10:
87
  return None
88
 
89
+ segments = {
90
+ 'top': (int(w*0.15), int(w*0.85), 0, int(h*0.2)),
91
+ 'middle': (int(w*0.15), int(w*0.85), int(h*0.4), int(h*0.6)),
92
+ 'bottom': (int(w*0.15), int(w*0.85), int(h*0.8), h),
93
+ 'left_top': (0, int(w*0.25), int(h*0.05), int(h*0.5)),
94
+ 'left_bottom': (0, int(w*0.25), int(h*0.5), int(h*0.95)),
95
+ 'right_top': (int(w*0.75), w, int(h*0.05), int(h*0.5)),
96
+ 'right_bottom': (int(w*0.75), w, int(h*0.5), int(h*0.95))
97
+ }
98
 
99
+ segment_presence = {}
100
+ for name, (x1, x2, y1, y2) in segments.items():
101
+ x1, y1 = max(0, x1), max(0, y1)
102
+ x2, y2 = min(w, x2), min(h, y2)
103
+ region = digit_img[y1:y2, x1:x2]
104
+ if region.size == 0:
105
+ segment_presence[name] = False
106
+ continue
107
+ pixel_count = np.sum(region == 255)
108
+ total_pixels = region.size
109
+ segment_presence[name] = pixel_count / total_pixels > 0.45 # Lowered threshold
110
 
111
+ digit_patterns = {
112
+ '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
113
+ '1': ('right_top', 'right_bottom'),
114
+ '2': ('top', 'middle', 'bottom', 'left_bottom', 'right_top'),
115
+ '3': ('top', 'middle', 'bottom', 'right_top', 'right_bottom'),
116
+ '4': ('middle', 'left_top', 'right_top', 'right_bottom'),
117
+ '5': ('top', 'middle', 'bottom', 'left_top', 'right_bottom'),
118
+ '6': ('top', 'middle', 'bottom', 'left_top', 'left_bottom', 'right_bottom'),
119
+ '7': ('top', 'right_top', 'right_bottom'),
120
+ '8': ('top', 'middle', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
121
+ '9': ('top', 'middle', 'bottom', 'left_top', 'right_top', 'right_bottom')
122
+ }
123
 
124
+ best_match = None
125
+ max_score = -1
126
+ for digit, pattern in digit_patterns.items():
127
+ matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
128
+ non_matches_penalty = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
129
+ current_score = matches - non_matches_penalty
130
+ if all(segment_presence.get(s, False) for s in pattern):
131
+ current_score += 0.5
132
+ if current_score > max_score:
133
+ max_score = current_score
134
+ best_match = digit
135
+ elif current_score == max_score and best_match is not None:
136
+ current_digit_non_matches = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
137
+ best_digit_pattern = digit_patterns[best_match]
138
+ best_digit_non_matches = sum(1 for segment in segment_presence if segment not in best_digit_pattern and segment_presence[segment])
139
+ if current_digit_non_matches < best_digit_non_matches:
140
+ best_match = digit
141
+
142
+ logging.debug(f"Segment presence: {segment_presence}, Detected digit: {best_match}")
143
+ return best_match
144
 
145
+ def custom_seven_segment_ocr(img, roi_bbox):
146
+ """Perform custom OCR for seven-segment displays"""
147
+ try:
148
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
149
+ brightness = estimate_brightness(img)
150
+ if brightness > 150:
151
+ _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
152
+ else:
153
+ _, thresh = cv2.threshold(gray, 80, 255, cv2.THRESH_BINARY) # Lower threshold
154
+ save_debug_image(thresh, "06_roi_thresh_for_digits")
155
 
 
156
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
157
+ contrast_ths=0.2, adjust_contrast=0.8,
158
+ text_threshold=0.7, mag_ratio=2.0,
159
+ allowlist='0123456789.', y_ths=0.3)
160
 
161
  logging.info(f"EasyOCR results: {results}")
162
+ if not results:
 
 
 
 
 
 
 
 
163
  logging.info("EasyOCR found no digits.")
164
+ return None
165
 
166
+ digits_info = []
167
+ for (bbox, text, conf) in results:
168
+ (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
169
+ h_bbox = max(y1, y2, y3, y4) - min(y1, y2, y3, y4)
170
+ if len(text) == 1 and (text.isdigit() or text == '.') and h_bbox > 8:
171
+ x_min, x_max = int(min(x1, x4)), int(max(x2, x3))
172
+ y_min, y_max = int(min(y1, y2)), int(max(y3, y4))
173
+ digits_info.append((x_min, x_max, y_min, y_max, text, conf))
174
 
175
+ digits_info.sort(key=lambda x: x[0])
176
+ recognized_text = ""
177
+ for idx, (x_min, x_max, y_min, y_max, easyocr_char, easyocr_conf) in enumerate(digits_info):
178
+ x_min, y_min = max(0, x_min), max(0, y_min)
179
+ x_max, y_max = min(thresh.shape[1], x_max), min(thresh.shape[0], y_max)
180
+ if x_max <= x_min or y_max <= y_min:
181
+ continue
182
+ digit_img_crop = thresh[y_min:y_max, x_min:x_max]
183
+ save_debug_image(digit_img_crop, f"07_digit_crop_{idx}_{easyocr_char}")
184
+ if easyocr_conf > 0.9 or easyocr_char == '.' or digit_img_crop.shape[0] < 15 or digit_img_crop.shape[1] < 10:
185
+ recognized_text += easyocr_char
186
+ else:
187
+ digit_from_segments = detect_segments(digit_img_crop)
188
+ if digit_from_segments:
189
+ recognized_text += digit_from_segments
190
+ else:
191
+ recognized_text += easyocr_char
192
 
193
+ logging.info(f"Before validation, recognized_text: {recognized_text}")
194
+ text = re.sub(r"[^\d\.]", "", recognized_text)
195
  if text.count('.') > 1:
196
+ text = text.replace('.', '', text.count('.') - 1)
197
+ if text and re.fullmatch(r"^\d*\.?\d*$", text) and len(text) > 0:
198
+ if text.startswith('.'):
199
+ text = "0" + text
200
+ if text.endswith('.'):
201
+ text = text.rstrip('.')
202
+ if text == '.' or text == '':
203
+ return None
204
+ return text
205
+ logging.info(f"Custom OCR text '{recognized_text}' failed validation.")
206
+ return None
207
+ except Exception as e:
208
+ logging.error(f"Custom seven-segment OCR failed: {str(e)}")
209
+ return None
210
 
211
+ def extract_weight_from_image(pil_img):
212
+ """Extract weight from a PIL image of a digital scale display"""
213
+ try:
214
+ img = np.array(pil_img)
215
+ img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
216
+ save_debug_image(img, "00_input_image") # Log input image
217
+
218
+ brightness = estimate_brightness(img)
219
+ conf_threshold = 0.6 if brightness > 150 else (0.5 if brightness > 80 else 0.4)
220
+
221
+ roi_img, roi_bbox = detect_roi(img)
222
+ custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
223
+ if custom_result:
224
+ if "." in custom_result:
225
+ int_part, dec_part = custom_result.split(".")
226
  int_part = int_part.lstrip("0") or "0"
227
  dec_part = dec_part.rstrip('0')
228
  if not dec_part and int_part != "0":
229
+ custom_result = int_part
230
  elif not dec_part and int_part == "0":
231
+ custom_result = "0"
232
  else:
233
+ custom_result = f"{int_part}.{dec_part}"
234
  else:
235
+ custom_result = custom_result.lstrip('0') or "0"
236
+ try:
237
+ float(custom_result)
238
+ logging.info(f"Custom OCR result: {custom_result}, Confidence: 100.0%")
239
+ return custom_result, 100.0
240
+ except ValueError:
241
+ logging.warning(f"Custom OCR result '{custom_result}' is not a valid number, falling back.")
242
+ custom_result = None
243
+
244
+ logging.info("Custom OCR failed or invalid, falling back to general EasyOCR.")
245
+ processed_roi_img_gray = cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY)
246
+ kernel_sharpening = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
247
+ sharpened_roi = cv2.filter2D(processed_roi_img_gray, -1, kernel_sharpening)
248
+ save_debug_image(sharpened_roi, "08_fallback_sharpened")
249
+ processed_roi_img_final = cv2.adaptiveThreshold(sharpened_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
250
+ cv2.THRESH_BINARY, 21, 5)
251
+ save_debug_image(processed_roi_img_final, "09_fallback_adaptive_thresh")
252
+
253
+ results = easyocr_reader.readtext(processed_roi_img_final, detail=1, paragraph=False,
254
+ contrast_ths=0.3, adjust_contrast=0.9,
255
+ text_threshold=0.5, mag_ratio=2.0,
256
+ allowlist='0123456789.', batch_size=4, y_ths=0.3)
257
+
258
+ best_weight = None
259
+ best_conf = 0.0
260
+ best_score = 0.0
261
+ for (bbox, text, conf) in results:
262
+ text = text.lower().strip()
263
+ text = text.replace(",", ".").replace(";", ".").replace(":", ".").replace(" ", "")
264
+ text = text.replace("o", "0").replace("O", "0").replace("q", "0").replace("Q", "0")
265
+ text = text.replace("s", "5").replace("S", "5")
266
+ text = text.replace("g", "9").replace("G", "6")
267
+ text = text.replace("l", "1").replace("I", "1").replace("|", "1")
268
+ text = text.replace("b", "8").replace("B", "8")
269
+ text = text.replace("z", "2").replace("Z", "2")
270
+ text = text.replace("a", "4").replace("A", "4")
271
+ text = text.replace("e", "3")
272
+ text = text.replace("t", "7")
273
+ text = text.replace("~", "").replace("`", "")
274
+ text = re.sub(r"(kgs|kg|k|lb|g|gr|pounds|lbs)\b", "", text)
275
+ text = re.sub(r"[^\d\.]", "", text)
276
+ if text.count('.') > 1:
277
+ parts = text.split('.')
278
+ text = parts[0] + '.' + ''.join(parts[1:])
279
+ text = text.strip('.')
280
+ if re.fullmatch(r"^\d*\.?\d{0,3}$", text) and len(text.replace('.', '')) > 0:
281
+ try:
282
+ weight = float(text)
283
+ range_score = 1.0
284
+ if 0.1 <= weight <= 250:
285
+ range_score = 1.5
286
+ elif weight > 250 and weight <= 500:
287
+ range_score = 1.2
288
+ elif weight > 500 and weight <= 1000:
289
+ range_score = 1.0
290
+ else:
291
+ range_score = 0.5
292
+ digit_count = len(text.replace('.', ''))
293
+ digit_score = 1.0
294
+ if digit_count >= 2 and digit_count <= 5:
295
+ digit_score = 1.3
296
+ elif digit_count == 1:
297
+ digit_score = 0.8
298
+ score = conf * range_score * digit_score
299
+ if roi_bbox:
300
+ (x_roi, y_roi, w_roi, h_roi) = roi_bbox
301
+ roi_area = w_roi * h_roi
302
+ x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
303
+ x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
304
+ bbox_area = (x_max - x_min) * (y_max - y_min)
305
+ if roi_area > 0 and bbox_area / roi_area < 0.03:
306
+ score *= 0.5
307
+ bbox_aspect_ratio = (x_max - x_min) / (y_max - y_min) if (y_max - y_min) > 0 else 0
308
+ if bbox_aspect_ratio < 0.2:
309
+ score *= 0.7
310
+ if score > best_score and conf > conf_threshold:
311
+ best_weight = text
312
+ best_conf = conf
313
+ best_score = score
314
+ logging.info(f"Candidate EasyOCR weight: '{text}', Conf: {conf}, Score: {score}")
315
+ except ValueError:
316
+ logging.warning(f"Could not convert '{text}' to float during EasyOCR fallback.")
317
+ continue
318
+
319
+ if not best_weight:
320
+ logging.info("No valid weight detected after all attempts.")
321
  return "Not detected", 0.0
322
 
323
+ if "." in best_weight:
324
+ int_part, dec_part = best_weight.split(".")
325
+ int_part = int_part.lstrip("0") or "0"
326
+ dec_part = dec_part.rstrip('0')
327
+ if not dec_part and int_part != "0":
328
+ best_weight = int_part
329
+ elif not dec_part and int_part == "0":
330
+ best_weight = "0"
331
+ else:
332
+ best_weight = f"{int_part}.{dec_part}"
333
+ else:
334
+ best_weight = best_weight.lstrip('0') or "0"
335
+
336
+ try:
337
+ final_float_weight = float(best_weight)
338
+ if final_float_weight < 0.01 or final_float_weight > 1000:
339
+ logging.warning(f"Detected weight {final_float_weight} is outside typical range, reducing confidence.")
340
+ best_conf *= 0.5
341
+ except ValueError:
342
+ pass
343
+
344
+ logging.info(f"Final detected weight: {best_weight}, Confidence: {round(best_conf * 100, 2)}%")
345
+ return best_weight, round(best_conf * 100, 2)
346
+
347
  except Exception as e:
348
  logging.error(f"Weight extraction failed unexpectedly: {str(e)}")
349
  return "Not detected", 0.0