添加二维码处理能力

This commit is contained in:
zk
2026-07-24 15:46:46 +08:00
parent e71b950c8f
commit 2937dc91e6
3 changed files with 79 additions and 57 deletions
+1
View File
@@ -1,3 +1,4 @@
cryptography>=41
rapidocr>=3.9
onnxruntime>=1.17
opencv-python>=4.5
+24 -57
View File
@@ -1,33 +1,28 @@
"""二维码工具。
基于 OpenCV QRCodeDetector 完成二维码检测裁剪与解码
"""
"""二维码检测与裁剪工具。"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
import cv2
import numpy as np
@dataclass
class QrCodeResult:
"""单个二维码识别结果"""
class QrRegion:
"""单个二维码区域"""
text: str
points: tuple[tuple[int, int], ...]
crop: np.ndarray | None = None
@dataclass
class QrScanResult:
"""二维码扫描结果。"""
class QrDetectResult:
"""二维码检测结果。"""
has_qr: bool
items: list[QrCodeResult]
items: list[QrRegion]
def _load_image(image: bytes | np.ndarray | str | Path) -> np.ndarray:
@@ -110,58 +105,35 @@ def _normalize_points(points: np.ndarray | None) -> list[tuple[tuple[int, int],
return result
def _decode_with_multi_detector(img: np.ndarray) -> tuple[list[str], list[tuple[tuple[int, int], ...]], list[np.ndarray]]:
def _detect_points(img: np.ndarray) -> list[tuple[tuple[int, int], ...]]:
detector = cv2.QRCodeDetector()
texts: list[str] = []
points_list: list[tuple[tuple[int, int], ...]] = []
crops: list[np.ndarray] = []
if hasattr(detector, "detectAndDecodeMulti"):
ok, decoded_info, points, _ = detector.detectAndDecodeMulti(img)
if ok and points is not None:
normalized_points = _normalize_points(points)
if isinstance(decoded_info, Iterable) and not isinstance(
decoded_info, (str, bytes)
):
decoded_iter = [item or "" for item in decoded_info]
else:
decoded_iter = [decoded_info or ""]
for idx, quad in enumerate(normalized_points):
text = decoded_iter[idx] if idx < len(decoded_iter) else ""
crop = _warp_qr_image(img, np.array(quad, dtype=np.float32))
if not text:
fallback_text, _, _ = detector.detectAndDecode(crop)
text = fallback_text or ""
texts.append(text)
points_list.append(quad)
crops.append(crop)
return texts, points_list, crops
text, points, _ = detector.detectAndDecode(img)
if hasattr(detector, "detectMulti"):
ok, points = detector.detectMulti(img)
if ok:
normalized_points = _normalize_points(points)
if normalized_points:
crop = _warp_qr_image(img, np.array(normalized_points[0], dtype=np.float32))
if not text:
fallback_text, _, _ = detector.detectAndDecode(crop)
text = fallback_text or ""
return [text or ""], [normalized_points[0]], [crop]
return normalized_points
return [], [], []
ok, points = detector.detect(img)
if ok:
normalized_points = _normalize_points(points)
if normalized_points:
return normalized_points
return []
def scan_qr(image: bytes | np.ndarray | str | Path) -> QrScanResult:
"""扫描图片中二维码,返回是否存在、位置和文本"""
def scan_qr(image: bytes | np.ndarray | str | Path) -> QrDetectResult:
"""扫描图片中是否存在二维码,返回二维码区域"""
img = _load_image(image)
texts, points_list, crops = _decode_with_multi_detector(img)
points_list = _detect_points(img)
items = [
QrCodeResult(text=text, points=points, crop=crop)
for text, points, crop in zip(texts, points_list, crops)
QrRegion(points=points, crop=_warp_qr_image(img, np.array(points, dtype=np.float32)))
for points in points_list
]
return QrScanResult(has_qr=bool(items), items=items)
return QrDetectResult(has_qr=bool(items), items=items)
def has_qr(image: bytes | np.ndarray | str | Path) -> bool:
@@ -172,8 +144,3 @@ def has_qr(image: bytes | np.ndarray | str | Path) -> bool:
def crop_qr(image: bytes | np.ndarray | str | Path) -> list[np.ndarray]:
"""裁剪出图片中的二维码区域。"""
return [item.crop for item in scan_qr(image).items if item.crop is not None]
def decode_qr(image: bytes | np.ndarray | str | Path) -> list[str]:
"""识别图片中的二维码内容。"""
return [item.text for item in scan_qr(image).items if item.text]
+54
View File
@@ -0,0 +1,54 @@
"""二维码识别工具。"""
from __future__ import annotations
from pathlib import Path
import cv2
import numpy as np
from .cv import _load_image, _normalize_points, _warp_qr_image
def _decode_with_detector(img: np.ndarray) -> list[str]:
detector = cv2.QRCodeDetector()
texts: list[str] = []
if hasattr(detector, "detectAndDecodeMulti"):
ok, decoded_info, points, _ = detector.detectAndDecodeMulti(img)
if ok and points is not None:
normalized_points = _normalize_points(points)
if isinstance(decoded_info, (list, tuple)):
decoded_iter = [item or "" for item in decoded_info]
else:
decoded_iter = [decoded_info or ""]
for idx, points_item in enumerate(normalized_points):
text = decoded_iter[idx] if idx < len(decoded_iter) else ""
if not text:
crop = _warp_qr_image(img, np.array(points_item, dtype=np.float32))
fallback_text, _, _ = detector.detectAndDecode(crop)
text = fallback_text or ""
if text:
texts.append(text)
if texts:
return texts
text, points, _ = detector.detectAndDecode(img)
if text:
return [text]
normalized_points = _normalize_points(points)
if normalized_points:
crop = _warp_qr_image(img, np.array(normalized_points[0], dtype=np.float32))
fallback_text, _, _ = detector.detectAndDecode(crop)
if fallback_text:
return [fallback_text]
return []
def decode_qr(image: bytes | np.ndarray | str | Path) -> list[str]:
"""识别图片中的二维码内容。"""
img = _load_image(image)
return _decode_with_detector(img)