Sanjayraju30 commited on
Commit
373b0d2
·
verified ·
1 Parent(s): c320b80

Update ocr_engine.py

Browse files
Files changed (1) hide show
  1. ocr_engine.py +74 -97
ocr_engine.py CHANGED
@@ -34,12 +34,12 @@ def estimate_brightness(img):
34
  def preprocess_image(img):
35
  """Preprocess image for better OCR accuracy."""
36
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
37
- # Apply Gaussian blur to reduce noise
38
- blurred = cv2.GaussianBlur(gray, (5, 5), 0)
39
- save_debug_image(blurred, "01_preprocess_blurred")
40
  # Enhance contrast using CLAHE
41
  clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
42
- enhanced = clahe.apply(blurred)
43
  save_debug_image(enhanced, "02_preprocess_clahe")
44
  return enhanced
45
 
@@ -48,15 +48,19 @@ def detect_roi(img):
48
  try:
49
  save_debug_image(img, "03_original")
50
  preprocessed = preprocess_image(img)
 
51
 
52
- # Adaptive thresholding with refined parameters
 
53
  thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
54
- cv2.THRESH_BINARY_INV, 15, 5)
55
- save_debug_image(thresh, "04_roi_adaptive_threshold")
 
 
56
 
57
  # Morphological operations to connect digits
58
  kernel = np.ones((5, 5), np.uint8)
59
- dilated = cv2.dilate(thresh, kernel, iterations=2)
60
  eroded = cv2.erode(dilated, kernel, iterations=1)
61
  save_debug_image(eroded, "05_roi_morphological")
62
 
@@ -67,14 +71,16 @@ def detect_roi(img):
67
  valid_contours = []
68
  for c in contours:
69
  area = cv2.contourArea(c)
70
- if 1000 < area < (img_area * 0.9):
71
- x, y, w, h = cv2.boundingRect(c)
72
- aspect_ratio = w / h
73
- if 1.8 <= aspect_ratio <= 8.0 and w > 100 and h > 50:
74
- valid_contours.append(c)
 
 
75
 
76
  if valid_contours:
77
- contour = max(valid_contours, key=cv2.contourArea)
78
  x, y, w, h = cv2.boundingRect(contour)
79
  padding = 80
80
  x, y = max(0, x - padding), max(0, y - padding)
@@ -84,6 +90,21 @@ def detect_roi(img):
84
  logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h})")
85
  return roi_img, (x, y, w, h)
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  logging.info("No suitable ROI found, returning original image.")
88
  save_debug_image(img, "06_no_roi_original_fallback")
89
  return img, None
@@ -92,20 +113,20 @@ def detect_roi(img):
92
  save_debug_image(img, "06_roi_detection_error_fallback")
93
  return img, None
94
 
95
- def detect_segments(digit_img):
96
  """Detect seven-segment patterns in a digit image."""
97
  h, w = digit_img.shape
98
  if h < 20 or w < 15:
99
  return None
100
 
101
  segments = {
102
- 'top': (int(w*0.1), int(w*0.9), 0, int(h*0.15)),
103
- 'middle': (int(w*0.1), int(w*0.9), int(h*0.45), int(h*0.55)),
104
- 'bottom': (int(w*0.1), int(w*0.9), int(h*0.85), h),
105
- 'left_top': (0, int(w*0.2), int(h*0.1), int(h*0.5)),
106
- 'left_bottom': (0, int(w*0.2), int(h*0.5), int(h*0.9)),
107
- 'right_top': (int(w*0.8), w, int(h*0.1), int(h*0.5)),
108
- 'right_bottom': (int(w*0.8), w, int(h*0.5), int(h*0.9))
109
  }
110
 
111
  segment_presence = {}
@@ -118,7 +139,7 @@ def detect_segments(digit_img):
118
  continue
119
  pixel_count = np.sum(region == 255)
120
  total_pixels = region.size
121
- segment_presence[name] = pixel_count / total_pixels > 0.4
122
 
