104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
"""Detect and decode barcodes / QR codes from an image file using zxing-cpp."""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from dataclasses import dataclass, field
|
||
from pathlib import Path
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Human-readable names for zxing BarcodeFormat values
|
||
_FORMAT_NAMES: dict[str, str] = {
|
||
"QRCode": "二维码 (QR Code)",
|
||
"DataMatrix": "DataMatrix",
|
||
"Aztec": "Aztec",
|
||
"PDF417": "PDF417",
|
||
"MicroQRCode": "微型二维码",
|
||
"RMQRCode": "R型QR码",
|
||
"EAN8": "EAN-8",
|
||
"EAN13": "EAN-13",
|
||
"UPCE": "UPC-E",
|
||
"UPCA": "UPC-A",
|
||
"Code39": "Code 39",
|
||
"Code93": "Code 93",
|
||
"Code128": "Code 128",
|
||
"ITF": "ITF(交叉二五码)",
|
||
"Codabar": "Codabar",
|
||
"DataBar": "DataBar",
|
||
"DataBarExpanded": "DataBar Expanded",
|
||
"MaxiCode": "MaxiCode",
|
||
"DXFilmEdge": "DXFilmEdge",
|
||
"LinearCodes": "一维码",
|
||
"MatrixCodes": "矩阵码",
|
||
}
|
||
|
||
|
||
@dataclass
|
||
class BarcodeResult:
|
||
format: str # zxing format string, e.g. "EAN13"
|
||
format_label: str # Chinese-friendly label
|
||
text: str # decoded text / number
|
||
# bounding box in image pixels (top-left origin)
|
||
x0: int
|
||
y0: int
|
||
x1: int
|
||
y1: int
|
||
valid: bool # zxing isValid
|
||
|
||
|
||
def detect_barcodes(image_path: Path) -> list[BarcodeResult]:
|
||
"""Scan *image_path* for all barcodes and QR codes.
|
||
|
||
Returns a list of :class:`BarcodeResult`, one entry per detected code.
|
||
Returns an empty list when nothing is found or on error.
|
||
"""
|
||
try:
|
||
import zxingcpp
|
||
except ImportError:
|
||
logger.warning("zxing-cpp not installed; barcode detection skipped")
|
||
return []
|
||
|
||
try:
|
||
from PIL import Image
|
||
img = Image.open(image_path).convert("RGB")
|
||
except Exception as exc:
|
||
logger.warning("barcode_detector: cannot open image %s: %s", image_path, exc)
|
||
return []
|
||
|
||
try:
|
||
results = zxingcpp.read_barcodes(img)
|
||
except Exception as exc:
|
||
logger.warning("barcode_detector: zxing scan failed: %s", exc)
|
||
return []
|
||
|
||
output: list[BarcodeResult] = []
|
||
for r in results:
|
||
fmt_str = str(r.format).replace("BarcodeFormat.", "")
|
||
label = _FORMAT_NAMES.get(fmt_str, fmt_str)
|
||
|
||
# zxing-cpp position: r.position is a quadrilateral with four points
|
||
try:
|
||
pts = r.position
|
||
xs = [pts.top_left.x, pts.top_right.x, pts.bottom_right.x, pts.bottom_left.x]
|
||
ys = [pts.top_left.y, pts.top_right.y, pts.bottom_right.y, pts.bottom_left.y]
|
||
x0, y0, x1, y1 = int(min(xs)), int(min(ys)), int(max(xs)), int(max(ys))
|
||
except Exception:
|
||
x0 = y0 = x1 = y1 = 0
|
||
|
||
output.append(BarcodeResult(
|
||
format=fmt_str,
|
||
format_label=label,
|
||
text=r.text,
|
||
x0=x0, y0=y0, x1=x1, y1=y1,
|
||
valid=r.valid,
|
||
))
|
||
logger.info(
|
||
"barcode_detector: found %s text=%r bbox=(%d,%d,%d,%d)",
|
||
fmt_str, r.text, x0, y0, x1, y1,
|
||
)
|
||
|
||
if not output:
|
||
logger.info("barcode_detector: no barcode/QR found in %s", image_path.name)
|
||
|
||
return output
|