カテゴリー別アーカイブ: PIL

(104) 「PILで写真に日付を入れる」をWEBサービス化

(104) 「PILで写真に日付を入れる」をWEBサービス化

1. やりたいこと

Pythonは WEBとの親和性が高い。

よって…
先に書いた (102) PILで写真に日付を入れる をWEBブラウザから実行できるようにする。

それだけ…

2. 仕様

1) WEBブラウザから画像ファイルをアップロードする。
2) CGIで Pythonプログラムを起動する。
3) アップロードされた画像ファイルの EXIF情報から撮影日を抽出する。
4) アップロードされた画像ファイルを幅 640pxにリサイズする。
5) アップロードされた画像ファイルの右下に日付を埋め込む。
6) 処理後の画像を WEBブラウザに表示する。
7) 今回は排他制御は考えない。

3. やってみる

出来上がった物はこちら。
https://www.dogrow.net/python/sample/0104/

処理の詳細は (102) PILで写真に日付を入れる に記してある。

index.cgi
サーバーにアップロード後、当該ファイルに実行属性を付与すること。
出力ファイル名はなるべく散らばるように時刻値とプロセスIDを使用している。(※不完全な排他)

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
print 'Content-type: text/html'

import os
import cgi
from datetime import datetime
import mylib

print """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>サンプル No.104</title>
</head>
<body>
"""

form = cgi.FieldStorage()
if form.has_key('upfile'):     # formから投稿された情報 upfile あり?
    item = form['upfile']
    if item.file:              # upfile はアップロードされたファイル?
        img_path = './%s_%d_%s' % (datetime.now().strftime("%s"), os.getpid(), item.filename)
        mylib.save_img(item, img_path)       # 投稿ファイルを保存
        if True == mylib.exec_put_date(img_path) :   # 投稿画像ファイルに日付を書く
            print('<img src="' + img_path + '">')    # 出来上がった画像を表示
        else:
            os.remove(img_path)   # 失敗したならばアップロードファイルを削除

# 投稿フォームを表示
print """
<form enctype="multipart/form-data" action="./index.cgi" method="post">
  <input type="file" name="upfile">
  <input type="submit" value="実行">
</form>
</body>
</html>
"""

mylib.py

from PIL import Image, ImageDraw, ImageFont

########################################################################
class MyError(Exception):
    pass

########################################################################
def save_img( item, save_path ):
    fout = file(save_path, 'wb')
    while True:
        chunk = item.file.read(1000000)
        if not chunk:
            break
        fout.write(chunk)
    fout.close()

########################################################################
def resize_img( img ):
    w,h = img.size
    w_new = 640
    h_new = h * w_new / w
    imgR = img.resize((w_new, h_new), resample=Image.ANTIALIAS)
    return imgR

########################################################################
def exec_put_date( filepath ):
    ret = False
    try:
        img = Image.open(filepath)
        exif = img._getexif()
        if None == exif :
            raise MyError('No exif')

        if False == 36867 in exif :
            raise MyError('No DateTimeOriginal in exif')

        text_datetime = exif[36867]
        text_date = text_datetime.split()[0]
        text_date = text_date.replace(':','-')

        img = resize_img(img)
        pos_x = img.size[0] - 150
        pos_y = img.size[1] - 30

        obj_draw = ImageDraw.Draw(img)
        obj_font = ImageFont.truetype("./ipagp.ttf", 24)
        obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
        obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

        img.save(filepath)
        ret = True

    except MyError as e:
        print('<p style="color:#f00">%s</p>' % e.message)

    except IOError:
        print('<p style="color:#f00">Invalid file</p>')

    return ret

できた!


(102) PILで写真に日付を入れる

(102) PILで写真に日付を入れる

1. やりたいこと

写真の右下に「1988/10/16」とかプリントされるあれを、写真画像に付けてみたい。

2. 実現手段

1) 日付は、写真画像に埋め込まれている EXIF情報から取得する。
2) 写真画像への日付の表示には PIL(Pillow)を使う。

3. やってみる

こんな画像でやってみる。2018年1月14日に山梨県の山中湖で撮影した写真だ。

Step 1 : EXIF情報を見てみる。

画像ファイルから読みだした EXIF情報を列挙してみるだけのプログラムを書いた。

from PIL import Image
from PIL import ExifTags

img = Image.open("img001.jpg")
exif = img._getexif()

