[一个抽象开源] 全网最快的摄像头扫动态二维码传数据项目

live demo: https://qr.linkto.host/

github 链接: GitHub - infrost/RaptorQR: Ultra-fast offline file and text transfer over animated QR codes with WASM fast_qr, ZXing and RaptorQ. · GitHub

这个项目大概是前段时间突发奇想,想看看“离线摄像头拍屏幕识别数据”这条赛道的上限大概在哪。

如果你不想看我叨叨的话,结论就是:工程实现上已经能稳定100-200多KB/s,上限应该在几百KB/s。,iPhone16上扫码传6.5MB文件用时44s,目前我应该是做到了这个赛道的全网最快。

一开始是想像 sz3/libcimbar / Cimbar 那种自定义彩色码或者Aztec Code之类的编码,后来发现虽然彩色码信息高,但是识别逻辑远没有二维码成熟,ZXing还有wasm的识别方案,能做到我手机上测得平均每秒钟识别100+个V30二维码(V20甚至能测到了每秒钟300个码)

二维码传文件编码上面难点就是漏扫二维码/不按顺序扫/怎么办。不过好消息是fountain code并不是个特别冷门的领域,而且很多卫星向地球广播的方案都要用类似编码。为了速度和正确性我就没有重复造轮子,拿了raptorQ标准(RFC6330)的rust实现,然后我把它编译成了wasm。

最后的瓶颈是二维码生成,大部分现有的纯二维码传文件项目速度其实都卡在这里。我也是同样的操作把rust实现的fastqr编译成了wasm,然后js(typescript)直接内存直读fastqr的生成区的原始二维码矩阵,后台走web worker多线程并行生成,然后用rAF丢到canvas里面去画二维码再插值放大。这个三板斧下来,生成可以在我浏览器上8个二维码@60fps也就是每秒钟480个二维码都没有压力。
(zxing writer其实也能生成二维码,但是测下来zxing的二维码生成能力甚至不如纯js的二维码生成逻辑,可能是因为来回拷贝数据和gc的问题,所以我编译wasm的时候直接让它划出一块区间永远在上面写数据,就不用gc了)

直觉上根据传奇祖师爷香农的guide,要想完美还原信号,采样率至少要是原始信号频率的两倍。手机摄像头api能暴露出来的最多就60fps,那比较不浪费资源的生成速率实际上只有30fps了。虽然我这个直觉毫无道理吧,但我在测试上真就发现,对于一个60fps的摄像头来说,30fps确实是个甜点区间,再高的话二维码识别速率其实反而会降低,我不知道是因为物理屏幕刷新率和二维码生成帧之间的某种问题,还是LCD像素切换的残影问题(我是165HZ的屏幕,可能240HZ的会比较好),还是实际上手机采样率根本到不了60fps又或者是快门速度问题,总之就是效果没那么好,但具体为什么还需要后续去验证。

我屏幕是16寸的,测试下来大概是4个二维码平行扫描,大小是V30,30fps能达到最佳的效率。

这个项目用了monorepo,所以想要底层的wasm包来研究,可以直接npm/pnpm install我的包;或者玩玩就用我的live demo或者自己vercel跑跑玩玩。

just for fun

这种项目应该提供一个离线使用的单网页版比较好,使用更简单

After the first load, you can open the same link again even without an internet connection.

问题是很多需要用到的场景就是无网的,只允许文件进出的,压根不能完成第一步…

至于cli在终端输出二维码,这…完全看终端能力吧,cmd上真的不会有问题吗

一个很有意思的概念验证项目,而且逻辑上做的挺好的
想问的是

  • 如果二维码被遮挡,或者本身有破损,会有多大程度的效率下降。这个情况会在有人经过摄像头,或者有个虫子飞过摄像头的时候发生
  • 如果二维码有几何变形(就类似于手机不是平行于二维码平面,而是有倾角,导致视觉上二维码的一边比另一边更大),又会多大程度上影响效率。这个问题会在摄像头架设的时候发生
  • 如果二维码的对比度不够,不是严格的纯黑纯白,而是白底灰色二维码,或者白底浅灰二维码,又会再多大程度上影响扫描。这个会在老式显示器,或者低质量显示器上发生

已经考虑到了这个问题,这个项目本身已经是“离线使用”的单网页版了。

  1. 这个项目是有service worker的,已经是完整的PWA了。也就是说你第一次打开后,即使是断网,你再访问那个链接,甚至是刷新那个网页,也是能打开的并正常用的(只要你不手动把浏览器缓存清掉,你可以试试。

  2. build之后的纯静态html可以在apps\web\dist里面找到(我仓库没给,如果有需要我可以给一份静态的在release里面),只需要在那个目录下起一个http server就可以了比如python -m http.server

  3. cli输出二维码没有问题,因为cli的能力借助的是node.js的运行时画的,也就是你先要装node.js,无关终端能力,cmd、powershell、zsh都可以

一张高分辨率的照片都要大不少,

如果是视频那就更大了

好问题。
关于被遮挡问题:会下降,下降程度取决于你丢了多少帧以及你在raptorq的参数里设置了多少的repair rate(默认10%,可以调)。

实际上的话,扫码miss是常态发生,在10% repair的情况下,按你说的只是虫子飞过,影响微乎其微,有人经过的情况,基本上也丢失不会很多。10% repair你可以粗略理解为,如果丢了一帧,接下来可以用正常的10帧的冗余可以把这丢掉的一帧通过数学算法恢复出来。如果中间丢掉了很长的一段帧,那就会恢复的比较慢,丢掉太多的话可能需要等待二维码loop回原始帧。

给你个参考,其他的基于fountain code的信息通信项目(像我提到的卫星往地球广播消息),他们大多数设置的repair rate是5%。我个人觉得二维码良好条件下10%,恶劣环境可以试20%-50%。

关于变形问题,这个其实取决于底层zxing库的能力而不是我项目的能力,我可以说的是zxing对这方面的优化还是很不错的,然后可以通过高级设置里面勾选try harder、try rotate(try rotate我记得好像是可以提供倾角大于45度的尝试,小于45度默认就能处理的了)等参数尝试解决,可能会增加CPU占用,在好的设备上表现不会很明显,但是差的设备我确实不知道。我也提供了几个Decode preset(fast到robust,robust为最大解码尝试)可以供实验。

关于对比度问题,同上,这个我可以确定的是现有的解码算法已经非常成熟了,基本上不影响。而且默认的二值化参数是LocalAverage,已经是最稳健的了,大多数情况都能正确处理复杂的光照条件。

但是对于频闪严重的那种老式逐行扫描的显示器来说,影响扫描的不是对比度,而是摄像头有可能在高快门速度下拍出来的二维码是条状的而不是正常二维码,这种情况基本没什么解决办法,因为是网页应用,没办法去访问设备相机硬件调参数。

此外,二维码标准里其实就带了ECC纠错,项目也支持。默认是最低一档,可以调高增加识别率(代价是单张二维码承载信息减少)

第一次打开后

我解释了,完全不连接网络的机器就没法做到打开

能提供html就行

关于终端cli,看来是与win7无缘了…别问为什么还是win7

另外,理论速度是比libcimbar快吗?