Sanjayraju30 commited on
Commit
554a2ee
·
verified ·
1 Parent(s): a6294cd

Update ocr_engine.py

Browse files
Files changed (1) hide show
  1. ocr_engine.py +190 -141
ocr_engine.py CHANGED
@@ -31,20 +31,23 @@ def estimate_brightness(img):
31
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
32
  return np.mean(gray)
33
 
34
- def preprocess_image(img, scale=1.0):
35
  """Preprocess image for better OCR accuracy."""
36
  if scale != 1.0:
37
  img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
38
  save_debug_image(img, f"01_preprocess_scaled_{scale}")
39
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
40
- # Apply bilateral filter to preserve edges
41
- denoised = cv2.bilateralFilter(gray, 9, 15, 15)
42
  save_debug_image(denoised, "02_preprocess_bilateral")
43
- # Enhance contrast using CLAHE
44
- clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
45
- enhanced = clahe.apply(denoised)
46
- save_debug_image(enhanced, "03_preprocess_clahe")
47
- # Sharpen the image
 
 
 
48
  kernel_sharpening = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
49
  sharpened = cv2.filter2D(enhanced, -1, kernel_sharpening)
50
  save_debug_image(sharpened, "04_preprocess_sharpened")
@@ -54,11 +57,11 @@ def correct_rotation(img):
54
  """Correct image rotation using Hough Transform."""
55
  try:
56
  edges = cv2.Canny(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 50, 150)
57
- lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=80, minLineLength=50, maxLineGap=10)
58
  if lines is not None:
59
  angles = [np.arctan2(line[0][3] - line[0][1], line[0][2] - line[0][0]) * 180 / np.pi for line in lines]
60
  angle = np.median(angles)
61
- if abs(angle) > 3:
62
  (h, w) = img.shape[:2]
63
  center = (w // 2, h // 2)
64
  M = cv2.getRotationMatrix2D(center, angle, 1.0)
@@ -76,64 +79,66 @@ def detect_roi(img):
76
  save_debug_image(img, "05_original")
77
  brightness_map = cv2.GaussianBlur(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), (15, 15), 0)
78
 
79
- # Try multiple scales for preprocessing
80
- scales = [1.0, 1.5, 0.75]
 
81
  for scale in scales:
82
- preprocessed = preprocess_image(img, scale)
83
- block_size = max(11, min(31, int(img.shape[0] / 20) * 2 + 1))
84
- thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
85
- cv2.THRESH_BINARY_INV, block_size, 5)
86
- _, otsu_thresh = cv2.threshold(preprocessed, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
87
- combined_thresh = cv2.bitwise_and(thresh, otsu_thresh)
88
- save_debug_image(combined_thresh, f"06_roi_combined_threshold_scale_{scale}")
89
-
90
- # Morphological operations
91
- kernel = np.ones((5, 5), np.uint8)
92
- dilated = cv2.dilate(combined_thresh, kernel, iterations=2)
93
- eroded = cv2.erode(dilated, kernel, iterations=1)
94
- save_debug_image(eroded, f"07_roi_morphological_scale_{scale}")
95
-
96
- contours, _ = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
97
-
98
- if contours:
99
- img_area = img.shape[0] * img.shape[1]
100
- valid_contours = []
101
- for c in contours:
102
- area = cv2.contourArea(c)
103
- x, y, w, h = cv2.boundingRect(c)
104
- roi_brightness = np.mean(brightness_map[y:y+h, x:x+w] if scale == 1.0 else cv2.resize(brightness_map, (img.shape[1], img.shape[0])))
105
- aspect_ratio = w / h
106
- if (200 < area < (img_area * 0.95) and
107
- 0.5 <= aspect_ratio <= 15.0 and w > 50 and h > 20 and roi_brightness > 60):
108
- valid_contours.append((c, roi_brightness))
109
- logging.debug(f"Contour: Scale={scale}, Area={area}, Aspect={aspect_ratio:.2f}, Brightness={roi_brightness:.2f}")
110
 
111
- if valid_contours:
112
- contour, _ = max(valid_contours, key=lambda x: x[1])
113
- x, y, w, h = cv2.boundingRect(contour)
114
- if scale != 1.0:
115
- x, y, w, h = [int(v / scale) for v in (x, y, w, h)]
116
- padding = 120
117
- x, y = max(0, x - padding), max(0, y - padding)
118
- w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
119
- roi_img = img[y:y+h, x:x+w]
120
- save_debug_image(roi_img, f"08_detected_roi_scale_{scale}")
121
- logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h}) at scale {scale}")
122
- return roi_img, (x, y, w, h)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  logging.info("No suitable ROI found, attempting fallback criteria.")
125
  # Fallback with relaxed criteria
