『退屈なことはPythonにやらせよう』その15(17章:画像処理)

Python初心者です。『退屈なことはPythonにやらせよう』を走っています。

15回目の今回は、17章をやっていきます~。

かな~り手を抜いてしまった前回の記事はこちら↓

今回の目次


コンピュータ画像

色とRGBA

RGBAは以下の頭文字をつなげたものです。

  • Red (赤)
  • Green (緑)
  • Blue (青)
  • Alpha (不透明度)

これらを0~255の256段階で調節することで256の4乗通りの色を理論上表現することができます。ウェブ画像は、細かい四角が集まって出来ており、その一マスずつがピクセルと呼ばれます。各ピクセルがそれぞれの色を持っており、Pillowにおいてはその色をRGBAのタプルで表現します。昔ちょっといじったcv2はR,G,Bのそれぞれの色の値をもつ行列を重ね合わせている感じでした。

座標と矩形タプル

画像の左上を(0,0)として、そこからいくつ移動するかで画像上の位置を表します。今回の場合は移動の単位がピクセルで表されていました。

Pillowの関数やメソッドで渡すことのできる矩形タプルというものがあります。これは、座標の左上(x,y)と座標の右下(x,y)の座標を合わせたものになります。長方形か正方形で指定するので、左上と右下の座標を与えれば大きさと形が決まりますね。

矩形タプルは (左上のx座標, 左上のy座標, 右下のx座標, 右下のy座標)として表されます。


Pillowで画像を操作

Imageモジュール

ImageモジュールではImageオブジェクトを生成します。

Pillowを使う時はこれをまず最初にするイメージです。新しい画像を生成したり、既にある画像を読み込むことが出来ます。

from PIL import Image

#新規の画像を作成
im_new = Image.new("RGBA", (width, height), "塗りつぶしの色")

#既存の画像を読み込み
im = Image.open("ファイルのパス")

他に学んだ機能としては、以下のものが挙げられます。

  • .size: 幅と高さを返す
  • .filename: ファイル名を返す
  • .save(“保存する名前”): Imageオブジェクトを画像ファイルとして保存する
  • .crop((切り取りたい範囲の矩形タプル))
  • .copy(): 画像のコピーを作り、変更後と変更前をどっちも維持したい場合に便利
  • .paste(貼り付けたいImageオブジェクト, (貼り付けたい場所の左上のx,y座標), マスクImageオブジェクト)
  • .rotate(角度): 「角度」の分だけ左回りに回転する。expand=Trueとすると、画像全体の大きさが保たれる
  • .getpixel((矩形タプル)): 矩形タプルのRGBA値を取得する
  • .putpixel((矩形タプル), 色): 矩形タプル上を指定した色にする
  • .thumbnail((幅、高さ)): 画像を「幅、高さ」のサイズに収縮する。

ちなみに、putpixelで色を指定する時は(R,G,B,A)の数字を渡すよりもImageColor.getcolor(“欲しい色の名前”, “RGBA”)を変数に格納して渡したほうが良さそうです。

ImageDrawモジュール

画像に線や矩形や円、文字を書きたい時に使えるモジュールです。

使い方としてはImageDraw.Draw(Imageオブジェクト)として、Drawオブジェクトを生成します。それから、Drawオブジェクトに対してメソッドを適用していきます。

以下に示すようなものが描けます。

  • 点:point(xy, fill) —- xyは座標値のタプルのリストかリスト
  • 線分: line(xy, fill, width) —- xyは座標値のタプルのリストか、リスト
  • 矩形: rectangle(xy, fill, outline) —- xyは矩形タプル(左、上、右、下)
  • 楕円形: ellipse(xy, fill, outline) —- xyは矩形タプル(左、上、右、下)
  • 多角形: polygon(xy, fill, outline) —-xyは座標値のタプルのリストか、リスト
  • 文字: text(xy, text, fill, font) —-xyはテキストを入れる位置の左上の点/引数textは書きたい文字

メソッド共通で、fillでは塗りつぶし色指定、画像のoutlineは外枠の色指定です。

フォントの指定にはImageFontモジュールを使っています。詳しくは課題プロジェクトの17-3を見ていただくのが良いかと思います。


課題プロジェクト

