图像抖动算法的实践实现

在互联网上,关于图像抖动算法的理论资料非常丰富,但实际的编程实现却相对匮乏。因此,本文旨在提供一些快速且实用的图像抖动算法实现,而省略了理论部分的介绍。

在编程生涯早期,曾需要实现一个“HTML到WAP转换器”插件。这个插件需要作为代理服务器的一部分工作,以便让移动电话(使用Nokia 3330进行WAP转码器测试)能够浏览常规网站。由于当时的移动电话大多使用单色显示屏,需要实时将所有图像转换为黑白。

因此,需要一个快速且高质量的单色图像抖动算法。最终,实现了Floyd-Steinberg算法和有序抖动算法。后来,发现了Bayer算法,它更好且更快。

后来,在另一个项目中,实现了在三个平面(红、绿、蓝)上的抖动算法,这些算法生成了6bpp、12bpp和15bpp的图像。这些抖动算法的实现也包含在本文中。

使用代码

项目中包含两个文件:Dither.h和Dither.cpp。要使用这些代码,只需要将这些文件放入项目中,并包含Dither.h文件。

这些文件包含了所有算法的实现。所有函数接收以下参数:

BYTE* pixels - 图像的像素数组,格式为24位BGR(b,g,r, b,g,r, ..., b,g,r) int width - 图像的宽度(像素) int height - 图像的高度(像素)

需要额外参数ncolors的函数,实际上指定了将应用于图像的抖动带数量。例如,黑白使用1个带(从黑到白);6bpp抖动使用3个带(每个颜色平面3位);12bpp抖动使用4个带(每个颜色平面4位),等等。

为了执行简单的抖动,应该这样做:

#include "Dither.h" ... // 这将使用16x16矩阵将彩色图像转换为黑白 makeDitherBayer16(pixels, width, height);

有三组函数:

  • 有序抖动 - 使用不同阈值矩阵进行黑白有序抖动的函数:2x2, 3x3, 4x4, 8x8, 16x16。
  • 彩色有序抖动 - 将彩色图像转换为分别对每个颜色平面(红、绿、蓝)应用抖动的图像的函数。这意味着在红色平面、绿色平面和蓝色平面上分别应用抖动。
  • Floyd-Steinberg抖动 - 用于误差扩散算法的函数:Floyd-Steinberg、Sierra(3行)和Sierra Lite,以及它们的彩色版本。

代码本身简单且易于阅读。例如:

void makeDitherBayer16(BYTE* pixels, int width, int height) noexcept { int col = 0; int row = 0; for (int y = 0; y < height; y++) { row = y & 15; for (int x = 0; x < width; x++) { col = x & 15; const pixel blue = pixels[x * 3 + 0]; const pixel green = pixels[x * 3 + 1]; const pixel red = pixels[x * 3 + 2]; pixel color = ((red + green + blue) / 3 < BAYER_PATTERN_16X16[col][row] ? 0 : 255); pixels[x * 3 + 0] = color; // blue pixels[x * 3 + 1] = color; // green pixels[x * 3 + 2] = color; // red } pixels += width * 3; } }

对于需要更多速度的人,他们可以定义自定义表,其中包含预乘以3的值,从而消除在将RGB转换为灰度时的除法。

要点

请注意,抖动算法中使用的颜色量化会改变图像的平均对比度。对于彩色抖动,颜色本身变得更亮。这是正常的。

使用特定N(带数)进行抖动的函数会产生错误的误差扩散分布,其中一些值大于20。对于其他值,亮度会降低。这是由于算法不完善(使用基本离散化不当)所致。因此,建议使用特定算法,而不是使用N参数的算法。

彩色抖动可以用来“压缩”图像,同时保持相对良好的质量。

例如,RGB 6位方法为每个像素的颜色分量使用2位,因此每个像素使用6位,而不是原始的24位。为了恢复质量,可以应用“平均2x2”或“平均3x3”滤波器。

请注意,在源代码中,有注释的函数被标记为“快速但不准确”。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485