生成 PDF417 条形码只需四行 Python 代码。将其从手机照片、200 DPI 扫描件或在某人口袋中放置了一天的登机牌中读取,才是真正的挑战,也是大多数 PDF417 集成实际失败的地方。在本文中,您将学习在源图像不完美的情况下如何使用 Python 读取 PDF417 条形码。如果您更关注生成方面,之前的教程 使用 Python 生成 PDF417 条形码 介绍了创建基本和自定义 PDF417 符号,包括行列控制,而 Python 条形码读取器指南 则涵盖了跨符号集的通用扫描。下面的内容将对 PDF417 识别进行深入且细致的探讨。

下面的每个代码片段都是在 Linux 上使用 Python 3.12 对 Aspose.BarCode for Python via .NET 26.7 进行运行的。计时和读取/未读取结果是实际测量的,而非估计的,生成这些结果的脚本位于文末,您可以在自己的图像上重新运行它。

实际上导致 PDF417 识别失败的因素

PDF417 是一种堆叠式符号:行由代码字组成,每个代码字由四根条和四个空格构成,跨越 17 个模块。该结构采用 Reed‑Solomon 错误纠正,因此撕裂的角落或咖啡渍通常可以恢复。不可恢复的是丢失模块网格本身。

有三件事会破坏网格,按照它们在生产环境中出现的频率大致排序:

  • 有效模块宽度低于两个像素。 这是最主要的失败原因。它可能由于下采样、相机距离以及模糊导致,模糊会把一个模块的墨水扩散到相邻模块。一个每模块三像素生成的条码在模糊后实际上等同于每模块一个像素的条码。
  • 透视和旋转。 以角度拍摄的条码其行不再是平行的扫描线。检测器能够处理,但这会消耗一次检测过程,而最快的预设会跳过此步骤。
  • 对比度崩塌。 热敏纸褪色、在眩光下拍摄的屏幕,或亮度被固定的扫描。其危害并不像看起来那么致命,下面的数字已经说明。

实际的结果是,识别设置是一种恢复机制,而不是捕获质量的替代品。它们可以挽回中等水平的图像质量,但无法挽回底层质量。

设置 Python PDF417 阅读器

从 PyPI 安装库:

pip install aspose-barcode-for-python-via-net

识别 API 位于 aspose.barcode.barcoderecognition。本文涉及的两个类是 BarCodeReader(负责检测和解码)和 QualitySettings(控制读取器的尝试强度)。

在 Python 中读取 PDF417 条形码

最小可行的读取是两个参数和一个循环:

from aspose.barcode import barcoderecognition as rec

reader = rec.BarCodeReader("clean.png", rec.DecodeType.PDF417)

for result in reader.read_bar_codes():
    print("Code text:", result.code_text)
    print("Symbology:", result.code_type_name)
    print("Reading quality:", result.reading_quality)
在 Python 中读取 PDF417 条形码。

在 Python 中读取 PDF417 条形码。

在干净图像上的输出:

Code text: PASSENGER|KHAN/M|SEAT14C|LHE-DXB
Symbology: Pdf417
Reading quality: 100.0

实际上第二个参数不是可选的。 如果省略它,BarCodeReader 将回退到 DecodeType.ALL_SUPPORTED_TYPES,它会对图像中的每个候选区域运行超过六十种符号的检测。在仅使用 PDF417 的流水线中,这纯粹是浪费,并且会在 PDF417 行结构内部打开线性符号误报的大门。命名该类型。

如果您的输入是来自上传的字节而不是磁盘上的文件,请传递流并设置超时,以防止异常图像阻塞工作线程:

import io
from aspose.barcode import barcoderecognition as rec

def decode_pdf417(image_bytes, timeout_ms=2000):
    reader = rec.BarCodeReader(io.BytesIO(image_bytes), rec.DecodeType.PDF417)
    reader.timeout = timeout_ms
    return [r.code_text for r in reader.read_bar_codes()]

比较 PDF417 识别质量设置(Python)

QualitySettings 提供了四个值得了解的预设。将其中一个分配给读取器只需一行代码:

