PILのImage.transformの使い方 (AFFINE)

PILのImage.transformの使い方 (EXTENT) - umejanのブログのつづき。

Image.AFFINEの変形

拡大縮小、回転、移動、平行四辺形などに変換できる。

ネットでAFFINE変換を検索するとどんな変換かわかる。

Image.AFFINEのdata指定方法

6 tupples(a, b, c, d, e, f)

以下の要領で変換する。

x0 = ax + by + c
y0 = dx + ey + f

(x,y)は変換先の座標。(x0,y0)は変換元の座標。

で、以下の行列を使った式で表現できる。

{
  \begin{pmatrix}
   x0 \\
   y0 \\
   1
 \end{pmatrix}
    = 
    \begin{pmatrix}
      a & b & c \\
      d & e & f \\
      0 & 0 & 1
    \end{pmatrix}
  \begin{pmatrix}x \\ y \\ 1 \end{pmatrix}
}

元の画像から見る場合は、この行列の逆行列を求めればいい。

Image.AFFINEのサンプル

平行四辺形に変形するサンプルと
拡大された画像を必要に応じて縮小して画像サイズに収めるサンプル。

# -*- coding: utf-8 -*-
from __future__ import print_function
from PIL import Image
from PIL import ImageDraw
import numpy as np
import math
import argparse

parser = argparse.ArgumentParser(description="Image.transformのサンプル")

parser.add_argument("--output","-o", required=True, type=str
                   , help="出力ファイル")

args = parser.parse_args()

# 変換元の画像をロード
def load_image() :
  #im = Image.open("xxxx.png")

  # サンプルとして、41x33の画像を作成。グレースケール256階調
  # 8ドットごとに格子を描画
  im = Image.new("L", (41, 33), color=0)
  draw = ImageDraw.Draw(im)
  for i in range(6) :
    draw.line([(i*8, 0), (i*8, 32)], fill=255, width=1)
  for i in range(5) :
    draw.line([(0, i*8), (40, i*8)], fill=255, width=1)
  
  # 四角を左上の格子の中に描画
  draw.rectangle([3, 3, 6, 6], fill=255)
  
  del draw
  return im


im = load_image()


#
# Image.AFFINE
# 
# data : 6 tupples(a, b, c, d, e, f)
# 
# AFFINE変換
# 座標(ax + by + c, dx + ey + f)で変換
#   f(x) = Ax + t (Aは行列。x, tはベクトル)
#   写像先平面から見た元の平面への行列かな?
#
# たぶん、下記のイメージ。
# (x0) = ( a b )(xnew) + (c)
# (y0)   ( d e )(ynew)   (f)
# 
# 元の座標系から見た場合だと
# 写像先への座標は
# (xnew) = ( a0 b0 )(x0) + (c0)
# (ynew)   ( d0 e0 )(y0)   (f0)
# 
# detA0 = det(A^(-1)) = a0*e0 - b0*d0 とすると # (行列式)
#
# AはA^(-1)なので、
# a = 1/detA0 * d0
# b = 1/detA0 * b0 * (-1)
# d = 1/detA0 * d0 * (-1)
# e = 1/detA0 * a0
# 
# (c, f)^Tは-A^(-1)*tなので
# c = 1/detA0 * ( -e0*c0 + b0*f0 )
# f = 1/detA0 * (  d0*c0 - a0*f0 )
# 
# 上記はnumpyで簡単に計算可能
# 
# なお、[[a, b, c],[d,e,f],[0,0,1]]の行列で上記の計算が可能。
# こんなイメージ
# (x0) = ( a b c )(xnew)
# (y0)   ( d e f )(ynew)
# (1 )   ( 0 0 1 )(1)   
# 

#ptn1 縦軸が斜め (b<0の時左回転。b>0の時右回転)
subnm = "tate_naname"
data = (1.25, -0.25, 0, 0, 1, 0)
im2 = im.transform(im.size, Image.AFFINE, data, Image.BILINEAR)

filenm = args.output + "-" + subnm + "-" + "_".join(map(str, data)) + ".png"
print("filenm:", filenm)
im2.save(filenm)

#ptn1 横軸が斜め (d<0の時右回転。d>0の時左回転)
subnm = "yoko_naname"
data = (1, 0, 0, -0.25, 1.25, 0)
im2 = im.transform(im.size, Image.AFFINE, data, Image.BILINEAR)

filenm = args.output + "-" + subnm + "-" + "_".join(map(str, data)) + ".png"
print("filenm:", filenm)
im2.save(filenm)

#ptn3 縦横軸が斜め
subnm = "tateyoko_naname"
data = (1.3, -0.3, 0, -0.3, 1.3, 0)
im2 = im.transform(im.size, Image.AFFINE, data, Image.BILINEAR)

filenm = args.output + "-" + subnm + "-" + "_".join(map(str, data)) + ".png"
print("filenm:", filenm)
im2.save(filenm)


