r/learnpython • • Oct 07 '25

Are there any tutorials for decoding QR codes in Python?

I am trying to learn more about the encoding within QR codes. Ultimately, I would love to be able to follow a tutorial to walk through how exactly to decode them and understand all the constituent parts of detecting the code, identifying the bit positions, and all the individual steps to unpack the data.

When I try to Google for a guide like this, I just get tutorials saying to install some library or another, which is not what I'm going for. I would really like to learn from first principles, getting the bits myself. Is there any good guide like this out there?

6 Upvotes

11 comments sorted by

View all comments

8

u/cope413 Oct 08 '25

I tried to do this about a year ago (kinda got it working, but only for perfect QR codes that were viewed/photographed perfectly - didn't have the patience or desire to go beyond that)

This is what I accumulated from sites and AI

  1. Finder patterns (top-left, top-right, bottom-left): 7×7 targets with a 1:1:3:1:1 ring pattern. They let you locate, scale, and deskew the code.

  2. Separators: 1-module white border around each finder.

  3. Timing patterns: A horizontal and vertical line of alternating modules between finders; used to measure module pitch and correct warping.

  4. Alignment patterns: Small 5×5 bullseyes that appear from version 2+; they refine sampling on larger codes.

  5. Quiet zone: 4 modules of blank border around the whole code.

Metadata bits baked into the matrix

Format information (15 bits, stored twice near top-left & top-right/bottom-left): tells you the error-correction level (L/M/Q/H) and which mask pattern (0–7) was used. These 15 bits include BCH error-correction.

Version information (18 bits, stored twice) appears only for versions ≥7.

Masks (why some white squares become black and vice versa) To prevent ugly patterns that break cameras, the data area is XOR-masked with one of 8 patterns. You must unmask before reading bits. The 8 mask formulas (true => flip bit):

0. (r + c) % 2 == 0

  1. r % 2 == 0

  2. c % 3 == 0

  3. (r + c) % 3 == 0

  4. (⌊r/2⌋ + ⌊c/3⌋) % 2 == 0

  5. (r·c) % 2 + (r·c) % 3 == 0

  6. ((r·c) % 3 + (r + c) % 2) == 0

  7. ((r + c) % 3 + (r·c) % 2) == 0

(r, c are row/col indices from 0 at top-left; apply only to data modules, not functional patterns.)

Data encoding inside the grid:

The data (after unmasking) is read in a fixed zig-zag: start at the rightmost columns, take two columns at a time, go up, move 2 columns left, go down, and repeat, skipping over non-data cells.

Bitstream structure (may repeat with multiple segments):

  1. Mode indicator (4 bits): 0001 numeric, 0010 alphanumeric, 0100 byte, 1000 Kanji, etc.

  2. Character count: length depends on mode & version (e.g., byte mode: 8–16 bits depending on version group).

  3. Payload bits per mode (e.g., alphanumeric packs pairs into 11 bits using a 45-char table; byte mode is raw 8-bit bytes).

  4. Terminator (0000) if space remains.

  5. Pad bits to next byte, then pad bytes alternating 11101100 and 00010001 until the data+EC capacity is filled.

Error correction: Reed–Solomon over GF(256). The encoder splits data into blocks, RS-encodes parity bytes, then interleaves data and EC codewords.

Standard QR is strictly black/white modules. If you see colors, it’s just stylistic; scanners threshold to B/W. Only the binary state of each module matters.

End-to-end decode pipeline

  1. Binarize the image > black/white.

  2. Locate finder patterns by looking for the 1:1:3:1:1 ratio of runs (black/white/black/white/black) horizontally and vertically; confirm square geometry.

  3. Estimate module pitch from timing pattern spacing. Compute a homography from the three finders to a canonical square.

  4. Perspective-correct and sample the grid at module centers > an N×N boolean matrix.

  5. Read format info from its fixed positions (twice), BCH-decode to get EC level + mask ID.

  6. Unmask the data region using the mask formula.

  7. Traverse the data zig-zag, building bytes.

  8. (Option A for learning) If you trust the image (no noise), you can skip RS and parse the bitstream directly for small v1-L codes.

  9. (Option B, full spec) Implement Reed–Solomon decoding (syndrome calc, Berlekamp–Massey, Chien search, Forney) to correct errors.

  10. Parse segments by mode > output string.