126
- preprocessed = preprocess_image(img)
127
  thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
128
- cv2.THRESH_BINARY_INV, block_size, 8)
129
  save_debug_image(thresh, "06_roi_fallback_threshold")
130
  contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
131
- valid_contours = [c for c in contours if 100 < cv2.contourArea(c) < (img.shape[0] * img.shape[1] * 0.95) and
132
- 0.3 <= cv2.boundingRect(c)[2]/cv2.boundingRect(c)[3] <= 20.0]
133
  if valid_contours:
134
  contour = max(valid_contours, key=cv2.contourArea)
135
  x, y, w, h = cv2.boundingRect(contour)
136
- padding = 120
137
  x, y = max(0, x - padding), max(0, y - padding)
138
  w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
139
  roi_img = img[y:y+h, x:x+w]
@@ -152,13 +157,13 @@ def detect_roi(img):
152
  def detect_segments(digit_img, brightness):
153
  """Detect seven-segment patterns in a digit image."""
154
  h, w = digit_img.shape
155
- if h < 10 or w < 8:
156
  return None
157
 
158
  segments = {
159
  'top': (int(w*0.1), int(w*0.9), 0, int(h*0.25)),
160
  'middle': (int(w*0.1), int(w*0.9), int(h*0.45), int(h*0.55)),
161
- 'bottom': (int(w*0.1), int看到的: int(w*0.9), int(h*0.75), h),
162
  'left_top': (0, int(w*0.3), int(h*0.1), int(h*0.5)),
163
  'left_bottom': (0, int(w*0.3), int(h*0.5), int(h*0.9)),
164
  'right_top': (int(w*0.7), w, int(h*0.1), int(h*0.5)),
@@ -175,7 +180,7 @@ def detect_segments(digit_img, brightness):
175
  continue
176
  pixel_count = np.sum(region == 255)
177
  total_pixels = region.size
178
- segment_presence[name] = pixel_count / total_pixels > (0.2 if brightness < 80 else 0.4)
179
 
180
  digit_patterns = {
181
  '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
@@ -195,8 +200,8 @@ def detect_segments(digit_img, brightness):
195
  for digit, pattern in digit_patterns.items():
196
  matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
197
  non_matches_penalty = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
198
- score = matches - 0.2 * non_matches_penalty
199
- if matches >= len(pattern) * 0.7:
200
  score += 1.0
201
  if score > max_score:
202
  max_score = score
@@ -208,9 +213,9 @@ def detect_segments(digit_img, brightness):
208
  def custom_seven_segment_ocr(img, roi_bbox):
209
  """Perform custom OCR for seven-segment displays."""
210
  try:
211
- preprocessed = preprocess_image(img)
212
  brightness = estimate_brightness(img)
213
- thresh_value = 80 if brightness < 80 else 0
214
  _, thresh = cv2.threshold(preprocessed, thresh_value, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
215
  save_debug_image(thresh, "09_roi_thresh_for_digits")
216
 
@@ -221,20 +226,20 @@ def custom_seven_segment_ocr(img, roi_bbox):
221
 
222
  batch_size = max(4, min(16, int(img.shape[0] * img.shape[1] / 100000)))
223
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
224
- contrast_ths=0.2, adjust_contrast=1.2,
225
- text_threshold=0.5, mag_ratio=4.0,
226
- allowlist='0123456789.', batch_size=batch_size, y_ths=0.3)
227
 
228
- logging.info(f"EasyOCR results: {results}")
229
  if not results:
230
- logging.info("EasyOCR found no digits.")
231
  return None
232
 
233
  digits_info = []
234
  for (bbox, text, conf) in results:
235
  (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
236
  h_bbox = max(y1, y2, y3, y4) - min(y1, y2, y3, y4)
237
- if (text.isdigit() or text == '.') and h_bbox > 6:
238
  x_min, x_max = int(min(x1, x4)), int(max(x2, x3))
239
  y_min, y_max = int(min(y1, y2)), int(max(y3, y4))
240
  digits_info.append((x_min, x_max, y_min, y_max, text, conf))
@@ -248,7 +253,7 @@ def custom_seven_segment_ocr(img, roi_bbox):
248
  continue
249
  digit_img_crop = thresh[y_min:y_max, x_min:x_max]
250
  save_debug_image(digit_img_crop, f"11_digit_crop_{idx}_{easyocr_char}")
251
- if easyocr_conf > 0.9 or easyocr_char == '.':
252
  recognized_text += easyocr_char
253
  else:
254
  digit_from_segments = detect_segments(digit_img_crop, brightness)
@@ -280,7 +285,7 @@ def extract_weight_from_image(pil_img):
280
  img = correct_rotation(img)
281
 
282
  brightness = estimate_brightness(img)
283
- conf_threshold = 0.7 if brightness > 150 else (0.5 if brightness > 80 else 0.3)
284
 
285
  roi_img, roi_bbox = detect_roi(img)
286
  if roi_bbox:
@@ -288,10 +293,10 @@ def extract_weight_from_image(pil_img):
288
  conf_threshold *= 1.1 if roi_area > (img.shape[0] * img.shape[1] * 0.5) else 1.0
289
 
290
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
291
- if custom_result:
292
  try:
293
  weight = float(custom_result)
294
- if 0.001 <= weight <= 2000:
295
  logging.info(f"Custom OCR result: {custom_result}, Confidence: 95.0%")
296
  return custom_result, 95.0
297
  else:
@@ -300,78 +305,122 @@ def extract_weight_from_image(pil_img):
300
  logging.warning(f"Custom OCR result '{custom_result}' is not a valid number.")
301
 
302
  logging.info("Custom OCR failed or invalid, falling back to enhanced EasyOCR.")
303
- preprocessed_roi = preprocess_image(roi_img)
304
- block_size = max(11, min(31, int(roi_img.shape[0] / 20) * 2 + 1))
305
  final_roi = cv2.adaptiveThreshold(preprocessed_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
306
- cv2.THRESH_BINARY_INV, block_size, 8)
307
  save_debug_image(final_roi, "12_fallback_adaptive_thresh")
308
 
309
  batch_size = max(4, min(16, int(roi_img.shape[0] * roi_img.shape[1] / 100000)))
310
- results = easyocr_reader.readtext(final_roi, detail=1, paragraph=False,
311
- contrast_ths=0.3, adjust_contrast=1.2,
312
- text_threshold=0.4, mag_ratio=5.0,
313
- allowlist='0123456789. kglb', batch_size=batch_size, y_ths=0.3)
314
-
315
- # Secondary EasyOCR pass with different parameters
316
- if not results:
317
- logging.info("First EasyOCR pass failed, trying with relaxed parameters.")
318
  results = easyocr_reader.readtext(final_roi, detail=1, paragraph=False,
319
- contrast_ths=0.2, adjust_contrast=1.5,
320
- text_threshold=0.3, mag_ratio=6.0,
321
- allowlist='0123456789. kglb', batch_size=batch_size, y_ths=0.4)
322
- save_debug_image(final_roi, "12_fallback_adaptive_thresh_relaxed")
323
-
324
- logging.info(f"EasyOCR results: {results}")
325
- best_weight = None
326
- best_conf = 0.0
327
- best_score = 0.0
328
- unit = None
329
- for (bbox, text, conf) in results:
330
- if 'kg' in text.lower():
331
- unit = 'kg'
332
- continue
333
- elif 'g' in text.lower():
334
- unit = 'g'
335
- continue
336
- elif 'lb' in text.lower():
337
- unit = 'lb'
338
- continue
339
- text = re.sub(r"[^\d\.]", "", text)
340
- if text.count('.') > 1:
341
- text = text.replace('.', '', text.count('.') - 1)
342
- text = text.strip('.')
343
- if re.fullmatch(r"^\d*\.?\d*$", text):
344
- try:
345
- weight = float(text)
346
- if unit == 'g':
347
- weight /= 1000 # Convert grams to kilograms
348
- elif unit == 'lb':
349
- weight *= 0.453592 # Convert pounds to kilograms
350
- range_score = 1.5 if 0.001 <= weight <= 2000 else 0.7
351
- digit_count = len(text.replace('.', ''))
352
- digit_score = 1.3 if 1 <= digit_count <= 8 else 0.8
353
- score = conf * range_score * digit_score
354
- if roi_bbox:
355
- (x_roi, y_roi, w_roi, h_roi) = roi_bbox
356
- roi_area = w_roi * h_roi
357
- x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
358
- x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
359
- bbox_area = (x_max - x_min) * (y_max - y_min)
360
- if roi_area > 0 and bbox_area / roi_area < 0.03:
361
- score *= 0.5
362
- if score > best_score and conf > conf_threshold:
363
- best_weight = text
364
- best_conf = conf
365
- best_score = score
366
  logging.info(f"Candidate EasyOCR weight: '{text}', Unit: {unit or 'none'}, Conf: {conf}, Score: {score}")
367
- except ValueError:
368
- logging.warning(f"Could not convert '{text}' to float during EasyOCR fallback.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  continue
370
-
371
- if not best_weight:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  logging.info("No valid weight detected after all attempts.")
373
  return "Not detected", 0.0
374
 
 
 
 
375
  # Format the weight
376
  if "." in best_weight:
377
  int_part, dec_part = best_weight.split(".")
@@ -383,14 +432,14 @@ def extract_weight_from_image(pil_img):
383
 
384
  try:
385
  final_weight = float(best_weight)
386
- if final_weight < 0.001 or final_weight > 2000:
387
- best_conf *= 0.6
388
- elif final_weight == 0 and best_conf < 0.9:
389
- best_conf *= 0.7 # Penalize zero weights with low confidence
390
  except ValueError:
391
  pass
392
 
393
- logging.info(f"Final detected weight: {best_weight} kg, Confidence: {round(best_conf * 100, 2)}%")
394
  return best_weight, round(best_conf * 100, 2)
395
 
396
  except Exception as e:
 
31
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
32
  return np.mean(gray)
33
 
34
+ def preprocess_image(img, scale=1.0, method='clahe'):
35
  """Preprocess image for better OCR accuracy."""
36
  if scale != 1.0:
37
  img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
38
  save_debug_image(img, f"01_preprocess_scaled_{scale}")
39
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
40
+ # Gentle denoising
41
+ denoised = cv2.bilateralFilter(gray, 7, 10, 10)
42
  save_debug_image(denoised, "02_preprocess_bilateral")
43
+ # Enhance contrast
44
+ if method == 'clahe':
45
+ clahe = cv2.createCLAHE(clipLimit=3.5, tileGridSize=(8, 8))
46
+ enhanced = clahe.apply(denoised)
47
+ else: # Histogram equalization
48
+ enhanced = cv2.equalizeHist(denoised)
49
+ save_debug_image(enhanced, f"03_preprocess_{method}")
50
+ # Sharpen
51
  kernel_sharpening = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
52
  sharpened = cv2.filter2D(enhanced, -1, kernel_sharpening)
53
  save_debug_image(sharpened, "04_preprocess_sharpened")
 
57
  """Correct image rotation using Hough Transform."""
58
  try:
59
  edges = cv2.Canny(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 50, 150)
60
+ lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=50, minLineLength=40, maxLineGap=10)
61
  if lines is not None:
62
  angles = [np.arctan2(line[0][3] - line[0][1], line[0][2] - line[0][0]) * 180 / np.pi for line in lines]
63
  angle = np.median(angles)
64
+ if abs(angle) > 2:
65
  (h, w) = img.shape[:2]
66
  center = (w // 2, h // 2)
67
  M = cv2.getRotationMatrix2D(center, angle, 1.0)
 
79
  save_debug_image(img, "05_original")
80
  brightness_map = cv2.GaussianBlur(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), (15, 15), 0)
81
 
82
+ # Try multiple scales and methods
83
+ scales = [1.0, 1.5, 0.5]
84
+ methods = ['clahe', 'hist']
85
  for scale in scales:
86
+ for method in methods:
87
+ preprocessed = preprocess_image(img, scale, method)
88
+ block_size = max(9, min(31, int(img.shape[0] / 25) * 2 + 1))
89
+ thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
90
+ cv2.THRESH_BINARY_INV, block_size, 3)
91
+ _, otsu_thresh = cv2.threshold(preprocessed, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
92
+ combined_thresh = cv2.bitwise_and(thresh, otsu_thresh)
93
+ save_debug_image(combined_thresh, f"06_roi_combined_threshold_scale_{scale}_{method}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # Morphological operations
96
+ kernel = np.ones((3, 3), np.uint8)
97
+ dilated = cv2.dilate(combined_thresh, kernel, iterations=2)
98
+ eroded = cv2.erode(dilated, kernel, iterations=1)
99
+ save_debug_image(eroded, f"07_roi_morphological_scale_{scale}_{method}")
100
+
101
+ contours, _ = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
102
+
103
+ if contours:
104
+ img_area = img.shape[0] * img.shape[1]
105
+ valid_contours = []
106
+ for c in contours:
107
+ area = cv2.contourArea(c)
108
+ x, y, w, h = cv2.boundingRect(c)
109
+ roi_brightness = np.mean(brightness_map[y:y+h, x:x+w] if scale == 1.0 else cv2.resize(brightness_map, (img.shape[1], img.shape[0])))
110
+ aspect_ratio = w / h
111
+ if (100 < area < (img_area * 0.95) and
112
+ 0.3 <= aspect_ratio <= 20.0 and w > 40 and h > 15 and roi_brightness > 50):
113
+ valid_contours.append((c, roi_brightness))
114
+ logging.debug(f"Contour: Scale={scale}, Method={method}, Area={area}, Aspect={aspect_ratio:.2f}, Brightness={roi_brightness:.2f}")
115
+
116
+ if valid_contours:
117
+ contour, _ = max(valid_contours, key=lambda x: x[1])
118
+ x, y, w, h = cv2.boundingRect(contour)
119
+ if scale != 1.0:
120
+ x, y, w, h = [int(v / scale) for v in (x, y, w, h)]
121
+ padding = 150
122
+ x, y = max(0, x - padding), max(0, y - padding)
123
+ w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
124
+ roi_img = img[y:y+h, x:x+w]
125
+ save_debug_image(roi_img, f"08_detected_roi_scale_{scale}_{method}")
126
+ logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h}) at scale {scale}, method {method}")
127
+ return roi_img, (x, y, w, h)
128
 
129
  logging.info("No suitable ROI found, attempting fallback criteria.")
130
  # Fallback with relaxed criteria
131
+ preprocessed = preprocess_image(img, method='clahe')
132
  thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
133
+ cv2.THRESH_BINARY_INV, block_size, 5)
134
  save_debug_image(thresh, "06_roi_fallback_threshold")
135
  contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
136
+ valid_contours = [c for c in contours if 50 < cv2.contourArea(c) < (img.shape[0] * img.shape[1] * 0.95) and
137
+ 0.2 <= cv2.boundingRect(c)[2]/cv2.boundingRect(c)[3] <= 25.0]
138
  if valid_contours:
139
  contour = max(valid_contours, key=cv2.contourArea)
140
  x, y, w, h = cv2.boundingRect(contour)
141
+ padding = 150
142
  x, y = max(0, x - padding), max(0, y - padding)
143
  w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
144
  roi_img = img[y:y+h, x:x+w]
 
157
  def detect_segments(digit_img, brightness):
158
  """Detect seven-segment patterns in a digit image."""
159
  h, w = digit_img.shape
160
+ if h < 8 or w < 6:
161
  return None
162
 
163
  segments = {
164
  'top': (int(w*0.1), int(w*0.9), 0, int(h*0.25)),
165
  'middle': (int(w*0.1), int(w*0.9), int(h*0.45), int(h*0.55)),
166
+ 'bottom': (int(w*0.1), int(w*0.9), int(h*0.75), h),
167
  'left_top': (0, int(w*0.3), int(h*0.1), int(h*0.5)),
168
  'left_bottom': (0, int(w*0.3), int(h*0.5), int(h*0.9)),
169
  'right_top': (int(w*0.7), w, int(h*0.1), int(h*0.5)),
 
180
  continue
181
  pixel_count = np.sum(region == 255)
182
  total_pixels = region.size
183
+ segment_presence[name] = pixel_count / total_pixels > (0.15 if brightness < 80 else 0.35)
184
 
185
  digit_patterns = {
186
  '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
 
200
  for digit, pattern in digit_patterns.items():
201
  matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
202
  non_matches_penalty = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
203
+ score = matches - 0.15 * non_matches_penalty
204
+ if matches >= len(pattern) * 0.65:
205
  score += 1.0
206
  if score > max_score:
207
  max_score = score
 
213
  def custom_seven_segment_ocr(img, roi_bbox):
214
  """Perform custom OCR for seven-segment displays."""
215
  try:
216
+ preprocessed = preprocess_image(img, method='clahe')
217
  brightness = estimate_brightness(img)
218
+ thresh_value = 60 if brightness < 80 else 0
219
  _, thresh = cv2.threshold(preprocessed, thresh_value, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
220
  save_debug_image(thresh, "09_roi_thresh_for_digits")
221
 
 
226
 
227
  batch_size = max(4, min(16, int(img.shape[0] * img.shape[1] / 100000)))
228
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
229
+ contrast_ths=0.1, adjust_contrast=1.3,
230
+ text_threshold=0.3, mag_ratio=6.0,
231
+ allowlist='0123456789.', batch_size=batch_size, y_ths=0.4)
232
 
233
+ logging.info(f"EasyOCR results (seven-segment): {results}")
234
  if not results:
235
+ logging.info("EasyOCR found no digits in seven-segment OCR.")
236
  return None
237
 
238
  digits_info = []
239
  for (bbox, text, conf) in results:
240
  (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
241
  h_bbox = max(y1, y2, y3, y4) - min(y1, y2, y3, y4)
242
+ if (text.isdigit() or text == '.') and h_bbox > 5:
243
  x_min, x_max = int(min(x1, x4)), int(max(x2, x3))
244
  y_min, y_max = int(min(y1, y2)), int(max(y3, y4))
245
  digits_info.append((x_min, x_max, y_min, y_max, text, conf))
 
253
  continue
254
  digit_img_crop = thresh[y_min:y_max, x_min:x_max]
255
  save_debug_image(digit_img_crop, f"11_digit_crop_{idx}_{easyocr_char}")
256
+ if easyocr_conf > 0.85 or easyocr_char == '.':
257
  recognized_text += easyocr_char
258
  else:
259
  digit_from_segments = detect_segments(digit_img_crop, brightness)
 
285
  img = correct_rotation(img)
286
 
287
  brightness = estimate_brightness(img)
288
+ conf_threshold = 0.65 if brightness > 150 else (0.45 if brightness > 80 else 0.25)
289
 
290
  roi_img, roi_bbox = detect_roi(img)
291
  if roi_bbox:
 
293
  conf_threshold *= 1.1 if roi_area > (img.shape[0] * img.shape[1] * 0.5) else 1.0
294
 
295
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
296
+ if custom_result and custom_result != '0':
297
  try:
298
  weight = float(custom_result)
299
+ if 0.0001 <= weight <= 5000:
300
  logging.info(f"Custom OCR result: {custom_result}, Confidence: 95.0%")
301
  return custom_result, 95.0
302
  else:
 
305
  logging.warning(f"Custom OCR result '{custom_result}' is not a valid number.")
306
 
307
  logging.info("Custom OCR failed or invalid, falling back to enhanced EasyOCR.")
308
+ preprocessed_roi = preprocess_image(roi_img, method='hist')
309
+ block_size = max(9, min(31, int(roi_img.shape[0] / 25) * 2 + 1))
310
  final_roi = cv2.adaptiveThreshold(preprocessed_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
311
+ cv2.THRESH_BINARY_INV, block_size, 5)
312
  save_debug_image(final_roi, "12_fallback_adaptive_thresh")
313
 
314
  batch_size = max(4, min(16, int(roi_img.shape[0] * roi_img.shape[1] / 100000)))
315
+ ocr_passes = [
316
+ {'contrast_ths': 0.2, 'text_threshold': 0.3, 'mag_ratio': 6.0, 'y_ths': 0.4, 'label': 'first'},
317
+ {'contrast_ths': 0.1, 'text_threshold': 0.2, 'mag_ratio': 7.0, 'y_ths': 0.5, 'label': 'second'},
318
+ {'contrast_ths': 0.05, 'text_threshold': 0.1, 'mag_ratio': 8.0, 'y_ths': 0.6, 'label': 'third'}
319
+ ]
320
+ candidates = []
321
+
322
+ for ocr_pass in ocr_passes:
323
  results = easyocr_reader.readtext(final_roi, detail=1, paragraph=False,
324
+ contrast_ths=ocr_pass['contrast_ths'],
325
+ adjust_contrast=1.4,
326
+ text_threshold=ocr_pass['text_threshold'],
327
+ mag_ratio=ocr_pass['mag_ratio'],
328
+ allowlist='0123456789. kglb',
329
+ batch_size=batch_size,
330
+ y_ths=ocr_pass['y_ths'])
331
+ logging.info(f"EasyOCR results ({ocr_pass['label']} pass): {results}")
332
+ save_debug_image(final_roi, f"12_fallback_adaptive_thresh_{ocr_pass['label']}_pass")
333
+
334
+ unit = None
335
+ for (bbox, text, conf) in results:
336
+ if 'kg' in text.lower():
337
+ unit = 'kg'
338
+ continue
339
+ elif 'g' in text.lower():
340
+ unit = 'g'
341
+ continue
342
+ elif 'lb' in text.lower():
343
+ unit = 'lb'
344
+ continue
345
+ text = re.sub(r"[^\d\.]", "", text)
346
+ if text.count('.') > 1:
347
+ text = text.replace('.', '', text.count('.') - 1)
348
+ text = text.strip('.')
349
+ if re.fullmatch(r"^\d*\.?\d*$", text):
350
+ try:
351
+ weight = float(text)
352
+ if unit == 'g':
353
+ weight /= 1000
354
+ elif unit == 'lb':
355
+ weight *= 0.453592
356
+ range_score = 1.5 if 0.0001 <= weight <= 5000 else 0.6
357
+ digit_count = len(text.replace('.', ''))
358
+ digit_score = 1.4 if 1 <= digit_count <= 8 else 0.7
359
+ score = conf * range_score * digit_score
360
+ if roi_bbox:
361
+ (x_roi, y_roi, w_roi, h_roi) = roi_bbox
362
+ roi_area = w_roi * h_roi
363
+ x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
364
+ x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
365
+ bbox_area = (x_max - x_min) * (y_max - y_min)
366
+ if roi_area > 0 and bbox_area / roi_area < 0.02:
367
+ score *= 0.4
368
+ candidates.append((text, conf, score, unit))
 
 
369
  logging.info(f"Candidate EasyOCR weight: '{text}', Unit: {unit or 'none'}, Conf: {conf}, Score: {score}")
370
+ except ValueError:
371
+ logging.warning(f"Could not convert '{text}' to float during EasyOCR fallback.")
372
+
373
+ # Fallback to full image if no candidates
374
+ if not candidates:
375
+ logging.info("No candidates from ROI, trying full image.")
376
+ preprocessed_full = preprocess_image(img, method='hist')
377
+ final_full = cv2.adaptiveThreshold(preprocessed_full, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
378
+ cv2.THRESH_BINARY_INV, block_size, 5)
379
+ save_debug_image(final_full, "12_fallback_full_image")
380
+ results = easyocr_reader.readtext(final_full, detail=1, paragraph=False,
381
+ contrast_ths=0.1, adjust_contrast=1.5,
382
+ text_threshold=0.2, mag_ratio=7.0,
383
+ allowlist='0123456789. kglb', batch_size=batch_size, y_ths=0.5)
384
+ logging.info(f"EasyOCR results (full image): {results}")
385
+
386
+ unit = None
387
+ for (bbox, text, conf) in results:
388
+ if 'kg' in text.lower():
389
+ unit = 'kg'
390
  continue
391
+ elif 'g' in text.lower():
392
+ unit = 'g'
393
+ continue
394
+ elif 'lb' in text.lower():
395
+ unit = 'lb'
396
+ continue
397
+ text = re.sub(r"[^\d\.]", "", text)
398
+ if text.count('.') > 1:
399
+ text = text.replace('.', '', text.count('.') - 1)
400
+ text = text.strip('.')
401
+ if re.fullmatch(r"^\d*\.?\d*$", text):
402
+ try:
403
+ weight = float(text)
404
+ if unit == 'g':
405
+ weight /= 1000
406
+ elif unit == 'lb':
407
+ weight *= 0.453592
408
+ range_score = 1.2 if 0.0001 <= weight <= 5000 else 0.5
409
+ digit_count = len(text.replace('.', ''))
410
+ digit_score = 1.2 if 1 <= digit_count <= 8 else 0.6
411
+ score = conf * range_score * digit_score * 0.8 # Penalty for full image
412
+ candidates.append((text, conf, score, unit))
413
+ logging.info(f"Candidate EasyOCR weight (full image): '{text}', Unit: {unit or 'none'}, Conf: {conf}, Score: {score}")
414
+ except ValueError:
415
+ logging.warning(f"Could not convert '{text}' to float during full image fallback.")
416
+
417
+ if not candidates:
418
  logging.info("No valid weight detected after all attempts.")
419
  return "Not detected", 0.0
420
 
421
+ # Select best candidate
422
+ best_weight, best_conf, best_score, best_unit = max(candidates, key=lambda x: x[2])
423
+
424
  # Format the weight
425
  if "." in best_weight:
426
  int_part, dec_part = best_weight.split(".")
 
432
 
433
  try:
434
  final_weight = float(best_weight)
435
+ if final_weight < 0.0001 or final_weight > 5000:
436
+ best_conf *= 0.5
437
+ elif final_weight == 0 and best_conf < 0.95:
438
+ best_conf *= 0.6 # Penalize zero weights
439
  except ValueError:
440
  pass
441
 
442
+ logging.info(f"Final detected weight: {best_weight} kg, Confidence: {round(best_conf * 100, 2)}%, Unit: {best_unit or 'none'}")
443
  return best_weight, round(best_conf * 100, 2)
444
 
445
  except Exception as e: