LINUX.ORG.RU

Как преобразовать цвета?

 ,


0

1

Нужно склеить 2 изображения с разными палитрами. (Одно требуется сохранить без изменений, в другом изменения допустимы.) Поступил просто: создал 3-е изображение нужного размера с палитрой от 1-го, Image.paste()-ом вставил туда 1-е и 2-е. В результате во 2-ом часть оттенков синего стала жёлтыми. Попробовал предварительно преобразовать 2-е в 24-битное — синие цвета покрылись красными точками. Если перед paste() обрабатывать вставляемое convert()-ом, результат тот же (как и следует из докуентации).

ar1 =  fromfile( f1, dtype=uint8 )
pal1 = fromfile( p1, dtype=uint8 )
pic1 = Image.frombuffer( 'P', (32*13, 32*3), ar1, 'raw', 'P', 0, 1 )
pic1.putpalette(pal1[0:768])

ar2 =  fromfile( f2, dtype=uint8 )
pal2 = fromfile( p2, dtype=uint8 )
pic2 = Image.frombuffer( 'P', (32*13, 32*3), ar2, 'raw', 'P', 0, 1 )
pic2.putpalette(pal2[0:768])
pic2.show()
#pict = pic2.convert( mode='RGB' )
#pic3 = pict.convert( mode='P', palette=pic1.getpalette() )

picc = Image.new( 'P', (32*13*2, 32*3) )
picc.putpalette(pal1[0:768])
picc.paste( pic1, ( 0, 0 ) )
picc.paste( pic2, ( 32*13, 0 ) )
#picc.paste( pict, ( 32*13, 0 ) )
#picc.paste( pic3, ( 32*13, 0 ) )
picc.show()

Каким ещё образом управлять преобразованием палитры? Особо точное совпадение мне не нужно, главное — сохранить яркость и уровень синего.

Ответ:

  • При paste() из индексированного в индексированный различия палитр не учитываются; синий стал жёлтым из-за того, что серия оттенков жёлтого в 1-ой палитре и синего во 2-ой имели одинаковые индексы.
  • При paste() из RGB в индексированный для вставляемого вызывается convert(), который в свою очередь вызывает quantize() с какой-то своей палитрой. Отсюда уродливые красные пятна.
  • Чтобы преобразовать индексированную картинку к другой палитре, нужно сперва вызвать convert( mode='RGB' ), затем quantize() с требуемой палитрой.
  • Если нет подходящих цветов, quantize() добавит регулярную сетку пикселей для получения в среднем нужного цвета. Полезно на больших разрешениях, вредно на малых — в моём случае.
  • Чтобы этого избежать, можно заменить цвета во 2-ой палитре близкими цветами из 1-ой.
  • Правильный вариант скрипта — в 3-ем посте. Буду рад, если кто-то посоветует, как его ускорить.

https://www.opennet.ru/openforum/vsluhforumID9/10243.html

★★★★★

Последнее исправление: question4 (всего исправлений: 3)

Похоже, простого способа нет.

Пока сделал такую функцию для сравнения цветов:

def colDist( a1, a2 ):
    m = array( ((0.299,    0.587,    0.114   ),
                (0.168736, 0.331264, 0.5     ),
                (0.5,      0.418688, 0.081312)) )
    yCbCr1 = dot(m, a1) # + v
    yCbCr2 = dot(m, a2) # + v
    d = abs(yCbCr1 - yCbCr2)
    return( d[0] + d[1] + d[2] )
Получает на вход 2 3-байтных массива RGB, превращает их в YCbCr (формула для JPEG из Википедии), берёт модули разностей компонентов, возвращает сумму этих модулей. Можно было поэкспериментировать с весами, но визуально 1:1:1 дал лучший результат, чем 1:0:0 и 1:1:0, и на этом я остановился.

question4 ★★★★★
() автор топика

Простой способ нашёлся — Image.quantize()

picp = pic2.convert( mode = 'RGB' ).quantize( colors=256, palette=pic1 ) 
Но качество заметно уступает тому, что получается подстановкой 1-ой палитры сравнением с весами 1:1:1. Если нет подходящих оттенков, quantize() добавляет сетку из точек, чтобы усреднённый цвет соответствовал оригиналу. Для низких разрешений это неприемлемо.

Если превращать RGB в индексированный цвет вызовом convert(), он тоже внутри вызывает quantize(), но с палитрой, не то вычисляемой по какому-то алгоритму, не то с жёстко прошитой. Поэтому paste() из RGB в индексированный давал странные цвета.

question4 ★★★★★
() автор топика

Окончательный вариант скрипта:

from numpy import set_printoptions, array, fromfile, uint8, zeros, dot, argmin
from PIL import Image

def yccs( a1 ):
    return dot( array( ((0.299,    0.587,    0.114   ),
                        (0.168736, 0.331264, 0.5     ),
                        (0.5,      0.418688, 0.081312)) ), a1 )

f1 = ...
f2 = ...
p1 = ...
p2 = ...
fo = 'merged.png'
set_printoptions( threshold=1000000 )

ar1 =  fromfile( f1, dtype=uint8 )
pal1 = fromfile( p1, dtype=uint8 )
pic1 = Image.frombuffer( 'P', (32*13, 32*3), ar1, 'raw', 'P', 0, 1 )
pic1.putpalette(pal1[0:768])

ar2 =  fromfile( f2, dtype=uint8 )
pal2 = fromfile( p2, dtype=uint8 )
pic2 = Image.frombuffer( 'P', (32*13, 32*3), ar2, 'raw', 'P', 0, 1 )
pic2.putpalette(pal2[0:768])

#Сравнение цветов и замена палитры.
cmpM = zeros( (256, 256), float )
palj1, palj2 = zeros( (256,3), float ), zeros( (256,3), float )
for c in range(0, 256):
    offs = c*3
    palj1[c] = yccs( pic1.getpalette()[offs:offs+3] )
    palj2[c] = yccs( pic2.getpalette()[offs:offs+3] )
for c1 in range(0, 256):
    for c2 in range(0, 256):
        cmpM[c2, c1] = sum( abs( palj2[c2] - palj1[c1] ) )
newPal  = zeros( 768, uint8 )
for c2 in range(0, 256):
    c1 = argmin( cmpM[c2] )
    newPal[c2*3:c2*3+3] = pic1.getpalette()[c1*3:c1*3+3]
pic2.putpalette(newPal)
# Буду признателен если посоветуете, как ещё можно ускорить эту часть

picp = pic2.convert( mode = 'RGB' ).quantize( colors=256, palette=pic1 )

picc = Image.new( 'P', (32*13*2, 32*3) )
picc.putpalette(pal1[0:768])
picc.paste( pic1, ( 0, 0 ) )
picc.paste( picp, ( 32*13, 0 ) )
picc.save( fo )

Так как алгоритмом сравнения палитр планирую пользоваться часто, буду рад советам как ускорить эту часть программы.

question4 ★★★★★
() автор топика
Ответ на: комментарий от lovesan

В своём собственном. Не проблема сохранить их в какой-нибудь PNG, но необходимо, чтобы конечный результат имел 1-ю палитру — и набор цветов, и их номера. ImageMagic палитру гарантировано покорёжит.

Как склеивать статичные PNG ffmpeg-ом?

question4 ★★★★★
() автор топика
Ответ на: комментарий от lovesan

И как? Поищу те же слова в мануале.

question4 ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.