场景: 最近在做一个功能,通过识别发票照片获取发票数据用以匹配开票数据存档。 在搜索了各种库踩了一堆坑后,发现微信开源过他们的二维码扫码引擎c++版本,一阵折腾后编译出js版本,整理一下分享出来。本文介绍的方法主要的特点是:
- 纯前端处理,无须调用后端OCR API等接口;
- 可处理标准正方形的二维码,最重要的是可以处理照片上的二维码;二维码变形、旋转都可以识别;
- 基于opencv 编译后的webassembly版本,编译添加了微信开源的wechat_qrcode,有极高的识别率。
在线测试(使用发票照片测试):https://leidenglai.github.io/opencv-js-qrcode/
demo源码: leidenglai/opencv-js-qrcode · GitHub
本文中采用的是自定义编译的opencv库,添加了三方组件wechat_qrcode微信二维码引擎,并且去掉不需要的库。 开始找了很多”强大的二维码识别库“,结果发现基本都不识别照片,或者失败率低,只能识别标准的qrcode。后来发现c++的opencv并且可编译为wsam版本。由于没有任何c++基础,在将wechat_qrcode编译进opencv时异常痛苦,好在后来找到一篇blog:编译微信二维码引擎到webAssembly实践 | 虚幻 ,感谢qwertyyb。
在编译好OpenCV后,导出opencv.js,文件比较大进过优化大约有5 M,所以使用时需要异步加载。opencv加载后就能通过window.cv调用到opencv的相关方法;此时并不意味着我们能正常运行使用了,因为我们还需要加载二维码扫码引擎的模型文件:
async function loadModels() {
const detect_proto = "detect.prototxt";
const detect_weight = "detect.caffemodel";
const sr_proto = "sr.prototxt";
const sr_weight = "sr.caffemodel";
if (qrcode_detector != undefined) {
updateStatus("Model Existed");
} else {
const dp = await utils.fetchModelsData(detect_proto);
const dw = await utils.fetchModelsData(detect_weight);
const sp = await utils.fetchModelsData(sr_proto);
const sw = await utils.fetchModelsData(sr_weight);
cv.FS_createDataFile("/", "detect.prototxt", dp, true, false, false);
cv.FS_createDataFile("/", "detect.caffemodel", dw, true, false, false);
cv.FS_createDataFile("/", "sr.prototxt", sp, true, false, false);
cv.FS_createDataFile("/", "sr.caffemodel", sw, true, false, false);
qrcode_detector = new cv.wechat_qrcode_WeChatQRCode(
"detect.prototxt",
"detect.caffemodel",
"sr.prototxt",
"sr.caffemodel"
);
updateStatus("OpenCV Model Created");
}
}
这些文件为wechat_qrcode引擎的 CNN models,Detector model 和 Super scale model。将模型文件加载成功后转换为 Uint8Array
加载到引擎中,然后调用new cv.wechat_qrcode_WeChatQRCode( “detect.prototxt”, “detect.caffemodel”, “sr.prototxt”, “sr.caffemodel” )
实例化返回引擎实例。
官方model文件 GitHub - WeChatCV/opencv_3rdparty: OpenCV - 3rdparty
作为对比,demo中添加了git上star比较多的jsqr库一同对照测试。
因为demo特定需求,发票二维码基本在左上角,所以在canvas加载图片时只截取左上角图片,提高引擎识别效率。
OpenCV识别结果:
将二维码信息和二维码区域都正确的处理,OpenCV耗时: 224.42822265625 ms。
而jsqr是处理不了这样的图片的。
识别旋转的二维码
照片被旋转了90度且不太清晰,同样也被正确的处理,OpenCV耗时: 169.523193359375 ms。
jsqr也不能处理这样的图片。
电子二维码识别:
电子版的发票图片都正确的识别了;OpenCV耗时: 165.333984375 ms,QRjs耗时: 44.613037109375 ms。不过QRjs的效率相对会高一些。
暨WebAssembly兼容性,基本上现代浏览器都是支持的:
OpenCV因为需要加入wechat_qrcode组件,所以必须使用自定义编译。需要在本地搭建c++的编译环境,是前端基本不曾涉足的领域,相对较为繁琐,也会有一些困难,但这也让前端具备了更强大的本领,由于WebAssembly的存在,前端也有了更多的可能。
总之,由于前端越强大,我们能做的事也越多,挑战也会越大,大家共勉。
编译微信二维码引擎到webAssembly实践 | 虚幻 OpenCV: https://github.com/opencv/opencv OpenCV’s contrib: https://github.com/opencv/opencv_contrib