|
import gradio as gr |
|
import cv2 |
|
import pytesseract |
|
from PIL import Image |
|
import io |
|
import base64 |
|
from datetime import datetime |
|
import pytz |
|
import numpy as np |
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
|
|
|
|
try: |
|
pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract' |
|
pytesseract.get_tesseract_version() |
|
logging.info("Tesseract is available") |
|
except Exception as e: |
|
logging.error(f"Tesseract not found or misconfigured: {str(e)}") |
|
|
|
|
|
def preprocess_image(img_cv): |
|
"""Preprocess image for OCR: enhance contrast, reduce noise, and apply adaptive thresholding.""" |
|
try: |
|
|
|
gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY) |
|
|
|
clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8)) |
|
contrast = clahe.apply(gray) |
|
|
|
blurred = cv2.GaussianBlur(contrast, (5, 5), 0) |
|
|
|
thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2) |
|
|
|
sharpened = cv2.filter2D(thresh, -1, np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])) |
|
return sharpened |
|
except Exception as e: |
|
logging.error(f"Image preprocessing failed: {str(e)}") |
|
return img_cv |
|
|
|
|
|
def extract_weight(img): |
|
"""Extract weight from image using Tesseract OCR.""" |
|
try: |
|
if img is None: |
|
logging.error("No image provided for OCR") |
|
return "Not detected", 0.0, None |
|
|
|
|
|
img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
|
|
|
processed_img = preprocess_image(img_cv) |
|
|
|
|
|
custom_config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789.' |
|
|
|
|
|
text = pytesseract.image_to_string(processed_img, config=custom_config) |
|
logging.info(f"OCR result: '{text}'") |
|
|
|
|
|
weight = ''.join(filter(lambda x: x in '0123456789.', text.strip())) |
|
if weight: |
|
try: |
|
weight_float = float(weight) |
|
if weight_float >= 0: |
|
confidence = 95.0 |
|
logging.info(f"Weight detected: {weight} (Confidence: {confidence:.2f}%)") |
|
return weight, confidence, processed_img |
|
except ValueError: |
|
logging.warning(f"Invalid number format: {weight}") |
|
|
|
logging.error("OCR failed to detect a valid weight") |
|
return "Not detected", 0.0, None |
|
except Exception as e: |
|
logging.error(f"OCR processing failed: {str(e)}") |
|
return "Not detected", 0.0, None |
|
|
|
|
|
def process_image(img): |
|
"""Process uploaded or captured image and extract weight.""" |
|
if img is None: |
|
logging.error("No image provided") |
|
return "No image uploaded", None, gr.update(visible=False), gr.update(visible=False) |
|
|
|
|
|
ist_time = datetime.now(pytz.timezone("Asia/Kolkata")).strftime("%d-%m-%Y %I:%M:%S %p") |
|
|
|
|
|
weight, confidence, processed_img = extract_weight(img) |
|
|
|
|
|
if weight == "Not detected" or confidence < 95.0: |
|
logging.warning(f"Weight detection failed: {weight} (Confidence: {confidence:.2f}%)") |
|
return f"{weight} (Confidence: {confidence:.2f}%)", ist_time, gr.update(visible=True), gr.update(visible=False) |
|
|
|
|
|
pil_image = Image.fromarray(processed_img) |
|
buffered = io.BytesIO() |
|
pil_image.save(buffered, format="PNG") |
|
img_base64 = base64.b64encode(buffered.getvalue()).decode() |
|
|
|
return f"{weight} kg (Confidence: {confidence:.2f}%)", ist_time, img_base64, gr.update(visible=True) |
|
|
|
|
|
with gr.Blocks(title="βοΈ Auto Weight Logger") as demo: |
|
gr.Markdown("## βοΈ Auto Weight Logger") |
|
gr.Markdown("π· Upload or capture an image of a digital weight scale (max 5MB).") |
|
|
|
with gr.Row(): |
|
image_input = gr.Image(type="pil", label="Upload / Capture Image", sources=["upload", "webcam"]) |
|
output_weight = gr.Textbox(label="βοΈ Detected Weight (in kg)") |
|
|
|
with gr.Row(): |
|
timestamp = gr.Textbox(label="π Captured At (IST)") |
|
snapshot = gr.Image(label="πΈ Snapshot Image", type="pil") |
|
|
|
submit = gr.Button("π Detect Weight") |
|
submit.click( |
|
fn=process_image, |
|
inputs=image_input, |
|
outputs=[output_weight, timestamp, snapshot] |
|
) |
|
|
|
gr.Markdown(""" |
|
### Instructions |
|
- Upload a clear, well-lit image of a digital weight scale display (7-segment font preferred). |
|
- Ensure the image is < 5MB (automatically resized if larger). |
|
- Review the detected weight and try again if it's incorrect. |
|
""") |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |
|
|