PNG形式の画像をRGBモードからパレットモードに変換する

なにも考えずに変換

RGBモードの画像をパレットモードに変換したい

from PIL import Image
img = Image.open('./image.png')
img.convert('P')
img.save('./image_p.png')

パレットモード変換するだけならこれで終わるのだが、これだと色が微妙に変わってしまう上パレットも意図したものとはならない。
これを何とかする。

なんとかする

とりあえず完成はこんな感じ。

from PIL import Image
import numpy as np

def convert_pmode(img_path, palette):
    img = Image.open(img_path).convert('P', dither=Image.NONE, palette=Image.ADAPTIVE)
    ori_palette = list(zip(*[iter(img.getpalette())]*3))
    np_img = np.asarray(img)
    for num, col in enumerate(zip(*[iter(palette)]*3)):
        try:
            np_img = np.where(np_img == ori_palette.index(tuple(col)), num+256, np_img)
        except:
            pass
    p_img = Image.fromarray(np.uint8(np_img-256), mode='P')
    p_img.putpalette(palette)
    return p_img

if __name__ == '__main__':
   img_path = './image.png'
   palette = [0,0,0,0,0,255.....]
   p_img = convert_pmode(img_path, palette)
   p_img.save('./image_p.png')

やってることとしては画像をいったんパレットモードに変換して読み込む。そこから自分が用意したパレットに合うように画像を操作していく感じ。

上から順に説明していく。
convert()の引数でdither=Image.NONEpalette=Image.ADAPTIVEにしておく。
ditherは色の変化を滑らかにしてくれる処理。今回は必要ないので処理しない設定に。
paletteは正直よくわからない。Image.ADAPTIVEにしておくと色が変わることなく変換できたので設定している。
ADAPTIVEの意味が「適応できる」とかだったから画像に応じてパレットを作るみたいなことかな?

変換した画像からパレットを取得してRGBの順になるように3つずつのlistに格納する。zip(*[iter(img.getpalette())]*3)の書き方についてはここ とか見てください。
んで、画像をndarrayに変換してインデックスの値をいじっていく。
用意したパレットの色が今のパレットでは何番目になっているかをindex()で調べてnp.where()で置換。変換に支障が出ないようにインデックスの値+256で置換しておく。

あとはImage.fromarray()でndarrayを変換して、putpalette()でパレットを置き換えて終わり。

おわり

ずいぶん苦労して変換している気がする。もっと簡単な方法ありそう。