Pythonで画像の減色をする

PythonのPillow(PIL)モジュールを使って、画像を減色してみました。

目次

  1. フルカラー画像を256色にする
  2. Pillowのconvertメソッドでモード変換する
  3. quantizeメソッドで減色する(median cut)
  4. quantizeメソッドで減色する(maximum coverage)
  5. quantizeメソッドで減色する(fast octree)
  6. quantizeメソッドで減色する(libimagequant)
  7. k平均法を100回かけてみる
  8. まとめ

フルカラー画像を256色にする

ウェブサイトに使う画像データなどは軽ければ軽いほど良いわけで、ビットマップよりJPEGやGIFやPNGが使われます。そこで、今回はフルカラー画像を256色に減色してみました。

サンプルの画像は3つです。

自前のカラーグラデーション画像

Pillowを使って作成した24 bitカラーの画像です。

カラーグラデーション画像

この画像を生成したときのコードはこんな感じです。

import numpy as np
from PIL import Image

line_data = np.arange(256)
hue = np.tile(line_data, (256,1))
sat = np.transpose(hue)
val = np.full_like(hue, 255)

bw = np.stack([np.full_like(hue, 255), np.full_like(hue,0), hue], 2)
mat1 = np.stack([hue, sat, val], 2)
mat2 = np.stack([hue, val, sat], 2)

mat = np.concatenate([mat1, mat2, bw], 1)

im = Image.fromarray(np.uint8(mat), 'HSV')
im_rgb = im.convert('RGB')
im_rgb.save('out.png')

写真

Pixabay の画像です。

写真

イラスト

プロ生ちゃん のイラストです。

イラスト

Pillowのconvertメソッドでモード変換する

Pillowの画像オブジェクトのモードを24 bitカラー(RGB)から8 bitカラー(P)に切り替えます。GIFやPNG-8で保存する前に行う処理です。

コードはこんな感じです。

from PIL import Image

im = Image.open('in.png')
c = im.getcolors(im.width * im.height)
print('Original colors :', len(c))

im_p = im.convert('P')
c = im_p.getcolors(im.width * im.height)
print('Modified colors :', len(c))
im_p.save('out.png')

オリジナル

減色後

119467色

174色

15KB

64KB

カラーグラデーション画像 convert

オリジナル

減色後

28923色

56色

36KB

82KB

写真 convert

オリジナル

減色後

31532色

74色

70KB

61KB

イラスト convert

JPEGってすごいですね。

quantizeメソッドで減色する(median cut)

Pillowのquantizeメソッドで減色します。いくつかアルゴリズムが選べるのですが、まずはmedian cutです。

減色アルゴリズムについては、 こちらのサイト が参考になります。

コードはこんな感じです。

from PIL import Image

im = Image.open('in.png')

im_q = im.quantize(colors=256, method=0, dither=1)
im_q.save('out.png')

オリジナル

減色後

119467色

228色

15KB

10KB

カラーグラデーション画像 median cut

オリジナル

減色後

28923色

256色

36KB

121KB

写真 median cut

オリジナル

減色後

31532色

256色

70KB

88KB

イラスト median cut

quantizeメソッドで減色する(maximum coverage)

次はmaximum coverageにしてみます。quantizeメソッドの引数が変わります。

from PIL import Image

im = Image.open('in.png')

im_q = im.quantize(colors=256, method=1, dither=1)
im_q.save('out.png')

オリジナル

減色後

119467色

256色

15KB

12KB

カラーグラデーション画像 max

オリジナル

減色後

28923色

256色

36KB

86KB

写真 max

オリジナル

減色後

31532色

256色

70KB

55KB

イラスト max

quantizeメソッドで減色する(fast octree)

続いて、fast octreeで減色します。

from PIL import Image

im = Image.open('in.png')

im_q = im.quantize(colors=256, method=2, dither=1)
im_q.save('out.png')

オリジナル

減色後

119467色

256色

15KB

7KB

カラーグラデーション画像 octree

オリジナル

減色後

28923色

249色

36KB

76KB

写真 octree

オリジナル

減色後

31532色

256色

70KB

60KB

イラスト octree

quantizeメソッドで減色する(libimagequant)

libimagequantという指定もできるようなのですが、私の環境では動きませんでした。

k平均法を100回かけてみる

アルゴリズムをmedian cutに戻して、kmeansオプションを100にしてみます。パレットの最適化がされるはずです。

from PIL import Image

im = Image.open('in.png')

im_q = im.quantize(colors=256, method=0, kmeans=100, dither=1)
im_q.save('out.png')

オリジナル

減色後

119467色

253色

15KB

11KB

カラーグラデーション画像 kmeans

オリジナル

減色後

28923色

256色

36KB

119KB

写真 kmeans

オリジナル

減色後

31532色

256色

70KB

86KB

イラスト kmeans

まとめ

カラーグラデーション画像

オリジナル

convert

median cut

max coverage

fast octree

median cut + kmeans

119467色

174色

228色

256色

256色

253色

15KB

64KB

10KB

12KB

7KB

11KB

写真

オリジナル

convert

median cut

max coverage

fast octree

median cut + kmeans

28923色

56色

256色

256色

249色

256色

36KB

82KB

121KB

86KB

76KB

119KB

イラスト

オリジナル

convert

median cut

max coverage

fast octree

median cut + kmeans

31532色

74色

256色

256色

256色

256色

70KB

61KB

88KB

55KB

60KB

86KB

減色のアルゴリズムでできあがりの画像の感じが違うことがわかりましたが、元のファイルが小さくてファイルサイズの圧縮にはつながりませんでした。JPEGってすごいのね。

公開日

広告