DevelopmentMarch 10, 20253 min read

QR Code Scanner using Python and Streamlit

Decode QR codes from images with Python, Streamlit, and OpenCV — upload, scan, and copy the hidden text or URL instantly.

Galvan

Galvan

Founder & Creator

Introduction

You have already *built* QR codes in the QR generator tutorial — now decode them. Upload any photo containing a QR code and the app extracts the embedded text or URL, draws a box around what it found, and hands you a copy button. It is a genuinely useful utility (ever gotten a QR in a screenshot?) and a friendly first step into computer vision.

The heavy lifting is one OpenCV function: QRCodeDetector, which finds the three finder squares, warps the code flat, and decodes the payload — all in a single call.

Features

  • Upload and decode — PNG/JPG images via drag and drop.
  • Annotated preview — bounding box drawn around the detected code.
  • Multi-code support — detects several QR codes in one image.
  • One-click copy — decoded text ready for the clipboard.
  • Type detection — flags URLs so you know before tapping them.
  • Prerequisites

  • Python 3.8+ — from python.org.
  • Dependencies — install with pip:
  • code
    pip install streamlit opencv-python numpy

    Step 1: Create the Script

    Save as qr_scanner.py:

    code
    import streamlit as st
    import cv2
    import numpy as np
    
    st.set_page_config(page_title="QR Scanner", page_icon="🔍")
    st.title("🔍 QR Code Scanner")
    
    uploaded = st.file_uploader("Upload an image with a QR code", type=["png", "jpg", "jpeg"])
    
    if uploaded:
        bytes_data = uploaded.getvalue()
        img = cv2.imdecode(np.frombuffer(bytes_data, np.uint8), cv2.IMREAD_COLOR)
    
        detector = cv2.QRCodeDetector()
        retval, points, straight = detector.detectAndDecodeMulti(img)
    
        if points is not None:
            results = [r for r in retval if r]
            annotated = img.copy()
            for pt_set in points:
                cv2.polylines(annotated, [pt_set.astype(int)], True, (0, 200, 0), 6)
    
            st.image(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB), caption=f"{len(results)} code(s) found")
    
            for i, text in enumerate(results, 1):
                with st.expander(f"Result {i}: {'URL 🔗' if text.startswith('http') else 'Text 📝'}"):
                    st.code(text, language=None)
        else:
            st.error("No QR code found — try a sharper, straighter image.")

    Step 2: Run the App

    code
    streamlit run qr_scanner.py

    Generate a test code with the QR generator, screenshot it, and feed the screenshot back into this app — a satisfying full-circle demo.

    How It Works

    detectAndDecodeMulti returns three things: the decoded strings, the corner points of each code, and the rectified (straightened) binary images. The corner points matter more than people expect — cv2.polylines draws the green detection box through them, giving users visual proof the scan worked. OpenCV loads images in BGR order, so the app converts to RGB before handing the frame to st.image, or reds and blues swap.

    The bytes path is deliberate: file_uploader gives you an in-memory buffer, np.frombuffer turns it into an array, and cv2.imdecode parses it into an image — no temp files, same pattern as the image uploader.

    Detection quality is mostly image quality: QR codes carry error correction, but the detector still wants contrast, focus, and reasonably straight angles. That is why the failure message suggests a sharper, straighter photo rather than blaming the code.

    Common Errors & Fixes

  • `cv2.error: imdecode` returns None — the upload is corrupt or an unsupported variant (HEIC from iPhones). Convert to JPG first or add pillow-heif.
  • Code visible to your eye but not detected — too much skew or glare. Re-shoot straight on, or preprocess with cv2.GaussianBlur then cv2.threshold.
  • Colors look wrong (blue skin, orange sky) — you skipped the BGR→RGB conversion before display.
  • `ModuleNotFoundError: cv2` — the package installs as opencv-python, but imports as cv2; check pip install opencv-python actually succeeded in your venv.
  • Key Concepts

  • `QRCodeDetector` — detection, alignment, and decoding in one call.
  • BGR vs RGB — OpenCV's channel order vs everything else's.
  • In-memory decoding — buffer → ndarray → image, no filesystem.
  • Annotated output — drawing results back onto the image builds user trust.
  • What to Try Next

  • Add webcam scanning with streamlit-webrtc for live scanning.
  • Decode barcodes too with pyzbar (covers EAN/UPC product codes).
  • Auto-crop the detected region using the corner points and show it enlarged.
  • Batch mode: accept_multiple_files=True and a results table — the file organizer shows table rendering.
  • FAQ

    Can it scan QR codes from a phone camera live?

    Not with plain file_uploader — live camera needs streamlit-webrtc, which streams video frames into your Python function. The decoding logic above stays identical.

    Why does my screenshot fail but the original works?

    Screenshots compress and rescale, blurring the fine modules. Higher error-correction codes (level H, as used in the generator tutorial) survive this much better.

    Is OpenCV the only option?

    pyzbar is a popular alternative with excellent barcode support; OpenCV wins when you also want the corner geometry for drawing or cropping.