Sanjayraju30 commited on
Commit
32de3b7
·
verified ·
1 Parent(s): 6dfd01b

Update ocr_engine.py

Browse files
Files changed (1) hide show
  1. ocr_engine.py +103 -230
ocr_engine.py CHANGED
@@ -3,8 +3,6 @@ import numpy as np
3
  import cv2
4
  import re
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')
@@ -12,20 +10,6 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
12
  # Initialize EasyOCR
13
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
14
 
15
- # Directory for debug images
16
- DEBUG_DIR = "debug_images"
17
- os.makedirs(DEBUG_DIR, exist_ok=True)
18
-
19
- def save_debug_image(img, filename_suffix, prefix=""):
20
- """Saves an image to the debug directory with a timestamp."""
21
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
22
- filename = os.path.join(DEBUG_DIR, f"{prefix}{timestamp}_{filename_suffix}.png")
23
- if len(img.shape) == 3: # Color image
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)
@@ -34,80 +18,57 @@ def estimate_brightness(img):
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'),
@@ -122,228 +83,140 @@ def detect_segments(digit_img):
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
 
3
  import cv2
4
  import re
5
  import logging
 
 
6
 
7
  # Set up logging for debugging
8
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
10
  # Initialize EasyOCR
11
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  def estimate_brightness(img):
14
  """Estimate image brightness to detect illuminated displays"""
15
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
18
  def detect_roi(img):
19
  """Detect and crop the region of interest (likely the digital display)"""
20
  try:
 
21
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
22
+ brightness = estimate_brightness(img)
23
+ thresh_value = 230 if brightness > 100 else 190
24
+ _, thresh = cv2.threshold(gray, thresh_value, 255, cv2.THRESH_BINARY)
25
+ kernel = np.ones((9, 9), np.uint8)
26
+ dilated = cv2.dilate(thresh, kernel, iterations=3)
 
 
 
 
 
 
27
  contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
28
  if contours:
29
+ valid_contours = [c for c in contours if cv2.contourArea(c) > 500]
 
 
 
 
 
 
 
 
 
 
30
  if valid_contours:
31
  for contour in sorted(valid_contours, key=cv2.contourArea, reverse=True):
32
  x, y, w, h = cv2.boundingRect(contour)
33
+ aspect_ratio = w / h
34
+ if 1.5 <= aspect_ratio <= 4.0 and w > 50 and h > 30:
35
+ x, y = max(0, x-40), max(0, y-40)
36
+ w, h = min(w+80, img.shape[1]-x), min(h+80, img.shape[0]-y)
37
+ return img[y:y+h, x:x+w], (x, y, w, h)
 
 
 
 
 
38
  return img, None
39
  except Exception as e:
40
  logging.error(f"ROI detection failed: {str(e)}")
 
41
  return img, None
42
 
43
  def detect_segments(digit_img):
44
  """Detect seven-segment patterns in a digit image"""
45
  h, w = digit_img.shape
46
+ if h < 10 or w < 10:
47
  return None
48
 
