Based on contributions by Jasmijn.
Decoding a single data matrix code is slow enough that decoding many of them (for example 50 pre-defined regions on one 4K image) one after another can take a noticeable amount of time. This how-to explains the approach: crop each code’s region out of the image first, then decode the regions in parallel with a thread pool instead of one at a time.
What you need
- Python 3.8 or higher.
- OpenCV:
pip install opencv-python - The
zxing-cppdecoding library:pip install zxing-cpp - NumPy:
pip install numpy - Basic Python syntax and how image coordinates work in OpenCV — see How to zoom into an image or video frame with OpenCV using array slicing if you’re not familiar with slicing regions out of an image.
- The example project (a downsized FHD test image plus the decoder script): How-to Scan datamatrixes using threading.zip (3.0 MB)
Check: the original 4K test image (which the author reports gets a 100% decode success rate) was too large to attach to the forum post. Only a downsized FHD version is included in the zip, which the author reports gets roughly 40% success. Decode success rate depends heavily on image resolution — expect worse results if you scale your own images down too far.
Steps
1. Define regions of interest (ROI boxes)
Instead of scanning the whole image for data matrix codes, the script works from a list of pre-defined rectangular regions (“boxes”), each given as (top, bottom, left, right) — the same coordinate convention used for slicing an image, see How to zoom into an image or video frame with OpenCV using array slicing.
For example, (110, 196, 780, 866) means: rows 110–196 (86 px tall), columns 780–866 (86 px wide).
The example project defines 50 such boxes covering different areas of the source image.
2. Crop each box out of the image
For each box, crop the corresponding rectangle out of the full-resolution image using slicing:
crop = image[top:bottom, left:right]
You can optionally display all the crops in a grid first, to visually check that the box coordinates line up with where the codes actually are, before running the decoder.
3. Decode each crop with zxing-cpp
Each crop is scanned individually with the zxing-cpp library, which returns the decoded text if a data matrix code is found in that crop, or nothing if it isn’t.
4. Decode sequentially or in parallel
The decoder supports two modes:
- Sequential — decode each box one after another. Simpler and uses less CPU, but slower overall. Good for testing that your boxes are correct.
- Parallel — decode multiple boxes at once using a thread pool. Faster overall, at the cost of more CPU usage. The number of worker threads is configurable (2–16 is a reasonable range; use more if your machine has the cores to spare).
# Normal operation: decode and visualize results
MODE = 1
decode_and_visualize_datamatrices(
window_size="FHD", # Display size: "4K", "FHD", or "QHD"
enable_parallel=True, # Use multiple threads (faster)
num_workers=5, # Number of worker threads
show_all_crops=False, # True: show all 50 crop previews before decoding
show_final_result=True, # True: show result image with decoded data
print_decoder_results=False, # True: print timing for each box
print_detailed_info_results=False, # True: print decoded text per box
print_successrate=False, # True: print success-rate statistics
)
5. Find the best worker count for your hardware
A separate benchmark mode sweeps a range of worker counts and reports which one decodes fastest on your machine:
# Benchmark mode
MODE = 2
run_performance_benchmark(
start_workers=2, # start testing with 2 workers
max_workers=16, # test up to 16 workers
iterations_per_worker=5, # run 5 iterations per worker count
debugging=False, # True: show crops and results during testing
)
6. Read the results
The result image marks each ROI box:
- Green outline — exact position of the detected data matrix code inside the box.
- White box — a code was successfully decoded in this region.
- Red box — no code was found in this region.
Each successfully decoded box also shows its box number, the decoded text, and the code’s exact position and size.
If print_successrate=True, the script prints a summary like:
Success rate
============
Total boxes processed: 50
Success: 48 (96.0%)
Failed: 2 (4.0%)
Quick reference
| Setting | Values | Purpose |
|---|---|---|
window_size |
"4K", "FHD", "QHD" |
Display size for the result image |
enable_parallel |
True, False |
Use multiple threads |
num_workers |
2–16 | Number of parallel threads |
show_all_crops |
True, False |
Show all 50 crop previews |
show_final_result |
True, False |
Show the result image with decoded data |
print_decoder_results |
True, False |
Print timing info per box |
print_detailed_info_results |
True, False |
Print decoded text per box |
print_successrate |
True, False |
Print success statistics at the end |
Troubleshooting
- Low success rate: check the image resolution first. As noted above, decoding a downsized image gives noticeably worse results than the original high-resolution capture.
- A box consistently shows red: display the crops (
show_all_crops=True) and check that the box coordinates actually contain the code — a slightly misaligned ROI is the most common cause of a failed decode.
Related
- How to zoom into an image or video frame with OpenCV using array slicing
- How to detect and generate fiducial markers, barcodes, QR codes and data matrix codes with OpenCV
Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to detect more than 1 datamatrix fast using threading.