Skip to content

Commit 4eda0a5

Browse files
committed
Support barcode scanning with Dynamsoft Barcode Reader
1 parent 3c455e9 commit 4eda0a5

5 files changed

Lines changed: 603 additions & 12 deletions

File tree

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Barcode Scanner for IP Camera GUI Client
4+
Integrates Dynamsoft Capture Vision Bundle for real-time barcode detection.
5+
"""
6+
7+
import queue
8+
import threading
9+
import time
10+
from typing import List, Optional
11+
from PySide6.QtCore import QObject, Signal, QTimer
12+
13+
from dynamsoft_capture_vision_bundle import (
14+
LicenseManager, CaptureVisionRouter, ImageSourceAdapter,
15+
CapturedResultReceiver, EnumErrorCode, EnumPresetTemplate,
16+
EnumCapturedResultItemType
17+
)
18+
from utils import convertMat2ImageData
19+
20+
21+
class FrameFetcher(ImageSourceAdapter):
22+
"""Custom frame fetcher for Dynamsoft Capture Vision"""
23+
24+
def __init__(self):
25+
super().__init__()
26+
self._has_frame = False
27+
28+
def has_next_image_to_fetch(self) -> bool:
29+
return self._has_frame
30+
31+
def add_frame(self, image_data):
32+
"""Add frame to the buffer"""
33+
self.add_image_to_buffer(image_data)
34+
self._has_frame = True
35+
36+
37+
class BarcodeResultReceiver(CapturedResultReceiver):
38+
"""Custom result receiver for barcode detection results"""
39+
40+
def __init__(self, result_queue):
41+
super().__init__()
42+
self.result_queue = result_queue
43+
44+
def on_captured_result_received(self, captured_result):
45+
"""Called when barcode detection results are available"""
46+
try:
47+
self.result_queue.put_nowait(captured_result)
48+
except queue.Full:
49+
# Drop old results if queue is full
50+
try:
51+
self.result_queue.get_nowait()
52+
self.result_queue.put_nowait(captured_result)
53+
except queue.Empty:
54+
pass
55+
56+
57+
class BarcodeScanner(QObject):
58+
"""
59+
Barcode scanner component that processes video frames
60+
and emits barcode detection results
61+
"""
62+
63+
# Signals
64+
barcode_detected = Signal(list) # List of detected barcode results with details
65+
error_occurred = Signal(str)
66+
67+
def __init__(self, parent=None):
68+
super().__init__(parent)
69+
70+
# Scanner state
71+
self.is_initialized = False
72+
self.is_scanning = False
73+
74+
# Dynamsoft components
75+
self.cvr = None
76+
self.fetcher = None
77+
self.receiver = None
78+
self.result_queue = queue.Queue(maxsize=10)
79+
80+
# Latest barcode results with location information
81+
self.latest_barcodes = []
82+
self.last_detection_time = 0
83+
84+
# Initialize license
85+
self._initialize_license()
86+
87+
def _initialize_license(self):
88+
"""Initialize Dynamsoft license"""
89+
try:
90+
license_key = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="
91+
92+
error_code, error_msg = LicenseManager.init_license(license_key)
93+
94+
if error_code != EnumErrorCode.EC_OK and error_code != EnumErrorCode.EC_LICENSE_CACHE_USED:
95+
self.error_occurred.emit(f"License initialization failed: {error_msg}")
96+
return
97+
98+
self.is_initialized = True
99+
self._setup_scanner()
100+
101+
except Exception as e:
102+
self.error_occurred.emit(f"Failed to initialize barcode scanner: {str(e)}")
103+
104+
def _setup_scanner(self):
105+
"""Setup Dynamsoft Capture Vision components"""
106+
try:
107+
# Create capture vision router
108+
self.cvr = CaptureVisionRouter()
109+
110+
# Create frame fetcher
111+
self.fetcher = FrameFetcher()
112+
self.cvr.set_input(self.fetcher)
113+
114+
# Create result receiver
115+
self.receiver = BarcodeResultReceiver(self.result_queue)
116+
self.cvr.add_result_receiver(self.receiver)
117+
118+
except Exception as e:
119+
self.error_occurred.emit(f"Failed to setup barcode scanner: {str(e)}")
120+
self.is_initialized = False
121+
122+
def start_scanning(self):
123+
"""Start barcode scanning"""
124+
if not self.is_initialized or self.is_scanning:
125+
return
126+
127+
try:
128+
# Start capturing with barcode template
129+
error_code, error_msg = self.cvr.start_capturing(
130+
EnumPresetTemplate.PT_READ_BARCODES.value
131+
)
132+
133+
if error_code != EnumErrorCode.EC_OK:
134+
self.error_occurred.emit(f"Failed to start scanning: {error_msg}")
135+
return
136+
137+
self.is_scanning = True
138+
139+
except Exception as e:
140+
self.error_occurred.emit(f"Error starting barcode scanning: {str(e)}")
141+
142+
def stop_scanning(self):
143+
"""Stop barcode scanning"""
144+
if not self.is_scanning:
145+
return
146+
147+
try:
148+
# Stop capturing
149+
if self.cvr:
150+
self.cvr.stop_capturing()
151+
152+
# Clear result queue
153+
while not self.result_queue.empty():
154+
try:
155+
self.result_queue.get_nowait()
156+
except queue.Empty:
157+
break
158+
159+
self.is_scanning = False
160+
161+
except Exception as e:
162+
self.error_occurred.emit(f"Error stopping barcode scanning: {str(e)}")
163+
164+
def process_frame(self, mat):
165+
"""
166+
Process a frame for barcode detection
167+
168+
Args:
169+
mat: OpenCV Mat object (BGR format)
170+
"""
171+
if not self.is_scanning or not self.fetcher:
172+
return
173+
174+
try:
175+
# Convert Mat to ImageData
176+
image_data = convertMat2ImageData(mat)
177+
178+
# Add frame to scanner
179+
self.fetcher.add_frame(image_data)
180+
181+
except Exception as e:
182+
# Don't spam with frame processing errors
183+
pass
184+
185+
def get_fresh_results(self):
186+
"""Get fresh barcode detection results from the queue right now"""
187+
if not self.is_scanning:
188+
return []
189+
190+
detected_barcodes = []
191+
detected_texts = []
192+
193+
try:
194+
# Process all available results right now
195+
while not self.result_queue.empty():
196+
try:
197+
captured_result = self.result_queue.get_nowait()
198+
items = captured_result.get_items()
199+
200+
for item in items:
201+
if item.get_type() == EnumCapturedResultItemType.CRIT_BARCODE:
202+
text = item.get_text()
203+
location = item.get_location()
204+
205+
if text and location:
206+
# Extract corner points
207+
points = []
208+
for i in range(4):
209+
x = int(location.points[i].x)
210+
y = int(location.points[i].y)
211+
points.append((x, y))
212+
213+
# Create detailed barcode result
214+
barcode_result = {
215+
'text': text,
216+
'points': points,
217+
'timestamp': time.time()
218+
}
219+
220+
detected_barcodes.append(barcode_result)
221+
if text not in detected_texts:
222+
detected_texts.append(text)
223+
224+
# Clean up location object
225+
del location
226+
227+
except queue.Empty:
228+
break
229+
except Exception as e:
230+
# Continue processing other results
231+
continue
232+
233+
# Update latest results and emit if any barcodes detected
234+
if detected_barcodes:
235+
self.latest_barcodes = detected_barcodes
236+
self.last_detection_time = time.time()
237+
# Emit signal for text results panel
238+
self.barcode_detected.emit(detected_texts)
239+
240+
# Return fresh results for immediate rendering
241+
return detected_barcodes
242+
243+
except Exception as e:
244+
# Don't spam with result processing errors
245+
return []
246+
247+
def get_latest_barcodes(self):
248+
"""Get the cached barcode detection results (for backward compatibility)"""
249+
return self.latest_barcodes.copy() if self.latest_barcodes else []
250+
251+
def is_available(self) -> bool:
252+
"""Check if barcode scanning is available"""
253+
return self.is_initialized
254+
255+
def cleanup(self):
256+
"""Cleanup resources"""
257+
try:
258+
self.stop_scanning()
259+
260+
if self.cvr:
261+
self.cvr = None
262+
if self.fetcher:
263+
self.fetcher = None
264+
if self.receiver:
265+
self.receiver = None
266+
267+
except Exception:
268+
pass

0 commit comments

Comments
 (0)