How to Scan QR codes From your Web Cam in 3 Steps

What will we cover?

Do you want to create a QR scanner that works on a live cam feed?

Sounds complex, but it is easy to do. In this tutorial will learn how to create it with OpenCV in 3 easy steps.

You can find all the code on my GitHub repository.

Step 1: Install requirements

The first thing you need to do, is to install the needed libraries.

We use three libraries, which are defined in the requirements.txt file in the GitHub repository and given below.

opencv-python
qrcode
Pillow

The libraries are.

  • opencv-python OpenCV is an open source computer vision and machine learning software library. We need to make a live video stream from your web camera.
  • qrcode A library to read and write QR codes. We need it to generate a QR code and read QR codes from the web cam.
  • Pillow The Python Imaging Library adds image processing capabilities to your Python interprete. We needed for the qrcode library to write images.

If you downloaded the repository then you can install them all by.

pip install -r requirements.txt

Otherwise you need to install them one-by-one.

pip install opencv-python
pip install qrcode
pip install Pillow

Now we are ready to write a QR code image.

Step 2: Write a QR code to an image

This is straight forward.

import qrcode
img = qrcode.make('You are AWESOME')
img.save("awesome.png")

Simply import the qrcode, make on with the desired text and write it to a file.

This piece of code uses the Pillow library to write it.

Now we are ready to see if we can read QR codes from our webcam.

Step 3: Read QR codes from your webcam

This can be done by created a feed from the webcam.

# import the opencv library
import cv2
# define a video capture object
vid = cv2.VideoCapture(0)
detector = cv2.QRCodeDetector()
while True:
    # Capture the video frame by frame
    ret, frame = vid.read()
    data, bbox, straight_qrcode = detector.detectAndDecode(frame)
    if len(data) > 0:
        print(data)
    # Display the resulting frame
    cv2.imshow('frame', frame)
    # the 'q' button is set as the
    # quitting button you may use any
    # desired button of your choice
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
# After the loop release the cap object
vid.release()
# Destroy all the windows
cv2.destroyAllWindows()

This is done by using an endless loop, which reads a frame from your webcam, detects if there is any QR code, by the detector.

If so, read it in the terminal.

You can terminate the processing by pressing q.

Slideshow with Loading of Photos in a Background Thread in Python

What will we cover in this tutorial?

We will continue the work of this tutorial (Create a Moving Photo Slideshow with Weighted Transitions in OpenCV). The challenge is that construction is that we pre-load all the photos we need. The reason for that, is that loading the photos in each iteration would affect the performance of the slideshows.

The solution we present in this tutorial is to load photos in a background thread. This is not straightforward as we need to ensure the communication between the main thread and the background photo loading thread is done correctly.

The result will be similar.

Already done so far and the challenge

In the previous tutorial we made great progress, creating a nice slideshow. The challenge was the long pre-loading time of the photos.

If we did not pre-load the photos, then we would need to load the photos in each iteration. Say, in the beginning of the loop, we would need to load the next photo. This would require disk access, which is quite slow. As the frame is updated quite often, this loading time will not let the new position of the photo to be updated for a fraction of a second (or more, depending the photo size and the speed of the processor). This will make the movement of the photo lacking and not run smoothly.

Said differently, in one thread, the processor can only make one thing at the time. When you tell the processor to load a photo, it will stop all other work in this program, and do that first, before updating the frame. As this can be a big task, it will take long time. As the program needs to update the frame continuously, to make the photo move, then this will be visible no matter when you tell the processor to load the image.

So how can we deal with this?

Using another thread to load the image. Having multiple threads will make it possible to do more than one thing at the time. If you have two threads, you can do two things at the same time.

Introducing threading

A Python program is by default run in one thread. Hence, it can only do one thing at the time. If you need to do more than one thing at the time, you need to use threading.

Now this sound simple, but it introduces new problems.

When working with threading a lock is a good tool to know. Basically, a lock is similar to a lock. You can enter and lock the door after you, such that no one else can enter. When you are done, you can unlock the door and leave. Then someone else can enter.

This is the same principle with a lock with threading. You can take a lock, and ensure you only enter the code after the lock, if no-one else (another thread) is using the lock. Then when you are done, you release the lock.

We need a stack of photos that can load new photos when needed.

class ImageStack:
    def __init__(self, filenames, size=3):
        if size > len(filenames):
            raise Exception("Not enough file names")
        self.size = size
        self.filenames = filenames
        self.stack = []
        while len(self.stack) < self.size:
            filename = self.filenames[random.randrange(0, len(self.filenames))]
            if any(item[0] == filename for item in self.stack):
                continue
            self.stack.append((filename, Image(filename)))
        # Lock used for accessing the stack
        self.stack_lock = threading.Lock()
        self.add_image_lock = threading.Lock()
    def get_image(self):
        self.stack_lock.acquire()
        filename, img = self.stack.pop()
        print(f"Get image {filename} (stack size:{len(self.stack)})")
        self.stack_lock.release()
        return img
    def add_image(self):
        self.add_image_lock.acquire()
        filename = self.filenames[random.randrange(0, len(self.filenames))]
        self.stack_lock.acquire()
        while any(item[0] == filename for item in self.stack):
            filename = self.filenames[random.randrange(0, len(self.filenames))]
        self.stack_lock.release()
        img = Image(filename)
        self.stack_lock.acquire()
        self.stack.append((filename, img))
        print(f"Add image {filename} (stack size: {len(self.stack)})")
        self.stack_lock.release()
        self.add_image_lock.release()

The above is an image stack which has two locks. One for accessing the stack and one for adding images.

The lock for stack is to ensure that only one thread is accessing the stack. Hence if we have the code.

stack = ImageStack(filenames)
load_next_image_process = threading.Thread(target=buffer.add_image)
stack.get_image()

The code above will create an ImageStack (notice that filenames is not defined here). Then on the second line it will start a new process to add a new image. After that it will try to get an image. But here the lock comes into the picture. If the thread with add_image has acquired the stack lock, then get_image call cannot start (it will be waiting in the first line to acquire stack lock).

There are more possible situations where the lock hits in. If the 3rd line with stack.get_image acquires the stack lock before that the call to add_image reaches the lock, then add_image needs to wait until the lock is released by the stack.get_image call.

Threading is a lot of fun but you need to understand how locks work and how to avoid deadlocks.

Full code

Below you will find the full code using a threading approach to load photos in the background.

import cv2
import glob
import os
import random
import threading

class Image:
    def __init__(self, filename, time=500, size=500):
        self.filename = filename
        self.size = size
        self.time = time
        self.shifted = 0.0
        img = cv2.imread(filename)
        height, width, _ = img.shape
        if width < height:
            self.height = int(height*size/width)
            self.width = size
            self.img = cv2.resize(img, (self.width, self.height))
            self.shift = self.height - size
            self.shift_height = True
        else:
            self.width = int(width*size/height)
            self.height = size
            self.shift = self.width - size
            self.img = cv2.resize(img, (self.width, self.height))
            self.shift_height = False
        self.delta_shift = self.shift/self.time
        self.reset()
    def reset(self):
        if random.randint(0, 1) == 0:
            self.shifted = 0.0
            self.delta_shift = abs(self.delta_shift)
        else:
            self.shifted = self.shift
            self.delta_shift = -abs(self.delta_shift)
    def get_frame(self):
        if self.shift_height:
            roi = self.img[int(self.shifted):int(self.shifted) + self.size, :, :]
        else:
            roi = self.img[:, int(self.shifted):int(self.shifted) + self.size, :]
        self.shifted += self.delta_shift
        if self.shifted > self.shift:
            self.shifted = self.shift
        if self.shifted < 0:
            self.shifted = 0
        return roi