#
# AFFINE変換(2)
# 元の画像をはみ出さないようにする場合
# -> 元の画像サイズからはみ出したら、
#    それに応じて縮小する
#
# 画像の4隅がはみ出していたら、
# 必要に応じて移動、縮小すればいいはず。
# 
# 以下の方針で対応することにする場合
#  - マイナス座標であれば、+方向に移動させる。
#  - 移動させたあと、x軸、y軸のMaxを調べ、
#    元のx軸、y軸のMaxと比較する。
#    大きければ、x軸、y軸の大きい方(元のxy比を考慮)を
#    規準に作成する画像サイズを決定する。
#  - 画像サイズが元と異なる場合は、元の画像サイズにリサイズする。
#
# detAがゼロの場合どうするか。
# -> よくわからないので元の画像サイズのままとする。
#     そもそも、点か線にしかならないので、
#     与えられたパラメータが悪いと考えておくことにする。
# 

subnm = "hamidasi_nasi"
#data = (1.5, -0.3, 0, -0.3, 1.3, 0)
data = (0.8, -0.3, 0, -0.3, 1.3, 0)

(a, b, c, d, e, f) = data
detA = (float)(a * e - b * d)
print("detA:", detA)

if detA == 0.0 :
  raise Exception, "このパラメータは処理できません。"

#
#a0 =   e / detA 
#b0 = - b / detA 
#d0 = - d / detA 
#e0 =   a / detA 
#
#c0 = ( -e*c + b*f ) / detA
#f0 = (  d*c - a*f ) / detA
#
#
#A0 = np.matrix([[a0, b0, c0], [d0, e0, f0], [0, 0, 1]])
#print("A0:", A0)

# 変換後の点を取得したいので、下記の行列で計算させる。
A = np.matrix([[a, b, c], [d, e, f], [0, 0, 1]])
invA = np.linalg.inv(A) # 変換元から変換先の行列がほしいので。
print("invA:", invA)

# 画像の4隅の点
Points = np.matrix([
  [0         , 0         , 1],
  [im.size[0], 0         , 1],
  [0         , im.size[1], 1],
  [im.size[0], im.size[1], 1]
]).T
print("Points:", Points)

# invA x 画像の4隅の点 で変換後の4点を計算。
PointsNew = invA * Points

print("PointsNew:", PointsNew)
p_min = PointsNew.min(1) # x軸、y軸のそれぞれのminを取得。min(0)だとx,yのminを取得する意味になる。
p_max = PointsNew.max(1) # x軸、y軸のそれぞれのmaxを取得。
print("p_min:", p_min)
print("p_max:", p_max)
x_min = math.floor(p_min[0])
x_max = math.ceil(p_max[0])
y_min = math.floor(p_min[1])
y_max = math.ceil(p_max[1])
print("x(min,max):", x_min, x_max, "y(min,max)", y_min, y_max)

x_shift = 0
y_shift = 0

if x_min < 0 :
  x_shift = -x_min
if y_min < 0 :
  y_shift = -y_min

hamidashi = 0
if x_max + x_shift > im.size[0] :
  hamidashi = 1
if y_max + y_shift > im.size[1] :
  hamidashi = 1

y_p_x = im.size[1] / float(im.size[0])

size_wk = [im.size[0], im.size[1]]

if hamidashi == 1 :
  if (x_max + x_shift) > float(y_max + y_shift) / y_p_x :
    size_wk[0] = int(x_max + x_shift)
    size_wk[1] = int(math.ceil(im.size[1]*(x_max + x_shift)/im.size[0]))
  else :
    size_wk[0] = int(math.ceil(im.size[0]*(y_max + y_shift)/im.size[1]))
    size_wk[1] = int(y_max + y_shift)

print("size_wk:", size_wk)
print("x_shift,y_shift:", x_shift, y_shift)

# 移動
invA_Add = np.matrix([[0, 0, x_shift], [0, 0, y_shift], [0, 0, 0]])
B = invA + invA_Add
invB = np.linalg.inv(B)
print("invB", invB)

im2 = im.transform((size_wk[0], size_wk[1]), Image.AFFINE,
                   (a,b,invB[0,2],d,e,invB[1,2]), Image.BILINEAR)

if im2.size[0] == im.size[0] and im2.size[1] == im.size[1] :
  filenm = args.output + "_" + subnm + "_" + "_".join(map(str, data)) + ".png"
  im2.save(filenm)
else :
  filenm = args.output + "-t_" + subnm + "_" + "_".join(map(str, data)) + ".png"
  im3 = im2.resize(im.size)
  im3.save(filenm)


変換元の画像

f:id:umejan:20160424202553p:plain ※ふちが白枠。

変換後の画像

  • 縦軸が斜め
    f:id:umejan:20160425123232p:plain
  • 横軸が斜め
    f:id:umejan:20160425123243p:plain
  • 縦横軸が斜め
    f:id:umejan:20160425123253p:plain

関連

PILのImage.transformの使い方 (EXTENT) - umejanのブログ

PILのImage.transformの使い方 (QUAD, MESH) - umejanのブログ

PILのImage.transformの使い方 (PERSPECTIVE) - umejanのブログ