Files
PartsInquiry/backend/txm/app/pipeline.py
2025-09-27 22:57:59 +08:00

140 lines
5.3 KiB
Python

from typing import Optional, List, Dict
import cv2
import logging
from .config_loader import load_config
from .image_processing import (
binarize_image,
enhance_barcode_stripes,
find_barcode_roi,
morph_close,
resize_keep_aspect,
warp_barcode_region,
)
from .ean13_decoder import sample_and_decode
from .io_utils import read_image_bgr
from .pyzbar_engine import decode_with_pyzbar
class EAN13Recognizer:
def __init__(self, config_path: str = "config/config.yaml") -> None:
self.logger = logging.getLogger(self.__class__.__name__)
self.config = load_config(config_path)
self.logger.debug("配置加载完成: preprocess=%s, roi=%s, decoder=%s", self.config.get("preprocess"), self.config.get("roi"), self.config.get("decoder"))
def recognize_from_path(self, image_path: str) -> Optional[str]:
image = read_image_bgr(image_path)
if image is None:
self.logger.warning("读取图像失败: path=%s", image_path)
return None
return self.recognize_from_image(image)
def _recognize_with_pyzbar(self, img) -> Optional[str]:
decoder_cfg = self.config["decoder"]
pyz_res = decode_with_pyzbar(
img,
try_invert=decoder_cfg.get("try_invert", True),
rotations=decoder_cfg.get("rotations", [0, 90, 180, 270]),
)
if pyz_res:
self.logger.debug("pyzbar 识别到 %d 条结果", len(pyz_res))
for r in pyz_res:
if r.get("type") in ("EAN13", "EAN-13") and r.get("code"):
self.logger.info("pyzbar 命中 EAN13: %s", r.get("code"))
return r.get("code")
return None
def recognize_from_image(self, image) -> Optional[str]:
cfg = self.config
preprocess_cfg = cfg["preprocess"]
roi_cfg = cfg["roi"]
decoder_cfg = cfg["decoder"]
img = resize_keep_aspect(image, preprocess_cfg.get("resize_width", 0))
self.logger.debug("输入尺寸=%s, 预处理后尺寸=%s", getattr(image, 'shape', None), getattr(img, 'shape', None))
# 先按引擎优先级尝试 Pyzbar
engine_order: List[str] = decoder_cfg.get("engine_order", ["pyzbar", "ean13"])
if "pyzbar" in engine_order:
code = self._recognize_with_pyzbar(img)
if code:
return code
if "ean13" not in engine_order:
return None
# 回退到自研 EAN-13 解码
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
grad = enhance_barcode_stripes(
gray,
gaussian_ksize=preprocess_cfg.get("gaussian_blur_ksize", 3),
sobel_ksize=preprocess_cfg.get("sobel_ksize", 3),
)
bin_img = binarize_image(grad, preprocess_cfg.get("binarize", "otsu"))
closed = morph_close(bin_img, preprocess_cfg.get("close_kernel", 21))
roi_result = find_barcode_roi(
closed,
original_shape=gray.shape[:2],
min_area_ratio=roi_cfg.get("min_area_ratio", 0.01),
min_wh_ratio=roi_cfg.get("min_wh_ratio", 2.0),
)
if not roi_result:
self.logger.debug("未找到条码 ROI")
return None
_, bbox = roi_result
self.logger.debug("ROI bbox=%s", bbox)
warped = warp_barcode_region(
gray,
bbox,
roi_cfg.get("warp_target_height", 120),
crop_bottom_ratio=roi_cfg.get("crop_bottom_ratio", 0.0),
)
self.logger.debug("透视矫正后尺寸=%s", getattr(warped, 'shape', None))
code = sample_and_decode(
warped,
decoder_cfg.get("sample_rows", [0.5]),
decoder_cfg.get("total_modules", 95),
)
if code:
self.logger.info("自研 EAN13 解码成功: %s", code)
else:
self.logger.debug("自研 EAN13 解码失败")
return code
def recognize_any_from_image(self, image) -> Dict[str, object]:
cfg = self.config
decoder_cfg = cfg["decoder"]
img = resize_keep_aspect(image, cfg["preprocess"].get("resize_width", 0))
# Pyzbar 全量识别
pyz_res = decode_with_pyzbar(
img,
try_invert=decoder_cfg.get("try_invert", True),
rotations=decoder_cfg.get("rotations", [0, 90, 180, 270]),
)
self.logger.debug("pyzbar 返回 %d 条结果", len(pyz_res) if isinstance(pyz_res, list) else 0)
ean13 = ""
for r in pyz_res:
if r.get("type") in ("EAN13", "EAN-13") and r.get("code"):
ean13 = r.get("code")
break
others = [r for r in pyz_res if r.get("type") not in ("EAN13", "EAN-13")]
if not ean13:
e = self.recognize_from_image(img)
if e:
ean13 = e
if ean13:
self.logger.info("recognize_any 命中 EAN13: %s, others=%d", ean13, len(others))
else:
self.logger.debug("recognize_any 未命中 EAN13, others=%d", len(others))
return {"ean13": ean13, "others": others}
def recognize_any_from_path(self, image_path: str) -> Dict[str, object]:
image = read_image_bgr(image_path)
if image is None:
return {"ean13": "", "others": []}
return self.recognize_any_from_image(image)