for id, val in exif.items():
    tag = ExifTags.TAGS.get(id)
    print("%d : %s : %s" % (id, tag, val))

実行結果は以下の通り。(長いものは省略した)
この中で日付っぽいものを色付けしてみた。
緑色のコメントは、後から自分が付け足した。

271 : Make : xxxxx
272 : Model : xxxxx
274 : Orientation : 1
282 : XResolution : (72, 1)
283 : YResolution : (72, 1)
296 : ResolutionUnit : 2
305 : Software : xxxxx
306 : DateTime : 2018:01:14 15:11:41 # 画像ファイルの更新日
531 : YCbCrPositioning : 1
33434 : ExposureTime : (1, 934)
33437 : FNumber : (9, 5)
34850 : ExposureProgram : 2
34853 : GPSInfo : xxxxx
34855 : ISOSpeedRatings : 20
34665 : ExifOffset : 206
36864 : ExifVersion : b'0221'
36867 : DateTimeOriginal : 2018:01:14 15:11:41 # 撮影された日時
36868 : DateTimeDigitized : 2018:01:14 15:11:41 # 画像がデジタルデータ化された日時
37121 : ComponentsConfiguration : xxxxx
37377 : ShutterSpeedValue : (8298, 841)
37378 : ApertureValue : (2159, 1273)
37379 : BrightnessValue : (9533, 1036)
37380 : ExposureBiasValue : (0, 1)
37383 : MeteringMode : 5
37385 : Flash : 16
37386 : FocalLength : (399, 100)
37396 : SubjectLocation : (2015, 1511, 2217, 1330)
37500 : MakerNote : xxxxx
37521 : SubsecTimeOriginal : 425
37522 : SubsecTimeDigitized : 425
40960 : FlashPixVersion : b'0100'
40961 : ColorSpace : 65535
40962 : ExifImageWidth : 4032
40963 : ExifImageHeight : 3024
41495 : SensingMethod : 2
41729 : SceneType : xxxxx
41985 : CustomRendered : 2
41986 : ExposureMode : 0
41987 : WhiteBalance : 0
41989 : FocalLengthIn35mmFilm : 28
41990 : SceneCaptureType : 0
42034 : LensSpecification : xxxxx
42035 : LensMake : xxxxx
42036 : LensModel : xxxxx

画像に追加表示したいのは撮影日時なので DateTimeOriginal を使えばよいみたいだ。

Step 2 : 画像ファイルから DateTimeOriginal だけを取得する。

今度は関数にしてみた。

from PIL import Image

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

# 画像ファイルを開き、撮影日を取得し、結果を表示する。
img = Image.open("img001.jpg")
datetime = get_datetime(img)
print(datetime)

実行結果は以下の通り。

2018:01:14 15:11:41

Step 3 : 画像にテキストを追記する。

実は (99) OpenCV #4 : ガンマ補正で画像を見やすく調整 ですでにやっている。
これも PIL を使って実現している。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((10, 10), "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

こんな感じになった。
因みにフォントはフリーの IPAフォント を使わせていただいた。

ここで 2点問題がある。
1) 文字が見づらい。
2) 文字の表示位置は右下がよい。

