初めてのPython画像処理

## はじめに
初めまして、今回がQiita初投稿となります。

普段仕事ではRubyメインなのですが、最近趣味でpythonを勉強しはじめ、画像を加工するのが意外と簡単だと分かったので、簡単な画像処理について書いてみました。私のブログ（http://www.uosansatox.biz/ ）の方に、この記事とその続編を書きました。

pythonについてはまだまだ勉強中なので、python的にはこうは書かない、これだと処理が遅い、コードが汚いなどなどありましたら、コメントで教えていただけると幸いです。

また、以下のコードではpythonのライブラリ、numpy, pillowを使用しています。
著者の環境ではwindows10上でAnaconda3を使用しているため別途インストールは不要でしたが、実行する際は必要に応じてインストールをお願いします。

## 画像処理の概要
画像の加工は単純に各ピクセルの色を画像の端から端まで順番に変更することで実現しています。
そこで、まずは画像ファイルを読み込み、操作しやすいように大きさが 画像の高さ × 画像の幅 × 3 の３次元配列に変換します。
このようにすることで、例えば画像の左から100番目、上から200番目のピクセルの色は
img_pixels[100][200]として取得できるようになります。取得した値は要素数が３の配列```[r,g,b]```になります。
まず最初に最低限のpillow(PIL)の使い方と画像加工で用いる共通の処理を説明します。
### 画像の読み込み、新規作成
行頭で必要なライブラリをインポートします。
わかりやすいように使用する画像はpyファイルと同じフォルダに置いてあります。

```python:edit_img.py
from PIL import Image
import numpy as np

# 元となる画像の読み込み
img = Image.open('original.jpg')
#オリジナル画像の幅と高さを取得
width, height = img.size
# オリジナル画像と同じサイズのImageオブジェクトを作成する
img2 = Image.new('RGB', (width, height))
```

### 画像ファイルの配列化
読み込んだオリジナル画像を配列に変換します。

```python
img_pixels = []
for y in range(height):
  for x in range(width):
    # getpixel((x,y))で左からx番目,上からy番目のピクセルの色を取得し、img_pixelsに追加する
    img_pixels.append(img.getpixel((x,y)))
# あとで計算しやすいようにnumpyのarrayに変換しておく
img_pixels = np.array(img_pixels)
```

ちなみに、以下のように一行で書くこともできます。

```python:
img_pixels = np.array([[img.getpixel((i,j)) for j in range(height)] for i in range(width)])
```


### 各ピクセルの値の取得
上記でも書きましたが、各ピクセルの色の取得は以下のようにします

```python
img_pixels[100][200]
# => array([255,255,255])
```

### ピクセルへの値のセット
加工後の画像オブジェクトimg2の左から100番目、上から200番目のピクセルへ色をセットするには以下のように
putpixelメソッドを使います。以下の例では青色にセットしています。
赤色にセットするには後ろ3つの引数を255,0,0とすればいいです。

```python
img2.putpixel((100, 200), (0, 0, 255))
```

### 表示と保存
編集した画像インスタンスを表示するにはshowメソッドを使います。

```python
img2.show()
```

保存する場合はsaveメソッドを使用します。

```python
img2.save('edited_img.jpg')
```

