这是有序抖动的正确实现吗?

时间:2019-01-25 20:40:22

标签: c# image-processing dithering

我对抖动感兴趣,请更精确地命令抖动。 我花了很多时间进行研究和实验。并希望我的代码经过一切努力。

我将提供执行抖动的代码和一些示例。

我的问题

  • 它如何工作?
  • 我的代码正确吗?
  • 可以简化吗?

这是this维基百科文章中抖动算法的伪代码,我的代码基于该伪代码。

Pseudo code

这是变量的解释:

  

M(i,j)是第 i 行和第 j c上的阈值图′是变换后的颜色,而 r 是在颜色空间中的扩散量。假设RGB调色板具有均匀选择的个颜色,其中每个颜色坐标均由一个八位位组表示,则通常会选择:enter image description here


现在,这是我的实现(使用伪代码):

ditherMatrix = 4x4; // By 4x4 I mean the normal 4x4 dither matrix from the wikipedia article
ditherMatrixSize = 4;
offset = (ditherMatrixSize * (ditherMatrixSize / 2) - 0.5); 
/* In the wiki article it uses 0.5 as an offset, 
   but I have to do this instead. 
   Is this correct? Atleast it works. */

allowedChanelValues = 1; // example: 2 = (0 or 128 or 255 red, 0 or 128 or 255 green, 0 or 128 or 255 blue)
r = 255 / allowedChanelValues / (ditherMatrixSize * ditherMatrixSize);

// Applying the dither
transfromedColor.r = 
    currentPixel.r + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);
transfromedColor.g = 
    currentPixel.g + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);
transfromedColor.g = 
    currentPixel.g + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);

// Quantizing, see https://youtu.be/0L2n8Tg2FwI 6:40 and 9:58 for explanation
transfromedColor.r = Round(allowedChanelValues * oldR / 255) * (255 / allowedChanelValues);
transfromedColor.g = Round(allowedChanelValues * oldG / 255) * (255 / allowedChanelValues);
transfromedColor.b = Round(allowedChanelValues * oldB / 255) * (255 / allowedChanelValues);

重要提示:现在,可以通过多种方式对这段代码进行优化,但这并不是我的意图(目前),我只是想使其正常工作,然后我看一下可能是什么优化。

侧注::刚开始时,我正在对值进行硬编码,例如,不是对ditherMatrixSize *(ditherMatrixSize / 2)-0.5进行硬编码 7.5。但是在尝试其他抖动矩阵之后,我只是使用此代码来获取值,而不是对其进行硬编码。


以下是一些示例

enter image description here

