添加二维码处理能力
This commit is contained in:
@@ -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
@@ -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]
|
|
||||||
@@ -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