Building a barcode and QR code scanner with OpenCV and PiCamera is one of the most practical embedded vision projects for India businesses. From inventory management in warehouses to contactless payment verification and attendance systems, a Raspberry Pi-based scanner offers a low-cost alternative to commercial handheld scanners. This tutorial covers pyzbar and OpenCV integration, multi-format decoding, and building a complete scanning application with data logging.
Table of Contents
- Supported Barcode and QR Formats
- Hardware Setup
- Installation: pyzbar and OpenCV
- Basic Scanner Code
- Live Camera Scanner
- Data Logging and Export
- Optimisation for Speed
- FAQ
Supported Barcode and QR Formats
pyzbar (based on ZBar) supports: QR Code, EAN-13, EAN-8, UPC-A, UPC-E, Code 128, Code 39, Code 93, ITF (Interleaved 2 of 5), DataMatrix, PDF417, and Aztec codes. For India retail, EAN-13 and QR Code are the most common formats. UPI QR codes use standard QR Code format and are fully decodable.
Hardware Setup
Arducam OV5642 Auto-Focus Camera for Pi
5MP OV5642 with motorised auto-focus – essential for barcode scanning at variable distances. Auto-focus ensures sharp barcode images whether scanning at 5cm or 30cm. CSI interface for Raspberry Pi.
Arducam IMX219 8MP Camera Module
8MP fixed-focus camera that works well for QR codes at 15-20cm distance. More affordable than auto-focus option, suitable for fixed mounting positions in scanner kiosks.
Installation: pyzbar and OpenCV
sudo apt update
sudo apt install -y libzbar0 python3-opencv python3-picamera2
pip3 install pyzbar
# Verify installation
python3 -c "import pyzbar; print('pyzbar OK')"
ZBar library (libzbar0) must be installed as the system library – pyzbar wraps it via ctypes. On Raspberry Pi OS Bullseye and Bookworm, it installs cleanly from apt.
Basic Scanner Code
import cv2
from pyzbar import pyzbar
import numpy as np
def decode_image(image_path):
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
codes = pyzbar.decode(gray)
for code in codes:
data = code.data.decode('utf-8')
fmt = code.type
print(f'Format: {fmt}, Data: {data}')
# Draw bounding box
points = code.polygon
if len(points) > 4:
hull = cv2.convexHull(np.array([pt for pt in points], dtype=np.float32))
hull = list(map(tuple, np.squeeze(hull)))
else:
hull = points
n = len(hull)
for j in range(n):
cv2.line(img, hull[j], hull[(j+1) % n], (0,255,0), 3)
cv2.putText(img, data, (code.rect.left, code.rect.top - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)
return img, codes
# Test with a saved image
result, codes = decode_image('test_barcode.jpg')
cv2.imshow('Decoded', result)
cv2.waitKey(0)
Live Camera Scanner
import cv2
from pyzbar import pyzbar
from picamera2 import Picamera2
import time
picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(
main={'size': (1280, 720), 'format': 'BGR888'}
))
picam2.start()
time.sleep(2)
scanned_codes = set() # Track already-scanned codes
print('Scanner ready. Point camera at barcode or QR code.')
print('Press q to quit.')
while True:
frame = picam2.capture_array()
# Use grayscale for faster decoding
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
codes = pyzbar.decode(gray)
for code in codes:
data = code.data.decode('utf-8')
fmt = code.type
# Draw outline
pts = [(p.x, p.y) for p in code.polygon]
for i in range(len(pts)):
cv2.line(frame, pts[i], pts[(i+1)%len(pts)], (0,255,0), 2)
cv2.putText(frame, f'{fmt}: {data[:30]}', (code.rect.left, code.rect.top-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)
if data not in scanned_codes:
scanned_codes.add(data)
print(f'NEW SCAN [{fmt}]: {data}')
print(f' Timestamp: {time.strftime("%Y-%m-%d %H:%M:%S")}')
cv2.putText(frame, f'Scanned: {len(scanned_codes)} codes', (10,30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255,255,0), 2)
cv2.imshow('Barcode Scanner', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
picam2.stop()
cv2.destroyAllWindows()
Data Logging and Export
import csv, os, time
LOG_FILE = '/home/pi/scan_log.csv'
def log_scan(data, fmt, extra_info=''):
file_exists = os.path.isfile(LOG_FILE)
with open(LOG_FILE, 'a', newline='') as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(['timestamp', 'format', 'data', 'extra_info'])
writer.writerow([time.strftime('%Y-%m-%d %H:%M:%S'), fmt, data, extra_info])
# Call inside scanning loop when new code is detected:
log_scan(data, fmt, extra_info='checkout_station_1')
# View log:
# cat /home/pi/scan_log.csv
For inventory systems in Indian warehouses, consider connecting to a MySQL database via mysql-connector-python. For UPI payment verification, parse the QR payload which follows the format: upi://pay?pa=merchant@bank&pn=MerchantName&am=Amount
Waveshare IMX219-160 Wide Angle Camera
Wide FOV captures barcodes even when items are not perfectly aligned. Useful for conveyor belt scanning where item orientation varies. 8MP resolution for reliable barcode reads.
Optimisation for Speed
pyzbar decoding speed depends on image size. At 1280×720, expect 100-200ms per frame on Pi 4. Optimise with:
- Reduce resolution: Capture at 640×480 for 3-4x speed improvement
- Central crop: Crop the centre 320×320 region where the barcode is likely positioned
- Skip frames: Run pyzbar every 3rd frame, show camera feed on all frames
- Brightness adaptive: Apply CLAHE (Contrast Limited Adaptive Histogram Equalisation) in poor lighting
# CLAHE for better scanning in dim Indian indoor lighting
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
gray_enhanced = clahe.apply(gray)
codes = pyzbar.decode(gray_enhanced)
FAQ
Can this scanner read UPI QR codes?
Yes. UPI QR codes are standard QR Code format. pyzbar decodes them as UTF-8 strings starting with upi://pay?. You can parse the query parameters to extract merchant ID, amount, and name.
What is the minimum barcode size for reliable scanning?
For EAN-13 barcodes, minimum 2cm width at 20cm distance. QR codes can be as small as 1cm at 10cm distance. Use auto-focus camera for variable distances.
How do I scan barcodes from India’s GST invoices?
GST e-invoices use QR codes containing IRN, GSTIN, invoice number, date, and amount. These are standard QR codes fully readable with pyzbar.
Can I build a toll-free barcode scanner without a screen?
Yes. Run in headless mode (no display) and use audio feedback with pygame.mixer for successful scans. Log results to file or send via HTTP to a central server using Python requests library.
Add comment