17-1.章プロジェクトの修正

画像をリサイズしてロゴを追加するプログラムを書いたのですが、それを改良するというプロジェクトです。

#! python3
# resizeAndAddLogo.py - カレントディレクトリのすべての画像を300x300に収まる
#                       ようにサイズ変更し、catlogo.pngを右下に追加する。

import os
from PIL import Image

SQUARE_FIT_SIZE = 300
LOGO_FILENAME = "catlogo.png"

logo_im = Image.open(LOGO_FILENAME)
logo_width, logo_height = logo_im.size

os.makedirs("withLogo", exist_ok=True)

#カレントディレクトリの全画像をループする
for filename in os.listdir("."):
    if not (filename.lower().endswith('.png') or filename.lower().endswith(".jpg") or \
        filename.lower().endswith(".gif") or filename.lower().endswith(".bmp"))\
        or filename == LOGO_FILENAME:
        continue

    im = Image.open(filename)
    width_origin, height_origin = im.size

    if width_origin <= logo_width*2 or height_origin <= logo_height*2:
        continue
    

    #画像サイズを変更する
    im.thumbnail((SQUARE_FIT_SIZE, SQUARE_FIT_SIZE))
    width, height = im.size
    
    #ロゴを追加する
    print("ロゴを追加中 {}...".format(filename))
    im.paste(logo_im, (width-logo_width, height-logo_height), logo_im)
        
    #変更を保存する
    im.save(os.path.join("withLogo", filename))

17-2. ハードドライブの写真フォルダを探す

os.walk()を利用して、ハードドライブ中の写真が格納されたフォルダをみつけるやつです。

#! python3
# findPhotoDir.py - ハードドライブの写真フォルダを探す

import os
from PIL import Image

for foldername, subfolders, filenames in os.walk("C:\\"):
    num_photo_files = 0
    num_non_photo_files = 0
    for filename in filenames:
        if not (filename.lower().endswith('.png') or filename.lower().endswith(".jpg")):
            num_non_photo_files += 1
            continue
        try:
            im = Image.open(os.path.join(foldername,filename))
            width, height = im.size
            if width >= 500 and height >= 500:
                num_photo_files += 1
            else:
                num_non_photo_files += 1
        except:
            num_non_photo_files += 1
    
    if num_photo_files > 0 and num_photo_files >= num_non_photo_files:
        print(os.path.abspath(foldername))

骨組みは本文中で与えられているので、それに従って書くだけです。

17-3. カスタム座席カード

13章でやった招待状課題の要領で、出席者カードみたいなのを作る課題です。(13章の記事はこちら)

そんなに大変でもなかったのでとりあえずコードを。

#! python3
# check17_3.py  -  

#debug
import logging
logging.basicConfig(level=logging.DEBUG,
format = " %(asctime)s - %(levelname)s - %(message)s")
logging.debug("プログラム開始")
#logging.disable(logging.CRITICAL)

import os
from PIL import Image, ImageDraw, ImageFont


to_send_file = open("guests17.txt", "r")
guests = to_send_file.readlines()
to_send_file.close()

CARDS_WIDTH = 360
CARDS_HEIGHT = 288

for guest in guests:
    im = Image.new("RGBA", (CARDS_WIDTH, CARDS_HEIGHT))
    flower_im = Image.open("flower.png")
    #花の絵を貼り付ける
    im.paste(flower_im, (0,0), flower_im)

    draw = ImageDraw.Draw(im)
    #guestの名前を書く
    arial_font = ImageFont.truetype("arial.ttf", 32)
    text_width, text_height = arial_font.getsize(guest)
    draw.text(((CARDS_WIDTH-text_width) / 2, (CARDS_HEIGHT - text_height) / 2), guest, fill = "black", font = arial_font)

    #わくせん
    draw.rectangle((0, 0, CARDS_WIDTH-1, CARDS_HEIGHT-1), outline="black")

    #ほぞん
    im.save("card_for_{}.png".format(guest.strip()))


    
logging.debug("プログラム終了")

とりあえずこんな感じで描けました。

check17_3_result

感想

PILモジュールの使い方がなんとなく分かりました。次でラストの章なので気合をいれてさっさと終わらせたいと思います。

次の記事はこちら↓

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です