123
  digit_patterns = {
124
  '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
@@ -138,8 +159,8 @@ def detect_segments(digit_img):
138
  for digit, pattern in digit_patterns.items():
139
  matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
140
  non_matches_penalty = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
141
- score = matches - 0.5 * non_matches_penalty
142
- if all(segment_presence.get(s, False) for s in pattern):
143
  score += 1.0
144
  if score > max_score:
145
  max_score = score
@@ -162,10 +183,11 @@ def custom_seven_segment_ocr(img, roi_bbox):
162
  thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
163
  save_debug_image(thresh, "08_morph_closed")
164
 
 
165
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
166
  contrast_ths=0.3, adjust_contrast=1.0,
167
  text_threshold=0.6, mag_ratio=3.0,
168
- allowlist='0123456789.', y_ths=0.2)
169
 
170
  logging.info(f"EasyOCR results: {results}")
171
  if not results:
@@ -193,21 +215,18 @@ def custom_seven_segment_ocr(img, roi_bbox):
193
  if easyocr_conf > 0.95 or easyocr_char == '.':
194
  recognized_text += easyocr_char
195
  else:
196
- digit_from_segments = detect_segments(digit_img_crop)
197
  recognized_text += digit_from_segments if digit_from_segments else easyocr_char
198
 
199
  logging.info(f"Before validation, recognized_text: {recognized_text}")
200
  text = re.sub(r"[^\d\.]", "", recognized_text)
201
  if text.count('.') > 1:
202
  text = text.replace('.', '', text.count('.') - 1)
203
- if text and re.fullmatch(r"^\d*\.?\d+$", text):
204
- if text.startswith('.'):
205
- text = "0" + text
206
- if text.endswith('.'):
207
- text = text.rstrip('.')
208
- if text == '.' or text == '':
209
  return None
210
- return text
211
  logging.info(f"Custom OCR text '{recognized_text}' failed validation.")
212
  return None
213
  except Exception as e:
@@ -221,15 +240,30 @@ def extract_weight_from_image(pil_img):
221
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
222
  save_debug_image(img, "00_input_image")
223
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  brightness = estimate_brightness(img)
225
- conf_threshold = 0.7 if brightness > 150 else (0.6 if brightness > 80 else 0.5)
 
 
 
226
 
227
  roi_img, roi_bbox = detect_roi(img)
228
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
229
  if custom_result:
230
  try:
231
  weight = float(custom_result)
232
- if 0.1 <= weight <= 300:
233
  logging.info(f"Custom OCR result: {custom_result}, Confidence: 95.0%")
234
  return custom_result, 95.0
235
  else:
@@ -242,70 +276,13 @@ def extract_weight_from_image(pil_img):
242
  kernel_sharpening = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
243
  sharpened_roi = cv2.filter2D(preprocessed_roi, -1, kernel_sharpening)
244
  save_debug_image(sharpened_roi, "10_fallback_sharpened")
 
245
  final_roi = cv2.adaptiveThreshold(sharpened_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
246
- cv2.THRESH_BINARY_INV, 25, 8)
247
  save_debug_image(final_roi, "11_fallback_adaptive_thresh")
