85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
from typing import List, Optional, Tuple
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
|
|
def resize_keep_aspect(image: np.ndarray, target_width: int) -> np.ndarray:
|
|
if target_width <= 0:
|
|
return image
|
|
h, w = image.shape[:2]
|
|
if w == target_width:
|
|
return image
|
|
scale = target_width / float(w)
|
|
new_size = (target_width, int(round(h * scale)))
|
|
return cv2.resize(image, new_size, interpolation=cv2.INTER_AREA)
|
|
|
|
|
|
def enhance_barcode_stripes(gray: np.ndarray, gaussian_ksize: int, sobel_ksize: int) -> np.ndarray:
|
|
g = gray
|
|
if gaussian_ksize and gaussian_ksize > 1:
|
|
g = cv2.GaussianBlur(g, (gaussian_ksize, gaussian_ksize), 0)
|
|
# 使用水平 Sobel 捕捉垂直边缘(条纹)
|
|
grad_x = cv2.Sobel(g, cv2.CV_32F, 1, 0, ksize=sobel_ksize)
|
|
grad_x = cv2.convertScaleAbs(grad_x)
|
|
return grad_x
|
|
|
|
|
|
def binarize_image(img: np.ndarray, method: str = "otsu") -> np.ndarray:
|
|
if method == "adaptive":
|
|
return cv2.adaptiveThreshold(
|
|
img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 31, 10
|
|
)
|
|
# 默认 OTSU
|
|
_, th = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
|
|
return th
|
|
|
|
|
|
def morph_close(img: np.ndarray, kernel_size: int) -> np.ndarray:
|
|
if kernel_size <= 1:
|
|
return img
|
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
|
|
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
|
|
return closed
|
|
|
|
|
|
def find_barcode_roi(binary: np.ndarray, original_shape: Tuple[int, int], min_area_ratio: float, min_wh_ratio: float) -> Optional[Tuple[np.ndarray, Tuple[int, int, int, int]]]:
|
|
h, w = original_shape
|
|
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
image_area = h * w
|
|
candidates: List[Tuple[float, Tuple[int, int, int, int]]] = []
|
|
for cnt in contours:
|
|
x, y, cw, ch = cv2.boundingRect(cnt)
|
|
area = cw * ch
|
|
if area / image_area < min_area_ratio:
|
|
continue
|
|
wh_ratio = cw / float(ch + 1e-6)
|
|
if wh_ratio < min_wh_ratio:
|
|
continue
|
|
candidates.append((area, (x, y, cw, ch)))
|
|
if not candidates:
|
|
return None
|
|
candidates.sort(key=lambda t: t[0], reverse=True)
|
|
_, bbox = candidates[0]
|
|
x, y, cw, ch = bbox
|
|
roi = binary[y : y + ch, x : x + cw]
|
|
return roi, bbox
|
|
|
|
|
|
def warp_barcode_region(gray: np.ndarray, bbox: Tuple[int, int, int, int], target_height: int, crop_bottom_ratio: float = 0.0) -> np.ndarray:
|
|
x, y, cw, ch = bbox
|
|
crop = gray[y : y + ch, x : x + cw]
|
|
# 去除底部数字区域干扰
|
|
if 0 < crop_bottom_ratio < 1:
|
|
hb = int(round(ch * (1.0 - crop_bottom_ratio)))
|
|
hb = max(10, min(ch, hb))
|
|
crop = crop[:hb, :]
|
|
if target_height <= 0:
|
|
return crop
|
|
scale = target_height / float(ch)
|
|
target_width = int(round(cw * scale))
|
|
warped = cv2.resize(crop, (target_width, target_height), interpolation=cv2.INTER_CUBIC)
|
|
return warped
|
|
|
|
|