Sanjayraju30 commited on
Commit
204176c
·
verified ·
1 Parent(s): d373620

Update ocr_engine.py

Browse files
Files changed (1) hide show
  1. ocr_engine.py +112 -87
ocr_engine.py CHANGED
@@ -6,11 +6,12 @@ import logging
6
  from datetime import datetime
7
  import os
8
  from PIL import Image, ImageEnhance
 
9
 
10
  # Set up logging for detailed debugging
11
  logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
12
 
13
- # Initialize EasyOCR with English and GPU disabled (enable if you have a compatible GPU)
14
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
15
 
16
  # Directory for debug images
@@ -28,34 +29,67 @@ def save_debug_image(img, filename_suffix, prefix=""):
28
  logging.debug(f"Saved debug image: {filename}")
29
 
30
  def estimate_brightness(img):
31
- """Estimate image brightness to detect illuminated displays"""
32
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
33
  brightness = np.mean(gray)
34
  logging.debug(f"Estimated brightness: {brightness}")
35
  return brightness
36
 
37
- def preprocess_image(img):
38
- """Enhance contrast, brightness, and reduce noise for better digit detection"""
39
- # Convert to PIL for initial enhancement
40
- pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
41
- pil_img = ImageEnhance.Contrast(pil_img).enhance(2.0) # Stronger contrast
42
- pil_img = ImageEnhance.Brightness(pil_img).enhance(1.3) # Moderate brightness boost
43
- img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
44
- save_debug_image(img, "00_preprocessed_pil")
45
-
46
- # Apply CLAHE to enhance local contrast
47
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
48
- clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
49
- enhanced = clahe.apply(gray)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  save_debug_image(enhanced, "00_clahe_enhanced")
51
 
52
- # Apply bilateral filter to reduce noise while preserving edges
53
- filtered = cv2.bilateralFilter(enhanced, d=11, sigmaColor=100, sigmaSpace=100)
54
  save_debug_image(filtered, "00_bilateral_filtered")
55
  return filtered
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  def detect_roi(img):
58
- """Detect and crop the region of interest (likely the digital display)"""
59
  try:
60
  save_debug_image(img, "01_original")
61
  gray = preprocess_image(img)
@@ -63,21 +97,21 @@ def detect_roi(img):
63
 
64
  # Try multiple thresholding methods
65
  brightness = estimate_brightness(img)
66
- if brightness > 150:
67
  thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
68
- cv2.THRESH_BINARY, 31, 5)
69
  save_debug_image(thresh, "03_roi_adaptive_threshold_high")
70
  else:
71
- _, thresh = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
72
- save_debug_image(thresh, "03_roi_otsu_threshold_low")
73
 
74
- # Morphological operations to clean up noise and connect digits
75
- kernel = np.ones((5, 5), np.uint8)
76
- thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
77
  save_debug_image(thresh, "03_roi_morph_cleaned")
78
 
79
- kernel = np.ones((11, 11), np.uint8)
80
- dilated = cv2.dilate(thresh, kernel, iterations=5)
81
  save_debug_image(dilated, "04_roi_dilated")
82
 
83
  contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
@@ -87,16 +121,16 @@ def detect_roi(img):
87
  valid_contours = []
88
  for c in contours:
89
  area = cv2.contourArea(c)
90
- if 200 < area < (img_area * 0.99): # Very relaxed area filter
91
  x, y, w, h = cv2.boundingRect(c)
92
  aspect_ratio = w / h if h > 0 else 0
93
- if 0.5 <= aspect_ratio <= 10.0 and w > 30 and h > 20: # Very relaxed filters
94
  valid_contours.append(c)
95
 
96
  if valid_contours:
97
- contour = max(valid_contours, key=cv2.contourArea) # Largest contour
98
  x, y, w, h = cv2.boundingRect(contour)
99
- padding = 100 # Generous padding
100
  x, y = max(0, x - padding), max(0, y - padding)
101
  w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
102
  roi_img = img[y:y+h, x:x+w]
@@ -104,8 +138,8 @@ def detect_roi(img):
104
  logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h})")
105
  return roi_img, (x, y, w, h)
106
 
107
- logging.info("No suitable ROI found, returning preprocessed image.")
108
- save_debug_image(img, "05_no_roi_original_fallback")
109
  return img, None
110
  except Exception as e:
111
  logging.error(f"ROI detection failed: {str(e)}")
@@ -115,18 +149,18 @@ def detect_roi(img):
115
  def detect_segments(digit_img):
