webエンジニアの日常

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

python画像処理入門4 ミニチュア風画像をつくる

こんにちは、エンジニアのさもです。

前回は塗り絵風の画像を生成してみました。

今回はミニチュア風の画像を作ってみたいと思います。

目次

スポンサーリンク

はじめに

今回は以下の画像を加工してみます。

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

この画像に「彩度を上げるフィルタ」と、「画面の上下を少しぼかすフィルタ」をかけることによって、ミニチュア風の画像に加工していきます。

加工後はこのようになります。

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

どうでしょうか?

お、良い感じにミニチュアっぽい!と思った方は続きをお読みください。

ん~微妙だなと思った方は、続きをお読みになって改善点を教えてください。

実装

それでは実装していきます

外枠

まずは例によって外枠だけを実装していきます。

from PIL import Image, ImageDraw
import numpy as np

# 平均化フィルタをかけた画像を返す
def unclear_filter(img, w, h):
  return img

# 彩度を上げた画像を返す
def clear_filter(img, w, h):
  return img

# 画像ファイルの読み込み
filename = "ina.jpg"
img = Image.open("./" + filename).convert("RGB")
w, h = img.size

# 画像の彩度を上げる
clear_img = clear_filter(img, w, h)

# 画像の上下をぼかす
unclear_img = unclear_filter(clear_img, w, h)

# 加工した画像を保存
unclear_img.save("./mini_" + filename)

解説

  • clear_filterメソッドは、引数の画像の彩度を上げる加工を行い、加工後の画像を返します。
  • unclear_filterメソッドは、引数の画像の上部と下部だけをぼかし加工し、加工後の画像を返します。

彩度を上げるフィルタ

ミニチュアって、絵の具やペンキでべた塗りしたみたいな色合いですよね。

そこで、彩度を上げる、すなわち色をはっきりさせるような加工を施します。

このような加工は、画像の各画素に次の行列をかけることで実現できます.

{\displaystyle
  \begin{pmatrix}
    K_1 & K_2 & K_2\\\
    K_2 & K_1 & K_2\\\
    K_2 & K_2 & K_1
  \end{pmatrix}\\\
  K_1 = 2.0, K_2 = -0.5
}

すなわち、

{\displaystyle
  \begin{pmatrix}
    new\_r \\\ new\_g \\\ new\_b
  \end{pmatrix}
  =
  \begin{pmatrix}
    K_1 & K_2 & K_2\\\
    K_2 & K_1 & K_2\\\
    K_2 & K_2 & K_1
  \end{pmatrix}
  \begin{pmatrix}
    r \\\ g \\\ b
  \end{pmatrix}
}

要するに、次のような変換です。

{\displaystyle
new\_r = K_1 * r + K_2 * g + K_2 * b\\\
new\_g = K_2 * r + K_1 * g + K_2 * b\\\
new\_b = K_2 * r + K_2 * g + K_1 * b
}

 K_1, K_2はそれぞれ彩度を調節するパラメータです。いろいろ変えて実験してみてください。

以下が、実装したコードです。

def clear_filter(img, w, h):
  # STEP1
  k1 = 2.0
  k2 = -0.5
  image_pixcels = np.array([[img.getpixel((x,y)) for x in range(w)] for y in range(h)])
  filtered_img = Image.new('RGB', (w, h))
  # STEP2
  for x in range(w):
    for y in range(h):
      r, g, b = image_pixcels[y][x]
      fr = int(min(255, max(0, r * k1 + g * k2 + b * k2)))
      fg = int(min(255, max(0, r * k2 + g * k1 + b * k2)))
      fb = int(min(255, max(0, r * k2 + g * k2 + b * k1)))
      filtered_img.putpixel((x,y), (fr, fg, fb))
  return filtered_img

解説

  • STEP1
    • 変数やパラメータの定義を行っています
    • k1, k2は上でかいた K_1, K_2になります
  • STEP2
    • 画像中の全ての画素に対して、変換を行いなす
    • r * k1 + g * k2 + b * k2 というのが上で書いた変換 new\_r = K_1 * r + K_2 * g + K_2 * bに対応しています
    • すべての画素を変換すると、変換後の画像を返します

ぼかしフィルタ

毎度おなじみのぼかしフィルタですが、今回は、全体にかけるのではなく、部分的にかけていきます。

そこで、ぼかし処理を行うunclear_filterメソッドと、ぼかしフィルタのサイズを計算するfilter_sizeを実装します。

unclear_filterメソッドは、引数の画像に対してぼかし処理をした画像を返すメソッドです。

# フィルタのサイズを計算
def filter_size(w, h, x, y):
  size = 0
  if y < h * 2 / 3 or y > h * 10 / 11:
    size = 1
  if y < h / 2:
    size = 2
  if y < h / 3:
    size = 3
  return size

# 平均化フィルタをかけた画像を返す
def unclear_filter(img, w, h):
  # STEP1
  image_pixcels = np.array([[img.getpixel((x,y)) for x in range(w)] for y in range(h)])
  filtered_img = Image.new('RGB', (w, h))
  # STEP2
  for x in range(w):
    for y in range(h):
      # STEP2-1
      s = filter_size(w, h, x, y)
      # STEP2-2
      if s != 0:
        x1 = max(0    , x - s)
        x2 = min(x + s, w - 1)
        y1 = max(0    , y - s)
        y2 = min(y + s, h - 1)
        x_size = x2 - x1 + 1
        y_size = y2 - y1 + 1
        mean_r, mean_g, mean_b = image_pixcels[y1:y2 + 1, x1:x2 + 1].reshape(x_size * y_size, 3).mean(axis = 0)
      else:
        mean_r, mean_g, mean_b = image_pixcels[y][x]
      filtered_img.putpixel((x,y), (int(mean_r), int(mean_g), int(mean_b)))
  return filtered_img

解説

  • STEP1
    • 変数を定義しています
  • STEP2
    • 全ての画素を走査し、画面の上部または下部のみぼかし処理を行います。
  • STEP2-1
    • 今回は画像の上から高さの2/3、下から高さの10/11の領域にぼかしフィルタをかけています。
    • また、上から高さの半分の領域はさらに強いぼかしをかけるようにしています
    • それ以外の領域では、0を返すようにしています
  • STEP2-2
    • filter_sizeメソッドの返り値の大きさのぼかしフィルタをかけています
    • ただし、filter_sizeメソッドが0を返した場合は、ぼかしフィルタの処理をスキップしています
    • 加工する画像によって、フィルタをかける領域は変えてください

実行

上記のコードをmini.pyなどの名前で保存しておきます。

python mini.py

で実行され、しばらくすると、mini_元画像のファイル名 という名前のファイルが作られます。

その他の実行例

他の画像での実行結果を載せておきます。

  • 元画像1

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

  • ミニチュア風画像1

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

  • 元画像2

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

  • ミニチュア風画像2

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

参考書籍

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

入門 Python 3

入門 Python 3

ディジタル画像処理[改訂新版]

ディジタル画像処理[改訂新版]

最後に

いかがでしたでしょうか。

写真によってうまくいくものと、あんまりうまくいかないものがあります。

森や町を俯瞰したものや、スポーツしてる人を遠くから取った写真は比較的うまくいきます。

人の顔や近い物体はあんまり変わらないですね。

ミニチュア風画像を作ろうと思ったらフォトショップでされるのが一般的かと思いますが、pythonでもできるんだな~と思っていただければと思います。