PIL/Pillow チートシート

PIL/Pillowはコンパクトで高速なPython用の画像ライブラリです。
よく使う処理をまとめました（随時更新）

# PILとPillowの違い
基本的にPILを使う理由はありません、Pillowの方がリサイズフィルタのバグフィックスがされており高品質です。

# Pillowの速度について
Pillowは非常に高速にチューニングされており、同様なライブラリであるImageMagickよりも常に高速に動作します。
ただし、`getpixel`/`putpixel`は非常に低速です、画像生成以外には使わないようにしましょう。
更に高速な`pillow-simd`もあります。概ねオリジナルのPillowの4〜5倍の速度が出るようです。

pillow-simd
https://github.com/uploadcare/pillow-simd

####参考
https://python-pillow.org/pillow-perf/

# Imageモード一覧

| モード | 説明 |
|:-:|:--|
| 1  | 1bit　マスクに使用、論理演算が可能  |
|  L | 8bit グレイスケール  |
|  P | パレットモード  |
| RGB  | 8bit x 3  |
| RGBA  | 8bit x 4 透明度(アルファ)付き  |
| CMYK  | 8bit x 4 印刷関連でよく使われる |
| YCbCr  | 8bit x 3 ビデオ関連でよく使われる |
|  HSV |  8bit x 3 pillowのみ|
| RGBa | アルファチャンネルでRGB値を乗算 |
| LA | アルファチャンネルでL値を乗算 |
| I  |  32bit 整数 |
| F  |  32bit 浮動少数 |

# リサイズフィルタ
| フィルタ | ダウンスケーリング品質 | アップスケーリング品質 | パフォーマンス |
|:-:|:-:|:-:|:-:|
|  Image.NEAREST |   |   |  ⭐⭐⭐⭐⭐ |
|  Image.BOX | ⭐  |   | ⭐⭐⭐⭐  |
|  Image.BILINEAR |  ⭐ | ⭐  | ⭐⭐⭐  |
|  Image.HAMMING | ⭐⭐  |   | ⭐⭐⭐  |
|  Image.BICUBIC | ⭐⭐⭐  | ⭐⭐⭐  | ⭐⭐  |
| Image.LANCZOS  | ⭐⭐⭐⭐  | ⭐⭐⭐⭐  | ⭐  |
※Image.ANTIALIASはImage.LANCZOSの別名で、互換性のために残されています。
参考
http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#concept-filters

