Skip to content

Commit 80d1993

Browse files
committed
Add a webcam example
1 parent 1864b33 commit 80d1993

File tree

9 files changed

+986
-0
lines changed

9 files changed

+986
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Reading Barcodes and QR Codes Using Webcam, Python, and OpenCV
2+
This repository provides samples demonstrating how to create a simple barcode and QR code reader using a webcam in Python. The OpenCV Stitcher API is utilized to stitch multiple barcode and QR code results together.
3+
4+
## License Activation
5+
To activate the [Dynamsoft Barcode Reader SDK](https://www.dynamsoft.com/barcode-reader/sdk-desktop-server/), obtain a desktop license key from [here](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr):
6+
7+
```python
8+
BarcodeReader.init_license("LICENSE-KEY")
9+
```
10+
11+
## Installation
12+
Install the required dependencies using pip:
13+
14+
```
15+
pip install opencv-python dbr
16+
```
17+
18+
## Examples
19+
20+
- [scanner.py](./scanner.py)
21+
22+
Use your webcam to scan barcodes and QR codes in real-time.
23+
24+
![Python barcode and QR code reader](https://www.dynamsoft.com/codepool/img/2022/04/multiple-barcode-qrcode-scan.png)
25+
26+
- [stitcher.py](./stitcher.py)
27+
28+
Move the camera closer to scan barcodes and QR codes with higher precision, and stitch them into a panorama image.
29+
30+
![Python barcode and QR code reader with panorama stitching](https://www.dynamsoft.com/codepool/img/2022/04/panorama-barcode-qr-code.png)
31+
32+
- [barcode_based_panorama.py](./barcode_based_panorama.py)
33+
Concatenate images based on barcode and QR code detection results, without using any advanced image processing algorithms.
34+
35+
![concatenate barcode and QR code images](./output.png)
36+
37+
- [barcode_reader.py](./barcode_reader.py)
38+
Read barcodes and QR codes from image files:
39+
40+
```bash
41+
python barcode_reader.py <image-file>
42+
```
43+
44+
## Blog
45+
[Scanning Barcode and QR Code Using Webcam, OpenCV and Python](https://www.dynamsoft.com/codepool/opencv-python-webcam-barcode-reader.html)
46+
1.54 MB
Loading
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
from __future__ import print_function
2+
import re
3+
4+
import numpy as np
5+
import cv2 as cv
6+
7+
from multiprocessing.pool import ThreadPool
8+
from collections import deque
9+
10+
import dbr
11+
from dbr import *
12+
13+
import time
14+
from util import *
15+
16+
BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
17+
18+
class ScanManager:
19+
MODE_AUTO_STITCH = 0
20+
MODE_MANUAL_STITCH = 1
21+
MODE_CAMERA_ONLY = 2
22+
23+
def __init__(self):
24+
modes = (cv.Stitcher_PANORAMA, cv.Stitcher_SCANS)
25+
self.stitcher = cv.Stitcher.create(modes[1])
26+
self.stitcher.setPanoConfidenceThresh(0.1)
27+
self.panorama = []
28+
self.isPanoramaDone = False
29+
self.reader = BarcodeReader()
30+
31+
def count_barcodes(self, frame):
32+
try:
33+
results = self.reader.decode_buffer(frame)
34+
return len(results)
35+
except BarcodeReaderError as e:
36+
print(e)
37+
38+
return 0
39+
40+
def save_frame(self, frame):
41+
# frame = self.frame_overlay(frame)
42+
filename = str(time.time()) + "_panorama.jpg"
43+
cv.imwrite(filename, frame)
44+
print("Saved to " + filename)
45+
46+
def frame_overlay(self, frame):
47+
frame_cp = frame.copy()
48+
try:
49+
results = self.reader.decode_buffer(frame_cp)
50+
if results != None:
51+
for result in results:
52+
points = result.localization_result.localization_points
53+
cv.line(frame_cp, points[0], points[1], (0,255,0), 2)
54+
cv.line(frame_cp, points[1], points[2], (0,255,0), 2)
55+
cv.line(frame_cp, points[2], points[3], (0,255,0), 2)
56+
cv.line(frame_cp, points[3], points[0], (0,255,0), 2)
57+
cv.putText(frame_cp, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
58+
59+
return frame_cp
60+
except BarcodeReaderError as e:
61+
print(e)
62+
return None
63+
64+
def stitch_frame(self, frame):
65+
try:
66+
results = self.reader.decode_buffer(frame)
67+
if results != None:
68+
# Draw results on the copy of the frame. Keep original frame clean.
69+
frame_cp = frame.copy()
70+
for result in results:
71+
points = result.localization_result.localization_points
72+
cv.line(frame_cp, points[0], points[1], (0,255,0), 2)
73+
cv.line(frame_cp, points[1], points[2], (0,255,0), 2)
74+
cv.line(frame_cp, points[2], points[3], (0,255,0), 2)
75+
cv.line(frame_cp, points[3], points[0], (0,255,0), 2)
76+
cv.putText(frame_cp, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
77+
78+
# Save frame and barcode info if panorama is empty
79+
if len(self.panorama) == 0:
80+
self.panorama.append((frame, results, frame_cp))
81+
else:
82+
# Compare results. If there is an intersection, transform and stitch. Otherwise, discard.
83+
preFrame = self.panorama[0][0]
84+
preResults = self.panorama[0][1]
85+
preFrameCp = self.panorama[0][2]
86+
87+
while len(results) > 0:
88+
result = results.pop()
89+
for preResult in preResults:
90+
if preResult.barcode_text == result.barcode_text and preResult.barcode_format == result.barcode_format:
91+
prePoints = preResult.localization_result.localization_points
92+
# preContour = np.array([prePoints[0], prePoints[1], prePoints[2], prePoints[3]])
93+
# preArea = cv.minAreaRect(preContour)
94+
# preAreaSize = preArea[1][0] * preArea[1][1]
95+
# preBounding = cv.boxPoints(preArea)
96+
97+
points = result.localization_result.localization_points
98+
99+
# # Crop image based on min area rect
100+
preFrame = preFrame[0: preFrame.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10]
101+
frame = frame[0: frame.shape[0], max(points[0][0], points[1][0], points[2][0], points[3][0]): frame.shape[1] + 10]
102+
103+
preFrameCp = preFrameCp[0: preFrameCp.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10]
104+
frame_cp = frame_cp[0: frame_cp.shape[0], max(points[0][0], points[1][0], points[2][0], points[3][0]): frame_cp.shape[1] + 10]
105+
106+
# # Stitch images
107+
frame = concat_images([preFrame, frame])
108+
frame_cp = concat_images([preFrameCp, frame_cp])
109+
110+
# Re-detect barcodes from the new image
111+
results = self.reader.decode_buffer(frame)
112+
113+
# Save results
114+
self.panorama = [(frame, results, frame_cp)]
115+
return frame, frame_cp
116+
117+
return self.panorama[0][0], self.panorama[0][2]
118+
119+
except BarcodeReaderError as e:
120+
print(e)
121+
return None, None
122+
123+
return None, None
124+
125+
126+
def process_frame(self, frame):
127+
results = None
128+
try:
129+
results = self.reader.decode_buffer(frame)
130+
except BarcodeReaderError as bre:
131+
print(bre)
132+
133+
return results
134+
135+
def clean_deque(self, tasks):
136+
while len(tasks) > 0:
137+
tasks.popleft()
138+
139+
def close_window(self, window_name):
140+
try:
141+
cv.destroyWindow(window_name)
142+
except:
143+
pass
144+
145+
def run(self):
146+
import sys
147+
try:
148+
fn = sys.argv[1]
149+
except:
150+
fn = 0
151+
cap = cv.VideoCapture(fn)
152+
153+
threadn = 1 # cv.getNumberOfCPUs()
154+
barcodePool = ThreadPool(processes = threadn)
155+
panoramaPool = ThreadPool(processes = threadn)
156+
cameraTasks = deque()
157+
panoramaTask = deque()
158+
mode = self.MODE_CAMERA_ONLY
159+
image = None
160+
imageCp = None
161+
panoramaImage = None
162+
panoramaImageCp = None
163+
164+
while True:
165+
ret, frame = cap.read()
166+
frame_cp = frame.copy()
167+
cv.putText(frame, 'A: auto pano, M: manual pano, C: capture, O: camera, S: stop', (10, 20), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
168+
cv.putText(frame, 'Barcode & QR Code Scanning ...', (10, 50), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0))
169+
170+
# Scan and show barcode & QR code results
171+
while len(cameraTasks) > 0 and cameraTasks[0].ready():
172+
results = cameraTasks.popleft().get()
173+
if results != None:
174+
for result in results:
175+
points = result.localization_result.localization_points
176+
cv.line(frame, points[0], points[1], (0,255,0), 2)
177+
cv.line(frame, points[1], points[2], (0,255,0), 2)
178+
cv.line(frame, points[2], points[3], (0,255,0), 2)
179+
cv.line(frame, points[3], points[0], (0,255,0), 2)
180+
cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
181+
182+
if len(cameraTasks) < threadn:
183+
task = barcodePool.apply_async(self.process_frame, (frame_cp, ))
184+
cameraTasks.append(task)
185+
186+
# Stitch images for panorama
187+
if mode == self.MODE_MANUAL_STITCH:
188+
cv.putText(frame, 'Manual Panorama ...', (10, 70), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0))
189+
elif mode == self.MODE_AUTO_STITCH:
190+
cv.putText(frame, 'Auto Panorama ...', (10, 70), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0))
191+
if not self.isPanoramaDone and len(panoramaTask) < threadn:
192+
task = panoramaPool.apply_async(self.stitch_frame, (frame_cp, ))
193+
panoramaTask.append(task)
194+
195+
if mode == self.MODE_MANUAL_STITCH or mode == self.MODE_AUTO_STITCH:
196+
while len(panoramaTask) > 0 and panoramaTask[0].ready():
197+
image, imageCp = panoramaTask.popleft().get()
198+
if image is not None:
199+
panoramaImage = image.copy()
200+
panoramaImageCp = imageCp.copy()
201+
cv.imshow('panorama', panoramaImageCp)
202+
203+
# Key events
204+
ch = cv.waitKey(1)
205+
if ch == 27:
206+
break
207+
if ord('o') == ch:
208+
self.close_window('panorama')
209+
self.isPanoramaDone = True
210+
mode = self.MODE_CAMERA_ONLY
211+
self.clean_deque(panoramaTask)
212+
elif ord('a') == ch:
213+
self.close_window('panorama')
214+
self.isPanoramaDone = False
215+
mode = self.MODE_AUTO_STITCH
216+
self.clean_deque(panoramaTask)
217+
self.panorama = []
218+
elif ord('m') == ch:
219+
self.close_window('panorama')
220+
self.isPanoramaDone = False
221+
mode = self.MODE_MANUAL_STITCH
222+
self.clean_deque(panoramaTask)
223+
self.panorama = []
224+
elif ord('c') == ch and mode == self.MODE_MANUAL_STITCH and not self.isPanoramaDone:
225+
if len(panoramaTask) < threadn:
226+
task = panoramaPool.apply_async(self.stitch_frame, (frame_cp, ))
227+
panoramaTask.append(task)
228+
################################################### Test image operations
229+
elif ord('x') == ch:
230+
if panoramaImageCp is not None:
231+
panoramaImageCp = shiftX(panoramaImageCp, 5)
232+
cv.imshow('panorama', panoramaImageCp)
233+
elif ord('t') == ch:
234+
if panoramaImageCp is not None:
235+
panoramaImageCp = concat_images([panoramaImageCp, frame])
236+
cv.imshow('panorama', panoramaImageCp)
237+
elif ord('y') == ch:
238+
if panoramaImageCp is not None:
239+
panoramaImageCp = shiftY(panoramaImageCp, 5)
240+
cv.imshow('panorama', panoramaImageCp)
241+
elif ord('z') == ch:
242+
if panoramaImageCp is not None:
243+
panoramaImageCp = zoom_image(panoramaImageCp, 2)
244+
cv.imshow('panorama', panoramaImageCp)
245+
elif ord('r') == ch:
246+
if panoramaImageCp is not None:
247+
panoramaImageCp = rotate_image(panoramaImageCp, 1)
248+
cv.imshow('panorama', panoramaImageCp)
249+
###################################################
250+
251+
# Quit panorama mode
252+
if self.isPanoramaDone:
253+
self.close_window('panorama')
254+
mode = self.MODE_CAMERA_ONLY
255+
self.clean_deque(panoramaTask)
256+
self.isPanoramaDone = False
257+
if panoramaImage is not None:
258+
self.save_frame(panoramaImage)
259+
panoramaImage = None
260+
261+
cv.imshow('Barcode & QR Code Scanner', frame)
262+
263+
cv.destroyAllWindows()
264+
print('Done')
265+
266+
267+
if __name__ == '__main__':
268+
ScanManager().run()
269+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import numpy as np
2+
import cv2 as cv
3+
4+
from multiprocessing.pool import ThreadPool
5+
from collections import deque
6+
7+
import dbr
8+
from dbr import *
9+
10+
import time
11+
12+
BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
13+
reader = BarcodeReader()
14+
reader.init_runtime_settings_with_string("{\"ImageParameter\":{\"Name\":\"BestCoverage\",\"DeblurLevel\":9,\"ExpectedBarcodesCount\":512,\"ScaleDownThreshold\":100000,\"LocalizationModes\":[{\"Mode\":\"LM_CONNECTED_BLOCKS\"},{\"Mode\":\"LM_SCAN_DIRECTLY\"},{\"Mode\":\"LM_STATISTICS\"},{\"Mode\":\"LM_LINES\"},{\"Mode\":\"LM_STATISTICS_MARKS\"}],\"GrayscaleTransformationModes\":[{\"Mode\":\"GTM_ORIGINAL\"},{\"Mode\":\"GTM_INVERTED\"}]}}")
15+
16+
def main():
17+
import sys
18+
try:
19+
filename = sys.argv[1]
20+
except:
21+
filename = ''
22+
23+
if filename == '':
24+
print('Usage: python3 barcode-reader.py <filename>')
25+
exit(1)
26+
27+
frame = cv.imread(filename)
28+
results = reader.decode_buffer(frame)
29+
for result in results:
30+
points = result.localization_result.localization_points
31+
cv.line(frame, points[0], points[1], (0,255,0), 2)
32+
cv.line(frame, points[1], points[2], (0,255,0), 2)
33+
cv.line(frame, points[2], points[3], (0,255,0), 2)
34+
cv.line(frame, points[3], points[0], (0,255,0), 2)
35+
cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255))
36+
37+
cv.imshow('Barcode & QR Code Reader', frame)
38+
cv.waitKey(0)
39+
40+
cv.imwrite('output.png', frame)
41+
print('Saved to output.png')
42+
43+
44+
if __name__ == '__main__':
45+
main()
46+
cv.destroyAllWindows()

0 commit comments

Comments
 (0)