116
  """Detect seven-segment patterns in a digit image"""
117
  h, w = digit_img.shape
118
- if h < 8 or w < 4: # Very relaxed size constraints
119
  logging.debug(f"Digit image too small: {w}x{h}")
120
  return None
121
 
122
  segments = {
123
- 'top': (int(w*0.1), int(w*0.9), 0, int(h*0.25)),
124
- 'middle': (int(w*0.1), int(w*0.9), int(h*0.35), int(h*0.65)),
125
- 'bottom': (int(w*0.1), int(w*0.9), int(h*0.75), h),
126
- 'left_top': (0, int(w*0.3), int(h*0.05), int(h*0.55)),
127
- 'left_bottom': (0, int(w*0.3), int(h*0.45), int(h*0.95)),
128
- 'right_top': (int(w*0.7), w, int(h*0.05), int(h*0.55)),
129
- 'right_bottom': (int(w*0.7), w, int(h*0.45), int(h*0.95))
130
  }
131
 
132
  segment_presence = {}
@@ -139,7 +173,7 @@ def detect_segments(digit_img):
139
  continue
140
  pixel_count = np.sum(region == 255)
141
  total_pixels = region.size
142
- segment_presence[name] = pixel_count / total_pixels > 0.3 # Very low threshold
143
  logging.debug(f"Segment {name}: {pixel_count}/{total_pixels} = {pixel_count/total_pixels:.2f}")
144
 
145
  digit_patterns = {
@@ -179,25 +213,25 @@ def detect_segments(digit_img):
179
  def custom_seven_segment_ocr(img, roi_bbox):
180
  """Perform custom OCR for seven-segment displays"""
181
  try:
182
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
183
  brightness = estimate_brightness(img)
184
- # Try multiple thresholding approaches
185
- if brightness > 150:
186
- _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
187
  save_debug_image(thresh, "06_roi_otsu_threshold")
188
  else:
189
- _, thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
190
  save_debug_image(thresh, "06_roi_simple_threshold")
191
 
192
  # Morphological cleaning
193
- kernel = np.ones((3, 3), np.uint8)
194
- thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
195
  save_debug_image(thresh, "06_roi_morph_cleaned")
196
 
197
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
198
- contrast_ths=0.1, adjust_contrast=1.0,
199
- text_threshold=0.3, mag_ratio=4.0,
200
- allowlist='0123456789.-', y_ths=0.6)
201
 
202
  logging.info(f"Custom OCR EasyOCR results: {results}")
203
  if not results:
@@ -208,7 +242,7 @@ def custom_seven_segment_ocr(img, roi_bbox):
208
  for (bbox, text, conf) in results:
209
  (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
210
  h_bbox = max(y1, y2, y3, y4) - min(y1, y2, y3, y4)
211
- if len(text) == 1 and (text.isdigit() or text in '.-') and h_bbox > 4:
212
  x_min, x_max = int(min(x1, x4)), int(max(x2, x3))
213
  y_min, y_max = int(min(y1, y2)), int(max(y3, y4))
214
  digits_info.append((x_min, x_max, y_min, y_max, text, conf))
@@ -222,7 +256,7 @@ def custom_seven_segment_ocr(img, roi_bbox):
222
  continue
223
  digit_img_crop = thresh[y_min:y_max, x_min:x_max]
224
  save_debug_image(digit_img_crop, f"07_digit_crop_{idx}_{easyocr_char}")
225
- if easyocr_conf > 0.8 or easyocr_char in '.-' or digit_img_crop.shape[0] < 8 or digit_img_crop.shape[1] < 4:
226
  recognized_text += easyocr_char
227
  else:
228
  digit_from_segments = detect_segments(digit_img_crop)
@@ -232,10 +266,9 @@ def custom_seven_segment_ocr(img, roi_bbox):
232
  recognized_text += easyocr_char
233
 
234
  logging.info(f"Custom OCR before validation, recognized_text: {recognized_text}")
235
- # Relaxed validation for debugging
236
  if recognized_text:
237
  return recognized_text
238
- logging.info(f"Custom OCR text '{recognized_text}' failed validation.")
239
  return None
240
  except Exception as e:
241
  logging.error(f"Custom seven-segment OCR failed: {str(e)}")
@@ -248,13 +281,16 @@ def extract_weight_from_image(pil_img):
248
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
249
  save_debug_image(img, "00_input_image")
250
 
 
 
251
  brightness = estimate_brightness(img)
252
- conf_threshold = 0.3 if brightness > 150 else (0.2 if brightness > 80 else 0.1)
253
 
254
  roi_img, roi_bbox = detect_roi(img)
255
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
256
  if custom_result:
257
- # Basic cleaning
 
258
  text = re.sub(r"[^\d\.\-]", "", custom_result) # Allow negative signs
259
  if text.count('.') > 1:
260
  text = text.replace('.', '', text.count('.') - 1)
@@ -267,34 +303,34 @@ def extract_weight_from_image(pil_img):
267
  logging.warning(f"Custom OCR result '{text}' is invalid after cleaning.")
268
  else:
269
  try:
270
- float(text)
271
- logging.info(f"Custom OCR result: {text}, Confidence: 100.0%")
272
- return text, 100.0
273
  except ValueError:
274
  logging.warning(f"Custom OCR result '{text}' is not a valid number, falling back.")
275
- logging.warning(f"Custom OCR result '{custom_result}' failed validation, falling back.")
276
 
277
  logging.info("Custom OCR failed or invalid, falling back to general EasyOCR.")
278
  processed_roi_img = preprocess_image(roi_img)
279
 
280
- # Try multiple thresholding approaches
281
- if brightness > 150:
282
  thresh = cv2.adaptiveThreshold(processed_roi_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
283
- cv2.THRESH_BINARY, 41, 7)
284
  save_debug_image(thresh, "09_fallback_adaptive_thresh")
285
  else:
286
- _, thresh = cv2.threshold(processed_roi_img, 30, 255, cv2.THRESH_BINARY)
287
  save_debug_image(thresh, "09_fallback_simple_thresh")
288
 
289
  # Morphological cleaning
290
- kernel = np.ones((3, 3), np.uint8)
291
- thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
292
  save_debug_image(thresh, "09_fallback_morph_cleaned")
293
 
294
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
295
- contrast_ths=0.1, adjust_contrast=1.0,
296
- text_threshold=0.2, mag_ratio=5.0,
297
- allowlist='0123456789.-', batch_size=4, y_ths=0.6)
298
 
299
  best_weight = None
300
  best_conf = 0.0
@@ -319,21 +355,19 @@ def extract_weight_from_image(pil_img):
319
  parts = text.split('.')
320
  text = parts[0] + '.' + ''.join(parts[1:])
321
  text = text.strip('.')
322
- if len(text.replace('.', '').replace('-', '')) > 0: # Allow negative weights
323
  try:
324
  weight = float(text)
325
  range_score = 1.0
326
- if 0.0 <= weight <= 250:
327
  range_score = 1.5
328
- elif weight > 250 and weight <= 500:
329
- range_score = 1.2
330
- elif weight > 500 and weight <= 1000:
331
  range_score = 1.0
332
  else:
333
  range_score = 0.5
334
  digit_count = len(text.replace('.', '').replace('-', ''))
335
  digit_score = 1.0
336
- if digit_count >= 2 and digit_count <= 5:
337
  digit_score = 1.3
338
  elif digit_count == 1:
339
  digit_score = 0.8
@@ -344,10 +378,10 @@ def extract_weight_from_image(pil_img):
344
  x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
345
  x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
346
  bbox_area = (x_max - x_min) * (y_max - y_min)
347
- if roi_area > 0 and bbox_area / roi_area < 0.02:
348
  score *= 0.5
349
  bbox_aspect_ratio = (x_max - x_min) / (y_max - y_min) if (y_max - y_min) > 0 else 0
350
- if bbox_aspect_ratio < 0.1:
351
  score *= 0.7
352
  if score > best_score and conf > conf_threshold:
353
  best_weight = text
@@ -375,15 +409,6 @@ def extract_weight_from_image(pil_img):
375
  else:
376
  best_weight = best_weight.lstrip('0') or "0"
377
 
378
- try:
379
- final_float_weight = float(best_weight)
380
- if final_float_weight < 0.0 or final_float_weight > 1000:
381
- logging.warning(f"Detected weight {final_float_weight} is outside typical range, reducing confidence.")
382
- best_conf *= 0.5
383
- except ValueError:
384
- logging.warning(f"Final weight '{best_weight}' is not a valid number.")
385
- best_conf *= 0.5
386
-
387
  logging.info(f"Final detected weight: {best_weight}, Confidence: {round(best_conf * 100, 2)}%")
388
  return best_weight, round(best_conf * 100, 2)
