【问题标题】:How to fix the dithering method to create a color gradient?如何修复抖动方法以创建颜色渐变?
【发布时间】:2019-01-28 20:55:00
【问题描述】:

有一个程序可以为从白色到黑色的颜色渐变构建矩阵。然后,对矩阵应用抖动算法以消除“条纹”。实现了 4 种抖动方法:Ordered、Random、Floyd-Steinberg、Jarvis-Judice-Ninke。首先,我创建了一个一定大小的矩阵,将其转换为渐变,并将结果输出为文件格式.pgm,类型为P5。如果我将文件翻译成 .png,我会得到以下图像:

但是,当您放大图像时,您可以看到条纹(如果您仔细观察的话):

这是程序没有抖动的结果。问题是,如果您将其中一种抖动算法应用于矩阵,则条纹会保留在图像上。结果表明抖动不起作用。有什么问题?我需要先使用抖动,然后再构建渐变吗?或者错误是您需要创建一个浮点型或双精度型矩阵?我该如何解决?

代码:

#include "stdafx.h"
#include <iostream>
#include<algorithm>
#include<iterator>
#include<fstream>
#include<vector>
#include<cassert>
#include <ctime>
#include <sstream>

using namespace std;

vector<vector<int>> make_gradient(int height, int width)
{
    assert(height > 0 && width > 0);

    int cf = height / 255;
    int color = 0;
    vector<vector<int>> result(height, vector<int>(width));
    for (int i = 0; i < height; i += cf)
    {
        for (int j = 0; j < cf; ++j)
        {
            fill(result[i + j].begin(), result[i + j].end(), color % 255);
        }
        color++;
    }
    stable_sort(result.begin(), result.end());
    return result;
}

vector<vector<int>> ordered_dither(int height, int width, vector<vector<int>> result)
{
    int ditherSize = 4;
    int diterLookup[] = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 };

    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int xlocal = i%ditherSize;
            int ylocal = j%ditherSize;
            int requiredShade = diterLookup[xlocal + ylocal * 4] * 255 / 16;
            if ((requiredShade > 0) && (requiredShade < 1))
            {
                if (requiredShade >= (result[i][j] % 1)) {
                    result[i][j] = floor(result[i][j]);
                }
                else {
                    result[i][j] = ceil(result[i][j]);
                }
            }
            else requiredShade = 0;
        }
    }
    return result;
}

vector<vector<int>> random_dither(int height, int width, vector<vector<int>> result)
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int requiredShade = (float)rand() / RAND_MAX;
            if ((requiredShade > 0) && (requiredShade < 1))
            {
                if (requiredShade >= (result[i][j] % 1)) {
                    result[i][j] = floor(result[i][j]);
                }
                else {
                    result[i][j] = ceil(result[i][j]);
                }
            }
            else requiredShade = 0;
        }
    }
    return result;
}

vector<vector<int>> fs_dither(int height, int width, vector<vector<int>> result)
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int oldpixel = result[i][j];
            int newpixel = round(result[i][j]);
            result[i][j] = newpixel;
            int quanterror = oldpixel - newpixel;
            if (j < width - 1) {
                result[i][j + 1] += quanterror * 7 / 16;
            }
            if (i < height - 1) {
                if (j > 0) {
                    result[i + 1][j - 1] += quanterror * 3 / 16;
                }
                result[i + 1][j] += quanterror * 5 / 16;
                if (j < width - 1) {
                    result[i + 1][j + 1] += quanterror * 1 / 16;
                }
            }
        }
    }
    return result;
}