248
 
 
249
  results = easyocr_reader.readtext(final_roi, detail=1, paragraph=False,
250
  contrast_ths=0.4, adjust_contrast=1.2,
251
  text_threshold=0.5, mag_ratio=4.0,
252
- allowlist='0123456789.', batch_size=4, y_ths=0.2)
253
-
254
- best_weight = None
255
- best_conf = 0.0
256
- best_score = 0.0
257
- for (bbox, text, conf) in results:
258
- text = re.sub(r"[^\d\.]", "", text)
259
- if text.count('.') > 1:
260
- text = text.replace('.', '', text.count('.') - 1)
261
- text = text.strip('.')
262
- if re.fullmatch(r"^\d*\.?\d+$", text):
263
- try:
264
- weight = float(text)
265
- range_score = 1.5 if 0.1 <= weight <= 300 else 0.8
266
- digit_count = len(text.replace('.', ''))
267
- digit_score = 1.3 if 2 <= digit_count <= 5 else 0.9
268
- score = conf * range_score * digit_score
269
- if roi_bbox:
270
- (x_roi, y_roi, w_roi, h_roi) = roi_bbox
271
- roi_area = w_roi * h_roi
272
- x_min, y_min = int(min(b[0] for b in bbox)), int(min(b[1] for b in bbox))
273
- x_max, y_max = int(max(b[0] for b in bbox)), int(max(b[1] for b in bbox))
274
- bbox_area = (x_max - x_min) * (y_max - y_min)
275
- if roi_area > 0 and bbox_area / roi_area < 0.05:
276
- score *= 0.6
277
- if score > best_score and conf > conf_threshold:
278
- best_weight = text
279
- best_conf = conf
280
- best_score = score
281
- logging.info(f"Candidate EasyOCR weight: '{text}', Conf: {conf}, Score: {score}")
282
- except ValueError:
283
- logging.warning(f"Could not convert '{text}' to float during EasyOCR fallback.")
284
- continue
285
-
286
- if not best_weight:
287
- logging.info("No valid weight detected after all attempts.")
288
- return "Not detected", 0.0
289
-
290
- # Format the weight
291
- if "." in best_weight:
292
- int_part, dec_part = best_weight.split(".")
293
- int_part = int_part.lstrip("0") or "0"
294
- dec_part = dec_part.rstrip('0')
295
- best_weight = f"{int_part}.{dec_part}" if dec_part else int_part
296
- else:
297
- best_weight = best_weight.lstrip('0') or "0"
298
-
299
- try:
300
- final_weight = float(best_weight)
301
- if final_weight < 0.1 or final_weight > 300:
302
- best_conf *= 0.7
303
- except ValueError:
304
- pass
305
-
306
- logging.info(f"Final detected weight: {best_weight}, Confidence: {round(best_conf * 100, 2)}%")
307
- return best_weight, round(best_conf * 100, 2)
308
-
309
- except Exception as e:
310
- logging.error(f"Weight extraction failed unexpectedly: {str(e)}")
311
- return "Not detected", 0.0
 
34
  def preprocess_image(img):
35
  """Preprocess image for better OCR accuracy."""
36
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
37
+ # Apply bilateral filter to preserve edges
38
+ denoised = cv2.bilateralFilter(gray, 11, 17, 17)
39
+ save_debug_image(denoised, "01_preprocess_bilateral")
40
  # Enhance contrast using CLAHE
41
  clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
42
+ enhanced = clahe.apply(denoised)
43
  save_debug_image(enhanced, "02_preprocess_clahe")
44
  return enhanced
45
 
 
48
  try:
49
  save_debug_image(img, "03_original")
50
  preprocessed = preprocess_image(img)
51
+ brightness_map = cv2.GaussianBlur(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), (15, 15), 0)
52
 