reader = rec.BarCodeReader("scan.png", rec.DecodeType.PDF417)
reader.quality_settings = rec.QualitySettings.high_quality
预设适用场景成本
high_performance干净的机器生成图像最快,跳过恢复过程
normal_quality默认。常规扫描和导出均衡
high_quality相机拍摄,质量下降的扫描适中
max_quality对已失败的图像进行重试速度最慢,差距悬殊

为了了解这些描述在实际中的含义,我以每模块三像素的分辨率生成了一个 PDF417,使用 Pillow 以六种方式降级,并对每个预设运行了每个变体。取三次运行的中位数:

源图像high_performancenormal_qualityhigh_qualitymax_quality
干净读取, 35 毫秒读取, 50 毫秒读取, 45 毫秒读取, 106 毫秒
旋转 12°,模糊未命中, 15 毫秒读取, 74 毫秒读取, 67 毫秒读取, 157 毫秒
高斯噪声 (σ 40)读取, 31 毫秒读取, 49 毫秒读取, 52 毫秒读取, 123 毫秒
半分辨率读取, 22 毫秒读取, 37 毫秒读取, 35 毫秒读取, 86 毫秒
低对比度读取, 24 毫秒读取, 38 毫秒读取, 43 毫秒读取, 112 毫秒
强模糊 (σ 2.2)未命中, 5 毫秒未命中, 25 毫秒未命中, 85 毫秒未命中, 180 毫秒

从此表中可以得出四个要点,它们比单个数字更有用:

  1. 噪声和低对比度并不是人们想象的那样的问题。 Reed‑Solomon 加上检测器吸收了大量高斯噪声,并在最快的预设上降低了 82% 的对比度。不要为此进行调优。
  2. 旋转加模糊是区分预设的情况。 这也是手持手机拍摄的精确特征,这就是为什么 high_performance 对任何移动来源的流水线来说都是错误的默认设置。
  3. max_quality 的耗时是 normal_quality 的三倍,却没有读取额外内容。 它应作为第二次尝试的路径,而不是默认选项。
  4. 一旦模块网格消失,任何预设都无法恢复。 重度模糊行在整体上都是失误,max_quality 用了 180 毫秒来证明这一点。这是一个捕获问题,需要通过捕获来修复:更多像素、更好的对焦、在生成时使用更大的 x_dimension。

调整 QualitySettings 以在 Python 中读取模糊的 PDF417 条码

预设是一组单独属性的集合,您可以从预设开始并覆盖其中的某个属性。这正是已知、可重复缺陷实现真正收益的地方。

上面的旋转并模糊的图像是一个很好的演示。在 high_performance 下它缺失。启用去卷积(deconvolution)在相同的预设上,它会显示:

from aspose.barcode import barcoderecognition as rec

reader = rec.BarCodeReader("rotated.png", rec.DecodeType.PDF417)

quality = rec.QualitySettings.high_performance
quality.deconvolution = rec.DeconvolutionMode.NORMAL
reader.quality_settings = quality

for result in reader.read_bar_codes():
    print(result.code_text)
调优 QualitySettings 以在 Python 中读取模糊的 PDF417 条码。

调优 QualitySettings 以在 Python 中读取模糊的 PDF417 条码。

测量:在没有覆盖的情况下耗时 12 ms,使用覆盖时耗时 54 ms。一个属性改变了结果,且该结果仍然比 max_quality 预设更快返回。

值得了解的属性:

属性值何时使用
deconvolutionFAST, NORMAL, SLOW运动模糊和失焦的捕获
barcode_qualityHIGH, NORMAL, LOW打印缺陷、热衰减、传真伪影
x_dimensionAUTO, SMALL, NORMAL, LARGE, USE_MINIMAL_X_DIMENSION密集条码,或固定已知的模块尺寸
minimal_x_dimensionfloat与 USE_MINIMAL_X_DIMENSION 配合使用以设定下限
complex_backgroundAUTO, DISABLED, ENABLED条码印在艺术作品或图案化表单上
inverse_imageAUTO, DISABLED, ENABLED黑底白码条码,屏幕捕获
allow_incorrect_barcodesbool仅用于诊断,绝不用于生产

