mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
4dee742731
samples: add color-coded bounding boxes for multi QR code detection #29281 Improves the existing qrcode.py sample by adding color-coded bounding boxes when multiple QR codes are detected simultaneously. Previously all QR codes were drawn with the same green color, making it hard to distinguish between them visually. This change adds a QR_COLORS palette so each detected QR code gets a unique color, improving visual clarity for multi-QR detection. Co-authored-by: Ayush Gupta <gupta.ayushg@gmail.com>
276 lines
8.9 KiB
Python
276 lines
8.9 KiB
Python
#!/usr/bin/env python
|
|
|
|
'''
|
|
This program detects the QR-codes using OpenCV Library.
|
|
|
|
Usage:
|
|
qrcode.py
|
|
'''
|
|
|
|
|
|
# Python 2/3 compatibility
|
|
from __future__ import print_function
|
|
|
|
import numpy as np
|
|
import cv2 as cv
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
PY3 = sys.version_info[0] == 3
|
|
if PY3:
|
|
xrange = range
|
|
|
|
# Colors for distinguishing multiple QR codes visually
|
|
QR_COLORS = [
|
|
(0, 255, 0), # green
|
|
(255, 0, 0), # blue
|
|
(0, 0, 255), # red
|
|
(255, 255, 0), # cyan
|
|
(0, 255, 255), # yellow
|
|
(255, 0, 255), # magenta
|
|
(128, 255, 0), # lime
|
|
(255, 128, 0), # orange
|
|
]
|
|
|
|
|
|
class QrSample:
|
|
def __init__(self, args):
|
|
self.fname = ''
|
|
self.fext = ''
|
|
self.fsaveid = 0
|
|
self.input = args.input
|
|
self.detect = args.detect
|
|
self.out = args.out
|
|
self.multi = args.multi
|
|
self.saveDetections = args.save_detections
|
|
self.saveAll = args.save_all
|
|
self.arucoBased = args.aruco_based
|
|
|
|
def getQRModeString(self):
|
|
msg1 = "multi " if self.multi else ""
|
|
msg2 = "detector" if self.detect else "decoder"
|
|
msg = "QR {:s}{:s}".format(msg1, msg2)
|
|
return msg
|
|
|
|
def drawFPS(self, result, fps):
|
|
message = '{:.2f} FPS({:s})'.format(fps, self.getQRModeString())
|
|
cv.putText(result, message, (20, 20), 1,
|
|
cv.FONT_HERSHEY_DUPLEX, (0, 0, 255))
|
|
|
|
def drawQRCodeContours(self, image, cnt, color=(0, 255, 0)):
|
|
if cnt.size != 0:
|
|
rows, cols, _ = image.shape
|
|
show_radius = 2.813 * ((rows / cols) if rows > cols else (cols / rows))
|
|
contour_radius = show_radius * 0.4
|
|
cv.drawContours(image, [cnt], 0, color, int(round(contour_radius)))
|
|
tpl = cnt.reshape((-1, 2))
|
|
for x in tuple(tpl.tolist()):
|
|
cv.circle(image, tuple(x), int(round(contour_radius)), color, -1)
|
|
|
|
def drawQRCodeResults(self, result, points, decode_info, fps):
|
|
n = len(points)
|
|
if isinstance(decode_info, str):
|
|
decode_info = [decode_info]
|
|
if n > 0:
|
|
for i in range(n):
|
|
cnt = np.array(points[i]).reshape((-1, 1, 2)).astype(np.int32)
|
|
color = QR_COLORS[i % len(QR_COLORS)]
|
|
self.drawQRCodeContours(result, cnt, color)
|
|
msg = 'QR[{:d}]@{} : '.format(i, *(cnt.reshape(1, -1).tolist()))
|
|
print(msg, end="")
|
|
if len(decode_info) > i:
|
|
if decode_info[i]:
|
|
print("'", decode_info[i], "'")
|
|
else:
|
|
print("Can't decode QR code")
|
|
else:
|
|
print("Decode information is not available (disabled)")
|
|
else:
|
|
print("QRCode not detected!")
|
|
self.drawFPS(result, fps)
|
|
|
|
def runQR(self, qrCode, inputimg):
|
|
if not self.multi:
|
|
if not self.detect:
|
|
decode_info, points, _ = qrCode.detectAndDecode(inputimg)
|
|
dec_info = decode_info
|
|
else:
|
|
_, points = qrCode.detect(inputimg)
|
|
dec_info = []
|
|
else:
|
|
if not self.detect:
|
|
_, decode_info, points, _ = qrCode.detectAndDecodeMulti(
|
|
inputimg)
|
|
dec_info = decode_info
|
|
else:
|
|
_, points = qrCode.detectMulti(inputimg)
|
|
dec_info = []
|
|
if points is None:
|
|
points = []
|
|
return points, dec_info
|
|
|
|
def DetectQRFrmImage(self, inputfile):
|
|
inputimg = cv.imread(inputfile, cv.IMREAD_COLOR)
|
|
if inputimg is None:
|
|
print('ERROR: Can not read image: {}'.format(inputfile))
|
|
return
|
|
print('Run {:s} on image [{:d}x{:d}]'.format(
|
|
self.getQRModeString(), inputimg.shape[1], inputimg.shape[0]))
|
|
|
|
if self.arucoBased:
|
|
qrCode = cv.QRCodeDetectorAruco()
|
|
else:
|
|
qrCode = cv.QRCodeDetector()
|
|
|
|
count = 10
|
|
timer = cv.TickMeter()
|
|
for _ in range(count):
|
|
timer.start()
|
|
points, decode_info = self.runQR(qrCode, inputimg)
|
|
timer.stop()
|
|
fps = count / timer.getTimeSec()
|
|
print('FPS: {}'.format(fps))
|
|
result = inputimg
|
|
self.drawQRCodeResults(result, points, decode_info, fps)
|
|
cv.imshow("QR", result)
|
|
cv.waitKey(1)
|
|
if self.out != '':
|
|
outfile = self.fname + self.fext
|
|
print("Saving Result: {}".format(outfile))
|
|
cv.imwrite(outfile, result)
|
|
|
|
print("Press any key to exit ...")
|
|
cv.waitKey(0)
|
|
print("Exit")
|
|
|
|
def processQRCodeDetection(self, qrcode, frame):
|
|
if len(frame.shape) == 2:
|
|
result = cv.cvtColor(frame, cv.COLOR_GRAY2BGR)
|
|
else:
|
|
result = frame
|
|
print('Run {:s} on video frame [{:d}x{:d}]'.format(
|
|
self.getQRModeString(), frame.shape[1], frame.shape[0]))
|
|
timer = cv.TickMeter()
|
|
timer.start()
|
|
points, decode_info = self.runQR(qrcode, frame)
|
|
timer.stop()
|
|
|
|
fps = 1 / timer.getTimeSec()
|
|
self.drawQRCodeResults(result, points, decode_info, fps)
|
|
return fps, result, points
|
|
|
|
def DetectQRFrmCamera(self):
|
|
cap = cv.VideoCapture(0)
|
|
if not cap.isOpened():
|
|
print("Cannot open the camera")
|
|
return
|
|
print("Press 'm' to switch between detectAndDecode and detectAndDecodeMulti")
|
|
print("Press 'd' to switch between decoder and detector")
|
|
print("Press ' ' (space) to save result into images")
|
|
print("Press 'ESC' to exit")
|
|
|
|
if self.arucoBased:
|
|
qrcode = cv.QRCodeDetectorAruco()
|
|
else:
|
|
qrcode = cv.QRCodeDetector()
|
|
|
|
while True:
|
|
ret, frame = cap.read()
|
|
if not ret:
|
|
print("End of video stream")
|
|
break
|
|
forcesave = self.saveAll
|
|
result = frame
|
|
try:
|
|
fps, result, corners = self.processQRCodeDetection(qrcode, frame)
|
|
print('FPS: {:.2f}'.format(fps))
|
|
forcesave |= self.saveDetections and (len(corners) != 0)
|
|
except cv.error as e:
|
|
print("Error exception: ", e)
|
|
forcesave = True
|
|
cv.imshow("QR code", result)
|
|
code = cv.waitKey(1)
|
|
if code < 0 and (not forcesave):
|
|
continue
|
|
if code == ord(' ') or forcesave:
|
|
fsuffix = '-{:05d}'.format(self.fsaveid)
|
|
self.fsaveid += 1
|
|
fname_in = self.fname + fsuffix + "_input.png"
|
|
print("Saving QR code detection result: '{}' ...".format(fname_in))
|
|
cv.imwrite(fname_in, frame)
|
|
print("Saved")
|
|
if code == ord('m'):
|
|
self.multi = not self.multi
|
|
msg = 'Switching QR code mode ==> {:s}'.format(
|
|
"detectAndDecodeMulti" if self.multi else "detectAndDecode")
|
|
print(msg)
|
|
if code == ord('d'):
|
|
self.detect = not self.detect
|
|
msg = 'Switching QR code mode ==> {:s}'.format(
|
|
"detect" if self.detect else "decode")
|
|
print(msg)
|
|
if code == 27:
|
|
print("'ESC' is pressed. Exiting...")
|
|
break
|
|
print("Exit.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='This program detects the QR-codes input images using OpenCV Library.')
|
|
parser.add_argument(
|
|
'-i',
|
|
'--input',
|
|
help="input image path (for example, 'opencv_extra/testdata/cv/qrcode/multiple/*_qrcodes.png)",
|
|
default="",
|
|
metavar="")
|
|
parser.add_argument(
|
|
'--aruco_based',
|
|
help="use aruco-based detector",
|
|
action='store_true')
|
|
parser.add_argument(
|
|
'-d',
|
|
'--detect',
|
|
help="detect QR code only (skip decoding) (default: False)",
|
|
action='store_true')
|
|
parser.add_argument(
|
|
'-m',
|
|
'--multi',
|
|
help="enable multiple qr-codes detection",
|
|
action='store_true')
|
|
parser.add_argument(
|
|
'-o',
|
|
'--out',
|
|
help="path to result file (default: qr_code.png)",
|
|
default="qr_code.png",
|
|
metavar="")
|
|
parser.add_argument(
|
|
'--save_detections',
|
|
help="save all QR detections (video mode only)",
|
|
action='store_true')
|
|
parser.add_argument(
|
|
'--save_all',
|
|
help="save all processed frames (video mode only)",
|
|
action='store_true')
|
|
args = parser.parse_args()
|
|
qrinst = QrSample(args)
|
|
if args.out != '':
|
|
index = args.out.rfind('.')
|
|
if index != -1:
|
|
qrinst.fname = args.out[:index]
|
|
qrinst.fext = args.out[index:]
|
|
else:
|
|
qrinst.fname = args.out
|
|
qrinst.fext = ".png"
|
|
if args.input != '':
|
|
qrinst.DetectQRFrmImage(args.input)
|
|
else:
|
|
qrinst.DetectQRFrmCamera()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print(__doc__)
|
|
main()
|
|
cv.destroyAllWindows()
|