(顶部-抖动,底部-原始。矩阵为 4x4 ,允许的ChanelValues为 1


enter image description here

(左-抖动,右-原始。矩阵为 4x4 ,允许的ChanelValues为 1


enter image description here

(顶部-抖动,底部-原始。矩阵为 4x4 ,允许的ChanelValues为 2 Source


enter image description here

(顶部-抖动,底部-原始。矩阵为 4x4 ,这次我使用的是 16种颜色的预定义调色板,ChanelValues是 16的立方根-2.519 Source

这是没有调色板的样子,并且允许ChanelValues为 2 ,矩阵为 4x4

enter image description here

在这里我感到有些不对劲,也许是正确的,并且由于allowChanelValues太低而显得很奇怪。

这是如果我将allowedChanelValues更改为 4

的结果

enter image description here


我之前谈到过是否有可能简化代码。

我找到了此伪代码here

  Threshold = COLOR(256/4, 256/4, 256/4); /* Estimated precision of the palette */
  For each pixel, Input, in the original picture:
    Factor  = ThresholdMatrix[xcoordinate % X][ycoordinate % Y];
    Attempt = Input + Factor * Threshold
    Color = FindClosestColorFrom(Palette, Attempt)
    Draw pixel using Color

为了实现这一点,我必须对矩阵进行归一化,使其范围从0到1。 这就是我的实现方式:

factor = 1;
offset = (ditherMatrixSize * (ditherMatrixSize / 2) - 0.5d) / (ditherMatrixSize * ditherMatrixSize); 
// This time the offset is a bit different because I normalized the matrix
r = 256 / factor; // r is the same as the Threshold

transformedPixelColor.R = 
   currentPixel.R + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)
transformedPixelColor.G = 
   currentPixel.G + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)
transformedPixelColor.B = 
   currentPixel.B + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)

这与另一个相同。 我什至找到了第三种抖动方法,但是它更复杂,并且抖动与这两种方法相同,因此我将不介绍它。

那你怎么看? 该算法工作正常吗?

提前谢谢!

1 个答案:

答案 0 :(得分:-1)

它看起来不正确。在计算中的某处累积了错误。

有序抖动不需要浮点算术。它可以通过纯整数计算来实现。

预先计算矩阵中的值也很好。

这是一个用 C 语言编写的代码,它使用矩阵 16x16 执行 3bpp 有序二元分类器 (Bayer) 以获得最佳结果。

const   int BAYER_PATTERN_16X16[16][16] =   {   //  16x16 Bayer Dithering Matrix.  Color levels: 256
                                                {     0, 191,  48, 239,  12, 203,  60, 251,   3, 194,  51, 242,  15, 206,  63, 254  }, 
                                                {   127,  64, 175, 112, 139,  76, 187, 124, 130,  67, 178, 115, 142,  79, 190, 127  },
                                                {    32, 223,  16, 207,  44, 235,  28, 219,  35, 226,  19, 210,  47, 238,  31, 222  },
                                                {   159,  96, 143,  80, 171, 108, 155,  92, 162,  99, 146,  83, 174, 111, 158,  95  },
                                                {     8, 199,  56, 247,   4, 195,  52, 243,  11, 202,  59, 250,   7, 198,  55, 246  },
                                                {   135,  72, 183, 120, 131,  68, 179, 116, 138,  75, 186, 123, 134,  71, 182, 119  },
                                                {    40, 231,  24, 215,  36, 227,  20, 211,  43, 234,  27, 218,  39, 230,  23, 214  },
                                                {   167, 104, 151,  88, 163, 100, 147,  84, 170, 107, 154,  91, 166, 103, 150,  87  },
                                                {     2, 193,  50, 241,  14, 205,  62, 253,   1, 192,  49, 240,  13, 204,  61, 252  },
                                                {   129,  66, 177, 114, 141,  78, 189, 126, 128,  65, 176, 113, 140,  77, 188, 125  },
                                                {    34, 225,  18, 209,  46, 237,  30, 221,  33, 224,  17, 208,  45, 236,  29, 220  },
                                                {   161,  98, 145,  82, 173, 110, 157,  94, 160,  97, 144,  81, 172, 109, 156,  93  },
                                                {    10, 201,  58, 249,   6, 197,  54, 245,   9, 200,  57, 248,   5, 196,  53, 244  },
                                                {   137,  74, 185, 122, 133,  70, 181, 118, 136,  73, 184, 121, 132,  69, 180, 117  },
                                                {    42, 233,  26, 217,  38, 229,  22, 213,  41, 232,  25, 216,  37, 228,  21, 212  },
                                                {   169, 106, 153,  90, 165, 102, 149,  86, 168, 105, 152,  89, 164, 101, 148,  85  }
                                            };

//  Color ordered dither using 3 bits per pixel (1 bit per color plane)
void    makeDitherBayerRgb3bpp( BYTE* pixels, int width, int height )   noexcept
{
    int col = 0;
    int row = 0;

    for( int y = 0; y < height; y++ )
    {
        row = y & 15;   //  y % 16
        
        for( int x = 0; x < width; x++ )
        {
            col = x & 15;   //  x % 16

            pixels[x * 3 + 0]   = (pixels[x * 3 + 0] > BAYER_PATTERN_16X16[col][row] ? 255 : 0);
            pixels[x * 3 + 1]   = (pixels[x * 3 + 1] > BAYER_PATTERN_16X16[col][row] ? 255 : 0);
            pixels[x * 3 + 2]   = (pixels[x * 3 + 2] > BAYER_PATTERN_16X16[col][row] ? 255 : 0);
        }

        pixels  += width * 3;
    }
}

这是这个实现的结果: enter image description here

有关抖动的更多算法、源代码和演示,您可以查看here