webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

pythonによる画像処理入門

はじめに

今回はpythonによる画像処理のお話です。

普段仕事ではRubyメインなのですが、最近趣味でpythonを勉強しはじめ、画像を加工するのが意外と簡単だと分かったので、簡単な画像処理について書いてみました。 pythonについてはまだまだ勉強中なので、python的にはこうは書かない、これだと処理が遅い、コードが汚いなどなどありましたら、コメントで教えていただけると幸いです。

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

画像処理の概要

画像の加工は単純に各ピクセルの色を画像の端から端まで順番に変更することで実現しています。

そこで、まずは画像ファイルを読み込み、操作しやすいように大きさが 画像の高さ × 画像の幅 × 3 の3次元配列に変換します。

このようにすることで、例えば画像の左から100番目、上から200番目のピクセルの色は、 img_pixels[100][200]として取得できるようになります。取得した値は要素数が3の配列[r,g,b]になります。

まず最初に最低限のpillow(PIL)の使い方と画像加工で用いる共通の処理を説明します。

画像の読み込み、新規作成

行頭で必要なライブラリをインポートします。

わかりやすいように使用する画像は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))

画像ファイルの配列化

読み込んだオリジナル画像を配列に変換します。

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)

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

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

ピクセルの値の取得

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

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

ピクセルへの値のセット

加工後の画像オブジェクトimg2の左から100番目、上から200番目のピクセルへ色をセットするには以下のように putpixelメソッドを使います。以下の例では青色にセットしています。

赤色にセットするには後ろ3つの引数を255,0,0とすればいいです。

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

表示と保存

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

img2.show()

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

img2.save('edited_img.jpg')

以上のメソッドを使用して実際に画像を加工してみます。

画像加工に使用するオリジナル画像は以下のものを使います。

f:id:s-uotani-zetakansu:20170816000945j:plain

フリー画像サイトのぱくたそさん( https://www.pakutaso.com/ )の画像を使わせていただきました。

ぼかし加工

画像全体が少しかすんだような、ぼかし加工を実装します。

フィルターサイズ分画像が小さくなってしまうのが難点です。

周りの色との平均の色を加工後画像に書き込んでいくことでにじませたような画像を作成します。

本当は起点からみて上下左右にある色との平均を取りたかったのですが、簡単のため右下方向にある色との平均をとっています。

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)])

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')

以下が出力される画像です。

f:id:s-uotani-zetakansu:20170816001031j:plain

モザイク

画像全体にモザイクがかかったような画像を生成します。

縦横フィルターサイズ分の部分画像の中で、一番濃い色で部分画像と同じ大きさの(単色の)画像を作成します。

作成した単色の画像を加工後画像に次々とはめ込んでいきモザイク画像を作成します。

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')

以下が出力結果になります。

4重ループがあるので気持ち悪いですが、これくらいの大きさの画像なら数秒で表示されました。

f:id:s-uotani-zetakansu:20170816001121j:plain

色反転

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

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')

以下が生成された画像です。

元の画像が綺麗だからか、恐ろしげではありますが、反転しても綺麗ですね。 f:id:s-uotani-zetakansu:20170816001223j:plain

まとめ

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

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

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

追記

この記事の続編を書いています。画像処理に興味を持っていただいた方は、お進みください。

読者登録していただけるとはげみになります。よろしくお願いします