vector<vector<int>> jjn_dither(int height, int width, vector<vector<int>> result)
{
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            int oldpixel = result[i][j];
            int newpixel = round(result[i][j]);;
            result[i][j] = newpixel;
            int quanterror = oldpixel - newpixel;
            if (j < width - 1) {
                result[i][j + 1] += quanterror * 7 / 48;
                if (j<width - 2)
                    result[i][j + 2] += quanterror * 5 / 48;
            }

            if (i < height - 1) {
                if (j > 0) {
                    if (j > 1)
                        result[i + 1][j - 2] += quanterror * 3 / 48;
                    result[i + 1][j - 1] += quanterror * 5 / 48;
                }

                result[i + 1][j] += quanterror * 7 / 48;
                if (j < width - 1) {
                    result[i + 1][j + 1] += quanterror * 5 / 48;
                    if (j < width - 2)
                        result[i + 1][j + 2] += quanterror * 3 / 48;
                }
            }

            if (i < height - 2) {
                if (j > 0) {
                    if (j>1)
                        result[i + 2][j - 2] += quanterror * 1 / 48;
                    result[i + 2][j - 1] += quanterror * 3 / 48;
                }
                result[i + 2][j] += quanterror * 5 / 48;
                if (j < width - 1) {
                    result[i + 2][j + 1] += quanterror * 3 / 48;
                    if (j < width - 2)
                        result[i + 2][j + 2] += quanterror * 1 / 48;
                }
            }

        }
    }
    return result;
}

int main(int argc, char *argv[])
{
    if (argc < 5) {
        cout << "usage:" << endl << "prog.exe <filename> <width> <height> <dithering>" << endl;
        return 0;
    }
    stringstream w(argv[2]);
    stringstream h(argv[3]);
    stringstream d(argv[4]);
    int numcols, numrows, dithering;

    if (!(w >> numcols)) {
        cout << "width is not a number" << endl;
        return 0;
    }
    if (numcols < 1) {
        cout << "width must be more than zero" << endl;
        return 0;
    }

    if (!(h >> numrows)) {
        cout << "height is not a number" << endl;
        return 0;
    }
    if (numrows < 1) {
        cout << "height must be more than zero" << endl;
        return 0;
    }

    if (!(d >> dithering)) {
        cout << "dithering is not a number" << endl;
        return 0;
    }
    if (dithering < 0 || dithering>4) {
        cout << "dithering must be [0-4]" << endl;
        return 0;
    }

    srand(time(0));
    ofstream file;

    file.open(argv[1]);

    if (!file)
    {
        cout << "can't open file" << endl;
        return 0;
    }

    file << "P5" << "\n";

    file << numrows << " " << numcols << "\n";

    file << 255 << "\n";

    vector<vector<int>> pixmap{ make_gradient(numrows, numcols) };
    switch (dithering) {
    case 1:
        pixmap = ordered_dither(numrows, numcols, pixmap);
        break;
    case 2:
        pixmap = random_dither(numrows, numcols, pixmap);
        break;
    case 3:
        pixmap = fs_dither(numrows, numcols, pixmap);
        break;
    case 4:
        pixmap = jjn_dither(numrows, numcols, pixmap);
        break;
    default:
        break;
    }
    for_each(pixmap.begin(), pixmap.end(), [&](const auto& v) {
        copy(v.begin(), v.end(), ostream_iterator<char>{file, ""});
    });

    file.close();

}

