资讯动态

ZXing.Net 0.16.8.0 在 Unity 中的扫码实战:跨平台解码与性能调优

发布时间:2026/10/18 1:59:43 来源:尧图企业网站定制
简介ZXing.Net.0.16.8.0 是面向 C# 与 Unity 开发者的开源条码处理库基于 Zebra Crossing 项目可在 .NET 框架与 Unity 引擎中实现 QR 码、Aztec、Code 128、EAN 等多种条码的读取与生成。开发者只需调用少量 API 即可为桌面应用、移动端或游戏加入扫码功能例如让玩家扫描屏幕条码获取游戏内物品信息。压缩包共 132 个文件约 18.27MB以 40 个 dll 动态库、40 个 pdb 调试符号、39 个 xml 文档注释为主另含 pri、winmd、cmd 脚本与 COPYING 许可文件覆盖 net4.0、net4.6、net5.0、wp8.0、sl4/sl5、ce2.0 等多套框架版本便于按目标平台挑选依赖。目前已有 923 人学习下载。借助其中的调试符号与 XML 注释读者可快速定位接口用法、排查兼容性问题并参考 register.cmd 完成程序集注册在 Unity 多平台发布时更从容地适配 .NET 后端。1. 从一次扫码翻车说起ZXing.Net 0.16.8.0 到底能干什么去年帮一个做仓储管理系统的朋友排查问题他们的 Unity 客户端在 PC 上扫码识别率挺稳打包到 Android 平板后同一张二维码十次里有三次读不出来剩下七次还要等两三秒。日志里没有任何异常就是Decode返回 null。这种玄学问题最折磨人——代码没改、图片没换换个平台就翻车。后来定位到是图像预处理和TryHarder参数没配对而他们用的正是 ZXing.Net 这个库。ZXing.Net 是 Java 版 ZXingZebra Crossing的 .NET 移植0.16.8.0 是它在 .NET 生态里一个相当成熟的版本。它干的事很纯粹把摄像头帧、位图、内存里的像素数据喂进去吐出二维码/条形码里的字符串。支持 QR Code、Data Matrix、PDF417、Aztec、Code 128、EAN-13 等一长串格式解码和编码都能做。对 Unity C# 的开发者来说它的价值在于纯托管实现不依赖原生插件能直接编进 Unity 的脚本程序集里跑省掉了 Android/iOS 各自接一套原生 SDK 的麻烦。这份资源适合三类人一是做 Unity 移动端扫码功能、被跨平台兼容性折腾过的二是需要在 .NET 服务端批量解析条码图片的三是想在自己的 C# 项目里嵌一个轻量识别模块、又不想引入 OpenCV 这种重依赖的。接下来我会把它的接入方式、参数调优、以及我踩过的几个坑拆开讲让你拿到手就能跑而不是对着 API 文档猜。2. 把 ZXing.Net 接进 Unity 工程程序集配置与最小可跑示例2.1 为什么 Unity 里接它容易出问题Unity 的程序集加载机制和普通 .NET 控制台程序不一样。ZXing.Net 0.16.8.0 的 NuGet 包里带了针对不同目标框架的 DLL比如netstandard2.0、net45、netcoreapp3.1等。Unity 2019 之后的版本对netstandard2.0支持最好所以选 DLL 时优先拿这个目录下的。如果你直接把net45的塞进去编辑器里可能不报错但打包到 IL2CPP 后就会出现类型加载失败表现是运行时抛TypeLoadException或者干脆扫码功能静默失效。另一个高频翻车点是System.Drawing。ZXing.Net 在桌面端常用Bitmap做输入输出但 Unity 里没有System.Drawing的完整实现你如果照着网上桌面端的示例抄编译就过不去。Unity 里正确的做法是用Color32[]或Texture2D转成 ZXing 的LuminanceSource。2.2 导入 DLL 与设置程序集第一步从包里找到netstandard2.0目录下的zxing.dll有些包会叫ZXing.Net.dll以实际文件名为准。在 Unity 工程的Assets下建一个Plugins文件夹把 DLL 拖进去。选中这个 DLL在 Inspector 里确认Any Platform勾上Validate References保持默认如果工程开了 Assembly Definition需要在你自己的 asmdef 里引用这个 DLL或者把它放进Plugins让它自动全局可见第二步检查 Player Settings 里的Api Compatibility Level设为.NET Standard 2.0或.NET 4.x。设成.NET Standard 2.0更稳因为 ZXing.Net 的 netstandard 版本就是按这个编译的。提示如果导入后 Console 报Multiple precompiled assemblies with the same name说明你重复导入了不同框架版本的 DLL删掉多余的只留 netstandard2.0 那份。2.3 一个能直接跑的解码脚本下面这段代码是我在 Unity 里验证过的最小解码流程输入是Texture2D输出是识别到的字符串。你可以挂到任意 GameObject 上在 Inspector 里拖一张二维码贴图进去测试。using UnityEngine; using ZXing; using ZXing.Common; public class QRDecoder : MonoBehaviour { public Texture2D sourceTexture; // Inspector 里拖入待识别的图片 void Start() { if (sourceTexture null) { Debug.LogError(没有指定图片); return; } // 1. 把 Texture2D 的像素转成 Color32 数组 Color32[] pixels sourceTexture.GetPixels32(); // 2. 用 RGBLuminanceSource 包装ZXing 只关心亮度信息 var luminanceSource new RGBLuminanceSource( ConvertToRGBBytes(pixels), sourceTexture.width, sourceTexture.height, RGBLuminanceSource.BitmapFormat.RGB24 ); // 3. 构造 BinaryBitmapHybridBinarizer 对光照不均的图片更友好 var bitmap new BinaryBitmap(new HybridBinarizer(luminanceSource)); // 4. 配置解码参数 var hints new DecodingOptions { TryHarder true, // 开启深度扫描提升识别率但更耗时 PossibleFormats new[] { BarcodeFormat.QR_CODE } // 限定格式减少误判 }; var reader new BarcodeReaderGeneric { Options hints }; // 5. 执行解码 Result result reader.Decode(bitmap); if (result ! null) { Debug.Log($识别成功: {result.Text}格式: {result.BarcodeFormat}); } else { Debug.LogWarning(未能识别出条码); } } // 把 Color32 数组转成 RGB24 字节数组 byte[] ConvertToRGBBytes(Color32[] pixels) { byte[] bytes new byte[pixels.Length * 3]; for (int i 0; i pixels.Length; i) { bytes[i * 3] pixels[i].r; bytes[i * 3 1] pixels[i].g; bytes[i * 3 2] pixels[i].b; } return bytes; } }逻辑说明RGBLuminanceSource是 ZXing 里把彩色像素转成灰度矩阵的入口它只取 RGB 通道算亮度Alpha 通道会被忽略所以透明背景的 PNG 要先合成到白底上再传进来否则透明区域会被当成黑色直接破坏二维码的定位图案。HybridBinarizer是二值化器负责把灰度图转成黑白点阵它比GlobalHistogramBinarizer更能扛局部阴影代价是计算量略大。TryHarder打开后解码器会尝试更多旋转角度和采样方式识别率上去了但单帧耗时可能翻倍实时扫码场景要权衡。参数说明PossibleFormats强烈建议显式指定。不指定的话解码器会遍历所有支持的格式一张模糊图片可能被误判成某种一维码返回一个乱七八糟的字符串。限定成QR_CODE后误识别率明显下降。TryHarder在静态图片识别里可以无脑开在摄像头逐帧解码的场景里建议先关掉测帧率不够用再开。3. 摄像头实时扫码帧数据流转与性能参数怎么调3.1 从 WebCamTexture 到解码器的数据链路Unity 里做实时扫码标准链路是WebCamTexture抓帧 → 取像素 → 转LuminanceSource→ 解码。这里有个容易忽略的点WebCamTexture.GetPixels32()返回的像素数组原点在左下角而 ZXing 默认按左上角原点处理。对于二维码这种有方向性的图案原点反了会导致解码失败或者只能识别正着放的码。解决办法是在转换时做一次垂直翻转或者用Texture2D中转时设置正确的GraphicsFormat。另一个性能瓶颈是每帧都做全图解码。一张 1920×1080 的帧GetPixels32就要拷贝 200 多万个像素再走一遍二值化和解码中低端手机上单帧轻松超过 100ms帧率直接掉到个位数。我一般会做两件事一是把摄像头请求分辨率降到 640×480二维码识别不需要高清二是限制解码频率比如每 3 帧解一次中间帧直接跳过。3.2 解码频率与分辨率的取舍下面是一个带节流和区域裁剪的实时解码片段核心思路是降低单帧数据量、控制解码调用频率。using UnityEngine; using ZXing; using ZXing.Common; public class RealtimeScanner : MonoBehaviour { public int targetWidth 640; public int targetHeight 480; public int decodeInterval 3; // 每 3 帧解码一次 private WebCamTexture camTexture; private BarcodeReaderGeneric reader; private int frameCounter 0; void Start() { // 请求较低分辨率减少像素拷贝开销 camTexture new WebCamTexture(targetWidth, targetHeight, 30); camTexture.Play(); reader new BarcodeReaderGeneric { Options new DecodingOptions { TryHarder false, // 实时场景先关帧率优先 PossibleFormats new[] { BarcodeFormat.QR_CODE } } }; } void Update() { if (camTexture null || !camTexture.isPlaying) return; frameCounter; if (frameCounter % decodeInterval ! 0) return; // 节流 Color32[] pixels camTexture.GetPixels32(); int w camTexture.width; int h camTexture.height; // 垂直翻转修正原点方向 Color32[] flipped new Color32[pixels.Length]; for (int y 0; y h; y) { int srcRow y * w; int dstRow (h - 1 - y) * w; for (int x 0; x w; x) { flipped[dstRow x] pixels[srcRow x]; } } byte[] rgb new byte[flipped.Length * 3]; for (int i 0; i flipped.Length; i) { rgb[i * 3] flipped[i].r; rgb[i * 3 1] flipped[i].g; rgb[i * 3 2] flipped[i].b; } var source new RGBLuminanceSource(rgb, w, h, RGBLuminanceSource.BitmapFormat.RGB24); var bitmap new BinaryBitmap(new HybridBinarizer(source)); Result result reader.Decode(bitmap); if (result ! null) { Debug.Log($扫到: {result.Text}); // 实际项目里这里可以暂停摄像头、播放提示音、跳转逻辑 } } void OnDestroy() { if (camTexture ! null) camTexture.Stop(); } }逻辑说明decodeInterval控制解码频率设成 3 表示每 3 帧解一次30fps 下相当于每秒解码 10 次对扫码来说完全够用但 CPU 占用能降一大截。垂直翻转那段是血泪经验——不翻的话二维码在画面里正着放反而识别不了斜着放偶尔能中现象极其迷惑。TryHarder在实时场景里关掉因为每帧都深度扫描会让帧率崩掉等识别失败几次后再临时开启更合理。参数说明targetWidth和targetHeight不是越大越好。640×480 对二维码识别足够再高只是徒增拷贝开销。WebCamTexture的构造参数里第三个是帧率设 30 即可设太高有些设备会直接回退到默认值。如果你的场景里二维码只出现在画面中央区域可以把GetPixels32换成只取中央一块区域数据量能再降一半以上。3.3 识别率上不去的几个调节旋钮除了TryHarderZXing.Net 还有几个影响识别率的参数值得关注。AutoRotate打开后解码器会自动尝试 90 度、180 度、270 度旋转对横竖屏切换的场景有用但会增加耗时。TryInverted用于识别反色二维码白码黑底默认关闭如果你的码是反色的要手动打开。CharacterSet在二维码内容包含中文时要设成UTF-8否则可能返回乱码——这个坑我在一个物流面单项目里踩过二维码里存的是 UTF-8 中文解码出来全是问号查了半天才发现是字符集没指定。注意AutoRotate和TryHarder同时开启时单帧解码时间可能是默认配置的 5 到 8 倍。移动端实时扫码建议只开其中一个静态图片识别可以全开。4. 避坑排查ZXing.Net 在 Unity 里最容易翻车的五件事4.1 现象编辑器里正常打包后扫码无反应原因DLL 的框架版本和 Unity 的 Api Compatibility Level 不匹配最常见的是用了net45版本编辑器走 Mono 能加载IL2CPP 打包后类型被裁剪或加载失败。解决只保留netstandard2.0目录下的 DLLPlayer Settings 里 Api Compatibility Level 设为.NET Standard 2.0。如果还不行在link.xml里加上assembly fullnamezxing preserveall/防止 IL2CPP 的代码裁剪把解码器用到的反射路径裁掉。4.2 现象识别出来的中文是乱码原因二维码内容用 UTF-8 编码但解码时没有指定字符集ZXing 默认按 ISO-8859-1 解析。解决在DecodingOptions里设置CharacterSet UTF-8。如果内容里混合了中英文和符号UTF-8 是唯一稳妥的选择。另外编码端也要确认生成二维码时用的是 UTF-8两端不一致照样乱。4.3 现象同一张图 PC 上能识别手机上识别不了原因手机摄像头拍出来的图片往往有更大的透视畸变和光照不均GlobalHistogramBinarizer在这种图上表现很差。解决把二值化器换成HybridBinarizer它做的是局部自适应阈值对阴影和反光更鲁棒。如果还不行在解码前先对图像做一次直方图均衡化或者手动裁掉二维码周围的无关区域减少干扰。4.4 现象实时扫码帧率骤降画面卡顿原因每帧都在做全分辨率像素拷贝 二值化 解码CPU 被吃满。解决三步走——降摄像头请求分辨率到 640×480加解码节流每 2 到 3 帧解一次关掉TryHarder和AutoRotate。如果还卡把GetPixels32换成只取画面中央区域数据量能再降一个量级。4.5 现象透明背景的二维码图片识别失败原因RGBLuminanceSource忽略 Alpha 通道透明区域被当成黑色二维码的白色模块变成黑块定位图案直接废掉。解决解码前先把图片合成到白色背景上。Unity 里可以用RenderTexture做一次 Blit或者手动遍历像素把 Alpha 小于阈值的像素强制设成白色。这个坑在从设计稿导出的二维码图片上特别常见。5. 进阶批量解码与编码回写的一个实用套路批量处理条码图片时逐个 newBarcodeReaderGeneric是浪费——解码器内部会缓存一些格式解析器复用同一个实例能省下重复初始化的开销。我一般会维护一个静态的 reader 实例配合Parallel.For做多线程解码。但要注意BarcodeReaderGeneric的Decode方法本身不是线程安全的多线程下要么加锁要么每个线程一个实例。实测下来每个线程一个实例、线程数控制在 CPU 核心数以内吞吐量比单线程串行高 3 到 4 倍。编码方向ZXing.Net 的BarcodeWriterGeneric能把字符串写成二维码像素矩阵。Unity 里拿到矩阵后可以逐像素填进Texture2D再Apply()一下就能显示。这里有个细节BarcodeWriterGeneric的Options里Width和Height设的是模块数还是像素数取决于你用的渲染器。用PixelDataRenderer时设的是像素尺寸用BitmapRenderer时行为又不一样。我一般直接用PixelDataRenderer拿到Color32[]后自己建Texture2D可控性最强。using ZXing; using ZXing.Common; using ZXing.QrCode; using UnityEngine; public static class QRCodeGenerator { public static Texture2D Encode(string content, int size 256) { var writer new BarcodeWriterGeneric { Format BarcodeFormat.QR_CODE, Options new QrCodeEncodingOptions { Width size, Height size, CharacterSet UTF-8, Margin 1 // 静默区太小会影响识别 }, Renderer new PixelDataRenderer() }; Color32[] pixels writer.Write(content); Texture2D tex new Texture2D(size, size, TextureFormat.RGBA32, false); tex.SetPixels32(pixels); tex.Apply(); return tex; } }逻辑说明Margin是二维码四周的静默区宽度单位是模块数。设成 0 或者太小扫码器可能因为找不到边界而失败一般设 1 到 4 之间。CharacterSet同样要设 UTF-8否则中文内容编码进去就是乱码。PixelDataRenderer返回的Color32[]原点在左上角和Texture2D.SetPixels32的期望一致不需要翻转。验证编码结果是否正确的习惯生成后不要只看图一定用解码器再解一遍确认拿到的字符串和原始内容完全一致。我见过因为Margin设成 0 导致生成的码自己都解不出来的情况肉眼看着没问题机器读就是失败。从那以后我每次生成二维码都会强制走一遍「编码 → 解码 → 比对」的闭环多花几毫秒省掉后面排查的几小时。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