49
+ # Define segment regions (top, middle, bottom, left-top, left-bottom, right-top, right-bottom)
50
  segments = {
51
+ 'top': (0, w, 0, h//5),
52
+ 'middle': (0, w, 2*h//5, 3*h//5),
53
+ 'bottom': (0, w, 4*h//5, h),
54
+ 'left_top': (0, w//5, 0, h//2),
55
+ 'left_bottom': (0, w//5, h//2, h),
56
+ 'right_top': (4*w//5, w, 0, h//2),
57
+ 'right_bottom': (4*w//5, w, h//2, h)
58
  }
59
 
60
  segment_presence = {}
61
  for name, (x1, x2, y1, y2) in segments.items():
 
 
62
  region = digit_img[y1:y2, x1:x2]
63
  if region.size == 0:
64
+ return None
65
+ # Count white pixels in the region
66
  pixel_count = np.sum(region == 255)
67
  total_pixels = region.size
68
+ # Segment is present if more than 50% of the region is white
69
+ segment_presence[name] = pixel_count > total_pixels * 0.5
70
 
71
+ # Seven-segment digit patterns
72
  digit_patterns = {
73
  '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
74
  '1': ('right_top', 'right_bottom'),
 
83
  }
84
 
85
  best_match = None
86
+ max_matches = 0
87
  for digit, pattern in digit_patterns.items():
88
  matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
89
+ non_matches = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
90
+ score = matches - non_matches
91
+ if score > max_matches:
92
+ max_matches = score
 
 
93
  best_match = digit
94
+
 
 
 
 
 
 
 
95
  return best_match
96
 
97
  def custom_seven_segment_ocr(img, roi_bbox):
98
  """Perform custom OCR for seven-segment displays"""
99
  try:
100
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
101
+ _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
 
 
 
 
 
102
 
103
+ # Use EasyOCR to get bounding boxes for digits
104
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
105
+ contrast_ths=0.1, adjust_contrast=0.7,
106
+ text_threshold=0.9, mag_ratio=1.5,
107
+ allowlist='0123456789.')
108
+
 
109
  if not results:
 
110
  return None
111
 
112
+ # Sort bounding boxes left to right
113
+ digits = []
114
+ for (bbox, _, _) in results:
115
  (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
116
+ x_min, x_max = min(x1, x4), max(x2, x3)
117
+ y_min, y_max = min(y1, y2), max(y3, y4)
118
+ digits.append((x_min, x_max, y_min, y_max))
119
+
120
+ digits.sort(key=lambda x: x[0]) # Sort by x_min (left to right)
121
 
122
+ # Extract and recognize each digit
123
  recognized_text = ""
124
+ for x_min, x_max, y_min, y_max in digits:
125
+ x_min, y_min = max(0, int(x_min)), max(0, int(y_min))
126
+ x_max, y_max = min(thresh.shape[1], int(x_max)), min(thresh.shape[0], int(y_max))
127
  if x_max <= x_min or y_max <= y_min:
128
  continue
129
+ digit_img = thresh[y_min:y_max, x_min:x_max]
130
+ digit = detect_segments(digit_img)
131
+ if digit:
132
+ recognized_text += digit
133
+
134
+ # Validate the recognized text
135
+ text = recognized_text
136
+ text = re.sub(r"[^\d\.]", "", text)
137
+ if re.fullmatch(r"\d{1,4}(\.\d{0,3})?", text):
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  return text
 
139
  return None
140
  except Exception as e:
141
  logging.error(f"Custom seven-segment OCR failed: {str(e)}")
142
  return None
143
 
144
  def extract_weight_from_image(pil_img):
 
145
  try:
146
  img = np.array(pil_img)
147
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
 
148
 
149
  brightness = estimate_brightness(img)
150
+ conf_threshold = 0.9 if brightness > 100 else 0.7
151
 
152
+ # Detect ROI
153
  roi_img, roi_bbox = detect_roi(img)
154
+
155
+ # Try custom seven-segment OCR first
156
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
157
  if custom_result:
158
+ # Format the custom result
159
  if "." in custom_result:
160
  int_part, dec_part = custom_result.split(".")
161
  int_part = int_part.lstrip("0") or "0"
162
+ custom_result = f"{int_part}.{dec_part.rstrip('0')}"
 
 
 
 
 
 
163
  else:
164
  custom_result = custom_result.lstrip('0') or "0"
165
+ return custom_result, 100.0 # High confidence for custom OCR
 
 
 
 
 
 
166
 
167
+ # Fallback to EasyOCR if custom OCR fails
168
+ images_to_process = [
169
+ ("raw", roi_img, {'contrast_ths': 0.1, 'adjust_contrast': 0.7, 'text_threshold': 0.9, 'mag_ratio': 1.5, 'allowlist': '0123456789.'}),
170
+ ]
 
 
 
 
 
 
 
 
 
171
 
172
  best_weight = None
173
  best_conf = 0.0
174
  best_score = 0.0
175
+
176
+ for mode, proc_img, ocr_params in images_to_process:
177
+ if mode == "raw":
178
+ proc_img = cv2.cvtColor(proc_img, cv2.COLOR_BGR2GRAY)
179
+ results = easyocr_reader.readtext(proc_img, detail=1, paragraph=False, **ocr_params)
180
+
181
+ for (bbox, text, conf) in results:
182
+ text = text.lower().strip()
183
+ text = text.replace(",", ".").replace(";", ".")
184
+ text = text.replace("o", "0").replace("O", "0")
185
+ text = text.replace("s", "5").replace("S", "5")
186
+ text = text.replace("g", "9").replace("G", "6")
187
+ text = text.replace("l", "1").replace("I", "1")
188
+ text = text.replace("b", "8").replace("B", "8")
189
+ text = text.replace("z", "2").replace("Z", "2")
190
+ text = text.replace("q", "9").replace("Q", "9")
191
+ text = text.replace("kgs", "").replace("kg", "").replace("k", "")
192
+ text = re.sub(r"[^\d\.]", "", text)
193
+
194
+ if re.fullmatch(r"\d{1,4}(\.\d{0,3})?", text):
195
+ try:
196
+ weight = float(text)
197
+ range_score = 1.0 if 0.1 <= weight <= 500 else 0.3
198
+ digit_score = 1.5 if 10 <= weight < 100 else 1.0
199
+ score = conf * range_score * digit_score
200
+ if score > best_score and conf > conf_threshold:
201
+ best_weight = text
202
+ best_conf = conf
203
+ best_score = score
204
+ except ValueError:
205
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  if not best_weight:
208
+ logging.info("No valid weight detected")
209
  return "Not detected", 0.0
210
 
211
  if "." in best_weight:
212
  int_part, dec_part = best_weight.split(".")
213
  int_part = int_part.lstrip("0") or "0"
214
+ best_weight = f"{int_part}.{dec_part.rstrip('0')}"
 
 
 
 
 
 
215
  else:
216
  best_weight = best_weight.lstrip('0') or "0"
217
 
 
 
 
 
 
 
 
 
 
218
  return best_weight, round(best_conf * 100, 2)
219
 
220
  except Exception as e:
221
+ logging.error(f"Weight extraction failed: {str(e)}")
222
  return "Not detected", 0.0