389
 
 
6
  from datetime import datetime
7
  import os
8
  from PIL import Image, ImageEnhance
9
+ from scipy.signal import convolve2d
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 with English (enable GPU if available)
15
  easyocr_reader = easyocr.Reader(['en'], gpu=False)
16
 
17
  # Directory for debug images
 
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 deconvolution to reduce blur (approximate Wiener filter)"""
 
 
 
 
 
 
 
 
40
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
41
+ # Create a simple point spread function (PSF) for deblurring
42
+ psf = np.ones((5, 5)) / 25
43
+ # Normalize image to float32
44
+ img_float = gray.astype(np.float32) / 255.0
45
+ # Convolve with PSF (simulate blur)
46
+ img_blurred = convolve2d(img_float, psf, mode='same')
47
+ # Avoid division by zero
48
+ img_blurred = np.where(img_blurred == 0, 1e-10, img_blurred)
49
+ # Deconvolve
50
+ img_deblurred = img_float / img_blurred
51
+ img_deblurred = np.clip(img_deblurred * 255, 0, 255).astype(np.uint8)
52
+ save_debug_image(img_deblurred, "00_deblurred")
53
+ return img_deblurred
54
+
55
+ def preprocess_image(img):
56
+ """Enhance contrast, brightness, reduce noise, and deblur for digit detection"""
57
+ # Deblur first
58
+ deblurred = deblur_image(img)
59
+
60
+ # Convert to PIL for enhancement
61
+ pil_img = Image.fromarray(deblurred)
62
+ pil_img = ImageEnhance.Contrast(pil_img).enhance(2.5) # Aggressive contrast
63
+ pil_img = ImageEnhance.Brightness(pil_img).enhance(1.5) # Stronger brightness
64
+ img_enhanced = np.array(pil_img)
65
+ save_debug_image(img_enhanced, "00_preprocessed_pil")
66
+
67
+ # Apply CLAHE for local contrast enhancement
68
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
69
+ enhanced = clahe.apply(img_enhanced)
70
  save_debug_image(enhanced, "00_clahe_enhanced")
71
 
72
+ # Aggressive noise reduction
73
+ filtered = cv2.bilateralFilter(enhanced, d=15, sigmaColor=150, sigmaSpace=150)
74
  save_debug_image(filtered, "00_bilateral_filtered")
75
  return filtered
76
 
77
+ def normalize_image(img):
78
+ """Resize image to standard dimensions while preserving aspect ratio"""
79
+ h, w = img.shape[:2]
80
+ target_height = 720
81
+ aspect_ratio = w / h
82
+ target_width = int(target_height * aspect_ratio)
83
+ if target_width < 320:
84
+ target_width = 320
85
+ target_height = int(target_width / aspect_ratio)
86
+ resized = cv2.resize(img, (target_width, target_height), interpolation=cv2.INTER_CUBIC)
87
+ save_debug_image(resized, "00_normalized")
88
+ logging.debug(f"Normalized image to {target_width}x{target_height}")
89
+ return resized
90
+
91
  def detect_roi(img):
92
+ """Detect the digital display region, with fallback to full image"""
93
  try:
94
  save_debug_image(img, "01_original")
95
  gray = preprocess_image(img)
 
97
 
98
  # Try multiple thresholding methods
99
  brightness = estimate_brightness(img)
100
+ if brightness > 120:
101
  thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
102
+ cv2.THRESH_BINARY_INV, 41, 7) # Inverted for bright displays
103
  save_debug_image(thresh, "03_roi_adaptive_threshold_high")
104
  else:
105
+ _, thresh = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY_INV) # Low threshold for dim displays
106
+ save_debug_image(thresh, "03_roi_simple_threshold_low")
107
 
108
+ # Morphological operations to connect digits
109
+ kernel = np.ones((7, 7), np.uint8)
110
+ thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
111
  save_debug_image(thresh, "03_roi_morph_cleaned")
112
 
113
+ kernel = np.ones((15, 15), np.uint8)
114
+ dilated = cv2.dilate(thresh, kernel, iterations=6)
115
  save_debug_image(dilated, "04_roi_dilated")
116
 
117
  contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
121
  valid_contours = []
122
  for c in contours:
123
  area = cv2.contourArea(c)
124
+ if 100 < area < (img_area * 0.999): # Extremely relaxed area filter
125
  x, y, w, h = cv2.boundingRect(c)
126
  aspect_ratio = w / h if h > 0 else 0
127
+ if 0.3 <= aspect_ratio <= 15.0 and w > 20 and h > 10: # Very relaxed filters
128
  valid_contours.append(c)
129
 
130
  if valid_contours:
131
+ contour = max(valid_contours, key=cv2.contourArea)
132
  x, y, w, h = cv2.boundingRect(contour)
133
+ padding = 120 # Very generous padding
134
  x, y = max(0, x - padding), max(0, y - padding)
135
  w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
136
  roi_img = img[y:y+h, x:x+w]
 
138
  logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h})")
139
  return roi_img, (x, y, w, h)
140
 
141
+ logging.info("No suitable ROI found, returning full image.")
142
+ save_debug_image(img, "05_no_roi_full_fallback")
143
  return img, None
144
  except Exception as e:
145
  logging.error(f"ROI detection failed: {str(e)}")
 
149
  def detect_segments(digit_img):
150
  """Detect seven-segment patterns in a digit image"""
151
  h, w = digit_img.shape
152
+ if h < 6 or w < 3: # Extremely relaxed size constraints
153
  logging.debug(f"Digit image too small: {w}x{h}")
154
  return None
155
 
156
  segments = {
157
+ 'top': (int(w*0.05), int(w*0.95), 0, int(h*0.3)),
158
+ 'middle': (int(w*0.05), int(w*0.95), int(h*0.35), int(h*0.65)),
159
+ 'bottom': (int(w*0.05), int(w*0.95), int(h*0.7), h),
160
+ 'left_top': (0, int(w*0.35), int(h*0.05), int(h*0.55)),
161
+ 'left_bottom': (0, int(w*0.35), int(h*0.45), int(h*0.95)),
162
+ 'right_top': (int(w*0.65), w, int(h*0.05), int(h*0.55)),
163
+ 'right_bottom': (int(w*0.65), w, int(h*0.45), int(h*0.95))
164
  }
165
 
166
  segment_presence = {}
 
173
  continue
174
  pixel_count = np.sum(region == 255)
175
  total_pixels = region.size
176
+ segment_presence[name] = pixel_count / total_pixels > 0.25 # Very low threshold
177
  logging.debug(f"Segment {name}: {pixel_count}/{total_pixels} = {pixel_count/total_pixels:.2f}")
178
 
179
  digit_patterns = {
 
213
  def custom_seven_segment_ocr(img, roi_bbox):
214
  """Perform custom OCR for seven-segment displays"""
215
  try:
216
+ gray = preprocess_image(img)
217
  brightness = estimate_brightness(img)
218
+ # Multiple thresholding approaches
219
+ if brightness > 120:
220
+ _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
221
  save_debug_image(thresh, "06_roi_otsu_threshold")
222
  else:
223
+ _, thresh = cv2.threshold(gray, 15, 255, cv2.THRESH_BINARY_INV) # Very low threshold
224
  save_debug_image(thresh, "06_roi_simple_threshold")
225
 
226
  # Morphological cleaning
227
+ kernel = np.ones((5, 5), np.uint8)
228
+ thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
229
  save_debug_image(thresh, "06_roi_morph_cleaned")
230
 
231
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
232
+ contrast_ths=0.05, adjust_contrast=1.2,
233
+ text_threshold=0.2, mag_ratio=6.0,
234
+ allowlist='0123456789.-', y_ths=0.7)
235
 
236
  logging.info(f"Custom OCR EasyOCR results: {results}")
237
  if not results:
 
242
  for (bbox, text, conf) in results:
243
  (x1, y1), (x2, y2), (x3, y3), (x4, y4) = bbox
244
  h_bbox = max(y1, y2, y3, y4) - min(y1, y2, y3, y4)
245
+ if len(text) <= 2 and any(c in '0123456789.-' for c in text) and h_bbox > 3:
246
  x_min, x_max = int(min(x1, x4)), int(max(x2, x3))
247
  y_min, y_max = int(min(y1, y2)), int(max(y3, y4))
248
  digits_info.append((x_min, x_max, y_min, y_max, text, conf))
 
256
  continue
257
  digit_img_crop = thresh[y_min:y_max, x_min:x_max]
258
  save_debug_image(digit_img_crop, f"07_digit_crop_{idx}_{easyocr_char}")
259
+ if easyocr_conf > 0.7 or easyocr_char in '.-' or digit_img_crop.shape[0] < 6 or digit_img_crop.shape[1] < 3:
260
  recognized_text += easyocr_char
261
  else:
262
  digit_from_segments = detect_segments(digit_img_crop)
 
266
  recognized_text += easyocr_char
267
 
268
  logging.info(f"Custom OCR before validation, recognized_text: {recognized_text}")
 
269
  if recognized_text:
270
  return recognized_text
271
+ logging.info(f"Custom OCR text '{recognized_text}' is empty.")
272
  return None
273
  except Exception as e:
274
  logging.error(f"Custom seven-segment OCR failed: {str(e)}")
 
281
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
282
  save_debug_image(img, "00_input_image")
283
 
284
+ # Normalize image dimensions
285
+ img = normalize_image(img)
286
  brightness = estimate_brightness(img)
287
+ conf_threshold = 0.2 if brightness > 120 else 0.1
288
 
289
  roi_img, roi_bbox = detect_roi(img)
290
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
291
  if custom_result:
292
+ logging.info(f"Raw custom OCR result: {custom_result}")
293
+ # Minimal cleaning
294
  text = re.sub(r"[^\d\.\-]", "", custom_result) # Allow negative signs
295
  if text.count('.') > 1:
296
  text = text.replace('.', '', text.count('.') - 1)
 
303
  logging.warning(f"Custom OCR result '{text}' is invalid after cleaning.")
304
  else:
305
  try:
306
+ weight = float(text)
307
+ logging.info(f"Custom OCR result: {text}, Confidence: 90.0%")
308
+ return text, 90.0
309
  except ValueError:
310
  logging.warning(f"Custom OCR result '{text}' is not a valid number, falling back.")
311
+ logging.warning(f"Custom OCR result '{custom_result}' failed cleaning, falling back.")
312
 
313
  logging.info("Custom OCR failed or invalid, falling back to general EasyOCR.")
314
  processed_roi_img = preprocess_image(roi_img)
315
 
316
+ # Multiple thresholding approaches
317
+ if brightness > 120:
318
  thresh = cv2.adaptiveThreshold(processed_roi_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
319
+ cv2.THRESH_BINARY_INV, 51, 9)
320
  save_debug_image(thresh, "09_fallback_adaptive_thresh")
321
  else:
322
+ _, thresh = cv2.threshold(processed_roi_img, 15, 255, cv2.THRESH_BINARY_INV)
323
  save_debug_image(thresh, "09_fallback_simple_thresh")
324
 
325
  # Morphological cleaning
326
+ kernel = np.ones((5, 5), np.uint8)
327
+ thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
328
  save_debug_image(thresh, "09_fallback_morph_cleaned")
329
 
330
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
331
+ contrast_ths=0.05, adjust_contrast=1.2,
332
+ text_threshold=0.1, mag_ratio=7.0,
333
+ allowlist='0123456789.-', batch_size=4, y_ths=0.8)
334
 
335
  best_weight = None
336
  best_conf = 0.0
 
355
  parts = text.split('.')
356
  text = parts[0] + '.' + ''.join(parts[1:])
357
  text = text.strip('.')
358
+ if len(text.replace('.', '').replace('-', '')) > 0:
359
  try:
360
  weight = float(text)
361
  range_score = 1.0
362
+ if -1000 <= weight <= 1000: # Allow negative weights
363
  range_score = 1.5
364
+ elif weight > 1000 and weight <= 2000:
 
 
365
  range_score = 1.0
366
  else:
367
  range_score = 0.5
368
  digit_count = len(text.replace('.', '').replace('-', ''))
369
  digit_score = 1.0
370
+ if digit_count >= 2 and digit_count <= 6:
371
  digit_score = 1.3
372
  elif digit_count == 1:
373
  digit_score = 0.8
 
378
  x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
379
  x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
380
  bbox_area = (x_max - x_min) * (y_max - y_min)
381
+ if roi_area > 0 and bbox_area / roi_area < 0.01:
382
  score *= 0.5
383
  bbox_aspect_ratio = (x_max - x_min) / (y_max - y_min) if (y_max - y_min) > 0 else 0
384
+ if bbox_aspect_ratio < 0.05:
385
  score *= 0.7
386
  if score > best_score and conf > conf_threshold:
387
  best_weight = text
 
409
  else:
410
  best_weight = best_weight.lstrip('0') or "0"
411
 
 
 
 
 
 
 
 
 
 
412
  logging.info(f"Final detected weight: {best_weight}, Confidence: {round(best_conf * 100, 2)}%")
413
  return best_weight, round(best_conf * 100, 2)
414