Ordered dithering
От: Aniskin  
Дата: 26.04.24 06:12
Оценка:
Пытаюсь научиться переводить полноцветные изображения в изображения RGB333, в которых на каждый канал отводится по три бита, с использованием ordered dithering.

Рассмотрим для простоты матрицу 2x2. В ней есть четыре коэффициента: 0, 2, 3, 1. Эти коэффициенты я делю на три и вычитаю 0.5. Поскольку в палитре RGB333 для каждого канала есть только восемь градаций цветов и соответственно семь промежутков между отдельными градациями, то коэффициенты дополнительно делю на 7. Новые коэффициенты в сумме дают ноль. Все эти манипуляции с коэффициентами нужны для того, чтобы в условном примере, где в палитре есть цвет 8 и 10, и есть равномерно закрашенное изображение цветом 9, то в одних случаях прибавление коэффициента даст цвет ближе к 8, в других ближе к 10, и в среднем яркость результата будет совпадать с оригиналом.

Т.е. для каждого пикселя в соответствии с его положением в изображении я прибавляю соответствующий коэффициент матрицы и ищу в палитре RGB333 наиболее близкий цвет. И в целом получаю достаточно приемлемый результат, общая яркость примерно совпадает (в примере использована матрица 16x16):



Но есть одна проблема. Для обработки изображения я работаю с линейным RGB. А соответствие между обычным RGB и линейным RGB как бы немножно нелинейно Вверху обычные значения, внизу соответствующие им линейные.



Рассчитанные коэффициенты могут работать с простым RGB (но дают некорректную общую яркость результата), но с линейным RGB есть проблемы. В идеале прибавление коэффициента к цвету должно сдвинуть цвет до ближайшего в палитре, но с линейным RGB прибавление коэффициента может привести к перепрыгиванию ближайшего цвета в палитре к следующему, особенно в темных цветах, (хотя это все равно дает более-менее корректную общую яркость результата). Даже если однородно закрашенная поверхность исходного изображения имеет точный цвет из палитры, то вследствие перепрыгиваний в преобразованном изображении она будут составлена из нескольких цветов:



И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.
Re: Ordered dithering
От: kov_serg Россия  
Дата: 26.04.24 10:16
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.

Так сделайте преобразование цвета L2N, N2L : L2N(N2L(z))=z
c2(x,y) = nearest_color( N2L( L2N(c1(x,y)) + noise(x,y) ) )
Re[2]: Ordered dithering
От: Aniskin  
Дата: 26.04.24 11:04
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>c2(x,y) = nearest_color( N2L( L2N(c1(x,y)) + noise(x,y) ) )


Эта формула даст некорректную итоговую яркость.
Re[3]: Ordered dithering
От: kov_serg Россия  
Дата: 26.04.24 12:31
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Здравствуйте, kov_serg, Вы писали:


_>>c2(x,y) = nearest_color( N2L( L2N(c1(x,y)) + noise(x,y) ) )


A>Эта формула даст некорректную итоговую яркость.

Да. Если вы правильно подберёте функции L2N и N2L. Более того вы может сделать их параметрическими и подобрать параметры по своему вкусу или просто минимизируя целевую функцию.
Более того вам никто не запрещает использовать не sRGB, а другие цветовые пространства. Экспериментируйте.
Re: Ordered dithering
От: kov_serg Россия  
Дата: 26.04.24 21:04
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Пытаюсь научиться переводить полноцветные изображения в изображения RGB333, в которых на каждый канал отводится по три бита, с использованием ordered dithering.

Если сделать тупо в лоб без коррекции. То результат от вашего RGB333 заметно отличается, причем как для матриц 2x2, 4x4 так и 8x8.


Верхний ряд преобразованное изображение (8x8), нижний оригинал.


  main.lua
if not love then os.execute "love ." os.exit() end -- https://love2d.org
local app,g={ P=3, CL=8 }, love.graphics
app.pixel_shader=string.format([[
#define CL %d
#define W  %d
uniform float noise[W*W];
vec4 effect(vec4 c,Image tex,vec2 uv,vec2 xy){
    vec4 p=Texel(tex,uv);
    int idx=int(mod(floor(xy.x),W)+W*mod(floor(xy.y),W));
    float e=noise[idx]/CL;
    vec4 q=p*c+vec4(e,e,e,0);
    q=floor(q*CL)/(CL-1);
    return q;
}
]],app.CL,2^app.P)

function love.load()
    app.shader=g.newShader(app.pixel_shader)
    app.shader:send("noise",unpack(gen_noise_table(app.P)))
    app.image=g.newImage "OrderedSample.png"
    -- app.image=g.newImage "LinRGB2.png"
end
function love.draw()
    g.setShader(app.shader) g.draw(app.image,8,8) g.setShader()
    g.draw(app.image,8,208)
end
function love.keypressed(key)
    if key=='escape' then love.event.quit() end
end

function gen_noise_table(p) -- https://en.wikipedia.org/wiki/Ordered_dithering
    local function f(x,y,n) local r=0
        for i=1,n do r=r+r+x%2 x=bit.rshift(x,1) r=r+r+y%2 y=bit.rshift(y,1) end
        return r
    end
    local r,n,k = {}, 2^p, 2/(4^p-1)
    for y=0,n-1 do for x=0,n-1 do table.insert(r,f(bit.bxor(x,y),y,p)*k-1) end end
    return r
end
Отредактировано 26.04.2024 21:20 kov_serg . Предыдущая версия . Еще …
Отредактировано 26.04.2024 21:19 kov_serg . Предыдущая версия .
Отредактировано 26.04.2024 21:16 kov_serg . Предыдущая версия .
Re[2]: Ordered dithering
От: Aniskin  
Дата: 26.04.24 22:41
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>результат от вашего RGB333 заметно отличается


Разные реализации алгоритма — разные результаты, все логично.

_>Верхний ряд преобразованное изображение (8x8), нижний оригинал.


Мне мой вариант больше нравится — в нем меньше шума.
Re[3]: Ordered dithering
От: kov_serg Россия  
Дата: 27.04.24 12:50
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>Мне мой вариант больше нравится — в нем меньше шума.

Так уровень шума регулируется его амплитудой:

Re: Ordered dithering
От: Aniskin  
Дата: 27.04.24 23:51
Оценка:
Здравствуйте, Aniskin, Вы писали:

A>И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.


Решил для себя проблему следующим образом. Поскольку каждый конкретный цвет, не входящий в палитру, должен компенсироваться двумя ближайшими цветами из палитры, то я просто ищу в палитре эти два ближайшие цвета, и умножаю коэффициент из матрица на разницу между этими двумя цветами (все расчеты в линейных координатах). Соответственно прибавление коэффициента к цвету будет смещать цвет либо к первому, либо ко второму ближайшему цвету из палитры. И это дает мне 1) корректную итоговую яркость и 2) меньшую зашумленность итогово изображения. Тем не менее иногда прибавление отрицательного коэффициента может привести к перепрыгиванию ближайшего темного цвета на еще более темный, но это является скорее плюсом, нежели минусом.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.