That last one deserves a warning. allow_incorrect_barcodes 返回未通过校验和验证的符号。这是一个调试辅助,用于回答 “did the detector even find something here?” 将其发布意味着发布错误的已解码字符串,而不是缺失的字符串,这比漏检更糟。

在生产环境中效果良好的分层策略:首先尝试 normal_quality,如果未命中,则再次尝试一次 high_quality 并加上 deconvolution = SLOW。只有在需要时才为图像支付昂贵的路径。

在 Python 中验证 PDF417 解码结果

解码后的字符串并不一定是正确的字符串。BarCodeResult 上的三个字段可以帮助:

for result in reader.read_bar_codes():
    print("Text:", result.code_text)
    print("Reading quality:", result.reading_quality)
    print("Confidence:", result.confidence)
    print("Region:", [(p.x, p.y) for p in result.region.points])
Text: PASSENGER|KHAN/M|SEAT14C|LHE-DXB
Reading quality: 100.0
Confidence: 100
Region: [(7, 6), (261, 7), (261, 276), (7, 277)]

重新组装跨图像拆分的宏 PDF417

Macro PDF417 将一个逻辑消息拆分为多个符号,这在装运清单和多页文档中会出现。段元数据在往返传输中得以保留,因此重新组装是确定性的:

from aspose.barcode import barcoderecognition as rec

segments = {}
total = None

for path in ["page1.png", "page2.png"]:
    reader = rec.BarCodeReader(path, rec.DecodeType.MACRO_PDF417)
    for result in reader.read_bar_codes():
        meta = result.extended.pdf417
        segments[meta.macro_pdf_417_segment_id] = result.code_text
        total = meta.macro_pdf_417_segments_count
        print(f"{path}: segment {meta.macro_pdf_417_segment_id + 1} of {total}, "
              f"file id {meta.macro_pdf_417_file_id}")

if total and len(segments) == total:
    payload = "".join(segments[i] for i in sorted(segments))
    print("Reassembled:", payload)
else:
    print("Incomplete: missing segments")
第 1 页。

第 1 页。

第 2 页。

第 2 页。

Output:

Data\pdf417\page1.png: segment 1 of 2, file id 42
Data\pdf417\page2.png: segment 2 of 2, file id 42
Reassembled: SEGMENT-A-DATASEGMENT-B-DATA

请注意,macro_pdf_417_file_id 在生成和识别两端都是整数,而不是字符串。当编码器填充时,同一对象还会公开 macro_pdf_417_file_name、macro_pdf_417_sender、macro_pdf_417_addressee、macro_pdf_417_time_stamp 和 macro_pdf_417_checksum。

在您自己的图像上基准测试 PDF417 识别

预设选择应基于测量,而非猜测。将其指向实际流水线中具有代表性的捕获文件夹,它会告诉你哪个预设值得其成本:

import time, statistics, pathlib
from aspose.barcode import barcoderecognition as rec

PRESETS = ["high_performance", "normal_quality", "high_quality", "max_quality"]

def probe(path, preset, reps=3):
    times, found = [], False
    for _ in range(reps):
        reader = rec.BarCodeReader(str(path), rec.DecodeType.PDF417)
        reader.quality_settings = getattr(rec.QualitySettings, preset)
        start = time.perf_counter()
        results = list(reader.read_bar_codes())
        times.append((time.perf_counter() - start) * 1000)
        found = found or bool(results)
    return found, statistics.median(times)

for image in sorted(pathlib.Path("samples").glob("*.png")):
    row = []
    for preset in PRESETS:
        found, ms = probe(image, preset)
        row.append(f"{preset}: {'read' if found else 'miss'} {ms:.0f}ms")
    print(f"{image.name:28} " + " | ".join(row))