53
+ # Dynamic adaptive thresholding
54
+ block_size = max(11, min(31, int(img.shape[0] / 20) * 2 + 1))
55
  thresh = cv2.adaptiveThreshold(preprocessed, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
56
+ cv2.THRESH_BINARY_INV, block_size, 5)
57
+ _, otsu_thresh = cv2.threshold(preprocessed, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
58
+ combined_thresh = cv2.bitwise_and(thresh, otsu_thresh)
59
+ save_debug_image(combined_thresh, "04_roi_combined_threshold")
60
 
61
  # Morphological operations to connect digits
62
  kernel = np.ones((5, 5), np.uint8)
63
+ dilated = cv2.dilate(combined_thresh, kernel, iterations=2)
64
  eroded = cv2.erode(dilated, kernel, iterations=1)
65
  save_debug_image(eroded, "05_roi_morphological")
66
 
 
71
  valid_contours = []
72
  for c in contours:
73
  area = cv2.contourArea(c)
74
+ x, y, w, h = cv2.boundingRect(c)
75
+ roi_brightness = np.mean(brightness_map[y:y+h, x:x+w])
76
+ aspect_ratio = w / h
77
+ if (1000 < area < (img_area * 0.9) and
78
+ 1.0 <= aspect_ratio <= 10.0 and w > 80 and h > 40 and roi_brightness > 100):
79
+ valid_contours.append((c, roi_brightness))
80
+ logging.debug(f"Contour: Area={area}, Aspect={aspect_ratio:.2f}, Brightness={roi_brightness:.2f}")
81
 
82
  if valid_contours:
83
+ contour, _ = max(valid_contours, key=lambda x: x[1]) # Max brightness
84
  x, y, w, h = cv2.boundingRect(contour)
85
  padding = 80
86
  x, y = max(0, x - padding), max(0, y - padding)
 
90
  logging.info(f"Detected ROI with dimensions: ({x}, {y}, {w}, {h})")
91
  return roi_img, (x, y, w, h)
92
 
93
+ logging.info("No suitable ROI found, attempting fallback criteria.")
94
+ # Fallback with relaxed criteria
95
+ valid_contours = [c for c in contours if 500 < cv2.contourArea(c) < (img_area * 0.95) and
96
+ 0.8 <= cv2.boundingRect(c)[2]/cv2.boundingRect(c)[3] <= 12.0]
97
+ if valid_contours:
98
+ contour = max(valid_contours, key=cv2.contourArea)
99
+ x, y, w, h = cv2.boundingRect(contour)
100
+ padding = 80
101
+ x, y = max(0, x - padding), max(0, y - padding)
102
+ w, h = min(w + 2 * padding, img.shape[1] - x), min(h + 2 * padding, img.shape[0] - y)
103
+ roi_img = img[y:y+h, x:x+w]
104
+ save_debug_image(roi_img, "06_detected_roi_fallback")
105
+ logging.info(f"Detected fallback ROI with dimensions: ({x}, {y}, {w}, {h})")
106
+ return roi_img, (x, y, w, h)
107
+
108
  logging.info("No suitable ROI found, returning original image.")
109
  save_debug_image(img, "06_no_roi_original_fallback")
110
  return img, None
 
113
  save_debug_image(img, "06_roi_detection_error_fallback")
114
  return img, None
115
 
116
+ def detect_segments(digit_img, brightness):
117
  """Detect seven-segment patterns in a digit image."""
118
  h, w = digit_img.shape
119
  if h < 20 or w < 15:
120
  return None
121
 
122
  segments = {
123
+ 'top': (int(w*0.15), int(w*0.85), 0, int(h*0.2)),
124
+ 'middle': (int(w*0.15), int(w*0.85), int(h*0.45), int(h*0.55)),
125
+ 'bottom': (int(w*0.15), int(w*0.85), int(h*0.8), h),
126
+ 'left_top': (0, int(w*0.25), int(h*0.15), int(h*0.5)),
127
+ 'left_bottom': (0, int(w*0.25), int(h*0.5), int(h*0.85)),
128
+ 'right_top': (int(w*0.75), w, int(h*0.15), int(h*0.5)),
129
+ 'right_bottom': (int(w*0.75), w, int(h*0.5), int(h*0.85))
130
  }
131
 
132
  segment_presence = {}
 
139
  continue
140
  pixel_count = np.sum(region == 255)
141
  total_pixels = region.size
142
+ segment_presence[name] = pixel_count / total_pixels > (0.3 if brightness < 100 else 0.5)
143
 
144
  digit_patterns = {
145
  '0': ('top', 'bottom', 'left_top', 'left_bottom', 'right_top', 'right_bottom'),
 
159
  for digit, pattern in digit_patterns.items():
160
  matches = sum(1 for segment in pattern if segment_presence.get(segment, False))
161
  non_matches_penalty = sum(1 for segment in segment_presence if segment not in pattern and segment_presence[segment])
162
+ score = matches - 0.3 * non_matches_penalty
163
+ if matches >= len(pattern) * 0.8:
164
  score += 1.0
165
  if score > max_score:
166
  max_score = score
 
183
  thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
184
  save_debug_image(thresh, "08_morph_closed")
185
 
186
+ batch_size = max(4, min(16, int(img.shape[0] * img.shape[1] / 100000)))
187
  results = easyocr_reader.readtext(thresh, detail=1, paragraph=False,
188
  contrast_ths=0.3, adjust_contrast=1.0,
189
  text_threshold=0.6, mag_ratio=3.0,
190
+ allowlist='0123456789.', batch_size=batch_size, y_ths=0.2)
191
 
192
  logging.info(f"EasyOCR results: {results}")
193
  if not results:
 
215
  if easyocr_conf > 0.95 or easyocr_char == '.':
216
  recognized_text += easyocr_char
217
  else:
218
+ digit_from_segments = detect_segments(digit_img_crop, brightness)
219
  recognized_text += digit_from_segments if digit_from_segments else easyocr_char
220
 
221
  logging.info(f"Before validation, recognized_text: {recognized_text}")
222
  text = re.sub(r"[^\d\.]", "", recognized_text)
223
  if text.count('.') > 1:
224
  text = text.replace('.', '', text.count('.') - 1)
225
+ if text and re.fullmatch(r"^\d*\.?\d*$", text):
226
+ text = text.strip('.')
227
+ if text == '':
 
 
 
228
  return None
229
+ return text.lstrip('0') or '0'
230
  logging.info(f"Custom OCR text '{recognized_text}' failed validation.")
231
  return None
232
  except Exception as e:
 
240
  img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
241
  save_debug_image(img, "00_input_image")
242
 
243
+ # Rotation correction
244
+ edges = cv2.Canny(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), 100, 200)
245
+ lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=100, maxLineGap=10)
246
+ if lines is not None:
247
+ angle = np.mean([np.arctan2(line[0][3] - line[0][1], line[0][2] - line[0][0]) * 180 / np.pi for line in lines])
248
+ if abs(angle) > 5:
249
+ (h, w) = img.shape[:2]
250
+ center = (w // 2, h // 2)
251
+ M = cv2.getRotationMatrix2D(center, angle, 1.0)
252
+ img = cv2.warpAffine(img, M, (w, h))
253
+ save_debug_image(img, "00_rotated_image")
254
+
255
  brightness = estimate_brightness(img)
256
+ conf_threshold = 0.7 if brightness > 150 else (0.6 if brightness > 80 else 0.4)
257
+ if roi_bbox := detect_roi(img)[1]:
258
+ roi_area = roi_bbox[2] * roi_bbox[3]
259
+ conf_threshold *= 1.2 if roi_area > (img.shape[0] * img.shape[1] * 0.5) else 1.0
260
 
261
  roi_img, roi_bbox = detect_roi(img)
262
  custom_result = custom_seven_segment_ocr(roi_img, roi_bbox)
263
  if custom_result:
264
  try:
265
  weight = float(custom_result)
266
+ if 0.01 <= weight <= 500:
267
  logging.info(f"Custom OCR result: {custom_result}, Confidence: 95.0%")
268
  return custom_result, 95.0
269
  else:
 
276
  kernel_sharpening = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
277
  sharpened_roi = cv2.filter2D(preprocessed_roi, -1, kernel_sharpening)
278
  save_debug_image(sharpened_roi, "10_fallback_sharpened")
279
+ block_size = max(11, min(31, int(roi_img.shape[0] / 20) * 2 + 1))
280
  final_roi = cv2.adaptiveThreshold(sharpened_roi, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
281
+ cv2.THRESH_BINARY_INV, block_size, 8)
282
  save_debug_image(final_roi, "11_fallback_adaptive_thresh")
283
 
284
+ batch_size = max(4, min(16, int(roi_img.shape[0] * roi_img.shape[1] / 100000)))
285
  results = easyocr_reader.readtext(final_roi, detail=1, paragraph=False,
286
  contrast_ths=0.4, adjust_contrast=1.2,
287
  text_threshold=0.5, mag_ratio=4.0,
288
+ allowlist='0123456789. kglb', batch_size=batch Horrible error: invalid syntax