最近做的一个小工具涉及条形码和二维码的生成与识别前后折腾了几天总算把从生成、打印、到扫码入库这套流程完整跑通了。整个过程踩了不少坑比如pyzbar在Windows上安装失败、批量生成标签时编码乱码、摄像头实时识别时二维码一闪而过却识别不到所以我把这套最终可行的方案整理出来。如果你正打算用Python做条码相关的自动化或者只是单纯想给产品生成一批带二维码的标签这篇文章应该能帮你省掉不少时间。这个方案用到的都是Python生态里比较成熟的库qrcode负责二维码生成python-barcode负责条形码生成pyzbar负责解码识别再加上OpenCV做图像处理和实时视频扫码。整套代码量不大核心逻辑加起来不到两百行但覆盖了从单张生成到批量导出、从静态图片识别到摄像头扫码的完整链路适合仓库管理、产品追溯、固定资产盘点、图书借阅这类场景直接抄作业。1. 项目场景与技术选型为什么全用Python搞定1.1 这次项目到底要解决什么问题我一开始接到的需求其实很简单给一批产品生成唯一的追溯码标签标签上要同时包含一个可扫描的二维码和一个条形码出货前还要能扫码核对信息。最初图省事我直接用在线生成工具做一个两个没问题但产品数量一多就崩溃了几百个条码手动复制粘贴到网页里再一张张下载图片重复劳动且极易出错效率低到离谱。光生成还不够仓库那边还需要识别环节。进货的时候拿着标签来扫码软件能把码里的信息读出来自动和系统里的订单号对账。所以这个项目实际上包含两条主线一是批量生成条码图片二是把条码内容准确识别出来。这两件事用在线工具和商业软件都能做但一旦涉及批量、定制和系统对接动手写代码反而是最省事的路。我最终确定的技术路子是本地跑Python脚本生成端和识别端全用开源库实现不依赖任何在线服务数据不会外传速度也完全可控。这个方案最大的好处是灵活今天要生成一千个二维码明天要改成一万个脚本改个参数就行不需要为每一点需求变化去重新找工具。1.2 方案对比在线工具、商业软件还是Python脚本很多人在面对条码生成需求时第一反应是去搜在线生成器这确实是最快的方式。但你一旦用多了就会发现问题免费版通常带水印能生成的码型有限批量上传往往要付费对图片尺寸和分辨率的控制也不够。更麻烦的是在线工具的识别能力普遍一般遇到打印模糊或光照不好的条码经常识别失败。商业条码软件功能确实强大支持专业的标签设计、数据库连接和打印模板但价格不低对只需要简单生成和识别的场景来说属于过度投入。而且商业软件很难和现有系统对接比如你想让程序生成二维码后自动写进Excel表格、上传到服务器或者识别到条码后自动触发数据库操作这些个性化需求商业软件往往做不到。Python方案的优势在于完全免费、可批量处理、可自定义标签样式、生成和识别闭环搞定还能和数据库、Web服务、Excel无缝衔接。缺点是需要一点编程基础但对于一个已经会用Python处理Excel表格的人来说掌握这套条码流程的学习成本其实很低一晚上就能上手。1.3 选定库的详细分工这套方案我选用了四个核心库分工非常明确qrcode生成二维码支持自定义尺寸、颜色、纠错级别底层依赖Pillow处理图像。python-barcode生成条形码支持EAN13、Code128、Code39等常见码制。pyzbar对图片或视频帧中的二维码、条形码进行解码是识别环节的核心库。opencv-python负责图像读取、预处理灰度化、二值化、降噪以及摄像头实时视频流的读取。另外还会用到Pillow因为二维码和条形码最终都要输出成图片而且要拼接标签、添加文字和LogoPillow是必不可少的图像处理库。qrcode库安装时会自动带上Pillow依赖但为了保险起见我通常还是会显式安装一遍。2. 环境准备Python与四个依赖库的安装2.1 Python基础环境版本选择和虚拟环境建议这个方案我用的是Python 3.8到3.11都跑过没有遇到兼容性问题所以只要不是特别老的Python 2系列基本都可以直接跑。需要特别提醒的是千万别用系统自带的Python环境直接pip install我在实际项目中吃过亏系统环境里可能装了各种版本的依赖库互相冲突今天装个OpenCV把别的库顶掉了明天又发现pyzbar装不上。建议新建一个独立的虚拟环境操作非常简单python -m venv barcode_env # Windows激活 barcode_env\Scripts\activate # macOS / Linux激活 source barcode_env/bin/activate虚拟环境的好处是隔离依赖这个项目装什么库都不会影响其他项目。如果你用的是Anaconda也可以直接conda create -n barcode_env python3.9效果一样。确认虚拟环境激活后虚拟环境名称会出现在命令行前面这时候再开始装库。2.2 安装核心依赖库与Windows特有坑在激活的虚拟环境里执行下面这一条命令把需要的基础库全部装上pip install qrcode[pil] python-barcode opencv-python pyzbar pillow这里有几个细节值得单独说。qrcode要装qrcode[pil]这个扩展版本它会自动安装Pillow支持如果只装qrcode而不带pil选项生成的图片保存时可能会报错找不到图像后端。opencv-python这个包比较大安装时间会稍微长一点属正常现象。python-barcode和pyzbar都只有几十KB安装很快。Windows下最容易出问题的是pyzbar它其实只是zbar这个C库的Python封装。如果提示缺少DLL、导入失败光靠pip install pyzbar是解决不了的需要安装Microsoft Visual C Redistributable运行库或者直接把zbar的DLL文件放到系统目录。Mac用户相对省心用Homebrew先装好zbar再装pyzbar就能解决。Linux用户需要先apt install libzbar0或yum install zbar这个坑几乎每个人都会踩一次。2.3 安装验证三行代码确认环境没问题装完库之后我习惯先跑一个快速验证确认环境没问题再开始写正式代码。打开Python交互式终端或者新建一个test.py文件执行import qrcode import barcode import cv2 from pyzbar import pyzbar print(qrcode ok) print(python-barcode, barcode.__version__) print(opencv, cv2.__version__) print(pyzbar ok)如果四个库都能正常导入且打印出信息说明环境已经就绪。如果这里就报ModuleNotFoundError或者DLL加载错误先回头解决安装问题不要急着往下走否则后面所有代码都会卡在导入这一步。验证OpenCV时还可以加一句print(cv2.getBuildInformation())查看编译信息不过一般用不到。3. 生成端二维码和条形码的完整实现3.1 二维码生成qrcode库从入门到加Logo二维码生成的入门代码很简单一行就能搞定import qrcode img qrcode.make(https://example.com/product/20240001) img.save(qrcode_demo.png)qrcode.make()适合快速生成它会自动选择合适的版本。但实际项目里一般会用QRCode对象来精确控制参数因为标签上二维码尺寸有要求内容长度也可能变化各参数对应含义如下version二维码尺寸等级取值范围1到40数值越大包含信息越多。设为None并配合fitTrue可以让库自动计算最小版本。error_correction纠错级别分为ERROR_CORRECT_L约7%、M约15%、Q约25%、H约30%四档。如果二维码里要放Logo或者打印后可能被遮挡建议至少用M档。box_size每个像素点占多少像素点数值越大生成的二维码物理尺寸越大。border二维码四周留白宽度标准值是4这里设置过小会导致某些扫码软件识别困难。实际使用中的推荐写法import qrcode from qrcode.constants import ERROR_CORRECT_H qr qrcode.QRCode( versionNone, error_correctionERROR_CORRECT_H, box_size10, border4, ) qr.add_data(SN:20240001;TYPE:A) qr.make(fitTrue) img qr.make_image(fill_colorblack, back_colorwhite) img img.convert(RGB) img.save(qrcode_sn.png, dpi(300, 300))如果要在二维码中间加Logo可以利用Pillow把Logo图片粘贴到二维码中央。因为选择了H级纠错即使Logo遮挡了一部分图案扫码仍然可以正常识别但Logo大小别超过二维码整体尺寸的四分之一否则还是容易失败。3.2 条形码生成python-barcode的常用类型与保存条形码的情况比二维码略复杂一点因为条形码有编码标准差异比如EAN13只能编码13位数字常用于商品零售Code128可以编码所有ASCII字符常用于企业内部管理Code39只支持大写字母、数字和少量符号适合简单的资产编号。python-barcode支持多种码制使用方式差不多关键区别在于数据内容是否符合码制要求。我最常用的是Code128因为产品编号往往是字母加数字混排用Code128最合适import barcode from barcode.writer import ImageWriter code128 barcode.get(code128, 2024ABC0001, writerImageWriter()) filename code128.save(barcode_code128)这句话生成的是barcode_code128.png注意save()的返回值是实际保存的完整路径。默认情况下不指定writer的时候生成的是SVG矢量图虽然打印清晰但很多图片处理工具不方便操作所以要用ImageWriter明确指定输出PNG。EAN13这种商品条码对数据位数有严格要求必须是12位有效数据加1位校验码总共13位数字。python-barcode在初始化时可以只传12位然后让它自动计算校验位ean barcode.get(ean13, 690123456789, writerImageWriter()) ean.save(barcode_ean13)如果传入13位数字它会校验最后一位是否正确不对就报错。这里有个细节商品条码的前缀690到699代表中国大陆如果你是做出口商品要换成对应国家的前缀不然海外扫码软件可能会解析异常。3.3 批量生成从Excel读取数据自动排版标签单张生成只是开胃菜实际项目里真正的需求是批量。我做的项目里有一份产品清单包含产品名称、内部编码、生产批次和验证链接我需要把这些信息批量生成到标签上每张标签上面是二维码下面是条形码中间还有一行产品名称文字。这里给出一个可以直接改来用的完整脚本import csv import qrcode import barcode from barcode.writer import ImageWriter from PIL import Image, ImageDraw, ImageFont def generate_label(code, name, batch, output_path): # 生成二维码内容包含编码和批次 qr qrcode.QRCode(versionNone, error_correctionqrcode.constants.ERROR_CORRECT_M) qr.add_data(f{code};{batch}) qr.make(fitTrue) qr_img qr.make_image(fill_colorblack, back_colorwhite).convert(RGB) # 生成Code128条形码图片 bc barcode.get(code128, code, writerImageWriter()) bc_path ftemp_{code} bc.save(bc_path) bc_img Image.open(bc_path .png).convert(RGB) # 创建空白标签把二维码和条码拼在一起 label Image.new(RGB, (500, 400), white) label.paste(qr_img, (150, 20)) label.paste(bc_img, (100, 280)) draw ImageDraw.Draw(label) try: font ImageFont.truetype(C:/Windows/Fonts/msyh.ttc, 20) except Exception: font ImageFont.load_default() draw.text((250 - len(name) * 10, 250), name, fillblack, fontfont) label.save(output_path) print(生成:, output_path, code) with open(products.csv, encodingutf-8) as f: for row in csv.DictReader(f): generate_label(row[code], row[name], row[batch], flabels/{row[code]}.png)这段代码有一个容易踩坑的地方用Pillow的draw.text()画中文时如果直接使用系统默认字体中文会显示成一堆方块必须指定一个支持中文的字体文件Windows下通常是C:/Windows/Fonts/msyh.ttcmacOS下是/System/Library/Fonts/PingFang.ttc。另外ImageFont.truetype不能用空路径否则会报错所以我在代码里加了异常处理兜底。批量生成完成后我建议统一输出到单独的labels目录文件名使用产品编码方便后续按编码查找对应标签文件。如果你要打印记得生成时用高DPI比如img.save(..., dpi(300, 300))否则打印出来的条码可能因为像素不够而导致识别率下降。4. 识别端静态图片、图像增强与摄像头实时扫码4.1 静态图片识别pyzbar的用法与返回结果解析识别端我直接用pyzbar配合OpenCV读取图片。先看最简单的情况识别一张已经生成好的二维码图片import cv2 from pyzbar import pyzbar img cv2.imread(qrcode_sn.png) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) results pyzbar.decode(gray) for res in results: print(类型:, res.type) print(内容:, res.data.decode(utf-8, errorsignore)) print(位置:, res.rect)pyzbar.decode()返回的是一个列表即使图片里只有一个二维码它返回的也是一个长度为1的列表遍历处理是最稳妥的写法。每个结果对象有四个主要属性type表示码的类型取值为QRCODE或CODE128、EAN13等data是字节串需要解码成字符串rect是一个包含x、y、width、height的矩形区域可以用来画框标注polygon是更精确的四角顶点坐标适合做透视变换的场景。代码里最显著的问题就是res.data是bytes类型直接打印会是bSN:20240001这种带b前缀的格式。解码时优先尝试UTF-8但如果二维码内容是用GBK编码的中文UTF-8解码会变成乱码所以要加errorsignore或者针对实际情况依次尝试utf-8、gbk并捕获UnicodeDecodeError。识别到二维码之后把检测框画到原图上是非常自然的操作因为rect提供的就是像素坐标直接给cv2.rectangle()用即可。我这里用的是灰度图做识别但画框要画在原图img上这样色彩信息不会丢。4.2 图像预处理把模糊反光的图片救回来实际应用中你拿到的图片往往没有生成出来的那么干净。我在仓库试过碰到过三种典型情况一是标签有褶皱条码变形二是灯光反光二维码局部过曝三是照片拍摄角度倾斜条码有透视畸变。遇到这些问题直接pyzbar.decode()经常返回空列表这时候就需要OpenCV的预处理操作来救场。我常用的预处理流程如下import cv2 from pyzbar import pyzbar def decode_with_preprocess(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (3, 3), 0) processed cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 10 ) results pyzbar.decode(processed) # 如果二值化后识别不到退回到灰度图再试一次 if not results: results pyzbar.decode(gray) return resultsGaussianBlur用3x3的小核做轻微降噪去掉传感器噪点adaptiveThreshold是自适应二值化和全局阈值不同它会根据每个像素周围区域的光照强度动态计算阈值对光照不均匀的场景效果比固定阈值好很多。参数21是邻域大小10是常数C这两个值需要根据实际图片调整邻域太大会丢失细节太小又会被噪声干扰。图像预处理其实是整个识别环节里最需要经验的部分没有一套参数能适用所有场景。我的做法是先试原始灰度图不行再尝试二值化图还不行就调整阈值参数、改变图像分辨率。对于倾斜严重的条码可以先做透视校正用polygon的四个顶点计算出变换矩阵把条码区域拉正再识别。这个操作代码量略大但在实际项目中非常有用。4.3 摄像头实时识别做一个简易扫码程序如果只是识别本地图片用途还是比较有限在仓库环境下更多时候需要拿摄像头直接扫实物标签这里我用OpenCV读取摄像头视频流配合pyzbar逐帧解码实现一个简易的实时扫码程序。import cv2 from pyzbar import pyzbar cap cv2.VideoCapture(0) if not cap.isOpened(): print(摄像头打开失败) exit() last_text None stable_count 0 while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) results pyzbar.decode(gray) for res in results: text res.data.decode(utf-8, errorsignore) if text last_text: stable_count 1 else: last_text text stable_count 1 x, y, w, h res.rect cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 连续5帧识别到相同内容才认定扫码成功 if stable_count 5: print(扫码成功:, text) stable_count 0 cv2.imshow(barcode-scanner, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这个程序有两个关键设计。第一个是连续多帧确认机制因为摄像头画面一直在变二维码可能在画面里只出现了一瞬间直接打印会在单帧误识别时就触发一次我通过要求连续5帧识别到相同内容才输出大大减少了误判。第二个是把画面实时显示出来方便调整扫码角度和距离当出现绿色框的时候说明已经识别到了。摄像头参数设置也很关键。如果画面太暗或者太模糊可以手动调整分辨率和曝光参数。我习惯把分辨率设成1280x720太高的分辨率会导致每帧处理时间变长识别速度变慢太低则小条码容易识别失败。曝光时间不建议手动调除非环境光线确实很极端否则自动曝光一般更省心。5. 落地过程中的问题排查与经验复盘5.1 几个高频报错的清晰解法我把这段时间遇到的实际问题整理成了一张速查表基本覆盖了新手最常见的雷区报错现象根本原因解决办法ModuleNotFoundError: No module named pyzbar没有安装pyzbar虚拟环境中执行pip install pyzbar安装pyzbar后导入报DLL加载失败Windows缺少zbar运行时库安装Visual C Redistributable或下载zbar DLLpyzbar.decode返回空列表图片模糊、光照不均或码本身损坏做灰度化、二值化等预处理或调整拍摄角度res.data打印出来是bxxx返回的是bytes类型调用data.decode(utf-8, errorsignore)二维码包含中文时识别乱码原内容不是UTF-8编码尝试data.decode(gbk, errorsignore)python-barcode生成的是.svg而不是.png没有使用ImageWriter在barcode.get中指定writerImageWriter()Code128传入非ASCII字符报错码制不支持某些字符改用支持全字符的码制或去掉特殊字符VideoCapture(0)打开失败权限报错摄像头被其他软件占用关闭占用程序或更换索引值0/1测试这里最值得展开说的是Windows下pyzbar的DLL问题。很多人在pip install pyzbar之后一导入就崩这个和代码本身无关是缺了C语言运行库。先装运行库如果还不行就从zbar官网下载对应位数64位系统就下64位的预编译包把里面的zbar.dll放进C:\Windows\System32目录。装完重启Python进程再试基本上都能解决。5.2 识别率提升的独家技巧识别率这个话题我在测试时踩了不少坑总结下来有四点经验值得专门写出来。第一二维码的静态识别和动态识别要用不同策略。静态图片识别时可以把图片稍做放大处理用cv2.resize()把图片按1.5到2倍放大很多时候小尺寸二维码放大之后就立刻识别出来了。动态识别时反而不要过度处理每一帧因为处理耗时太长会导致视频卡顿通常只做灰度化和轻微降噪就够了。第二打印标签的材质直接影响识别率。热敏纸粗糙、反光少识别效果通常不错铜版纸表面覆膜后反光严重扫码时需要调整光源角度。如果你要大批量打印先打一张样张在识别程序里测试比批量打出来再报废要划算得多。第三二维码尺寸不是越大越好。我测试过普通手机摄像头在30到50厘米的距离下一个边长3厘米左右的二维码识别最舒服太小了看不清太大了需要拉远距离反而影响扫码效率。条形码的宽度和高度比例也要控制太窄的条形码对摄像头对焦要求极高很容易识别失败。第四码内容的编码统一比什么都重要。我曾经遇到过一批从老系统导出的数据里面混着GBK编码的中文生成二维码的时候没做编码转换结果实际扫码出来一部分是乱码。现在我在生成之前会对所有文本做一次编码检测统一转成UTF-8再写入二维码这是根治乱码的办法。5.3 后续扩展从单机脚本到扫码服务这套代码跑通之后我把它进一步扩展成了一个简单的本地扫码服务原理并不复杂把识别逻辑包装成一个函数用Flask起一个HTTP接口前端页面调用摄像头拍照后把图片POST到接口接口返回识别结果。这样就不用每台电脑都装Python环境只要浏览器能打开页面就能扫码。核心思路是把识别逻辑抽出来# 一个可以被其他地方调用的识别函数 def scan_image(file_path): img cv2.imread(file_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) results pyzbar.decode(gray) if not results: results decode_with_preprocess(img) return [(r.type, r.data.decode(utf-8, errorsignore)) for r in results]这样识别函数既可以在本地脚本里用也可以暴露给Web接口甚至可以接到企业微信机器人上扫码后自动推送信息到群里或者直接写入数据库做库存登记。如果你需要同时识别一张图里的多个二维码pyzbar天然支持返回列表里的每个元素就是其中一个码不需要额外处理。再往下走结合目标检测模型去定位复杂背景中的二维码识别率还能进一步提升但那是另一个更重的方案了。如果只是常规的标签扫码场景目前这套基于传统图像处理的方法已经足够稳定而且部署成本低、速度快完全不需要GPU跑模型。最后分享一个我在实际使用中体会最深的小细节二维码生成和识别看似是两个独立的功能但它们其实是同一套系统的两端生成时规范编码、留足边距、控制尺寸识别时再做灰度化和多帧确认两端的质量同时抓整体流程才会顺畅。有一次我花了很长时间排查识别率低的问题最后发现是生成时用了太小的box_size打印出来整个二维码只有指甲盖大这种问题再怎么调识别参数都是白费回头从生成端改参数反而一步到位。如果你准备动手做类似的项目建议先把生成端和识别端同时写出来再拿打印出来的实物标签反复测试用最接近真实场景的情况去校准两端参数。
