Python
ImageMagick
matplotlib
PIL

Python matplotlibとPILで画像処理(vs ImageMagick)

はじめに

通常は ImageMagick で処理しているものを,Python の matplotlib と PIL でプログラムを組んでやってみました.

ツインタワーの写真以外は,
こちら
の2軸棒グラフがネタになっています.

やってみた内容は以下の通り.

  • 画像に文字列を挿入
  • ファイルに格納された文字列を画像にする
  • 画像の余白削除・リサイズ・結合

作業環境は以下の通り.

  • MacBook Pro (Retina, 13-inch, Mid 2014)
  • macOS High Sierra
  • Python 3.6.4
  • ImageMagick 7.0.7-22

画像に文字列を挿入

テストに使っているのは,クアラルンプールにあるペトロナス・ツインタワーの写真です.
私は,夜のこの景色が大好きです.

なお,この写真はMacのPreviewでみると縦長の画像なのですが,ImagemagickでもPILでも読み込んでみると横長画像として認識されています.
そこで,ImageMagickでは,元画像はいじらず,書き込む文字列画像を90度回転させて書き込んでいます.

Pythonでは,読み込んだ画像を90度回転させてから,文字列の書き込みを行っています.また余白がついてくるので,最後に余白の削除を行っています.

ImageMagick

%%bash

flow=IMG_0694.JPG
fupp=_text.png
ffin=fig_KL_im.jpg
fknd=/System/Library/Fonts/Helvetica.ttc
convert -font $fknd -fill 'yellow' -background 'transparent' -pointsize 200 label:'Kuala Lumpur (KLCC)' $fupp
convert $fupp -rotate -90 $fupp
convert $flow $fupp -gravity southwest -geometry +40+40 -compose over -composite $ffin

Python

from PIL import Image, ImageChops
from matplotlib import pyplot as plt

def trim(im, border):
  bg = Image.new(im.mode, im.size, border)
  diff = ImageChops.difference(im, bg)
  bbox = diff.getbbox()
  if bbox:
    return im.crop(bbox)

flist=[
'IMG_0694.JPG'
]
fsz=40
for fn in flist:
    fig=fn
    img=Image.open(fig,'r')
    img=img.rotate(-90, expand=True)
    print(img.size)
    iw=800
    ih=int(img.size[1]/img.size[0]*iw)
    fig = plt.figure(figsize=(iw/100,ih/100),facecolor='w')
    plt.rcParams['font.family'] = 'Tahoma'
    plt.axis('off')
    img = img.resize((iw,ih))
    plt.text(20,20, 'Kuala Lumpur (KLCC)', rotation=0,color='yellow',fontsize=fsz,va='top', ha='left')
    plt.imshow(img)
    fnameF='fig_KL_py.jpg'
    plt.savefig(fnameF,dpi=150,bbox_inches='tight',pad_inches=0)
    plt.show()

    img=Image.open(fnameF,'r')
    img=trim(img,'#ffffff')
    img.save(fnameF, 'JPEG', quality=100, optimize=True)

ファイルに格納された文字列を画像にする

ImageMagick

%%bash

for fn in bar1
do
    fr=${fn}.txt
    fw=ft_${fn}.png

    convert -font /System/Library/Fonts/Menlo.ttc -pointsize 16 -interline-spacing 5 label:@$fr $fw
#    convert -font courier-new -pointsize 16 -interline-spacing 0 label:@$fr $fw
done

Python

import matplotlib.pyplot as plt

def DRAW(sp,fn):
    fsz=20
    w=fsz*len(sp[0])/100*0.5
    h=(fsz+10)*len(sp)/100
    fig = plt.figure(figsize=(w,h),facecolor='w')
    plt.rcParams['font.family'] = 'monospace'
#    plt.rcParams['font.family'] = 'Courier New'
#    plt.rcParams['font.family'] = 'Ricty Diminished'
    xmin=0
    xmax=float(len(sp[1]))
    ymin=0
    ymax=float(len(sp))
    plt.xlim(xmin,xmax)
    plt.ylim(ymax,ymin)
    plt.axis('off')
    for i,strp in enumerate(sp):
        plt.text(1,i+1,strp,ha='left',va='top')
    plt.tight_layout()
    fnameF='ftpy_'+fn+'.png'
    plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0)
    plt.show()


flist=[
'bar1'
]
for fn in flist:
    fnameR=fn+'.txt'
    fr=open(fnameR,'r')
    lines=fr.readlines()
    fr.close()
    sp=[]
    for text in lines:
        sp=sp+[text.replace('\n','')]
    DRAW(sp,fn)

画像の余白削除・リサイズ・結合

事前に準備された数表の画像 ft_bar1.png と棒グラフの画像 fig_bar1.png を縦に結合して,新しい画像 fig_bar1_b.png を作ります.

ImageMagick

for fn in bar1
do
    f1=ft_${fn}.png
    f2=fig_${fn}.png
    f3=fig_${fn}_a.png
    convert -trim -resize 760x -gravity center -background white -extent 800x200 $f1 _$f1
    convert -trim -resize 760x -gravity center -background white -extent 800x800 $f2 _$f2
    convert -append _$f1 _$f2 $f3
done
rm _*

Python

プログラム作成にあたり,以下のサイトを参考にしました(というか関数をいただきました)

以下のPythonプログラムにおいての関数の機能以下の通り.

  • trim:余白削除
  • resz:リサイズ
  • marg:余白追加
from PIL import Image, ImageChops

def trim(im, border):
  bg = Image.new(im.mode, im.size, border)
  diff = ImageChops.difference(im, bg)
  bbox = diff.getbbox()
  if bbox:
    return im.crop(bbox)

def resz(im,ix):
    iw=im.size[0]
    ih=im.size[1]
    im_res = img_new.resize((int(ix),int(ih/iw*ix)), Image.LANCZOS)
    return im_res

def marg(im, top, right, bottom, left, color):
    width, height = im.size
    new_width = width + right + left
    new_height = height + top + bottom
    result = Image.new(im.mode, (new_width, new_height), color)
    result.paste(im, (left, top))
    return result

# 上側画像を横幅800pxで作成(余白含む)
ix=760
fig='ftpy_bar1.png'
img_org=Image.open(fig,'r')
img_new=trim(img_org,'#ffffff')
img_res=resz(img_new,ix)
img_fin=marg(img_res, 15, 20, 15, 20, '#ffffff')
print(img_org.format,img_org.size, img_org.mode)
print(img_new.format,img_new.size, img_new.mode)
print(img_res.format,img_res.size, img_res.mode)
print(img_fin.format,img_fin.size, img_fin.mode)
img1=img_fin

# 下側画像を横幅800pxで作成(余白含む)
ix=760
fig='fig_bar1.png'
img_org=Image.open(fig,'r')
img_new=trim(img_org,'#ffffff')
img_res=resz(img_new,ix)
img_fin=marg(img_res, 20, 20, 20, 20, '#ffffff')
print(img_org.format,img_org.size, img_org.mode)
print(img_new.format,img_new.size, img_new.mode)
print(img_res.format,img_res.size, img_res.mode)
print(img_fin.format,img_fin.size, img_fin.mode)
img2=img_fin

# 上下で画像を結合(Image.newで台紙を作成しそこに2枚の画像をpasteで貼り込む)
new_width=img1.size[0]
new_height=img1.size[1]+img2.size[1]
img3=Image.new('RGBA', (new_width, new_height), '#ffffff')
img3.paste(img1,(0,0))
img3.paste(img2,(0,img1.size[1]))
img3.save('fig_bar1_b.png', 'PNG', quality=100, optimize=True)

img3.show()

以 上