以上のメソッドを使用して実際に画像を加工してみます。
画像加工に使用するオリジナル画像は以下のものを使います。
![original.jpg](https://qiita-image-store.s3.amazonaws.com/0/82541/e48b3230-9e30-aa1f-c018-e83a4a47a176.jpeg)
フリー画像サイトのぱくたそさん( https://www.pakutaso.com/ )の画像を使わせていただきました。

## ぼかし加工
画像全体が少しかすんだような、ぼかし加工を実装します。
フィルターサイズ分画像が小さくなってしまうのが難点です。
周りの色との平均の色を加工後画像に書き込んでいくことでにじませたような画像を作成します。
本当は起点からみて上下左右にある色との平均を取りたかったのですが、簡単のため右下方向にある色との平均をとっています。

```python:bokashi.py
from PIL import Image
import numpy as np

img = Image.open('original.jpg')
width, height = img.size
filter_size = 20
img2 = Image.new('RGB', (width - filter_size, height - filter_size))
img_pixels = np.array([[img.getpixel((x,y)) for x in range(width)] for y in range(height)])

filter_size = 20

for y in range(height - filter_size):
  for x in range(width - filter_size):
    # 位置(x,y)を起点に縦横フィルターサイズの小さい画像をオリジナル画像から切り取る            
    partial_img = img_pixels[y:y + filter_size, x:x + filter_size]
    # 小さい画像の各ピクセルの値を一列に並べる
    color_array = partial_img.reshape(filter_size ** 2, 3)
    # 各R,G,Bそれぞれの平均を求めて加工後画像の位置(x,y)のピクセルの値にセットする
    mean_r, mean_g, mean_b = color_array.mean(axis = 0)
    img2.putpixel((x,y), (int(mean_r), int(mean_g), int(mean_b)))

img2.show()
img2.save('bokashi.jpg')
```

以下が出力される画像です。
![bokashi.jpg](https://qiita-image-store.s3.amazonaws.com/0/82541/b2830a11-ee6b-2dbf-f696-de4a87c47ccd.jpeg)
## モザイク
画像全体にモザイクがかかったような画像を生成します。
縦横フィルターサイズ分の部分画像の中で、一番濃い色で部分画像と同じ大きさの(単色の)画像を作成します。
作成した単色の画像を加工後画像に次々とはめ込んでいきモザイク画像を作成します。

```python:mozaiku.py
from PIL import Image
import numpy as np

img = Image.open('original.jpg')
width, height = img.size
filter_size = 10
img2 = Image.new('RGB', (width, height))
img_pixels = np.array([[img.getpixel((x,y)) for x in range(width)] for y in range(height)])

# 
def draw_partial_img(img2, start_x, start_y, partial_size_x, partial_size_y, pixel_color):
  for y in range(start_y, start_y + partial_size_y):
    for x in range(start_x, start_x + partial_size_x):
      img2.putpixel((x, y), pixel_color)

for y in range(0, height, filter_size):
  for x in range(0, width, filter_size):
    # ぼかし加工同様に画像の一部分を切り出す
    partial_img = img_pixels[y:y + filter_size, x:x + filter_size]
    # 色の配列になるように変換する
    color_array = partial_img.reshape(partial_img.shape[0] * partial_img.shape[1], 3)
    # 各ピクセルごとのr + g + bが最大値を取る物の番号を取得する
    # ようするに切り出した画像の中で一番濃い色の番号
    max_index = np.argmax(color_array.sum(axis=1))
    max_r, max_g, max_b = color_array[max_index]
    # (x,y)を起点に縦横フィルターサイズで単色(上記の色)の画像をimg2へセットする
    draw_partial_img(img2, x, y, partial_img.shape[1], partial_img.shape[0], (max_r, max_g, max_b))

img2.show()
img2.save('mozaiku.jpg')
```

以下が出力結果になります。
４重ループがあるので気持ち悪いですが、これくらいの大きさの画像なら数秒で表示されました。
![mozaiku.jpg](https://qiita-image-store.s3.amazonaws.com/0/82541/f793c6d4-f955-ae33-97c7-ccf943630eef.jpeg)

## 色反転
ネガのような色が反転した画像を生成します。


```python:hanten.py
from PIL import Image
import numpy as np

img = Image.open('original.jpg')
width, height = img.size
img2 = Image.new('RGB', (width, height))
img_pixels = np.array([[img.getpixel((x,y)) for x in range(width)] for y in range(height)])

# 色を反転する
reverse_color_pixels = 255 - img_pixels
for y in range(height):
  for x in range(width):
    # 反転した色の画像を作成する
    r,g,b = reverse_color_pixels[y][x]
    img2.putpixel((x,y), (r,g,b))

img2.show()
img2.save('hanten.jpg')
```
以下が生成された画像です。
元の画像が綺麗だからか、恐ろしげではありますが、反転しても綺麗ですね。
![hanten.jpg](https://qiita-image-store.s3.amazonaws.com/0/82541/26db64f3-ee2d-470e-4798-50185730cd59.jpeg)

## まとめ
実際はpillowに色反転させるメソッドが準備されていたりするのですが、今回はpython練習のためnumpyの手を借りながら画像加工を実装してみました。

至らぬ文章ではありましたが、最後までお読みいただきありがとうございました。

もうすこしやりたいことがあるのでまた追記するかもしれません。

## 追記
以下のリンク先サイトに、この記事の続編を書きました。
もっといろんなことをやってみたいと思った方はぜひ訪問してみてください
[k平均法による減色処理](http://www.uosansatox.biz/entry/2017/08/28/210000)
[塗り絵画像の生成](http://www.uosansatox.biz/entry/2017/08/29/073000)
[ミニチュア風画像をつくる](http://www.uosansatox.biz/entry/2017/08/30/132710)
[アンシャープマスキング](http://www.uosansatox.biz/entry/2017/09/01/150353)
[2値化と３つの閾値](http://www.uosansatox.biz/entry/2017/09/04/150909)
[繰り返し部分の高速化](http://www.uosansatox.biz/entry/2017/09/08/140041)
[膨張と縮小](http://www.uosansatox.biz/entry/2017/09/11/175147)