from typing import Dict, List, Tuple import cv2 import numpy as np import logging # 尝试延迟引入 pyzbar;在未安装 zbar 运行库的环境下,保证服务可启动, # 后续仅使用自研 EAN-13 解码或返回空结果。 try: from pyzbar.pyzbar import decode as _pyzbar_decode, ZBarSymbol as _ZBarSymbol _PYZBAR_AVAILABLE = True except Exception as _e: # ImportError 或 zbar 动态库缺失 _PYZBAR_AVAILABLE = False _PYZBAR_IMPORT_ERR = _e _pyzbar_decode = None _ZBarSymbol = None def _prepare_images(gray: np.ndarray, try_invert: bool, rotations: List[int]) -> List[Tuple[np.ndarray, int, bool]]: # 返回 (图像, 旋转角度, 是否反色) images: List[Tuple[np.ndarray, int, bool]] = [] for ang in rotations: if ang % 360 == 0: rot = gray elif ang % 360 == 90: rot = cv2.rotate(gray, cv2.ROTATE_90_CLOCKWISE) elif ang % 360 == 180: rot = cv2.rotate(gray, cv2.ROTATE_180) elif ang % 360 == 270: rot = cv2.rotate(gray, cv2.ROTATE_90_COUNTERCLOCKWISE) else: # 任意角度插值旋转 h, w = gray.shape[:2] M = cv2.getRotationMatrix2D((w / 2, h / 2), ang, 1.0) rot = cv2.warpAffine(gray, M, (w, h), flags=cv2.INTER_LINEAR) images.append((rot, ang, False)) if try_invert: images.append((cv2.bitwise_not(rot), ang, True)) return images def _collect_supported_symbols() -> List["_ZBarSymbol"]: names = [ "EAN13", "EAN8", "UPCA", "UPCE", "CODE128", "CODE39", "QRCODE", # 兼容不同版本:有的叫 ITF,有的叫 I25 "ITF", "I25", ] symbols: List["_ZBarSymbol"] = [] if not _PYZBAR_AVAILABLE: return symbols for n in names: if hasattr(_ZBarSymbol, n): symbols.append(getattr(_ZBarSymbol, n)) # 若列表为空,退回由 zbar 自行决定的默认集合 return symbols def decode_with_pyzbar(image_bgr: np.ndarray, try_invert: bool, rotations: List[int]) -> List[Dict[str, str]]: logger = logging.getLogger("pyzbar_engine") if not _PYZBAR_AVAILABLE or _pyzbar_decode is None: # 缺少 pyzbar/zbar,返回空集合并提示 logger.warning("pyzbar 或 zbar 未就绪,已跳过 pyzbar 解码: %s", str(locals().get('_PYZBAR_IMPORT_ERR', ''))) # type: ignore return [] # 输入 BGR,转灰度 gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY) results: List[Dict[str, str]] = [] symbols = _collect_supported_symbols() logger.debug("调用 pyzbar: symbols=%d, rotations=%s, try_invert=%s", len(symbols) if symbols else 0, rotations, try_invert) for img, ang, inv in _prepare_images(gray, try_invert=try_invert, rotations=rotations): # pyzbar 要求 8bit 图像 decoded = _pyzbar_decode(img, symbols=symbols or None) for obj in decoded: data = obj.data.decode("utf-8", errors="ignore") typ = obj.type results.append({"type": typ, "code": data}) if results: # 若当前设置已识别出内容,继续下一个旋转场景以收集更多,但不必反复 # 这里不提前返回,以便尽量收集多结果 pass # 去重(按 type+code) uniq = [] seen = set() for r in results: key = (r["type"], r["code"]) if isinstance(r, dict) else (None, None) if key not in seen: seen.add(key) uniq.append(r) logger.debug("pyzbar 返回结果数: %d", len(uniq)) return uniq