clean.png                    high_performance: read 32ms | normal_quality: read 48ms | high_quality: read 51ms | max_quality: read 130ms
faded_thermal.png            high_performance: read 26ms | normal_quality: read 43ms | high_quality: read 46ms | max_quality: read 144ms
half_resolution.png          high_performance: read 27ms | normal_quality: read 41ms | high_quality: read 41ms | max_quality: read 89ms
heavy_blur.png               high_performance: miss 7ms | normal_quality: miss 24ms | high_quality: miss 69ms | max_quality: miss 180ms
noisy_scan.png               high_performance: read 26ms | normal_quality: read 42ms | high_quality: read 47ms | max_quality: read 116ms
Page1.png                    high_performance: read 21ms | normal_quality: read 34ms | high_quality: read 38ms | max_quality: read 90ms
Page2.png                    high_performance: read 21ms | normal_quality: read 33ms | high_quality: read 38ms | max_quality: read 89ms
rotated.png                  high_performance: miss 16ms | normal_quality: read 43ms | high_quality: read 57ms | max_quality: read 147ms

对五十个真实捕获运行一次,预设的争论就结束了。

实用指南

  • 将 DecodeType 限制为 PDF417,除非你真的预期会出现混合符号集。当你知道具体变体时,请使用 MACRO_PDF417、COMPACT_PDF417 或 MICRO_PDF417。
  • 在解码前先裁剪。 当条码只占扫描图像的 5% 时,直接传入整张 A4 扫描会浪费检测时间并增加误报。
  • 保持中间文件无损。 使用 PNG 或 TIFF。低质量设置的 JPEG 会产生环形伪影,恰好影响解码器所需的条码边缘。
  • 分层设置重试次数,而不是默认使用最慢的预设。
  • 在调优识别之前先完善生成。 如果你可以控制编码器,提升 x_dimension 不会增加成本,并能解决比任何读取器设置更多的漏检。该PDF417 生成文章 详细介绍了这些参数。
  • 为任何处理用户提供图像的读取器设置 timeout。

获取免费许可证

评估版会在除 Code 39 之外的其他条码符号的解码文本上添加水印,这会干扰您进行的任何往返测试。获取免费临时许可证即可在评估期间移除该水印。

免费的额外资源

结论

在 Python 中读取 PDF417 条形码在干净图像上只需两行代码,而在真实图像上则是一个工程问题。上面的测量指向一个简单的姿态:指定你的 DecodeType,默认使用 normal_quality,在一次重试时使用 high_quality 并启用去卷积,并在采取行动前验证每个结果。将昂贵的预设保留用于重试路径,当整类图像在所有设置下都失败时,修复捕获而不是读取器。欢迎在免费支持论坛提问。

常见问题

  1. 为什么相同的 PDF417 条码在一张图像中可以读取,而在另一张图像中却失败?
    识别取决于捕获后每个模块占据的像素数量。模糊、缩小以及 JPEG 压缩都会缩小有效模块宽度,一旦模块宽度降至大约两个干净像素以下,任何预设都无法恢复。

  2. 我应该为 PDF417 使用哪种识别预设?
    从 normal_quality 开始,它是默认设置。对于相机捕获和扫描,切换到 high_quality;将 max_quality 保留用于重试,因为它速度慢数倍,却并未显著提升读取量。

  3. 将 DecodeType 限制为 PDF417 真能加快读取速度吗?
    是的。默认值是 DecodeType.ALL_SUPPORTED_TYPES,它会对所有支持的符号进行检测。显式指定 PDF417 可省去这一步,同时也消除了来自其他符号的误报可能性。

  4. Aspose.BarCode 能读取跨多张图像的宏 PDF417 消息吗?
    可以。使用 DecodeType.MACRO_PDF417 读取每个片段,并在扩展结果中利用 macro_pdf_417_file_id、macro_pdf_417_segment_id 和 macro_pdf_417_segments_count 按顺序重新组装负载。

  5. 我如何判断解码结果是否可信?
    检查 BarCodeResult 上的 reading_quality 和 confidence,并使用 region 四边形确认条码在图像中出现在预期位置。

  6. 它能从驾驶执照或登机牌的照片中读取 PDF417 吗?
    可以,只要拍摄时对焦清晰且条码未被裁剪。读取前先裁剪到条码区域,并在中间文件中优先使用 PNG 而非 JPEG。

阅读更多