dondakeshimoの丸太

データサイエンス/Webアプリケーション/

Python3 + ImageMagick

ImageMagick

画像処理を行う用事があり、それにこれを使うと良いと言われました。何かで見たことあると思ったらlatexをインストールした時についてきた気がする。使ったことなかったけど!

メリット

簡単に画像処理ができる!シェルコマンドでできる!つまりプログラミングと相性が良い!多分こんな感じです。

インストー

homebrewをインストールしていればこれで!

$ brew install imagemagick

Python + ImageMagick

基本

ImageMagickのコマンドをPython

subprocess.call(cmd, shell=True)

で実行させるだけです。
shell=Trueはsubprocessが行うコマンドをshell上で実行するというオプションなのですが、これが必要です。筆者はこれが必要であるということを調べるのに30分以上費やしました。cmdにコマンド文字列を作成していきます。
ImageMagickのコマンドは下記リンクを見て勉強しましたー!

imagemagick.rulez.jp

サイズ変更

def changeSize(image, scale):
    """
    画像のサイズを変更
    縦横比は変えずにパーセンテージで指定
    """
    cmd = "convert {} ".format(image)
    o_resize = "-resize {}% ".format(scale)
    o_out = "{}".format(image)

    cmd = cmd + o_resize + o_out

    res = subprocess.call(cmd, shell=True)

イメージとしてはこんな感じでやっていきます。

二つの画像の合成

def overlayImage(bottom_img, top_img, gravity="center", geometry=(5, 5)):
    """
    2つの画像の合成
    bottom_imgの上にtop_imgを乗せる
    """
    cmd = "convert " + bottom_img + " " + top_img + " "
    o_gravity = "-gravity {} ".format(gravity)
    o_geometry = "-geometry +{0[0]}+{0[1]} ".format(geometry)
    o_compose = "-compose over "
    o_composite = "-composite {}".format(bottom_img)

    cmd += o_gravity + o_geometry + o_compose + o_composite

    res = subprocess.call(cmd, shell=True)

二つの画像を合成しています。gravityが基準点を決めるオプションで、geometryが基準点からの位置決めオプションです。めんどくさかったらgravity northwest にしておけばgeometryで左上からの位置を決められます。

ダミー画像生成

def makeBackground(name="muji", color="DodgerBlue"):
    name = name + ".png"

    res = subprocess.call("convert -size 1200x810 xc:white {}".format(name), shell=True)

このコマンドで無地のダミー画像が作れます。地味に使うこと多そう

ライン描画

def drawStroke(image, coords=(0, 0, 0, 0), color="black", width=1):
    """
    幅と色指定のできる線描画関数
    """
    cmd = "convert "
    o_color = "-stroke {} ".format(color)
    o_width = "-strokewidth {} ".format(width)
    o_draw = '-draw "line {0[0]},{0[1]} {0[2]},{0[3]}" '.format(coords)

    cmd = cmd + image + " " + o_color + o_width + o_draw  + image

    res = subprocess.call(cmd, shell=True)

これでラインが描けます。coordsで座標を決めていますが、

-draw "line (X1),(Y1) (X2),(Y2)"

の順で座標をとり2点間に直線を引きます。
図形は他にもいろいろあったけど、これしか使っていない。。

横並びに結合

def appendImage(img_list):
    """
    画像を横並びに結合する
    しばらく使わないので放置
    """
    cmd = "convert +append "

    for img in img_list:
        in_file = "{}".format(i)
        cmd += inFile + " "

    cmd += "{}".format(img_list[0])

    res = subprocess.call(cmd, shell=True)

forループ!!プログラミング使ってる感出てきた!(これで終わり説)

+append

で横並びの結合

-append

で縦並びの結合(らしい)

文字の挿入

def insertWords(image, words, color="black", font="Meiryo", size="50"):
    """
    画像に文字を挿入
    色とフォントと文字サイズの指定可能
    挿入位置は画像下中央から10px上
    """
    cmd = "convert "
    o_point = "-pointsize {} ".format(size)
    o_font = "-font {} ".format(font)
    o_gravity = "-gravity south "
    o_annotate = '-annotate +0+10 "{}" '.format(words)
    o_fill = '-fill {} '.format(color)
    o_in = "{} ".format(image)
    o_out = "{}".format(image)

    cmd = cmd + o_point + o_gravity + o_font + o_annotate + o_fill + o_in + o_out

    res = subprocess.call(cmd, shell=True)

これは少し流派がありそうでした。label派とannotate派。先に見た方を選びました。

font-awesomeを使う

def makeFontAwesome(icon_name, color="black"):
    """
    font awesome のunicodeから画像生成
    色の指定可能
    生成画像サイズは400x400
    戻り値は画像の名前(相対パス名)
    """
    icon = 'printf "{}" | '.format(icon_name)
    cmd = "convert "
    o_size = "-size 400x400 "
    o_background = '-background "none" -fill {} '.format(color)
    o_font = "-font font-awesome-4.7.0/fonts/fontawesome-webfont.ttf "
    o_point = "-pointsize 200 "
    o_gravity = "-gravity center label:@- "
    o_out = "temp/{}.png".format(icon_name)

    cmd = icon + cmd + o_size + o_background + o_font + o_point + o_gravity + o_out

    res = subprocess.call(cmd, shell=True)

こいつはめちゃくちゃ苦労いたしました。基本的には下記リンク通り

www.nofuture.tv

とりあえずfont-awesomeのフォントをダウンロードしてきて、それを-fontで指定します。

font-awesome注意点

こいつを扱うときの注意点はズバリunicodeです。python3を使ってるから大丈夫と思っているみなさん、そんなこと言ってられなかった事案にぶち当たることもありました!何かと言うと、font-awesomeのunicodeを引数としてとってくる時に、標準入力などを用いると文字列がraw storingになっちゃうんですね。

\ufe03

これで地図の記号なのですが、標準入力まんま打ち込んでとってくるとpython上では

r"\ufe03"

という文字列になってしまいます。 何で困るって、この6文字で1文字を表すから画像表記できていたのに、6文字のまんま扱われたらfont-awesomeじゃないということです!(日本語が変)
これの解決策ですが、

fa = input()  #r"\ufe03"
fa = fa.encode("utf-8")  #b"\\ufe03"
fa = fa.decode("unicode-escape")  #"\ufe03" → □

一回バイト列に変換してから、decodeのunicode-escapeとかいうやつでもう一回unicode文字列(raw string でない)に戻しました。絶対もっとうまい方法あると思うので知ってる方いたら教えてください。。

まとめ

ここに書いた関数群をモジュール化したらのちにも使えそうだなぁ〜(どうするのかさっぱりわからんし、そんなことするくらいならもっと他に画像編集できるライブラリありそう)