画像のポスタリゼーション

### まえおき

[Adobe](https://www.adobe.com/jp/creativecloud/photography/discover/posterize-photo.html) によれば、ポスタリゼーションは「色の数を少なくし、繊細なグラデーションを大まかな色合いにすることで、絵の具を塗ったような、またはエアブラシを使ったような効果」を得る画像処理の技法のひとつです。

Adobe 製品ならアーティスティックな効果も思いのままでしょうが、フリーでやりたくなるのが人情というものです。そこで、Python＋[Pillow](https://pillow.readthedocs.io/en/stable/) を試します。

<img src="https://www.python.org/static/community_logos/python-logo.png" height="50"> <img src="https://pillow.readthedocs.io/en/stable/_static/pillow-logo-dark-text.png" height="50">



### ImageOps.posterize

Pillowには`ImageOps.posterize`という、そのまんまの名称の関数があります。次の関数シグニチャ（👉[リファレンス](https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.posterize)）からわかるように、有効なビット数を指定することで減色します。このビット数は全カラーチャネルに等しく適用されます。

```
PIL.ImageOps.posterize(image: Image, bits: int) → Image
```

この関数でたとえば`bits=3`を指定すると、RGB のそれぞれの色のバリエーションが2<sup>3</sup>＝8になるので、8×8×8＝512色に減色されます。もとの1600万余の色数に比べれば格段に減っていますが、意外ともとの色合いは保たれるもので、ポスタリゼーションした気分にはなりません。

```python
>>> from PIL import Image, ImageOps
>>> rgb = Image.open('pexels-pixabay-279360.jpg')
>>> ImageOps.posterize(rgb, 3).save('rgb-b3.png')
```
![240913-rgb-b3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/87507cd0-87d7-8340-8d9d-82543105bb22.png)

> サンプルの猫画像は[Pexels](https://images.pexels.com/photos/279360/pexels-photo-279360.jpeg)のものを利用させてもらいました。

ビット数単位なので、これより減色させるとなると、`bits=2`（64色）か`bits=1`（8色）しかチョイスはありません。

![240913-rgb-b2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/4cc7c9be-ae89-cc16-5270-5a1cdf9fd4f8.png) ![240913-rgb-b1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/eccb246d-341c-0fd3-ae35-4b385308d215.png)

ポスタリゼーションというより、ブラウン管テレビの狂った色調みたいです（昭和ネタですみません）。


### Image.quantization

色数が少ないといえば GIF で、Pillow には RGB を GIF のカラーパレットに変換する`Image.quantize`という関数もあります。次の関数シグニチャ（👉[リファレンス](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.quantize)）からわかるように、`colors`から色数を1～256の間で指定できます。

```
Image.quantize(colors: int = 256, method: int | None = None, kmeans: int = 0,
	           palette=None, dither: Dither = Dither.FLOYDSTEINBERG) → Image
```

先ほどロードした`rgb`で、32色と4色を試してみます。

```python
>>> rgb.quantize(colors=32).save('gif-c32.png')
>>> rgb.quantize(colors=4).save('gif-c4.png')
```

![240913-gif-c32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/dcf3ca65-fcd6-d0c9-1fa0-fe03315d87bb.png) ![240913-gif-c4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/c3ce76cb-91e7-0516-81fb-9ed96f6cb603.png)

ややモノトーンですが、悪くはないですね。



### HSV経由

色の塩梅を調整するなら、RGB より HSV のほうがよい結果が出やすいのは周知のとおりです。しかし、残念ながら、`ImageOps.posterize`は HSV には対応していません。元画像を`Image.convert`から HSV に変換して試します。

```python
>>> hsv = rgb.convert('HSV')               # RGB画像をHSVに変換
>>> hsv.mode                               # モードの確認
'HSV'
>>> ImageOps.posterize(hsv, bits=2)
︙
OSError: not supported for mode HSV
```

そこで、HSV 画像を`Image.split`でチャネル単位に分解し、それぞれをモノクロ画像として`ImageOps.posterize`をかけ（モノクロ画像の L モードはサポートされている）、`Image.merge`でまたもとのHSVに戻すという迂路を使います。あと、保存する前にもとの RGB に戻します。

H（色相）、S（彩度）、V（明るさ）のすべてについて2ビットに減色します。

```python
>>> h_s_v = hsv.split()
>>> Image.merge('HSV', [ImageOps.posterize(c, 2) for c in h_s_v]).convert('RGB').save('hsv-b2.png')
```

![240913-hsv-b2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/d5ce1f7e-d0e4-a13a-c41f-42e3f62ac8c9.png)

ポスターというのものはもともと色鮮やかで、明るいものです。そこで、強制的に彩度と輝度を最大値（255）にリセットしてみます。今度は色相は4ビット（16色）にしました。

```python
>>> p255 = Image.new('L', hsv.size, color=255)
>>> Image.merge('HSV', [ImageOps.posterize(h_s_v[0], 4), p255, p255]).convert('RGB').save('hsv-b4-255.png')
```

![240913-hsv-b4-255.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/3e02331a-d344-eb39-d9a9-6943868b0c02.png)

ポスターのように色が限られたのは確かですが、ここまでやるともとの絵柄がなんだかわかりません。どちらかというとセグメンテーションみたいで、やりすぎな気配が濃厚です。



### おわりに

と、まぁ、いろいろなポスタリゼーションを試してみました。方法と各種のパラメータを組み合わせれば、少しは狙った効果が得られるのではないかと思います。

Pillow/PIL はかなり使い込んだつもりでいましたが、`ImageOps.posterize`が HSV や P（GIF）をサポートしていないのは今日まで知りませんでした（マニュアルには明記されていない）。まだまだ修行が足りないようです。2年前（2022年9月）に書いた Pillow/PIL の教本でも、ポスタリゼーションは取り上げても、`ImageOps.posterize`の制約にまでは手が及んでいませんでした。ここで、追記させてもらった次第です。

ご購入はこちらから【[出版社](https://www.cutt.co.jp/book/978-4-87783-525-5.html) | [ヨドバシカメラ](https://www.yodobashi.com/product/100000009003620044/) | [楽天ブックス](https://books.rakuten.co.jp/rb/17274403/) | [amazon.co.jp](https://www.amazon.co.jp/dp/4877835253)】。

<img src="https://www.cutt.co.jp/book/images/978-4-87783-525-5.png" width="200" alt="表紙"></img>