class ImageStack:
    def __init__(self, filenames, size=3):
        if size > len(filenames):
            raise Exception("Not enough file names")
        self.size = size
        self.filenames = filenames
        self.stack = []
        while len(self.stack) < self.size:
            filename = self.filenames[random.randrange(0, len(self.filenames))]
            if any(item[0] == filename for item in self.stack):
                continue
            self.stack.append((filename, Image(filename)))
        # Lock used for accessing the stack
        self.stack_lock = threading.Lock()
        self.add_image_lock = threading.Lock()
    def get_image(self):
        self.stack_lock.acquire()
        filename, img = self.stack.pop()
        print(f"Get image {filename} (stack size:{len(self.stack)})")
        self.stack_lock.release()
        return img
    def add_image(self):
        self.add_image_lock.acquire()
        filename = self.filenames[random.randrange(0, len(self.filenames))]
        self.stack_lock.acquire()
        while any(item[0] == filename for item in self.stack):
            filename = self.filenames[random.randrange(0, len(self.filenames))]
        self.stack_lock.release()
        img = Image(filename)
        self.stack_lock.acquire()
        self.stack.append((filename, img))
        print(f"Add image {filename} (stack size: {len(self.stack)})")
        self.stack_lock.release()
        self.add_image_lock.release()

def process():
    path = "pics"
    filenames = glob.glob(os.path.join(path, "*"))
    buffer = ImageStack(filenames)
    prev_image = buffer.get_image()
    buffer.add_image()
    current_image = buffer.get_image()
    buffer.add_image()
    while True:
        for i in range(100):
            alpha = i/100
            beta = 1.0 - alpha
            dst = cv2.addWeighted(current_image.get_frame(), alpha, prev_image.get_frame(), beta, 0.0)
            cv2.imshow("Slide", dst)
            if cv2.waitKey(1) == ord('q'):
                return
        for _ in range(300):
            cv2.imshow("Slide", current_image.get_frame())
            if cv2.waitKey(1) == ord('q'):
                return
        prev_image = current_image
        current_image = buffer.get_image()
        load_next_image_process = threading.Thread(target=buffer.add_image)
        load_next_image_process.start()

process()

Create a Moving Photo Slideshow with Weighted Transitions in OpenCV

What will we cover in this tutorial?

In this tutorial you will learn how to make a slideshow of your favorite photos moving across the screen with weighted transitions. This will be done in Python with OpenCV.

See the result in the video below.

Step 1: A simple approach without moving effect

If you want to build something great, start with something simple first. The reason for that is that you will learn along the way. It is difficult to understand all aspects from the beginning.

Start small. Start simple. Learn from each step.

Here we assume that you have all your favorite photos in a folder called pics. In the first run, you just want to show them on your screen one-by-one.

import cv2
import glob
import os
def process():
    path = "pics"
    filenames = glob.glob(os.path.join(path, "*"))
    for filename in filenames:
        print(filename)
        img = cv2.imread(filename)
        cv2.imshow("Slideshow", img)
        if cv2.waitKey(1000) == ord('q'):
            return

process()

As you will realize, this will show the photos in the size they stored. Hence, when photos change, the dimensions of the window will change as well (unless the two consecutive photos have the exact same dimensions). This will not make a good user experience. Also, if the photos dimensions are larger than your screen resolution, they will not be fully visible.

Step 2: Scaling images to fit inside the screen

We want the window size where we show the photos to have fixed size. This is not as simple as it sounds.

Image a photo has dimensions 1000 x 2000 pixels. Then the next one has 2000 x 1000. How would you scale it down? If you scale it down to 500 x 500 by default, then the objects in the images will be flatten or narrowed together.

Hence, what we do in our first attempt, is to scale it based on the dimensions. That is, a photo with dimensions 1000 x 2000 will become 500 x 1000. And a photo of dimensions 2000 x 1000 will become 1000 x 500. Then we will crop it to fit the 500 x 500 dimension. The cropping will take the middle of the photo.

