-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
986 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Reading Barcodes and QR Codes Using Webcam, Python, and OpenCV | ||
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. | ||
|
||
## License Activation | ||
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): | ||
|
||
```python | ||
BarcodeReader.init_license("LICENSE-KEY") | ||
``` | ||
|
||
## Installation | ||
Install the required dependencies using pip: | ||
|
||
``` | ||
pip install opencv-python dbr | ||
``` | ||
|
||
## Examples | ||
|
||
- [scanner.py](./scanner.py) | ||
|
||
Use your webcam to scan barcodes and QR codes in real-time. | ||
|
||
![Python barcode and QR code reader](https://www.dynamsoft.com/codepool/img/2022/04/multiple-barcode-qrcode-scan.png) | ||
|
||
- [stitcher.py](./stitcher.py) | ||
|
||
Move the camera closer to scan barcodes and QR codes with higher precision, and stitch them into a panorama image. | ||
|
||
![Python barcode and QR code reader with panorama stitching](https://www.dynamsoft.com/codepool/img/2022/04/panorama-barcode-qr-code.png) | ||
|
||
- [barcode_based_panorama.py](./barcode_based_panorama.py) | ||
Concatenate images based on barcode and QR code detection results, without using any advanced image processing algorithms. | ||
|
||
![concatenate barcode and QR code images](./output.png) | ||
|
||
- [barcode_reader.py](./barcode_reader.py) | ||
Read barcodes and QR codes from image files: | ||
|
||
```bash | ||
python barcode_reader.py <image-file> | ||
``` | ||
|
||
## Blog | ||
[Scanning Barcode and QR Code Using Webcam, OpenCV and Python](https://www.dynamsoft.com/codepool/opencv-python-webcam-barcode-reader.html) | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
from __future__ import print_function | ||
import re | ||
|
||
import numpy as np | ||
import cv2 as cv | ||
|
||
from multiprocessing.pool import ThreadPool | ||
from collections import deque | ||
|
||
import dbr | ||
from dbr import * | ||
|
||
import time | ||
from util import * | ||
|
||
BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==") | ||
|
||
class ScanManager: | ||
MODE_AUTO_STITCH = 0 | ||
MODE_MANUAL_STITCH = 1 | ||
MODE_CAMERA_ONLY = 2 | ||
|
||
def __init__(self): | ||
modes = (cv.Stitcher_PANORAMA, cv.Stitcher_SCANS) | ||
self.stitcher = cv.Stitcher.create(modes[1]) | ||
self.stitcher.setPanoConfidenceThresh(0.1) | ||
self.panorama = [] | ||
self.isPanoramaDone = False | ||
self.reader = BarcodeReader() | ||
|
||
def count_barcodes(self, frame): | ||
try: | ||
results = self.reader.decode_buffer(frame) | ||
return len(results) | ||
except BarcodeReaderError as e: | ||
print(e) | ||
|
||
return 0 | ||
|
||
def save_frame(self, frame): | ||
# frame = self.frame_overlay(frame) | ||
filename = str(time.time()) + "_panorama.jpg" | ||
cv.imwrite(filename, frame) | ||
print("Saved to " + filename) | ||
|
||
def frame_overlay(self, frame): | ||
frame_cp = frame.copy() | ||
try: | ||
results = self.reader.decode_buffer(frame_cp) | ||
if results != None: | ||
for result in results: | ||
points = result.localization_result.localization_points | ||
cv.line(frame_cp, points[0], points[1], (0,255,0), 2) | ||
cv.line(frame_cp, points[1], points[2], (0,255,0), 2) | ||
cv.line(frame_cp, points[2], points[3], (0,255,0), 2) | ||
cv.line(frame_cp, points[3], points[0], (0,255,0), 2) | ||
cv.putText(frame_cp, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) | ||
|
||
return frame_cp | ||
except BarcodeReaderError as e: | ||
print(e) | ||
return None | ||
|
||
def stitch_frame(self, frame): | ||
try: | ||
results = self.reader.decode_buffer(frame) | ||
if results != None: | ||
# Draw results on the copy of the frame. Keep original frame clean. | ||
frame_cp = frame.copy() | ||
for result in results: | ||
points = result.localization_result.localization_points | ||
cv.line(frame_cp, points[0], points[1], (0,255,0), 2) | ||
cv.line(frame_cp, points[1], points[2], (0,255,0), 2) | ||
cv.line(frame_cp, points[2], points[3], (0,255,0), 2) | ||
cv.line(frame_cp, points[3], points[0], (0,255,0), 2) | ||
cv.putText(frame_cp, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) | ||
|
||
# Save frame and barcode info if panorama is empty | ||
if len(self.panorama) == 0: | ||
self.panorama.append((frame, results, frame_cp)) | ||
else: | ||
# Compare results. If there is an intersection, transform and stitch. Otherwise, discard. | ||
preFrame = self.panorama[0][0] | ||
preResults = self.panorama[0][1] | ||
preFrameCp = self.panorama[0][2] | ||
|
||
while len(results) > 0: | ||
result = results.pop() | ||
for preResult in preResults: | ||
if preResult.barcode_text == result.barcode_text and preResult.barcode_format == result.barcode_format: | ||
prePoints = preResult.localization_result.localization_points | ||
# preContour = np.array([prePoints[0], prePoints[1], prePoints[2], prePoints[3]]) | ||
# preArea = cv.minAreaRect(preContour) | ||
# preAreaSize = preArea[1][0] * preArea[1][1] | ||
# preBounding = cv.boxPoints(preArea) | ||
|
||
points = result.localization_result.localization_points | ||
|
||
# # Crop image based on min area rect | ||
preFrame = preFrame[0: preFrame.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10] | ||
frame = frame[0: frame.shape[0], max(points[0][0], points[1][0], points[2][0], points[3][0]): frame.shape[1] + 10] | ||
|
||
preFrameCp = preFrameCp[0: preFrameCp.shape[0], 0: max(prePoints[0][0], prePoints[1][0], prePoints[2][0], prePoints[3][0]) + 10] | ||
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] | ||
|
||
# # Stitch images | ||
frame = concat_images([preFrame, frame]) | ||
frame_cp = concat_images([preFrameCp, frame_cp]) | ||
|
||
# Re-detect barcodes from the new image | ||
results = self.reader.decode_buffer(frame) | ||
|
||
# Save results | ||
self.panorama = [(frame, results, frame_cp)] | ||
return frame, frame_cp | ||
|
||
return self.panorama[0][0], self.panorama[0][2] | ||
|
||
except BarcodeReaderError as e: | ||
print(e) | ||
return None, None | ||
|
||
return None, None | ||
|
||
|
||
def process_frame(self, frame): | ||
results = None | ||
try: | ||
results = self.reader.decode_buffer(frame) | ||
except BarcodeReaderError as bre: | ||
print(bre) | ||
|
||
return results | ||
|
||
def clean_deque(self, tasks): | ||
while len(tasks) > 0: | ||
tasks.popleft() | ||
|
||
def close_window(self, window_name): | ||
try: | ||
cv.destroyWindow(window_name) | ||
except: | ||
pass | ||
|
||
def run(self): | ||
import sys | ||
try: | ||
fn = sys.argv[1] | ||
except: | ||
fn = 0 | ||
cap = cv.VideoCapture(fn) | ||
|
||
threadn = 1 # cv.getNumberOfCPUs() | ||
barcodePool = ThreadPool(processes = threadn) | ||
panoramaPool = ThreadPool(processes = threadn) | ||
cameraTasks = deque() | ||
panoramaTask = deque() | ||
mode = self.MODE_CAMERA_ONLY | ||
image = None | ||
imageCp = None | ||
panoramaImage = None | ||
panoramaImageCp = None | ||
|
||
while True: | ||
ret, frame = cap.read() | ||
frame_cp = frame.copy() | ||
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)) | ||
cv.putText(frame, 'Barcode & QR Code Scanning ...', (10, 50), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0)) | ||
|
||
# Scan and show barcode & QR code results | ||
while len(cameraTasks) > 0 and cameraTasks[0].ready(): | ||
results = cameraTasks.popleft().get() | ||
if results != None: | ||
for result in results: | ||
points = result.localization_result.localization_points | ||
cv.line(frame, points[0], points[1], (0,255,0), 2) | ||
cv.line(frame, points[1], points[2], (0,255,0), 2) | ||
cv.line(frame, points[2], points[3], (0,255,0), 2) | ||
cv.line(frame, points[3], points[0], (0,255,0), 2) | ||
cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) | ||
|
||
if len(cameraTasks) < threadn: | ||
task = barcodePool.apply_async(self.process_frame, (frame_cp, )) | ||
cameraTasks.append(task) | ||
|
||
# Stitch images for panorama | ||
if mode == self.MODE_MANUAL_STITCH: | ||
cv.putText(frame, 'Manual Panorama ...', (10, 70), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0)) | ||
elif mode == self.MODE_AUTO_STITCH: | ||
cv.putText(frame, 'Auto Panorama ...', (10, 70), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0)) | ||
if not self.isPanoramaDone and len(panoramaTask) < threadn: | ||
task = panoramaPool.apply_async(self.stitch_frame, (frame_cp, )) | ||
panoramaTask.append(task) | ||
|
||
if mode == self.MODE_MANUAL_STITCH or mode == self.MODE_AUTO_STITCH: | ||
while len(panoramaTask) > 0 and panoramaTask[0].ready(): | ||
image, imageCp = panoramaTask.popleft().get() | ||
if image is not None: | ||
panoramaImage = image.copy() | ||
panoramaImageCp = imageCp.copy() | ||
cv.imshow('panorama', panoramaImageCp) | ||
|
||
# Key events | ||
ch = cv.waitKey(1) | ||
if ch == 27: | ||
break | ||
if ord('o') == ch: | ||
self.close_window('panorama') | ||
self.isPanoramaDone = True | ||
mode = self.MODE_CAMERA_ONLY | ||
self.clean_deque(panoramaTask) | ||
elif ord('a') == ch: | ||
self.close_window('panorama') | ||
self.isPanoramaDone = False | ||
mode = self.MODE_AUTO_STITCH | ||
self.clean_deque(panoramaTask) | ||
self.panorama = [] | ||
elif ord('m') == ch: | ||
self.close_window('panorama') | ||
self.isPanoramaDone = False | ||
mode = self.MODE_MANUAL_STITCH | ||
self.clean_deque(panoramaTask) | ||
self.panorama = [] | ||
elif ord('c') == ch and mode == self.MODE_MANUAL_STITCH and not self.isPanoramaDone: | ||
if len(panoramaTask) < threadn: | ||
task = panoramaPool.apply_async(self.stitch_frame, (frame_cp, )) | ||
panoramaTask.append(task) | ||
################################################### Test image operations | ||
elif ord('x') == ch: | ||
if panoramaImageCp is not None: | ||
panoramaImageCp = shiftX(panoramaImageCp, 5) | ||
cv.imshow('panorama', panoramaImageCp) | ||
elif ord('t') == ch: | ||
if panoramaImageCp is not None: | ||
panoramaImageCp = concat_images([panoramaImageCp, frame]) | ||
cv.imshow('panorama', panoramaImageCp) | ||
elif ord('y') == ch: | ||
if panoramaImageCp is not None: | ||
panoramaImageCp = shiftY(panoramaImageCp, 5) | ||
cv.imshow('panorama', panoramaImageCp) | ||
elif ord('z') == ch: | ||
if panoramaImageCp is not None: | ||
panoramaImageCp = zoom_image(panoramaImageCp, 2) | ||
cv.imshow('panorama', panoramaImageCp) | ||
elif ord('r') == ch: | ||
if panoramaImageCp is not None: | ||
panoramaImageCp = rotate_image(panoramaImageCp, 1) | ||
cv.imshow('panorama', panoramaImageCp) | ||
################################################### | ||
|
||
# Quit panorama mode | ||
if self.isPanoramaDone: | ||
self.close_window('panorama') | ||
mode = self.MODE_CAMERA_ONLY | ||
self.clean_deque(panoramaTask) | ||
self.isPanoramaDone = False | ||
if panoramaImage is not None: | ||
self.save_frame(panoramaImage) | ||
panoramaImage = None | ||
|
||
cv.imshow('Barcode & QR Code Scanner', frame) | ||
|
||
cv.destroyAllWindows() | ||
print('Done') | ||
|
||
|
||
if __name__ == '__main__': | ||
ScanManager().run() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import numpy as np | ||
import cv2 as cv | ||
|
||
from multiprocessing.pool import ThreadPool | ||
from collections import deque | ||
|
||
import dbr | ||
from dbr import * | ||
|
||
import time | ||
|
||
BarcodeReader.init_license("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==") | ||
reader = BarcodeReader() | ||
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\"}]}}") | ||
|
||
def main(): | ||
import sys | ||
try: | ||
filename = sys.argv[1] | ||
except: | ||
filename = '' | ||
|
||
if filename == '': | ||
print('Usage: python3 barcode-reader.py <filename>') | ||
exit(1) | ||
|
||
frame = cv.imread(filename) | ||
results = reader.decode_buffer(frame) | ||
for result in results: | ||
points = result.localization_result.localization_points | ||
cv.line(frame, points[0], points[1], (0,255,0), 2) | ||
cv.line(frame, points[1], points[2], (0,255,0), 2) | ||
cv.line(frame, points[2], points[3], (0,255,0), 2) | ||
cv.line(frame, points[3], points[0], (0,255,0), 2) | ||
cv.putText(frame, result.barcode_text, points[0], cv.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255)) | ||
|
||
cv.imshow('Barcode & QR Code Reader', frame) | ||
cv.waitKey(0) | ||
|
||
cv.imwrite('output.png', frame) | ||
print('Saved to output.png') | ||
|
||
|
||
if __name__ == '__main__': | ||
main() | ||
cv.destroyAllWindows() |
Oops, something went wrong.