【问题讨论】:

  • 整数数学有很多问题。例如,if ((requiredShade &gt; 0) &amp;&amp; (requiredShade &lt; 1)) 将始终为 false。 result[i][j] % 1 将始终为 0。
  • 我修复了这个错误:double requiredShade = ...; if ((requiredShade >= 0) && (requiredShade
  • 欢迎来到 StackOverflow。请按照您创建此帐户时的建议阅读并遵循帮助文档中的发布指南。 Minimal, complete, verifiable example 适用于此。在您发布 MCVE 代码并准确描述问题之前,我们无法有效地帮助您。我们应该能够将您发布的代码粘贴到文本文件中并重现您描述的问题。 StackOverflow 不是编码、评论或教程资源。
  • 查看这个可爱的debug 博客寻求帮助。您发布了 visual 输出,而不是错误值,并且似乎没有调试尝试。您已包含与问题无关的 I/O 开销。
  • 如果渐变的分辨率大于 256,则没有抖动能够去除条纹,因为它们是照明中的单一步骤,您的“gfx 能够”...抖动用于减少使用的颜色数量,同时尽可能保留颜色信息。在这种情况下,您可以获得的最佳效果是向图像添加噪声,但这通常会以某种方式在视觉上降低图像质量。如果不是这种情况,请参阅simple dithering

标签: c++ algorithm serialization graphics


【解决方案1】:

看到您使用抖动消除那些几乎看不到的波段很有趣 - 在过去,我们只有在每个通道必须渲染 4 位左右时才会抖动。

无论如何...您的第一个问题是,在您使用抖动将渐变减少到 256 级之前,您必须将其渲染为更多而不是 256 级。 make_gradient 应该会在 65536 级甚至浮点上渲染渐变。

在我看来,您的第二个问题是,您的抖动目前根本没有任何作用。 result[i][j] 是一个整数,所以当你说result[i][j] = floor(result[i][j]); 之类的东西时(我猜你忽略了关于转换的编译器警告),它什么都没有。如果你在浮动中生成渐变,这个问题也会消失。

如果您解决了这些问题,那么您的抖动就可以工作,但是这些抖动方法都不是真正适合在如此紧密间隔的级别上操作的最佳选择。完成后,可能仍会残留某种条带伪影(尽管您必须真正仔细观察才能看到它们)。为了使您的结果看起来尽可能好,您应该真正使用幅度等于两个量化级别的 TPDF 抖动。对于粗略的空间关卡,这看起来比您的其他一些选择更嘈杂,但在统计上更均匀,并且当关卡间隔精细时看起来会更好。

这也很简单——只需在量化为整数之前将两个介于 -0.5 和 0.5 之间的随机数添加到每个像素。

此处提到了 TPDF:https://en.wikipedia.org/wiki/Dither,但它是最重要的,因为它是用于信号处理采样的抖动类型,以确保量化不会导致任何一阶或二阶伪影。

编辑:

感谢您一直致力于此,因此这里是一步创建最终形式的抖动渐变的代码:

vector<vector<int>> make_dithered_gradient(int height, int width)
{
    assert(height > 0 && width > 0);

    vector<vector<int>> result(height, vector<int>(width));
    for (int i = 0; i < height; ++i)
    {
        // the target shade for each line of pixels is the average
        // ideal gradient value in that line, which is the same as the
        // ideal value in the middle of the line
        double target = ((double)i+0.5)*255.0/height;
        for (int j = 0; j < width; ++j)
        {
            double dither = ((double)rand()-(double)rand())/RAND_MAX;
            int val = (int)round(target+dither);
            if (val < 0)
                val = 0;
            if (val > 255)
                val = 255;
            result[i][j] = val;
        }
    }
    return result;
}

【讨论】:

  • 我设法让抖动工作,虽然我没有设法让某些区域的较暗像素的比例更小。使用随机抖动时,我得到以下结果:@ 987654323@ 放大图像时,可以看到图像上出现伪影。 prntscr.com/me6q7x 这是方法的代码:pastebin.com/vLmA2LgC 这个结果更接近要求。我该如何解决?
  • double dither = ((double)rand() - (double)rand())/RAND_MAX;result[i,j] = round(tmp+dither)*8.0。没有ifmodfrequiredShade。您必须限制结果 255。我假设您正在执行 *8 只是为了使抖动更加明显。
  • 抖动看起来非常好——你再也看不到条带了。顶部的混乱是因为您没有检查结果
  • 我觉得你在这个圈子里跑来跑去,你原来的渐变初始化很奇怪,所以请参阅编辑^^
  • 哦,非常感谢。看来我终于明白了。现在我正在尝试修复剩余的方法。起初,我尝试以两种方式更改有序抖动。这是第一个选项:pastebin.com/ph3HzQxm。在这种情况下,抖动根本不起作用。第二个选项:pastebin.com/uQ57stNC。我得到了这个结果:prntscr.com/mep1ka。 FS 抖动和 JJN 抖动看起来是正确的。对于这些方法,我只是像在随机抖动中一样删除了第三个参数,但现在,由于他们的工作,获得了一个完全黑色的图像。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-29
  • 1970-01-01
  • 2021-01-20
  • 2019-05-14
  • 2014-05-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多