添加二维码处理能力
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
cryptography>=41
|
||||
rapidocr>=3.9
|
||||
onnxruntime>=1.17
|
||||
opencv-python>=4.5
|
||||
|
||||
+24
-57
@@ -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]
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user