• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dynobo / normcap / 14280746188

05 Apr 2025 09:50AM UTC coverage: 84.841% (-2.2%) from 87.002%
14280746188

Pull #690

github

web-flow
Merge d28da9558 into c7b17ed4a
Pull Request #690: feature/add-qr-support

318 of 410 new or added lines in 35 files covered. (77.56%)

5 existing lines in 2 files now uncovered.

2569 of 3028 relevant lines covered (84.84%)

3.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.33
/normcap/detection/codes/detector.py
1
"""Barcode and QR Code detection."""
2

3
import logging
4✔
4
import os
4✔
5
from pathlib import Path
4✔
6
from typing import Optional
4✔
7

8
import cv2
4✔
9
import numpy as np
4✔
10
from PySide6 import QtGui
4✔
11

12
from normcap import resources
4✔
13
from normcap.detection.codes.models import CodeType, TextType
4✔
14

15
logger = logging.getLogger(__name__)
4✔
16

17
model_path = Path(resources.__file__).parent / "qr"
4✔
18

19

20
def _detect_barcodes_sr(mat: np.ndarray) -> list[str]:
4✔
21
    """Barcode detection using super resolution.
22

23
    Significantly slower but more accurate than w/o SR.
24
    """
NEW
25
    retval, bar_decoded, types, points = cv2.barcode.BarcodeDetector(
×
26
        str((model_path / "sr.prototxt").resolve()),
27
        str((model_path / "sr.caffemodel").resolve()),
28
    ).detectAndDecodeMulti(mat)
NEW
29
    return [s for s in bar_decoded if s]
×
30

31

32
def _detect_barcodes(mat: np.ndarray) -> list[str]:
4✔
NEW
33
    retval, bar_decoded, types, points = (
×
34
        cv2.barcode.BarcodeDetector().detectAndDecodeMulti(mat)
35
    )
NEW
36
    return [s for s in bar_decoded if s]
×
37

38

39
def _detect_qrcodes(mat: np.ndarray) -> list[str]:
4✔
NEW
40
    retval, qr_decoded, points, straight_qrcode = (
×
41
        cv2.QRCodeDetector().detectAndDecodeMulti(mat)
42
    )
NEW
43
    return [s for s in qr_decoded if s]
×
44

45

46
def _detect_qrcodes_wechat(mat: np.ndarray) -> list[str]:
4✔
47
    """Detect QR codes using WeChat's models.
48

49
    Similar accuracy to the default OpenCV model but faster on large images.
50
    """
NEW
51
    qr_decoded, image = cv2.wechat_qrcode.WeChatQRCode(
×
52
        str((model_path / "detect.prototxt").resolve()),
53
        str((model_path / "detect.caffemodel").resolve()),
54
        str((model_path / "sr.prototxt").resolve()),
55
        str((model_path / "sr.caffemodel").resolve()),
56
    ).detectAndDecode(mat)
NEW
57
    return [s for s in qr_decoded if s]
×
58

59

60
def _image_to_mat(image: QtGui.QImage) -> np.ndarray:
4✔
NEW
61
    ptr = image.constBits()
×
NEW
62
    arr = np.array(ptr).reshape(image.height(), image.width(), 4)
×
NEW
63
    return arr[:, :, :3]
×
64

65

66
def _get_text_type(text: str) -> TextType:
4✔
NEW
67
    if text.startswith("http"):
×
NEW
68
        return TextType.URL
×
NEW
69
    if text.startswith("tel:"):
×
NEW
70
        return TextType.PHONE_NUMBER
×
NEW
71
    if text.startswith("mailto:"):
×
NEW
72
        return TextType.MAIL
×
NEW
73
    if os.sep + os.sep in text:
×
NEW
74
        return TextType.PARAGRAPH
×
NEW
75
    if os.sep in text:
×
NEW
76
        return TextType.MULTI_LINE
×
NEW
77
    return TextType.SINGLE_LINE
×
78

79

80
def detect_codes(image: QtGui.QImage) -> Optional[tuple[str, TextType, CodeType]]:
4✔
81
    """Decode QR and barcodes from image.
82

83
    Args:
84
        image: Input image.
85

86
    Returns:
87
        URL(s), separated bye newline
88
    """
NEW
89
    logger.info("Detect Barcodes and QR Codes")
×
90

91
    # mat = _image_to_mat(image)
NEW
92
    mat = image
×
93

NEW
94
    barcodes = _detect_barcodes_sr(mat=mat)
×
NEW
95
    qr_codes = _detect_qrcodes_wechat(mat=mat)
×
96

NEW
97
    codes = barcodes + qr_codes
×
98

NEW
99
    if not codes:
×
NEW
100
        return None
×
101

NEW
102
    if barcodes and not qr_codes:
×
NEW
103
        code_type = CodeType.BARCODE
×
NEW
104
    elif qr_codes and not barcodes:
×
NEW
105
        code_type = CodeType.QR
×
106
    else:
NEW
107
        code_type = CodeType.QR_AND_BARCODE
×
108

NEW
109
    logger.info("Found %s codes", len(codes))
×
110

NEW
111
    if len(codes) == 1:
×
NEW
112
        text = codes[0]
×
NEW
113
        return text, _get_text_type(text), code_type
×
114

NEW
115
    return os.linesep.join(codes), TextType.MULTI_LINE, code_type
×
116

117

118
if __name__ == "__main__":
4✔
NEW
119
    import os
×
NEW
120
    from pathlib import Path
×
NEW
121
    from timeit import timeit
×
122

NEW
123
    reps = 10
×
124

NEW
125
    image_file = (
×
126
        Path(__file__).parent.parent.parent
127
        / "tests"
128
        / "integration"
129
        / "testcases"
130
        / "13_qr_code_with_text.png"
131
    )
NEW
132
    image = QtGui.QImage(str(image_file.resolve()))
×
NEW
133
    mat = _image_to_mat(image)
×
134

NEW
135
    time_mat = timeit(lambda: _image_to_mat(image), globals=globals(), number=reps)
×
NEW
136
    time_qr = timeit(lambda: _detect_qrcodes(mat), globals=globals(), number=reps)
×
NEW
137
    time_qr_we = timeit(
×
138
        lambda: _detect_qrcodes_wechat(mat), globals=globals(), number=reps
139
    )
NEW
140
    time_bar = timeit(lambda: _detect_barcodes(mat), globals=globals(), number=reps)
×
NEW
141
    time_bar_sr = timeit(
×
142
        lambda: _detect_barcodes_sr(mat), globals=globals(), number=reps
143
    )
144

NEW
145
    qr_codes = _detect_qrcodes(mat)
×
NEW
146
    qr_codes_we = _detect_qrcodes_wechat(mat)
×
NEW
147
    barcodes_sr = _detect_barcodes_sr(mat)
×
NEW
148
    barcodes = _detect_barcodes(mat)
×
149

NEW
150
    print(  # noqa: T201
×
151
        f"{time_mat / reps:.7f} (convert to mat)\n"
152
        f"{time_qr / reps:.7f} ({len(qr_codes)} QR)\n"
153
        f"{time_qr_we / reps:.7f} ({len(qr_codes_we)} QR WeChat)\n"
154
        f"{time_bar / reps:.7f} ({len(barcodes_sr)} Barcodes)\n"
155
        f"{time_bar_sr / reps:.7f} ({len(barcodes_sr)} Barcodes SR)\n"
156
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc