Как исправить метод дизеринга для создания цветового градиента? - PullRequest
0 голосов
/ 28 января 2019

Существует программа, которая строит матрицу для градиента цвета от белого до черного.Затем алгоритм сглаживания применяется к матрице для устранения «полос».Реализовано 4 метода сглаживания: Ordered, Random, Floyd-Steinberg, Jarvis-Judice-Ninke.Сначала я создаю матрицу определенного размера, преобразую ее в градиент и выводю результат в файл формата .pgm, типа P5.Если я переведу файл в формат .png, я получу следующее изображение:

enter image description here

Однако, когда вы увеличиваете изображение, вы можете видеть полосы (если вы внимательно посмотрите):

enter image description here

Это результат работы программы без сглаживания.Проблема заключается в том, что если применить к матрице один из алгоритмов сглаживания, то на изображении останутся полосы.Результат предполагает, что дизеринг не работает.Что может быть не так?Нужно ли сначала использовать дизеринг, а затем строить градиент?Или ошибка в том, что вам нужно создать матрицу типа float или double?Как я могу это исправить?

Код:

#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();

}

1 Ответ

0 голосов
/ 29 января 2019

Интересно, что вы используете дизеринг, чтобы избавиться от тех полос, которые вы едва можете увидеть - в старые времена мы колебались только тогда, когда нам приходилось рендерить по 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;
}

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...