import cv2
import glob
import os
def process():
    path = "pics"
    filenames = glob.glob(os.path.join(path, "*"))
    for filename in filenames:
        print(filename)
        img = cv2.imread(filename)
        height, width, _ = img.shape
        if width < height:
            height = int(height*500/width)
            width = 500
            img = cv2.resize(img, (width, height))
            shift = height - 500
            img = img[shift//2:-shift//2,:,:]
        else:
            width = int(width*500/height)
            height = 500
            shift = width - 500
            img = cv2.resize(img, (width, height))
            img = img[:,shift//2:-shift//2,:]
        cv2.imshow("Slideshow", img)
        if cv2.waitKey(1000) == ord('q'):
            return
process()

This gives a better experience, but not perfect.

Step 3: Make a weighted transition between image switches

To make a weighted transition we make it by adding a transition phase of the photos.

import cv2
import glob
import os
import numpy as np
def process():
    path = "pics"
    filenames = glob.glob(os.path.join(path, "*"))
    prev_image = np.zeros((500, 500, 3), np.uint8)
    for filename in filenames:
        print(filename)
        img = cv2.imread(filename)
        height, width, _ = img.shape
        if width < height:
            height = int(height*500/width)
            width = 500
            img = cv2.resize(img, (width, height))
            shift = height - 500
            img = img[shift//2:-shift//2,:,:]
        else:
            width = int(width*500/height)
            height = 500
            shift = width - 500
            img = cv2.resize(img, (width, height))
            img = img[:,shift//2:-shift//2,:]
        for i in range(101):
            alpha = i/100
            beta = 1.0 - alpha
            dst = cv2.addWeighted(img, alpha, prev_image, beta, 0.0)
            cv2.imshow("Slideshow", dst)
            if cv2.waitKey(1) == ord('q'):
                return
        prev_image = img
        if cv2.waitKey(1000) == ord('q'):
            return
process()

Notice the prev_image variable that is needed. It is set to a black image when it enters the loop. The transition is made by using cv2.addWeighted(…) to get the effect.

Step 4: Make the photos move while showing

The idea is to let the photo move. Say, if the photo is scaled to dimension 500 x 1000. Then we want to create a view of that photo of size 500 x 500 that slides from one end to the other while it is showing.

This requires that we have a state for the photo, which stores where we are in of the current view.

For this purpose we create a class to represent a photo that keeps the current view. It also includes the resizing.

import cv2
import numpy as np
import glob
import os
import random

class Image:
    def __init__(self, filename, time=500, size=500):
        self.size = size
        self.time = time
        self.shifted = 0.0
        self.img = cv2.imread(filename)
        self.height, self.width, _ = self.img.shape
        if self.width < self.height:
            self.height = int(self.height*size/self.width)
            self.width = size
            self.img = cv2.resize(self.img, (self.width, self.height))
            self.shift = self.height - size
            self.shift_height = True
        else:
            self.width = int(self.width*size/self.height)
            self.height = size
            self.shift = self.width - size
            self.img = cv2.resize(self.img, (self.width, self.height))
            self.shift_height = False
        self.delta_shift = self.shift/self.time
    def reset(self):
        if random.randint(0, 1) == 0:
            self.shifted = 0.0
            self.delta_shift = abs(self.delta_shift)
        else:
            self.shifted = self.shift
            self.delta_shift = -abs(self.delta_shift)
    def get_frame(self):
        if self.shift_height:
            roi = self.img[int(self.shifted):int(self.shifted) + self.size, :, :]
        else:
            roi = self.img[:, int(self.shifted):int(self.shifted) + self.size, :]
        self.shifted += self.delta_shift
        if self.shifted > self.shift:
            self.shifted = self.shift
        if self.shifted < 0:
            self.shifted = 0
        return roi

def process():
    path = "pics"
    filenames = glob.glob(os.path.join(path, "*"))
    cnt = 0
    images = []
    for filename in filenames:
        print(filename)
        img = Image(filename)
        images.append(img)
        if cnt > 300:
            break
        cnt += 1
    prev_image = images[random.randrange(0, len(images))]
    prev_image.reset()
    while True:
        while True:
            img = images[random.randrange(0, len(images))]
            if img != prev_image:
                break
        img.reset()
        for i in range(100):
            alpha = i/100
            beta = 1.0 - alpha
            dst = cv2.addWeighted(img.get_frame(), alpha, prev_image.get_frame(), beta, 0.0)
            cv2.imshow("Slide", dst)
            if cv2.waitKey(1) == ord('q'):
                return
        prev_image = img
        for _ in range(300):
            cv2.imshow("Slide", img.get_frame())
            if cv2.waitKey(1) == ord('q'):
                return

process()

This results in a nice way where the photos slowly move through the view. It also has added some randomness. First of all, it takes a random photo. Also, the direction is set to be random.

This is a good start of having a nice slideshow of your favorite photos.