添加二维码处理能力

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 cryptography>=41
rapidocr>=3.9 rapidocr>=3.9
onnxruntime>=1.17 onnxruntime>=1.17
opencv-python>=4.5
+24 -57
View File
@@ -1,33 +1,28 @@
"""二维码工具。 """二维码检测与裁剪工具。"""
基于 OpenCV QRCodeDetector 完成二维码检测裁剪与解码
"""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Iterable
import cv2 import cv2
import numpy as np import numpy as np
@dataclass @dataclass
class QrCodeResult: class QrRegion:
"""单个二维码识别结果""" """单个二维码区域"""
text: str
points: tuple[tuple[int, int], ...] points: tuple[tuple[int, int], ...]
crop: np.ndarray | None = None crop: np.ndarray | None = None
@dataclass @dataclass
class QrScanResult: class QrDetectResult:
"""二维码扫描结果。""" """二维码检测结果。"""
has_qr: bool has_qr: bool
items: list[QrCodeResult] items: list[QrRegion]
def _load_image(image: bytes | np.ndarray | str | Path) -> np.ndarray: 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 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() detector = cv2.QRCodeDetector()
texts: list[str] = [] if hasattr(detector, "detectMulti"):
points_list: list[tuple[tuple[int, int], ...]] = [] ok, points = detector.detectMulti(img)
crops: list[np.ndarray] = [] if ok:
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)
normalized_points = _normalize_points(points) normalized_points = _normalize_points(points)
if normalized_points: if normalized_points:
crop = _warp_qr_image(img, np.array(normalized_points[0], dtype=np.float32)) return normalized_points
if not text:
fallback_text, _, _ = detector.detectAndDecode(crop)
text = fallback_text or ""
return [text or ""], [normalized_points[0]], [crop]
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) img = _load_image(image)
texts, points_list, crops = _decode_with_multi_detector(img) points_list = _detect_points(img)
items = [ items = [
QrCodeResult(text=text, points=points, crop=crop) QrRegion(points=points, crop=_warp_qr_image(img, np.array(points, dtype=np.float32)))
for text, points, crop in zip(texts, points_list, crops) 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: 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]: 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] 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)