まずは文字を見やすくするため、文字色と輝度差が大きい色で影を付けてみる。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((11, 11), "Hello!", fill=(0, 0, 0), font=obj_font)
obj_draw.text((10, 10), "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

次に、文字の表示位置を画像の右下にしてみる。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# テキスト表示位置を決定
pos_x = img.size[0] - 90
pos_y = img.size[1] - 40

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((pos_x+1, pos_y+1), "Hello!", fill=(0, 0, 0),     font=obj_font)
obj_draw.text((pos_x,   pos_y),   "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

文字の表示位置は、フォントサイズと表示するテキストの長さに合わせて後で調整しよう。
※自動的に計算すればよいのだが、それは今回のテーマではないので…

Step 4 : 本題! 写真に日付を入れる。

今までの Step 1 ~ Step 3 の合わせ技で実現する。

from PIL import Image, ImageDraw, ImageFont

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

# 画像をロード
img = Image.open("img001.jpg")

# 画像の撮影日を取得
text_datetime = get_datetime(img)
text_date = text_datetime.split()[0]     # 時刻を切り離す。
text_date = text_date.replace(':','-')   # 年月日の区切り文字を変更

# テキスト表示位置を決定
pos_x = img.size[0] - 150
pos_y = img.size[1] - 30

# 画像にテキストを追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 24)
obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

イメージした通りになったかも。
※上記の処理中ではエラー発時を一切考慮していないので、必要に応じて追加実装しようと思う。

4. 応用編

画像の指定、色の指定、エラー処理など、汎用性、冗長性を持たせる。

(1) 画像ファイルを指定出来るようにする。

put_text.py

import argparse
from PIL import Image, ImageDraw, ImageFont

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

def exec_put_date( filepath ):
    # 画像をロード
    img = Image.open(filepath)

    # 画像の撮影日を取得
    text_datetime = get_datetime(img)
    text_date = text_datetime.split()[0]     # 時刻を切り離す。
    text_date = text_date.replace(':','-')   # 年月日の区切り文字を変更

    # テキスト表示位置を決定
    pos_x = img.size[0] - 150
    pos_y = img.size[1] - 30

    # 画像にテキストを追記
    obj_draw = ImageDraw.Draw(img)
    obj_font = ImageFont.truetype("./ipagp.ttf", 24)
    obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
    obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

    # 画像表示
    img.show()

def main():
    # コマンドライン引数を取得
    parser = argparse.ArgumentParser()
    parser.add_argument('-f','--filepath',  required=True)
    args = parser.parse_args()
    # 画像に日付を埋め込み
    exec_put_date(args.filepath)

if __name__=="__main__": main()

Shellのコマンドラインから以下のように実行する。
-f オプションで処理対象画像のファイルパスを指定する。

python put_text.py -f img001.jpg

(96) OpenCV #1 : まずは使ってみる。

(96) OpenCV #1 : まずは使ってみる。

1. やりたいこと

Pythonで OpenCVを使ってみたい。

OpenCV については説明するまでもないが、CV(Computer Vision)の名の通り、画像認識などに代表される画像・映像処理関連のライブラリだ。

C++ではちょくちょく使っているが、Pythonでは使ったことがない。
画像データは2次元、3次元配列なので、配列操作を簡潔に書ける Pythonだと画像データを扱い易いはず。

2. やってみる

(0) 事前準備

ここでは anacondaでインストールした Python3 環境にインストールする。
pip等、anaconda配下のプログラムが起動するようにパスを通す。

export PATH=~/anaconda3/bin/:$PATH

※anacondaの入れ方はこちらを参照のこと。

(1) OpenCVをインストール

入門編なので、ソースからコンパイルではなく、パッケージとして用意されている物を使う。

# pipが入っているか?
python3 -m pip -V

# Case1 : なければインストール
wget https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py
rm get-pip.py

# Case2 : あっても更新しておく。
pip install -U pip

# OpenCVをインストール
pip install opencv-python

# msgpackが古いと怒られたのでインストール
pip install --upgrade msgpack

# ついでに PILをインストールしておく。
pip install pillow

(2) カラー画像を白黒画像に変換する。

import cv2

# 画像ファイルを読み出す。
img = cv2.imread("pic.jpg")

# 平均値を求める。
imgG = img.mean(axis=2)

# 表示
from PIL import Image
imgP = Image.fromarray(imgG) # numpy配列から変換
imgP.show()

できた!

でも…
これではファイル読み出しに OpenCVを使っているだけで宝の持ち腐れだ。

そこで、今度は OpenCVで白黒画像に変換する。

import cv2

# 画像ファイルを読み出す。
img = cv2.imread("pic.jpg")

# 白黒画像に変換する。
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 表示
from PIL import Image
imgP = Image.fromarray(imgG) # numpy配列から変換
imgP.show()

できた!

3. おまけ

(1) 画像の表示方法(cv2を使う場合)

上記のプログラム例では PILを使って表示している。
OpenCV公式サイトのプログラム例では、cv2を使って以下のように表示している。

import cv2
img = cv2.imread('image.jpg')
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('image', img)
cv2.waitKey()
cv2.destroyAllWindows()

Windowのタイトルバーに任意の文字列を書けるのでこちらの方がよいか?
でも、最後に waitkey() なる一行を実行しないと表示されないのはなぜ?

詳細はこちらの公式ページを参照のこと。
Operations with images

4. 所感

とりあえず Pythonで OpenCV初体験なので、これぐらいにしておこう。
続くかな…