推荐使用 zxing.net 读取摄像头帧,需将原始帧转为 bitmap 或灰度图再解码,控制帧率(5–10fps)、避免 ui 线程阻塞、显式设置 characterset="utf-8" 支持中文,注意 wpf 像素格式与线程冻结问题。

用 ZXing.Net 读取摄像头帧是最直接的路
不推荐自己写解码逻辑,ZXing.Net 是 C# 生态里最成熟、维护活跃的开源库,支持 QR Code、Code128、EAN-13 等主流码制,且能直接喂入 Bitmap 或 byte[] 帧数据。它不依赖 UI 框架,WinForms、WPF、甚至 .NET 6+ 控制台程序都能用。
常见错误是直接拿 WebCamTexture(Unity)或 MediaCapture(UWP)的原始数据往 ZXing.BarcodeReader.Decode 里塞——类型不匹配,会返回 null 或抛 ArgumentException。必须先转成 Bitmap 或符合 ZXing 要求的灰度 Bitmap。
实操建议:
- 用
Emgu.CV或System.Drawing.Common(注意:.NET Core 3.0+ 需装 NuGet 包并启用System.Drawing支持)做帧转换 - 优先使用
BarcodeReader.Decode(<code>Bitmap),别碰DecodeMultiple——它开销大,且对单码场景无优势 - 设置
Options.TryHarder = true只在低质量画面(模糊、倾斜、反光)下启用,否则识别延迟明显上升
VideoCapture 拿帧时容易卡死或丢帧
Windows 平台用 OpenCvSharp.VideoCapture 或 Emgu.CV.Capture 开摄像头,最常踩的坑是没控制采集频率。默认全速拉帧,CPU 占用飙升,ZXing 解码跟不上,结果就是界面卡、识别滞后、甚至 AccessViolationException。
关键不是“怎么开摄像头”,而是“怎么节制地喂帧”。真实场景中,每秒处理 5–10 帧足够应对大多数二维码扫描需求(人手抖动、对焦时间已占主导)。
实操建议:
- 用
Stopwatch控制最小帧间隔,例如:if (sw.ElapsedMilliseconds > 200) { ProcessFrame(); sw.Restart(); } - 避免在 UI 线程(如 WinForms 的
Timer.Tick)里直接调Capture.Read()+Decode()——解码可能耗时 50ms+,UI 会卡顿;改用Task.Run拆出解码逻辑 - 释放资源时务必调
Capture.Dispose(),否则下次打开可能报Unable to load DLL 'opencv_world455.dll'类似错误(实际是句柄残留)
中文内容识别失败?多半是编码没设对
二维码里含中文时,ZXing.Net 默认按 ISO-8859-1 解,结果就是乱码或 null。这不是库的问题,是 QR Code 标准允许指定字符集,而生成端(比如微信、草料)通常用 UTF-8,但没显式标记。
不能靠“猜”,得强制告诉解码器用什么编码。但注意:Options.CharacterSet 只影响文本输出,不影响解码过程本身;真正起作用的是 Options.PureBarcode = false(默认值)配合正确的字符集。
实操建议:
- 初始化
BarcodeReader时显式设:new BarcodeReader { Options = { CharacterSet = "UTF-8" } } - 如果仍乱码,检查二维码是否真含 UTF-8 数据——用手机扫码工具对比验证,排除生成端问题
- 条形码(如 Code128)不含字符集概念,
CharacterSet对它无效;中文条码基本不存在,遇到大概率是误扫了 QR Code
WPF 里显示预览画面却扫不到码?留心线程与像素格式
WPF 的 WriteableBitmap 和 BitmapSource 是冻结对象,跨线程访问会抛 InvalidOperationException: “The calling thread cannot access this object because a different thread owns it.”。很多人把摄像头帧直接赋给 Image.Source,再在另一个线程解码,结果解码永远返回 null——因为帧数据被冻结后无法被 ZXing 读取像素。
更隐蔽的是像素格式:WPF 默认用 Pbgra32,而 ZXing 要求 Format32bppRgb 或灰度。不转换就传进去,解码器内部会静默失败,不报错也不返回结果。
实操建议:
- 用
BitmapSource.CopyPixels()提取byte[],再用new Bitmap(width, height, PixelFormat.Format32bppRgb)构建新位图传给ZXing - 或者绕过 WPF 图像对象,直接用
Emgu.CV.Image<bgr byte></bgr>处理帧,它原生兼容 OpenCV 流程,省去格式转换烦恼 - 别在
Dispatcher.Invoke里做解码——那是为更新 UI 准备的,不是为跑 CPU 密集任务
最难调的永远不是“能不能扫”,而是“为什么看起来在扫,却总没结果”:帧没转对格式、线程锁死了图像内存、字符集设错、或者压根没等对焦完成就急着解码。这些点串起来,才是真实项目里花半天才能定位的问题。











