Dithered QR Codes

Dithered QR Codes / 抖动二维码

Making your own dithered QR codes 制作你自己的抖动二维码

QR codes: the basics A QR code is really just a way of encoding a few bytes of data in a way that can be easily read from a photo by a smartphone or similar device. They’re designed to be very robust to bad focus, bad printing, funny angles, and missing areas. Exactly how they work isn’t terribly important, but for the purposes of this page, a QR code is a grid of squares that divide into two parts — the function patterns (which are the bold shapes in the above example) and the data modules (everything else). The function modules are mostly used to let the scanner find the QR code easily, so they have to be very clear and distinct. The data modules are where the actual data is stored, along with some extra header data etc, and since those are only read after the scanner has used the function modules to work out exactly where the QR code is, they can be modified a bit more. This is often used by brands to create QR codes that look a bit distinctive, and while this kind of modification makes them a bit less robust when scanning, but you can generally get away with a fair bit before the codes become unscannable.

二维码基础知识: 二维码本质上是一种将少量字节数据编码的方式,以便智能手机或类似设备可以通过拍照轻松读取。它们的设计初衷是具备极强的鲁棒性,能够应对对焦不准、打印质量差、角度倾斜以及部分缺失等情况。其具体工作原理并不重要,但就本文而言,二维码是一个由方块组成的网格,分为两部分:功能模式(即上述示例中的粗体形状)和数据模块(其余部分)。功能模块主要用于让扫描器轻松定位二维码,因此它们必须非常清晰且独特。数据模块用于存储实际数据以及一些额外的头部数据等;由于这些数据只有在扫描器利用功能模块确定二维码位置后才会被读取,因此它们可以进行更多的修改。品牌经常利用这一点来制作外观独特的二维码。虽然这种修改会降低扫描时的鲁棒性,但通常在二维码变得无法扫描之前,你还是有相当大的调整空间。

Putting pictures in QR codes 在二维码中嵌入图片

This is a QR code I saw on Mastodon. One of the modifications you can do with the data modules is to shrink them — the scanner will look where the finder patterns say the centres of the pixels should be, and so as long as the code isn’t distorted when you try to scan it. That means the rest of the space is yours to play with. Dave divides each pixel into a three-by-three grid, and uses the middle one to store the data, and the others for a photo. The result is a low-res, one-bit photo with some salt-and-pepper noise on it.

这是我在 Mastodon 上看到的一个二维码。你可以对数据模块进行的一种修改是缩小它们——扫描器会根据定位图案寻找像素中心的位置,只要你在扫描时代码没有变形,它就能正常工作。这意味着剩下的空间可以任你发挥。Dave 将每个像素划分为 3x3 的网格,利用中间的方块存储数据,其余部分用于显示照片。最终得到的是一张带有椒盐噪声的低分辨率单比特照片。

Dithering 抖动(Dithering)

Obviously when we crush an image into a low colour depth, we don’t normally just threshold it — that is, make the dark pixels black and the light pixels white. We normally use a kind of chequerboard effect to create midtones as well. A Bayer filter can be used to apply this idea across a whole image without having to sacrifice too much fine detail.

显然,当我们把图像压缩到低色深时,通常不会仅仅使用阈值处理——即把深色像素变黑,浅色像素变白。我们通常会使用一种棋盘格效果来创建中间色调。Bayer 滤镜可以用来将这一理念应用到整张图像上,而无需牺牲太多的细节。

Error diffusion 误差扩散

An attempt to improve on this method was Floyd-Steinberg dithering. In this method, you start in the top left and threshold the pixel normally — if it’s more than 50% brightness, you make it white, and otherwise you make it black. Say it was 70% brightness — we’re going to make it white, which is 100% brightness, which means we’ve added 30% of a pixel too much brightness. To counteract that, we’re going to “diffuse” that error to other pixels — all the nearby pixels that we haven’t thresholded yet are made a few percent darker, to a total of 30% of a pixel. When we come to threshold those pixels, we’ll take that into account. The idea is that once we’ve thresholded the entire picture, every part of the image will be, on average, closer to the correct brightness than more regular dithering. The irregular patterns are also a bit less distracting.

Floyd-Steinberg 抖动算法是对上述方法的一种改进尝试。在这种方法中,你从左上角开始,正常对像素进行阈值处理——如果亮度超过 50%,就将其设为白色,否则设为黑色。假设亮度为 70%,我们将其设为白色(即 100% 亮度),这意味着我们多加了 30% 的亮度。为了抵消这种误差,我们将误差“扩散”到其他像素——将所有尚未处理的邻近像素调暗几个百分点,总计调暗 30%。当我们处理这些像素时,会将此误差考虑在内。其核心思想是,当整张图片处理完毕后,图像的每个部分在平均亮度上都会比常规抖动更接近原始亮度。此外,这种不规则的图案看起来也没那么突兀。

