trackit / backend /utils /image_processing.py
devZenaight's picture
npm run dev
479f206
Raw
History Blame Contribute Delete
2.94 kB
import cv2
import numpy as np
from typing import Tuple, Dict
import io
def crop_receipt_from_image(image_bytes: bytes) -> Tuple[bytes, Dict]:
"""Detect receipt edges and return a perspective-corrected JPEG.
Returns (jpeg_bytes, meta)
meta: { confidence: float, width: int, height: int }
"""
image_array = np.frombuffer(image_bytes, dtype=np.uint8)
img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
if img is None:
raise ValueError("Invalid image data")
orig = img.copy()
ratio = 500.0 / max(img.shape[0], img.shape[1])
if ratio < 1.0:
img = cv2.resize(img, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 50, 150)
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
receipt_contour = None
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
receipt_contour = approx
break
if receipt_contour is None:
# Fallback: treat whole image as receipt
h, w = img.shape[:2]
receipt_contour = np.array([[[0, 0]], [[w - 1, 0]], [[w - 1, h - 1]], [[0, h - 1]]])
confidence = 0.2
else:
confidence = 0.8
pts = receipt_contour.reshape(4, 2).astype("float32")
# Order points: top-left, top-right, bottom-right, bottom-left
rect = _order_points(pts)
(tl, tr, br, bl) = rect
width_top = np.linalg.norm(tr - tl)
width_bottom = np.linalg.norm(br - bl)
max_width = int(max(width_top, width_bottom))
height_right = np.linalg.norm(br - tr)
height_left = np.linalg.norm(bl - tl)
max_height = int(max(height_right, height_left))
dst = np.array(
[[0, 0], [max_width - 1, 0], [max_width - 1, max_height - 1], [0, max_height - 1]],
dtype="float32",
)
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(img, M, (max_width, max_height))
# Slight contrast enhancement and grayscale for OCR readiness
warped_gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
warped_eq = cv2.equalizeHist(warped_gray)
# Encode as JPEG
success, jpeg = cv2.imencode('.jpg', warped_eq, [int(cv2.IMWRITE_JPEG_QUALITY), 90])
if not success:
raise ValueError("Failed to encode JPEG")
return jpeg.tobytes(), {"confidence": float(confidence), "width": int(max_width), "height": int(max_height)}
def _order_points(pts: np.ndarray) -> np.ndarray:
x_sorted = pts[np.argsort(pts[:, 0]), :]
left = x_sorted[:2, :]
right = x_sorted[2:, :]
tl, bl = left[np.argsort(left[:, 1]), :]
tr, br = right[np.argsort(right[:, 1]), :]
return np.array([tl, tr, br, bl], dtype="float32")