Pythonで画像の減色をする
PythonのPillow(PIL)モジュールを使って、画像を減色してみました。
目次
- フルカラー画像を256色にする
- Pillowのconvertメソッドでモード変換する
- quantizeメソッドで減色する(median cut)
- quantizeメソッドで減色する(maximum coverage)
- quantizeメソッドで減色する(fast octree)
- quantizeメソッドで減色する(libimagequant)
- k平均法を100回かけてみる
- まとめ
フルカラー画像を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 |

オリジナル |
減色後 |
---|---|
28923色 |
56色 |
36KB |
82KB |

オリジナル |
減色後 |
---|---|
31532色 |
74色 |
70KB |
61KB |

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 |

オリジナル |
減色後 |
---|---|
28923色 |
256色 |
36KB |
121KB |

オリジナル |
減色後 |
---|---|
31532色 |
256色 |
70KB |
88KB |

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 |

オリジナル |
減色後 |
---|---|
28923色 |
256色 |
36KB |
86KB |

オリジナル |
減色後 |
---|---|
31532色 |
256色 |
70KB |
55KB |

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 |

オリジナル |
減色後 |
---|---|
28923色 |
249色 |
36KB |
76KB |

オリジナル |
減色後 |
---|---|
31532色 |
256色 |
70KB |
60KB |

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 |

オリジナル |
減色後 |
---|---|
28923色 |
256色 |
36KB |
119KB |

オリジナル |
減色後 |
---|---|
31532色 |
256色 |
70KB |
86KB |

まとめ
カラーグラデーション画像
オリジナル |
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ってすごいのね。
公開日
広告
Pythonで画像処理カテゴリの投稿
- Python (Pillow)でグラデーション画像を作る
- PythonでGIFアニメを作る(PILでファイルをつなげる編)
- PythonでOpenCVの画像データをMatplotlibで表示する
- PythonでPDFを画像に変換する(Windows10)
- PythonでPSDファイルをJPEGやPNGに変換する
- Pythonでカラー画像をグレイスケールに変換する(OpenCV編)
- Pythonでフォルダ内に同じ画像がないか検索してみた
- Pythonでマスク画像を作る方法(3選)
- Pythonで図形の外接矩形を描く(OpenCV編)
- Pythonで図形の輪郭の大きさを調べる(OpenCV編)
- Pythonで画像の余白を削除する(OpenCV編)
- Pythonで画像の余白を削除する(Pillow編)
- Pythonで画像の大きさやフォーマットを調べる(Pillow編)
- Pythonで画像の減色をする
- Pythonで画像をトリミングする(Pillow編)
- Pythonで画像を他の画像にペーストする(Pillow編)
- Pythonで画像を回転する(Pillow編)
- Pythonで画像を拡大縮小(リサイズ)する(Pillow編)
- Pythonで画像を読み込み、表示し、保存する(OpenCV編)
- Pythonで画像を読み込み、表示し、保存する(Pillow編)
- Pythonで画像を2値化する(OpenCV編)
- Pythonで記号の輪郭を検出する(OpenCV編)
- PythonとOpenCVで画像をグレースケールに変換してみた
- PythonとOpenCVで画像をネガポジ反転してみた