Help us understand the problem. What is going on with this article?

初めてのPython画像処理

More than 1 year has passed since last update.

はじめに

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

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

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

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

画像処理の概要

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

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

行頭で必要なライブラリをインポートします。
わかりやすいように使用する画像はpyファイルと同じフォルダに置いてあります。

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

画像ファイルの配列化

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

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(height)] for i in range(width)])

各ピクセルの値の取得

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

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

以上のメソッドを使用して実際に画像を加工してみます。
画像加工に使用するオリジナル画像は以下のものを使います。
original.jpg
フリー画像サイトのぱくたそさん( https://www.pakutaso.com/ )の画像を使わせていただきました。

ぼかし加工

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

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

モザイク

画像全体にモザイクがかかったような画像を生成します。
縦横フィルターサイズ分の部分画像の中で、一番濃い色で部分画像と同じ大きさの(単色の)画像を作成します。
作成した単色の画像を加工後画像に次々とはめ込んでいきモザイク画像を作成します。

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

以下が出力結果になります。
4重ループがあるので気持ち悪いですが、これくらいの大きさの画像なら数秒で表示されました。
mozaiku.jpg

色反転

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

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

まとめ

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

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

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

追記

以下のリンク先サイトに、この記事の続編を書きました。
もっといろんなことをやってみたいと思った方はぜひ訪問してみてください
k平均法による減色処理
塗り絵画像の生成
ミニチュア風画像をつくる
アンシャープマスキング
2値化と3つの閾値
繰り返し部分の高速化
膨張と縮小

uosansatox
関西在住のWebエンジニアです。Ruby,Rails歴3年です。 趣味でProcessingやp5.jsで作品を作っており、Processing(今はp5.jsのみ)の入門サイトを作りました。 https://processing-fan.firebaseapp.com/ 主にRuby/RailsやJavaScript、数学系の技術ネタを書いています。
https://www.uosansatox.biz/
maplesystems
エンジニアの未来を考えるサービスを開発・運営するスタートアップ
http://www.maplesystems.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした