ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

二维码生成与识别完整代码实战:从选型到避坑指南

二维码生成与识别完整代码实战:从选型到避坑指南 1. 二维码完整代码的核心问题生成和识别不能只靠一个库平时总收到私信问“有没有一份完整的二维码代码能直接生成和识别” 这个需求听起来简单真正落地时你会发现二维码生成和二维码识别是两套完全不同的技术栈。生成的难点在编码容错和图形渲染识别的难点在图像预处理和定位校正。很多项目卡壳就是没想明白“完整代码”究竟该包含哪几部分。所以这篇文章我不只贴代码还会把选型思路、真实业务里的坑一起说清楚。适合刚接触二维码开发、需要给系统加生成或扫码功能的人参考。1.1 生成是编码识别是解码——两套逻辑的底层差异二维码尤其是我们现在天天扫的QR Code本质是把一段文本或二进制数据转换成一张黑白点阵图。生成端做的事叫编码先选择数据模式数字、字母、字节、汉字再把数据拆块加版本信息、格式信息、纠错码Reed-Solomon最后按规则铺进矩阵并在三个角放上“回”字形定位图案。识别端做的事正好相反先找到三个定位角透视校正再采样网格解出0和1最后按同样的规则还原数据。这里的核心是“同样的规则”也就是库和库之间必须兼容。所以你会发现生成可以用很轻量的小库识别通常要引入图像处理库因为相机拍到的图不可能是标准网格。图像有透视变形、有噪点、有反光识别前还要做预处理。这就是为什么我经常建议不要把“生成代码”和“识别代码”写在一个文件里硬凑应该按前后端、按设备去拆分。1.2 技术选型别迷信一个大而全的方案我见过很多人一上来就问“有没有一个库能同时生成和识别”其实是钻了牛角尖。每个库都有自己的长项按场景组合才是工程的做法。下面这张表可以直接当选型参考场景推荐库语言/平台说明网页实时生成qrcode.jsJavaScript体积小、带容错级别和颜色配置适合前端展示服务端批量生成segno / qrcodePython可以输出SVG/PNG支持批量处理量大Java后端生成/解析ZXingJava/Kotlin生态成熟Java系首选C#/Unity生成识别ZXing.NetC#能直接用Texture2D渲染也可识别摄像头画面摄像头/图片识别OpenCV pyzbarPythonpyzbar封装了zbar识别率高需要先装系统依赖性能要求高的识别ZXing-CC适合Jetson等边缘设备可配合OpenCV做ROI裁剪选型定下来后再写代码才不会反复推翻。比如你只是在网页里生成一个二维码让用户扫码跳转那就别引入后端大框架反过来如果是支付接口要返回二维码链接一定得在后端生成因为前端生成的图片没有支付签名语义容易被篡改。我自己的习惯是先用一个最小demo跑通生成和识别确认库跟库之间兼容再往项目里塞。这一步能省掉后面大量联调时间。2. 一份可直接运行的二维码生成完整代码前端后端这一章我直接给两个可复制的代码一个纯HTML文件适合本地打开适合活动签到、临时分享一个Flask接口适合埋进现有后端系统用接口地址去生成不同内容的二维码。两段代码我都跑过可以放心用。2.1 前端HTML20行代码在网页上实时生成二维码先来最简单的。把下面这段保存成 qr_gen.html双击打开就能用。它依赖 qrcode.js 的CDN第一次打开需要联网之后可以换成自己服务器上的本地文件。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title二维码生成/title script srchttps://cdn.jsdelivr.net/npm/qrcodejs1.0.0/qrcode.min.js/script /head body h2二维码生成/h2 input idtext placeholder输入内容 stylewidth: 300px; padding: 8px; select idlevel option valueLL级容错/option option valueMM级容错/option option valueQQ级容错/option option valueH selectedH级容错/option /select button onclickgenerate()生成/button div idqrcode/div script function generate() { const text document.getElementById(text).value.trim(); if (!text) return alert(请输入内容); const level document.getElementById(level).value; const qr document.getElementById(qrcode); qr.innerHTML ; new QRCode(qr, { text: text, width: 256, height: 256, correctLevel: QRCode.CorrectLevel[level] }); } /script /body /html这段代码的核心是new QRCode(dom, options)你只需要给text传一个字符串它就会在指定容器里画出一个canvas部分旧版本是table。correctLevel是纠错等级L能存最多内容但最怕遮挡H最抗脏但容量小。我默认选H主要是考虑到扫码的人可能贴得很近产生遮挡或者打印后有磨损。如果不做定制这个页面已经能解决80%的临时需求。2.2 后端PythonFlask接口返回二维码图片前端生成只能手动一个个点业务系统里一般需要一个接口你给我内容我返回二维码图片或者返回二维码图片的URL。下面这段是用Flask加qrcode库实现的接口代码短逻辑完整。from flask import Flask, Response, request import qrcode from io import BytesIO app Flask(__name__) app.get(/qr.png) def generate_qr(): text request.args.get(text, hello) size int(request.args.get(size, 10)) border int(request.args.get(border, 4)) qr qrcode.QRCode( versionNone, error_correctionqrcode.constants.ERROR_CORRECT_H, box_sizesize, borderborder, ) qr.add_data(text) qr.make(fitTrue) img qr.make_image(fill_colorblack, back_colorwhite) buf BytesIO() img.save(buf, formatPNG) return Response(buf.getvalue(), mimetypeimage/png) if __name__ __main__: app.run(host0.0.0.0, port5000)运行前先装依赖pip install flask qrcode然后python app.py浏览器访问/qr.png?texthttps://example.com就能看到图片。这里box_size是每个点的像素大小border是四周留白宽度默认4个模块是QR规范的基本要求不要设成0否则很多手机扫不出来。如果你对接支付网关比如支付宝电脑网站支付返回的qr_code字段本身就是一串字符串你不需要把它先转成图片再处理直接把这个字符串丢给后端add_data返回给前端展示即可。如果你的项目需要返回JSON可以让接口返回{code: 0, data: {text: code_url, qr_url: /qr.png?text...}}前端再拿这个QR码链接去展示思路是一样的。2.3 带Logo、调颜色、设容错率容易被忽略的三个细节生成二维码时除了把内容变成图还有三个细节非常影响扫码成功率。第一个是Logo。在二维码中间放Logo看起来比纯码更品牌化但千万别用低容错。我通常选H级因为Logo会破坏约15%到20%的区域L级基本一盖就废。插入Logo的代码很简单用PIL把Logo缩放后贴到中心即可但要注意Logo尺寸不要超过二维码边长的四分之一否则纠错码补不回来。from PIL import Image img qr.make_image(fill_colorblack, back_colorwhite).convert(RGB) logo Image.open(logo.png).resize((img.size[0] // 4, img.size[1] // 4)) img.paste(logo, ((img.size[0] - logo.size[0]) // 2, (img.size[1] - logo.size[1]) // 2))第二个是颜色。默认黑白是兼容性最好的反色二维码虽然好看但很多低端扫码枪和灰度摄像头识别不了。如果要设计深色背景尽量保证模块和背景的明暗对比足够大不要用相近色。第三个是内容长度。同样的容错等级下内容越长模块越密手机在远距离就越难扫。比如一个带参数的网址可能比短网址难扫很多。我建议正式环境里二维码内容越短越好长链接可以先转短链再生成。3. 二维码识别完整代码本地图片与摄像头实时扫描生成的代码是输出图形识别则是反向操作。很多人的需求是“我要做一个扫码功能”实际上除了直接调手机相机很多场景要在自己的程序里完成识别比如自动化检测、门禁、机器人定位。这里我给出两套最常用的识别代码。3.1 Python读本地图片使用OpenCVpyzbar如果你有一张二维码图片想直接读出内容用OpenCVpyzbar是最省事的方式。import cv2 from pyzbar.pyzbar import decode image cv2.imread(test.png) results decode(image) for result in results: data result.data.decode(utf-8) print(识别结果:, data) print(位置:, result.rect) x, y, w, h result.rect cv2.rectangle(image, (x, y), (x w, y h), (0, 255, 0), 3) cv2.imshow(result, image) cv2.waitKey(0)这里有个细节decode(image)如果直接识别不到可以先转成灰度图再试。实际使用中彩色图包含大量干扰信息灰度化能提高命中率。另外result.data是bytes必须用UTF-8解码不然中文内容会显示成b\xe6\xb5\x8b...。如果你在Windows控制台遇到乱码再加一句sys.stdout.reconfigure(encodingutf-8)或者直接写到文件里。还有一个容易忽略的点不是所有二维码都是QR CodeData Matrix、PDF417也经常在物流和门票场景出现pyzbar能识别其中一部分但如果识别不出来先确认你的二维码格式是不是QR Code再考虑换库。3.2 摄像头实时识别与多码同时扫描实时识别和静态图片的最大区别是要在每一帧里都做解码所以性能很重要。下面的代码打开摄像头在画面里找到并标记二维码import cv2 from pyzbar.pyzbar import decode cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for result in decode(gray): data result.data.decode(utf-8) x, y, w, h result.rect cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 3) cv2.putText(frame, data, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(QR Scanner, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码在一般笔记本上就能跑。如果你用的是Jetson Nano这类边缘设备帧率会比较紧张建议把输入分辨率缩小到640x480或者先检测二维码的大致区域再做局部识别。如果画面来自海康监控摄像头可以用VideoCapture(rtsp://用户:密码IP:554/Streaming/Channels/101)的RTSP地址替换第0个参数不过一定要把主码流分辨率下调否则解码延迟会让你觉得系统卡死。另外摄像头扫到的二维码如果距离太近会失焦太远又太小可以让用户调整距离也可以做一个“对焦辅助框”提高识别成功率。3.3 识别不出的原因不是代码问题是图像问题我遇到过很多用户反馈“代码没问题但就是扫不出来”最后发现全在图像质量上。影响识别的常见因素有模糊快门太长或手抖条码边界糊成一团反光手机贴了膜灯管直射模块高光盖过黑白反差过曝白块和黑块都变成灰色对比度不够遮挡中心Logo过大、图片角上有杂物盖住定位角裁切打印时把周围的quiet zone裁掉了版本过高内容很长码密度很大相机解析力拉不满格式不对把Data Matrix、PDF417当QR Code处理遇到识别失败我的排查顺序是先看原图能不能用手机扫成功如果手机也扫不出来问题90%在生成端如果手机能扫程序扫不出再检查灰度化和缩放是不是丢了细节。这条经验能省很多时间。4. 把二维码完整代码接入真实业务场景很多项目不是单纯生成或识别而是要把二维码嵌进现有系统。这里我挑了四个高频场景直接给出思路和核心代码。4.1 扫码枪和Honeywell扫码枪输入处理扫码枪其实就是键盘扫码枪本身不是一个需要安装驱动的高端设备大多数型号默认以“模拟键盘”方式工作。你把枪对准二维码它会像键盘一样把内容一个字符一个字符打出来最后带一个回车。所以在Web页面里你只需要监听回车键就能拿到一整串扫码结果。let buffer ; document.addEventListener(keypress, (e) { if (e.key Enter) { if (buffer) { handleScan(buffer); buffer ; } } else { buffer e.key; } });Honeywell的很多型号比如需要改扫描后缀或启用USB虚拟串口时都是通过扫描手册里的配置二维码来设置的不需要写代码。你只要按照说明书扫对应的“增加回车”“切换USB模式”等配置码。注意在网页里获取扫码结果时不要让input输入框失焦否则键盘事件可能被浏览器拦截。更稳妥的做法是把扫码结果先写进一个隐藏输入框里再统一读取。4.2 ThinkPHP6 think-queue异步生成二维码的完整步骤与队列查看有些业务需要批量生成二维码比如每天给几千个用户生成入场券。如果同步生成接口响应会非常慢所以用队列是常见解法。以ThinkPHP6为例流程是这样第一步安装队列扩展composer require topthink/think-queue然后配置config/queue.php里的驱动和默认队列名我一般用Redis驱动。第二步创建一个任务类?php namespace app\job; use think\queue\Job; use think\facade\Log; class QrJob { public function fire(Job $job, $data) { // $data 里放uid、文本内容、保存路径等 $text $data[text] ?? ; $path runtime_path() . qr_ . md5($text) . .png; // 调用你自己的二维码生成方法 makeQrCode($text, $path); Log::write(二维码生成完成: . $path); if ($job-attempts() 3) { $job-delete(); } else { $job-delete(); // 成功后删除任务 } } }第三步在控制器里推入队列use think\facade\Queue; Queue::push(\app\job\QrJob::class, [text https://example.com, uid 123]);第四步启动消费进程php think queue:work建议用supervisor守护。查看队列情况时如果用的是Redis驱动可以用redis-cli LLEN queues:default查看待执行任务数用MySQL驱动则直接查建好的think_jobs表。不要指望queue:work的日志能实时反映积压数量那只是消费日志。队列跑起来以后批量生成二维码的耗时基本跟业务解耦接口响应能回到毫秒级这也是我推荐队列方案的根本原因。4.3 小程序扫普通链接二维码无法打开问题出在二维码内容不是二维码本身经常有人在群里问“我生成的二维码是普通http链接为什么微信扫出来不是打开小程序” 这是因为微信扫一扫有明确的路由规则普通链接默认在微信内置浏览器打开网页不会直接拉起小程序。要让微信扫码直接进入小程序需要生成“小程序码”或使用微信的URL Scheme/URL Link。所以不是二维码代码有问题而是内容选错了。如果你只是希望扫出来展示一段文字微信扫一扫会直接提示结果但如果那段文字是网址它就会跳转页面。做业务前先想清楚扫码后的行为再去选二维码的类型。这个小知识点能避免你浪费一整天跟渠道文档较劲。4.4 Unity、Jetson和Cardboard校准二维码可以干很多“奇怪”的活二维码不只是网址入口在很多硬件项目里它是标定和识别的锚点。Unity里用ZXing.Net识别摄像头的代码核心就是从一个WebCamTexture的像素数组里生成RGBLuminanceSource然后交给BarcodeReader解码。Lifecycle里每次取帧都做识别成功后回调业务逻辑。Jetson上则直接用OpenCV读取摄像头帧调用cv2.QRCodeDetector().detectAndDecode这个方法不用装额外库但遇到畸变大的画面有点吃力所以我通常配合pyzbar双重验证。另外Google Cardboard的纸盒校准也是二维码的典型应用它把一个包含畸变参数和镜头参数的文本编码成二维码手机应用扫描后就能适配对应镜片。这种场景下生成代码的时候别用中文也别在二维码里塞换行符因为设备端的解析库不一定支持多字节编码。如果你正在给这类硬件项目生成二维码先查清楚设备SDK到底约定了几种字段、字段顺序是什么再去拼内容比写一堆通用代码更有价值。5. 常见问题与排查技巧实录二维码开发最后拼的不是写码速度而是排查能力。我把这些年遇过的问题整理成了速查表和几条独家心得希望对你有帮助。5.1 一张速查表解决90%的识别失败现象可能原因解决方法微信扫普通二维码打不开小程序二维码内容不是小程序URL Scheme改用小程序码或URL Link打印后手机扫不了打印对比度低 / quiet zone被裁提高黑白对比留至少4模块白边识别程序找不到二维码图片太亮太暗 / 反光灰度化、直方图均衡化后再decode摄像头实时识别卡顿每帧全图解码消耗大缩小分辨率或先做区域检测中心Logo导致扫不出容错等级太低使用H级容错Logo小于边长的1/4中文内容乱码bytes没有按UTF-8解码统一用decode(utf-8)生成端也指定UTF-8接口返回的二维码图片缓存不更新HTTP缓存设置Cache-Control: no-store或在URL后加时间戳这张表基本覆盖了绝大多数“代码正常但用不起来”的情况。出现问题时先不要急着改代码按表里对应现象找原因通常都能一击命中。5.2 付款码和收款码的安全提醒二维码内容不只是网址关于付款二维码很多人会好奇“里面到底有没有我的余额和姓名” 简单说付款码被扫支付时展示的那个码通常是一个动态令牌里面会关联商户号、订单信息和一个有时效的支付凭证不等于你的账号密码也不直接包含余额。但它一旦被截图发给别人对方可能借助这个动态凭证发起扣款所以付款码截图和付款码数字千万不要外传。开发支付功能时也要遵守平台规则不要把付款码内容永久保存到日志。这块属于安全和合规底线别为了调试方便去打印用户的付款码。5.3 几个我踩过的坑和经验第一个坑生成中文二维码后用某些老型号扫码枪扫出来是乱码。后来发现是生成库默认编码问题。你需要在add_data之前把字符串转成UTF-8或者选用支持encodingutf-8的库。如果终端设备特别老就只在二维码里放ASCII字符比如把中文参数做短链接映射。第二个坑识别本地图片时我一开始直接对原图解码半米外拍的二维码怎么都识别不出来。后来添加了cv2.resize放大2到3倍识别率立刻上来了。原因是pyzbar对过小区域的采样不友好放大之后模块之间的边界更清晰。第三个坑扫码枪在网页里输入时如果不先判断焦点扫进来的字符可能被输入框的校验逻辑打断。我后来都是把扫码枪输出到一个独立缓冲区等完整回车后再统一处理这样无论焦点在哪个input上都不会出错。最后再分享一个做法不要把生成和识别代码写死成一种库。我在项目里通常会写一个qr_service.py里面封装生成、识别、校验三个函数底层实现可以随时替换。这样换来换去上层业务一行都不用改遇到问题排查起来也清爽很多。
返回列表