# Imageモジュール
## グレイスケール
```python
img.convert("L")
```
![mono.png](https://qiita-image-store.s3.amazonaws.com/0/140075/7c4230b5-8fa0-0546-047d-24fea2490b62.png)

## アルファ値を考慮したグレイスケール
```python
alpha.convert("LA")
```

![alpha.png](https://qiita-image-store.s3.amazonaws.com/0/140075/569cd912-0976-89f1-2d63-8add808939e5.png) → ![la.png](https://qiita-image-store.s3.amazonaws.com/0/140075/68da2c00-d1c7-b4e3-5045-53ed319aa01d.png)

ちなみに`convert('L')`はアルファ値を考慮してくれません。

```python
alpha.convert("L")
```
![alpha.png](https://qiita-image-store.s3.amazonaws.com/0/140075/569cd912-0976-89f1-2d63-8add808939e5.png) → ![l.png](https://qiita-image-store.s3.amazonaws.com/0/140075/5456195b-5016-caae-af6f-f0a3a808ce5f.png)


## HSV変換

```python
img.convert("HSV")
```
pillowのみHSV色空間にコンバートできます。色相(Hue)、彩度(Saturation)、明度(Value)の3つの成分で構成されています。
以下は、色相環をシフトする例です。

```python
h, s, v = img.convert("HSV").split()
_h = ImageMath.eval("(h + 128) % 255", h=h).convert("L")
Image.merge("HSV", (_h, s, v)).convert("RGB")
```
![hsv.png](https://qiita-image-store.s3.amazonaws.com/0/140075/cb12783b-2184-be18-7b17-5aa6a0564027.png)

## CIE XYZ変換
CIE XYZは色同士のユークリッド距離が、人間の知覚が感じる差異と同一に成るように調整された色空間。

```python
rgb2xyz = (
    0.412453, 0.357580, 0.180423, 0,
    0.212671, 0.715160, 0.072169, 0,
    0.019334, 0.119193, 0.950227, 0
)
img.convert("RGB", rgb2xyz)
```

## ２値化
```python
gray = img.convert("L")                     # グレイスケールに変換
gray.point(lambda x: 0 if x < 230 else x)   # 値が230以下は0になる
```
![bin.png](https://qiita-image-store.s3.amazonaws.com/0/140075/42763f3a-2684-0b01-8de4-aad52d02082b.png)

## 画像を明るくする / 暗くする

```python
img.point(lambda x: x * 1.5)    # 1.5倍明るくする
img.point(lambda x: x * 0.5)    # 1 / 2に暗くする
```

![ligter.png](https://qiita-image-store.s3.amazonaws.com/0/140075/65019a01-07a7-dcf3-7d48-81326884a322.png) ![darker.png](https://qiita-image-store.s3.amazonaws.com/0/140075/ff48a83d-8537-15cb-7d61-dff9a247970c.png)


## セピア
いったん画像をグレイスケールに変換してからセピア化します。

```python
gray = img.convert("L")
Image.merge(
    "RGB",
    (   
        gray.point(lambda x: x * 240 / 255),
        gray.point(lambda x: x * 200 / 255),
        gray.point(lambda x: x * 145 / 255)
    )
)
```
![cepia.png](https://qiita-image-store.s3.amazonaws.com/0/140075/f1f20eb7-efda-a415-cf96-70c605178cbd.png)

## ガンマ補正
ガンマ補正もルックアップテーブルを使えば非常に高速に画像変換できます。
src=入力色、γ=ガンマ値、g=ゲイン値とすると、ガンマ補正の式は以下のとおりです。

```math
dst = \biggl(\frac{src}{255}\biggr)^{1/γ} \times g \times 255
```

```python
def gamma_table(gamma_r, gamma_g, gamma_b, gain_r=1.0, gain_g=1.0, gain_b=1.0):
    r_tbl = [min(255, int((x / 255.) ** (1. / gamma_r) * gain_r * 255.)) for x in range(256)]
    g_tbl = [min(255, int((x / 255.) ** (1. / gamma_g) * gain_g * 255.)) for x in range(256)]
    b_tbl = [min(255, int((x / 255.) ** (1. / gamma_b) * gain_b * 255.)) for x in range(256)]
    return r_tbl + g_tbl + b_tbl

img.point(gamma_table(1.2, 0.5, 0.5))
```
![gamma.png](https://qiita-image-store.s3.amazonaws.com/0/140075/268439da-5ce5-fbeb-678e-869183cb835c.png)

## ループ内で使用する`point`の高速化

ループ内での`Image.point`には`lambda`を渡さない方がよいでしょう、`point`に渡す引数はあくまで変換テーブルであるため、事前に展開しておいた方が高速です。

```python
for ...:
   img.point(lambda x: x * 100)

# 下の処理は上の処理と等しいが高速です
table = [x * 100 for x in range(256)] * len(img.getbands())
for ...:
    img.point(table)
```

## getbbox
`Image.getbbox`は画像内で値が0でない最小領域を返します、値がすべて0の画像は`None`を返します。

### アルファ成分の余白カット
```python
alpha = Image.open("alpha.png")

crop = alpha.split()[-1].getbbox()
alpha.crop(crop)
```
![範囲を選択_003.png](https://qiita-image-store.s3.amazonaws.com/0/140075/ff60b42b-90e6-ef70-41e1-4def560a6c78.png)  → ![範囲を選択_004.png](https://qiita-image-store.s3.amazonaws.com/0/140075/a3dd5a01-65b0-da6f-7764-05734ef33382.png)

### 同一画素チェック
２つの画像の同一の場合`ImageChops.difference`はすべて0の画像を返すため、`getbbox`が`None`がならば同一と判断できます。

```python
ImageChops.difference(img1, img2).getbbox() is None
```

## リサイズ
```python
img.resize((128, 128), Image.LANCZOS)
```
![resize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/067109a1-8c2a-a9df-0bef-41977973031b.png)

## サムネイル
サムネイルはリサイズと違い、縦横比を維持します。
なぜか`thumbnail`は破壊的メソッドなので注意してください、`Image.copy`なので複製を作っておくとよいでしょう。

```python
img.thumbnail((128, 128), Image.LANCZOS)
img.size
# (128, 79)
```
![thumb.png](https://qiita-image-store.s3.amazonaws.com/0/140075/a9474beb-9bb1-cc56-ee29-068f139d4edc.png)

## 回転
引数`expand`に`True`を指定すると、回転時に画像が大きくなってしまう場合に画像を拡張します。

```python
img.rotate(90, expand=True)
```
![rotate90.png](https://qiita-image-store.s3.amazonaws.com/0/140075/1303867a-f6b0-c20b-0c78-a64702b0a2ca.png)

## モザイク処理
モザイク処理は`Image.LINEAR`で縮小と拡大をすれば良いが、ガウシアンブラーをかけてから縮小するとやわらかいモザイクになります。

```python
# ギザギザモザイク
img.resize([x // 8 for x in img.size]).resize(img.size)

# ガウシアンブラーをかけるとやわらかいモザイクに
gimg = img.filter(ImageFilter.GaussianBlur(4))
gimg.resize([x // 8 for x in img.size]).resize(img.size)
```

![mozaic2.png](https://qiita-image-store.s3.amazonaws.com/0/140075/654c6c72-7c28-4655-18fa-b743463b9de1.png)  ![mozaic.png](https://qiita-image-store.s3.amazonaws.com/0/140075/e3848ece-96b2-86e0-c734-6e020b624de8.png)


## アルファブレンド
```python
Image.blend(img,effect_img, 0.5)
```
![B9BSxGZmEQpmAAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/140075/aab8183a-2ea7-445a-28fe-85406f2b0f00.png) ![kL+ySM465ToAlAAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/140075/f9d5991d-1090-21ee-c029-b1b48b47556f.png) ![blend.png](https://qiita-image-store.s3.amazonaws.com/0/140075/5f9759d9-a40e-de8b-b6b5-14056a97b9ca.png)


## 減色
```python
img.quantize(4)    # 4色に減色
```
![quantize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/453f0c06-544e-e999-6d37-a53db3465130.png)

## アルファ画像を貼り付け
アルファ付き画像を貼り付ける場合は`Image.paste`の引数'mask'にアルファ付き画像を指定する。

```python
img.paste(alpha, mask=alpha)
```
![paste.png](https://qiita-image-store.s3.amazonaws.com/0/140075/41fdb7a0-bcfc-36ab-7bc8-a8521d96f01f.png)


## 使用されている色をカウント
使用されている色のカウントを取る、`Image.getcolors`では引数なしの場合は255色以上はカウントできません。
255色以上の色を使った画像の場合、ピクセル数を引数で渡しておけば安心です。[^1]

[^1]: 2017-02-07修正 'Image.getcount'となっていましたが、正しくは'Image.getcolors'です、修正いたします。

```python
img.getcolors(img.size[0] * img.size[1])
```

## ヒストグラム
イメージの色ヒストグラムをリストで返す。
各バンドを連続して返すため、RGBモードの場合は256 x 3=768個の要素を返します。

```python
img.histogram()
```

## 色の置換

色を置換するメソッドはありません、色を置換した場合は下記の記事を参照してください。

[PIL/Pillowで画像の色を高速に置換する](http://qiita.com/pashango2/items/d6dda5f07109ee5b6163)


# ImageOpsモジュール

## ネガポジ反転
```python
ImageOps.invert(img)
```
![nega.png](https://qiita-image-store.s3.amazonaws.com/0/140075/6e84c451-f751-d224-4be6-b9a63ebe0f86.png)


## 左右反転 / 上下反転

```python
ImageOps.mirror(img)    # 左右反転
ImageOps.flip(img)      # 上下反転
```
![mirror.png](https://qiita-image-store.s3.amazonaws.com/0/140075/2a7b9413-aece-5496-6ce4-069e96496cd3.png) ![flip.png](https://qiita-image-store.s3.amazonaws.com/0/140075/3ce5323e-f1fa-216e-8348-f92cca4c767d.png)

## カラー化
グレイスケール画像をピクセル値0を`black`に、ピクセル値255を`white`の色を着色します。

```python
gray = ImageOps.grayscale(img)
ImageOps.colorize(gray, black=(0, 0, 0), white=(255, 255, 0))
```

![mono.png](https://qiita-image-store.s3.amazonaws.com/0/140075/7c4230b5-8fa0-0546-047d-24fea2490b62.png)　→　![colorize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/23e85505-e3d8-6a32-d3fa-48afd7268a67.png)

## ポスタライズ 
画像のビット深度を引数の値に縮小して色を単純化します。

```python
ImageOps.posterize(img, 2)
```
![posterize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/3845fc73-5ead-143d-e195-7013e78005b1.png)

## ソーラライズ
しきい値を超えるすべてのピクセル値を反転させます。
使いどころは不明。

```python
ImageOps.solarize(img, 128)
```
![solarize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/c6481824-d066-76ec-44c5-5cfd6d4c2a3a.png)

## 平均化（イコライズ）
イメージのヒストグラムを均等化します。
入力画像に非線形マッピングを適用して、出力画像にグレースケール値の均一な分布を作成します。

```python
ImageOps.equalize(img)
```
![equalize.png](https://qiita-image-store.s3.amazonaws.com/0/140075/36448d44-be5f-f04f-90bd-6d633d6bc8e3.png)

# ImageChopsモジュール
`ImageChops`モジュールはチャンネルを操作するモジュールです。

![B9BSxGZmEQpmAAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/140075/aab8183a-2ea7-445a-28fe-85406f2b0f00.png) ![kL+ySM465ToAlAAAAAElFTkSuQmCC.png](https://qiita-image-store.s3.amazonaws.com/0/140075/f9d5991d-1090-21ee-c029-b1b48b47556f.png)

左が被エフェクト画像、右がエフェクト用画像です、この章ではこの２つの画像をサンプルとして使用していきます。

## 覆い焼き（リニア）/ 減算
```python
ImageChops.add(img, effect_img)         # img + effect_img
ImageChops.subtract(img, effect_img)    # img - effect_img
```
![wE7Grw7M8iQMgAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/140075/2dc58d5b-b3ff-6b86-470f-d83a5f5073ba.png) ![X+81Pf98mz2EwAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/140075/5e8d9011-2519-21b9-39c2-b50c4d31e200.png)

## mod演算
```python
ImageChops.add_modulo(img, effect_img)         # img + effect_img % MAX
ImageChops.subtract_modulo(img, effect_img)    # img - effect_img % MAX
```
![addm.png](https://qiita-image-store.s3.amazonaws.com/0/140075/1ca8d4dd-878c-0920-9830-e38c09e865fb.png) ![subm.png](https://qiita-image-store.s3.amazonaws.com/0/140075/d591af95-5202-e6c8-0943-8922f6b04129.png)

## 乗算 / スクリーン
```
ImageChops.multiply(img, effect_img)
ImageChops.screen(img, effect_img)
```
![8yo3kAAAAASUVORK5CYII=.png](https://qiita-image-store.s3.amazonaws.com/0/140075/4f2288f2-dbe2-9170-c1a6-7b29a98a72f1.png) ![P8BtLqhlqZTLrgAAAAASUVORK5CYII=.png](https://qiita-image-store.s3.amazonaws.com/0/140075/9d4f989f-cee7-a819-c8aa-f5cd19f5ba25.png)

## 比較（明）／比較（暗）
```
ImageChops.lighter(img, effect_img)
ImageChops.darker(img, effect_img)
```
![X+81Pf98mz2EwAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/140075/9ed4a37e-bb2f-e93e-5606-59ab936ccc93.png) ![wDMatqtY+79MgAAAABJRU5ErkJggg==.png](https://qiita-image-store.s3.amazonaws.com/0/140075/762c19be-c549-3900-de95-a9ec06907f2b.png)

## 差の絶対値
```python
ImageChops.difference(img, effect_img)
```
![diff.png](https://qiita-image-store.s3.amazonaws.com/0/140075/69d9c338-59ab-0ef1-c27a-0f12ea12b817.png)

## オフセット
```python
ImageChops.offset(img, 100, 100)
```
![offset.png](https://qiita-image-store.s3.amazonaws.com/0/140075/388aa659-b6ed-da57-bab9-b1f121b6ba54.png)


# ImageFilterモジュール
コンボリューション（畳み込み演算）を行います。
カーネルという行列を組み替えることにより、さまざまな画像変換を行います。

| パラメータ | 説明 |
|:-:|:--|
| size  | カーネルのサイズ  |
| scale |  行列演算後にこの値で除算 |
| offset  | 行列演算後にこの値で加算  |
|  kernel |  コンボリューション行列 |

#### 参考
https://github.com/python-pillow/Pillow/blob/6e7553fb0f12025306b2819b9b842adf6b598b2e/PIL/ImageFilter.py

## ImageFilter.BLUR
```python
img.filter(ImageFilter.BLUR)

# size: (5, 5),
# scale: 16,
# offset: 0,
# kernel:(
#     1,  1,  1,  1,  1,
#     1,  0,  0,  0,  1,
#     1,  0,  0,  0,  1,
#     1,  0,  0,  0,  1,
#     1,  1,  1,  1,  1
# )
```
![bluer.png](https://qiita-image-store.s3.amazonaws.com/0/140075/b8bc864e-1f4f-23f3-5c4a-d533bfa231de.png)

## ImageFilter.DETAIL
```python
img.filter(ImageFilter.DETAIL)

# size: (3, 3), 
# scale: 6,
# offset: 0,
# kernel: (
#     0, -1,  0,
#     -1, 10, -1,
#     0, -1,  0
# )
```
![detail.png](https://qiita-image-store.s3.amazonaws.com/0/140075/f240a30a-5c30-386d-0038-d1bef9c10ea7.png)

## ImageFilter.SHAPEN
```python
img.filter(ImageFilter.SHARPEN)

# size: (3, 3),
# scale: 16,
# offset: 0,
# kernel: (
#     -2, -2, -2,
#     -2, 32, -2,
#     -2, -2, -2
# )
```
 ![shapen.png](https://qiita-image-store.s3.amazonaws.com/0/140075/822c679a-a385-e30e-a8c2-b1a34babfb44.png)

## ImageFilter.CONTOUR
```python
img.filter(ImageFilter.CONTOUR)

# size: (3, 3),
# scale: 1,
# offset: 255,
# kernel: (
#     -1, -1, -1,
#     -1,  8, -1,
#     -1, -1, -1
# )
```

![contour.png](https://qiita-image-store.s3.amazonaws.com/0/140075/cb2f0ad5-2228-e734-d66a-a23cf75f2481.png)

## ImageFilter.EDGE_ENHANCE / ImageFilter.EDGE_ENHANCE_MORE
```python
img.filter(ImageFilter.EDGE_ENHANCE)

# size: (3, 3),
# scale: 2,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1, 10, -1,
#     -1, -1, -1
# )

img.filter(ImageFilter.EDGE_ENHANCE_MORE)

# size: (3, 3),
# scale: 1,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1,  9, -1,
#     -1, -1, -1
# )
```
![edge.png](https://qiita-image-store.s3.amazonaws.com/0/140075/8c29ea69-fd34-2223-925f-8c29aa3926c8.png) ![edge_more.png](https://qiita-image-store.s3.amazonaws.com/0/140075/7e61015e-a304-658a-5072-453a1a30359d.png)

## ImageFilter.EMBOSS
```python
img.filter(ImageFilter.EMBOSS)

# size: (3, 3),
# scale: 1,
# offset: 128,
# kernel: (
#     -1,  0,  0,
#     0,  1,  0,
#     0,  0,  0
# )
```
![emboss.png](https://qiita-image-store.s3.amazonaws.com/0/140075/0dc126e8-d56c-dc6f-f8d0-844cbc44c95f.png)

## ImageFilter.FIND_EDGES
```python
img.filter(ImageFilter.FIND_EDGES)

# size: (3, 3),
# scale: 1,
# offset: 0,
# kernel: (
#     -1, -1, -1,
#     -1,  8, -1,
#     -1, -1, -1
# )
```
![edge.png](https://qiita-image-store.s3.amazonaws.com/0/140075/0228b9ee-f94b-0b92-2165-a01017b9f4c2.png)

## ImageFilter.SMOOTH / ImageFilter.SMOOTH_MORE
```python
img.filter(ImageFilter.SMOOTH)
# size: (3, 3),
# scale: 13,
# offset: 0,
# kernel: (
#     1,  1,  1,
#     1,  5,  1,
#     1,  1,  1
# )
# 

img.filter(ImageFilter.SMOOTH_MORE)
# size: (5, 5),
# scale: 100,
# offset: 0,
# kernel: (
#     1,  1,  1,  1,  1,
#     1,  5,  5,  5,  1,
#     1,  5, 44,  5,  1,
#     1,  5,  5,  5,  1,
#     1,  1,  1,  1,  1
# )
```
![smooth.png](https://qiita-image-store.s3.amazonaws.com/0/140075/6bcf450c-4afc-e2a5-d5dc-0b965eb7ca11.png) ![smooth_more.png](https://qiita-image-store.s3.amazonaws.com/0/140075/260ca224-27cd-abb7-4815-0831816589f2.png)


## ガウシアンブラー
[ガウシアンぼかし](
https://ja.wikipedia.org/wiki/%E3%82%AC%E3%82%A6%E3%82%B7%E3%82%A2%E3%83%B3%E3%81%BC%E3%81%8B%E3%81%97)により画面の平滑化します。

```python
img.filter(ImageFilter.GaussianBlur(1.0))
img.filter(ImageFilter.GaussianBlur(1.5))
img.filter(ImageFilter.GaussianBlur(3.0))
```
![gb10.png](https://qiita-image-store.s3.amazonaws.com/0/140075/c51dc198-d788-1c9c-257e-e04120d6357b.png) ![gb15.png](https://qiita-image-store.s3.amazonaws.com/0/140075/d3fc4f79-c0b9-0b8f-ef18-c8fcd7d27fb2.png) ![gb30.png](https://qiita-image-store.s3.amazonaws.com/0/140075/8d07452c-7795-ba6b-5a35-54b5090272ce.png)

## 膨張 / 収縮
`MaxFilter`はDilation(膨張)、`MinFilter`はErosion（収縮）と呼ばれるものになります。

```python
img.filter(ImageFilter.MinFilter())
img.filter(ImageFilter.MaxFilter())
```
![min.png](https://qiita-image-store.s3.amazonaws.com/0/140075/829a9ad0-17b9-041a-7a2d-110ed71d6b08.png) ![max.png](https://qiita-image-store.s3.amazonaws.com/0/140075/f02d1707-2fec-5399-4f4d-ffa4349121b4.png) 

#### 参考
[膨張・収縮・オープニング・クロージング](http://imagingsolution.blog107.fc2.com/blog-entry-101.html)

## メディアンフィルタ
`MedianFilter`はノイズ除去によく使われます、ガウシアンフィルタに比べ輪郭があまりボケません。

```python
img.filter(ImageFilter.MedianFilter())
```

![noise.png](https://qiita-image-store.s3.amazonaws.com/0/140075/3ca276a1-e075-3edc-e44e-bdd316d9b8c5.png) → ![median.png](https://qiita-image-store.s3.amazonaws.com/0/140075/cd1307b2-fb8b-dfc3-c463-6e79f3f0ef01.png)

#### 参考
[ノイズ除去](https://jp.mathworks.com/help/images/noise-removal.html)

## モードフィルタ
指定されたサイズのボックスで最も頻繁に使用されるピクセル値を選択します。1回または2回だけ発生するピクセル値は無視されます。
~~使いどころ不明。~~[PillowのModeFilterを使って写真を絵画調にする](http://qiita.com/pashango2/items/8c7ce64c479de86ed718)を参照してください。

```python
img.filter(ImageFilter.ModeFilter(5))
```
![rank.png](https://qiita-image-store.s3.amazonaws.com/0/140075/7fb204a7-5c04-9b08-b173-f4695c4ac4c7.png)


# ImageEnhanceモジュール
## カラーバランス調整
```python
enhancer = ImageEnhance.Color(img)
enhancer.enhance(0.0)    # 白黒
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    # 元画像
```
![col0.png](https://qiita-image-store.s3.amazonaws.com/0/140075/a42bd9fd-b0e1-8e5a-c286-761e05d88062.png) ![col05.png](https://qiita-image-store.s3.amazonaws.com/0/140075/d430bf9a-fde2-d1c7-5279-d862ba801ddb.png) ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/140075/511c86ce-9bc7-be06-e7bb-2018c58d486c.png)




## コントラスト調整
```python
enhancer = ImageEnhance.Contrast(img)
enhancer.enhance(0.0)    # 灰色画像
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    # 元画像
```
![con0.png](https://qiita-image-store.s3.amazonaws.com/0/140075/e592d305-83f1-ac48-90a2-648d54df2c91.png) ![con5.png](https://qiita-image-store.s3.amazonaws.com/0/140075/e2bf3b93-ff4e-44ba-03ae-5d0973d382d5.png) ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/140075/511c86ce-9bc7-be06-e7bb-2018c58d486c.png)


## 明るさ調整
```python
enhancer = ImageEnhance.Brightness(img)
enhancer.enhance(0.0)    # 黒画像
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    # 元画像
```

![br0.png](https://qiita-image-store.s3.amazonaws.com/0/140075/7e42032b-2af8-0ef7-3400-8d4150a31203.png) ![br5.png](https://qiita-image-store.s3.amazonaws.com/0/140075/259a4975-227e-fbf6-c16e-9f49a3ef9888.png)  ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/140075/511c86ce-9bc7-be06-e7bb-2018c58d486c.png)



## シャープネス調整
```python
enhancer = ImageEnhance.Sharpness(img)
enhancer.enhance(0.0)    # ボケ画像
enhancer.enhance(0.5)    #  ↕
enhancer.enhance(1.0)    # 元画像
enhancer.enhance(1.5)    #  ↕
enhancer.enhance(2.0)    # シャープ画像
```
![sha0.png](https://qiita-image-store.s3.amazonaws.com/0/140075/9863e214-719c-fd29-5e6e-8ff09c3c2b04.png)  ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/140075/511c86ce-9bc7-be06-e7bb-2018c58d486c.png) ![sample.png](https://qiita-image-store.s3.amazonaws.com/0/140075/511c86ce-9bc7-be06-e7bb-2018c58d486c.png)

# ImageMathモジュール
`ImageMath`モジュールはピクセル同士の演算をあたかも数値演算のように書けるモジュールです。
使いこなせば複雑な画像処理も簡単に書けるようになります。

- シングルバンドのみ演算可能、マルチバンドはImage.splitで分割してから処理
- floatで演算する時の値の範囲は0.0～1.0ではなく0.0～255.0
- 演算中のImageはmodeが"I"(int)、または"F"(float)になる、最後にmodeを"L"にコンバートする
- 各種演算(+,-,\*,/,*\*,%)はピクセル処理ではなくイメージまるごとの演算
- 演算はピクセルごとではなくImageごとに行われ、演算ごとにImageが生成される
- 演算のかわりに`lambda`などのコール可能オブジェクトも渡せる

シングルバンドしか処理できないため、RGBなどのイメージを変換するときに面倒なので、以下のようなヘルパー関数を用意しておくとよいでしょう。

```python
def _blend_f(img1, img2, func):
    blend_eval = "convert(func(float(a), float(b)), 'L')"
    bands = [
        ImageMath.eval(
            blend_eval,
            a=a,
            b=b,
            func=func
        )
        for a, b in zip(img1.split(), img2.split())
    ]
    return Image.merge(img1.mode, bands)
```

#### 参考
[PIL/Pillowで高速にPhotoShopなどの描画モードを実装する](http://qiita.com/pashango2/items/3c99489ebccd468ab454)


## オーバーレイ
```python
def _over_lay(a, b):
    _cl = 2 * a * b / 255
    _ch = 2 * (a + b - a * b / 255) - 255
    return _cl * (a < 128) + _ch * (a >= 128)

_blend_f(img, effect_img, _over_lay)
```
![overlay.png](https://qiita-image-store.s3.amazonaws.com/0/140075/570a1743-0fcc-4aa8-4a76-a469e6b00abf.png)

## ソフトライト
```python
def _soft_light(a, b):
    _cl = (a / 255) ** ((255 - b) / 128) * 255
    _ch = (a / 255) ** (128 / b) * 255
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img, effect_img, _soft_light)
```
![softlight.png](https://qiita-image-store.s3.amazonaws.com/0/140075/2572e2bb-445d-f0dc-ee6d-dd533337bb4b.png)

## ハードライト
```python
def _hard_light(a, b):
    _cl = 2 * a * b / 255
    _ch = 2.0 * (a + b - a * b / 255.0) - 255.0
    return _cl * (b < 128) + _ch * (b >= 128)

_blend_f(img, effect_img, _hard_light)
```
![hardlight.png](https://qiita-image-store.s3.amazonaws.com/0/140075/a01426b8-a2f7-ec13-2c89-21d585f204dc.png)

# Photoshopの描画モードを使う

Photoshopの描画モードを実装した「Image4Layer」というモジュールを作成しています。

https://github.com/pashango2/Image4Layer

インストールはpipで簡単に行なえます、実行にはpillow(PIL)があらかじめインストールされている必要があります。

```
$pip install image4layer
```

使い方は簡単です、color-dodgeモードで合成する例です。

```python
from PIL import Image
from image4layer import Image4Layer

source = Image.open("ducky.png")
backdrop = Image.open("backdrop.png")

Image4Layer.color_dodge(backdrop, source)
```

![color_dodge.png](https://qiita-image-store.s3.amazonaws.com/0/140075/eaefde56-a08a-01ff-4777-14bc7d78d0c1.png)


# GIFの書き込み
複数のGIF（GIFアニメーション）を書き込むことができます。

```python
im.save(out, save_all=True, append_images=[im1, im2, ...])
```

簡単なアニメーションGIFの作成例です。

```
imgs = []
for i in range(100):
    imgs.append(img.point(lambda x: x * (1.0 - (i/100))))
    
img.save("anime.gif", save_all=True, append_images=imgs, loop=True)
```

![animation.gif](https://qiita-image-store.s3.amazonaws.com/0/140075/c4e3b1be-4f1e-536c-99a7-e27d33d5aac1.gif)


#### 参考
http://pillow.readthedocs.io/en/4.0.x/handbook/image-file-formats.html?highlight=seek#saving

# QImagaeへの変換

PyQtのQImageへの変換は`ImageQt`モジュールを使用します。

```python
ImageQt.ImageQt(img)
```

なお、PySideを使っている場合は、下記の方法が良いでしょう。

```python
from PySide.QtGui import *
import io

img_buffer = io.BytesIO()
base.save(img_buffer, "BMP")
qimage = QImage()
qimage.loadFromData(img_buffer.getvalue(), "BMP")
```

無駄な処理なように見えますが、RGB→BGR変換やY軸の反転問題など面倒な部分をPillow/PySideがやってくれます。

#### 参考
[PIL.Image と PyQt4.QtGui.QImageの相互変換](http://doloopwhile.hatenablog.com/entry/20100305/1267782841)

# PSNR

PSNRと2つの画像を比較する指標値です。
現在ではPSNRよりもSSIMが良いとされていますがPSNRもよく使用されます。
値が高いほうが画質が良く、圧縮の劣化度を測る際にPSNRが30～50の間が標準的な品質とされています。

計算式は以下の通り、MSEは平均二乗誤差で、MAXは255です。

```math
PSNR = 10 \times \log 10\frac{MAX^2}{MSE}
```

PSNRを求める関数は以下のようになります、`ImageStat`モジュールを使う事により高速にPSNRを求める事ができます。

```python
def psnr(img1, img2):
    diff_img = ImageChops.difference(img1, img2)
    stat = ImageStat.Stat(diff_img)
    mse = sum(stat.sum2) / len(stat.count) / stat.count[0]
    return 10 * math.log10(255 ** 2 / mse)
```

なお、SSIMはPIL/Pillowで高速に求めるのは現状では難しいです、[pyssim](https://github.com/jterrace/pyssim)モジュールを使うか、OpenCVを使ったほうがよいでしょう。

#### 参考
[ピーク信号対雑音比 - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%94%E3%83%BC%E3%82%AF%E4%BF%A1%E5%8F%B7%E5%AF%BE%E9%9B%91%E9%9F%B3%E6%AF%94)

# 線画抽出

```python
gray = img.convert("L")
gray2 = gray.filter(ImageFilter.MaxFilter(5))
senga_inv = ImageChops.difference(gray, gray2)
senga = ImageOps.invert(senga_inv)
```

![senga.png](https://qiita-image-store.s3.amazonaws.com/0/140075/695b4d30-5eb9-5b38-1dd6-7426ad42ad85.png)

[こちら](http://www.mathgram.xyz/entry/cv/contour)の方法を参考にしました、非常に素晴らしいです。