Пытаюсь научиться переводить полноцветные изображения в изображения 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 прибавление коэффициента может привести к перепрыгиванию ближайшего цвета в палитре к следующему, особенно в темных цветах, (хотя это все равно дает более-менее корректную общую яркость результата). Даже если однородно закрашенная поверхность исходного изображения имеет точный цвет из палитры, то вследствие перепрыгиваний в преобразованном изображении она будут составлена из нескольких цветов:
И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.
Здравствуйте, Aniskin, Вы писали:
A>И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.
Так сделайте преобразование цвета L2N, N2L : L2N(N2L(z))=z
c2(x,y) = nearest_color( N2L( L2N(c1(x,y)) + noise(x,y) ) )
Здравствуйте, Aniskin, Вы писали:
A>Здравствуйте, kov_serg, Вы писали:
_>>c2(x,y) = nearest_color( N2L( L2N(c1(x,y)) + noise(x,y) ) )
A>Эта формула даст некорректную итоговую яркость.
Да. Если вы правильно подберёте функции L2N и N2L. Более того вы может сделать их параметрическими и подобрать параметры по своему вкусу или просто минимизируя целевую функцию.
Более того вам никто не запрещает использовать не sRGB, а другие цветовые пространства. Экспериментируйте.
Здравствуйте, 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
Здравствуйте, Aniskin, Вы писали:
A>И вот хочется мне это перепрыгивание устранить. В голову приходит идея использовать коэффициенты, зависящие от обрабатываемого цвета. Т.е. чем ниже значение цвета, тем ниже коэффициент. И вот тут я в тупике. Не могу понять и придумать, как сделать эту зависимость.
Решил для себя проблему следующим образом. Поскольку каждый конкретный цвет, не входящий в палитру, должен компенсироваться двумя ближайшими цветами из палитры, то я просто ищу в палитре эти два ближайшие цвета, и умножаю коэффициент из матрица на разницу между этими двумя цветами (все расчеты в линейных координатах). Соответственно прибавление коэффициента к цвету будет смещать цвет либо к первому, либо ко второму ближайшему цвету из палитры. И это дает мне 1) корректную итоговую яркость и 2) меньшую зашумленность итогово изображения. Тем не менее иногда прибавление отрицательного коэффициента может привести к перепрыгиванию ближайшего темного цвета на еще более темный, но это является скорее плюсом, нежели минусом.