QR codes with error-diffusion dithering 带有误差扩散抖动的二维码

The other advantage of this irregular dithering is that the salt-and-pepper noise caused by the QR code data modules is much harder to spot. But it’s still there, and it’s why the image looks so noisy. I mean, it would look fairly noisy anyway because it’s a 147×147 pixel one-bit image, but some of that noise is because one in nine of the pixels are effectively random colours. But we can solve that using more error diffusion.

这种不规则抖动的另一个优点是,由二维码数据模块引起的椒盐噪声更难被察觉。但它依然存在,这也是图像看起来噪点很多的原因。当然,即便没有这些,作为一张 147×147 像素的单比特图像,它本身也会显得噪点较多,但部分噪声确实是因为九分之一的像素实际上是随机颜色。不过,我们可以通过更多的误差扩散来解决这个问题。

Error-diffusing the data modules 对数据模块进行误差扩散

In this case, we’re doing two passes on the error diffusion. The second is the same as normal, but the first is specifically to mask the data modules. We already know what colour all of those pixels have to be, so we can make them those colours and diffuse that error to the surrounding eight pixels. The error will potentially be quite big — normally we get to choose the colour so the error can never be more than 50%, but here we might have to make a pixel in a dark area pure white and end up creating 95% error. That sounds bad, but that’s exactly why it’s so important to diffuse this error instead of just accepting it. You can see that this makes the image much cleaner.

在这种情况下,我们进行了两次误差扩散处理。第二次与常规方法相同,但第一次专门用于掩盖数据模块。我们已经知道所有这些像素必须是什么颜色,所以我们可以将它们设为目标颜色,并将产生的误差扩散到周围的八个像素中。误差可能会非常大——通常我们可以选择颜色,因此误差永远不会超过 50%,但在这里,我们可能不得不将深色区域的一个像素设为纯白,从而产生 95% 的误差。这听起来很糟糕,但这正是为什么扩散误差比直接接受它更为重要的原因。你可以看到,这使得图像变得干净得多。

Getting fancy with it 进阶玩法

The generator tool allows you to do some extra tricks. You can rotate the QR code before adding the image, and tinker with the QR code settings to see if any of the alternative encodings happen to look nicer. You can also allow the generator to change a few of those 95%-error data modules — QR codes have enough error correction in them that you can afford to change a few pixels and they’ll still scan. (That’s how QR codes with logos in the middle work.) But in practice that doesn’t really affect image quality much if you diffuse the error from the data modules and it seriously affects how well the code scans.

生成器工具允许你进行一些额外的操作。你可以在添加图像之前旋转二维码,并调整二维码设置,看看是否有其他编码方式看起来更好。你还可以允许生成器修改那几个 95% 误差的数据模块——二维码本身具有足够的纠错能力,允许你修改几个像素而依然能被扫描。(中间带有 Logo 的二维码就是这样工作的。)但在实践中,如果你已经对数据模块进行了误差扩散,那么这种修改对图像质量的影响并不大,且它会严重影响二维码的扫描效果。

Using these in real life 在现实中使用

This all works pretty well if you don’t take it too far and the code is going to be on a big screen or a poster or something. If it’s on, say, a paper flyer that could get crumpled then you’re going to need more of that redundancy, robustness and error correction that we gave up to make the QR code pretty. Ultimately it’s a trade-off between aesthetics and scannability — and remember that just because a code scans on your phone, from a laptop screen, doesn’t mean it will scan on a random stranger’s potato phone from a printout in bad lighting. Also bear in mind that the generator tool makes tiny images with no margin — some margin is needed to make a QR code scan reliably, and browsers will normally blur images when upscaling them if you don’t disable that with CSS. The margin has to be the opposite colour to the middle of the big three “finder” pattern squares — normally that’s white but you can generate inverted QR codes that need a black background instead.

只要不过度操作,且二维码用于大屏幕或海报等场景,这些方法效果都很好。如果它是印在可能会被揉皱的纸质传单上,那么你就需要保留我们为了美观而放弃的冗余度、鲁棒性和纠错能力。归根结底,这是美观度与可扫描性之间的权衡——请记住,代码能在你的手机上从笔记本屏幕扫描出来,并不意味着它能在陌生人的低端手机上从光线不佳的打印件中被扫描出来。此外,请记住生成器工具制作的是没有边距的小图像——二维码需要一定的边距才能可靠扫描,而且如果不用 CSS 禁用,浏览器在放大图像时通常会使其模糊。边距的颜色必须与三个大“定位”方块中心的颜色相反——通常是白色,但你也可以生成需要黑色背景的倒置二维码。