盗用を防ぐ!Pythonを使って画像に透かしを入れる

Pythonを使って、画像に透かし(ウォーターマーク)を入れる方法を紹介します。透かしはPillowという、Pythonの画像関連のライブラリを使って入れます。

今回の記事のコードはPythonのバージョンが3.4.3、Pillowのバージョンが2.9.0で動かしました。

Pillowのインストール

Pillowのインストールはpipを使うと簡単にできます。

$ pip install Pillow

真ん中に透かし(ウォーターマーク)を入れる。

画像「images/test.png」の真ん中に透かしを入れるプログラムです。透かしを入れた画像は「output/test1.png」として保存されます。

元画像と透かしを入れた画像

このサンプルでは、以下の画像のように透かしが入ります。

透かしを入れる前の画像透かしを入れた画像
透かしを入れる前の画像透かしを入れた画像

コード全体

このプログラムはPillowのドキュメントにある、「Example: Draw Partial Opacity Text」を参考にして作成しました。

# coding: utf-8
from PIL import Image, ImageDraw, ImageFont

watermark = 'HELLOW WORLD'  # 透かし文字を定義します。
fontsize = 32  # 透かし文字の大きさを定義します。
opacity = 64  # 透かし文字の透明度を定義します。
color = (255, 255, 255)  # 透かし文字の色を定義します。

# 透かしを入れる画像を使って、画像オブジェクトを取得します。
base = Image.open('images/test.png').convert('RGBA')

# テキストを描画する画像オブジェクトを作成します。
txt = Image.new('RGBA', base.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(txt)

# フォントを取得します。
fnt = ImageFont.truetype(font='fonts/Arial Black.ttf', size=fontsize)

# 透かし文字の横幅、縦幅を取得します。
textw, texth = draw.textsize(watermark, font=fnt)

# 透かし文字を中央に入れます。
draw.text(((base.width - textw) / 2, (base.height - texth) / 2),
          watermark, font=fnt, fill=color + (opacity,))

# 画像オブジェクトを重ねます。
out = Image.alpha_composite(base, txt)

# 画像を出力します。
out.save('output/test.png', 'png', quality=95, optimize=True)

2015/7/27修正: 透かし文字の縦位置を計算するときに、base.widthを使っていたのでbase.heightに直しました。

コードの解説

画像オブジェクトの作成

# 透かしを入れる画像を使って、画像オブジェクトを取得します。
base = Image.open('images/test.png').convert('RGBA')

# テキストを描画する画像オブジェクトを作成します。
txt = Image.new('RGBA', base.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(txt)

透かしを入れるために、2つの画像オブジェクトを用意します。1つ目は、透かしを入れる画像を読み込んだ画像オブジェクトです。2つ目は、透かしのテキストを描画する画像オブジェクトを作成します。そして、最後にこの2つの画像オブジェクトを重ねることで、透かしを入れています。

ちなみに、2つ目の画像オブジェクトの大きさは、透かしを入れる画像と同じ大きさ(base.size)で作成しています。

フォントの取得

# フォントを取得します。
fnt = ImageFont.truetype(font='fonts/Arial Black.ttf', size=fontsize)

透かしのテキストに使うフォントを決定しています。このfonts/Arial Black.ttfは、/Library/Fonts/(Mac)にあるフォントをコピーしました。

透かし文字の挿入

# 透かし文字の横幅、縦幅を取得します。
textw, texth = draw.textsize(watermark, font=fnt)

# 透かし文字を中央に入れます。
draw.text(((base.width - textw) / 2, (base.height - texth) / 2),
          watermark, font=fnt, fill=color + (opacity,))

透かしを画像の真ん中に入れるために、まずは透かしの横幅と縦幅を取得しています。そして、横位置が「(元画像の横幅 - 透かしの横幅) / 2」、縦位置が「(元画像の縦幅 - 透かしの縦幅) / 2」の位置に透かし文字を入れています。

画像を重ねて保存

# 画像オブジェクトを重ねます。
out = Image.alpha_composite(base, txt)

# 画像を出力します。
out.save('output/test1.png', 'png', quality=95, optimize=True)

最後に、元画像と透かしを描画した画像を重ねて、保存しています。

回転させた透かしを沢山入れる。

次は、画像に透かしを沢山入れます。また、入れる透かしは30度回転させます。

元画像と透かしを入れた画像

このサンプルでは、以下の画像のように透かしが入ります。

透かしを入れる前の画像透かしを入れた画像
透かしを入れる前の画像透かしを入れた画像

回転させるときのポイント

先ほど真ん中に透かしを入れた時は、透かしを描画する画像オブジェクトは元画像と同じ大きさで作成しました。ただ、回転させる場合は、同じ大きさだと隙間ができてしまいます。

拡大なし

上の画像で、赤が元画像オブジェクト、青がテキストを描画する画像オブジェクトです。赤と青を重ねて30度回転させたのですが、赤と青が重なっていない部分ができています。この重なっていない部分には、透かしが描画されません。

この隙間ができてしまう問題を解決するため、次のサンプルでは透かしを描画する画像オブジェクトは元画像を拡大して作成しています。

拡大あり

そうすることで、上の画像の様に隙間がなくなり、まんべんなく透かしを入れることができます。

コード全体

# coding: utf-8
from PIL import Image, ImageDraw, ImageFont

watermark = 'HELLOW'  # 透かし文字を定義します。
fontsize = 16  # 透かし文字の大きさを定義します。
opacity = 64  # 透かし文字の透明度を定義します。
color = (255, 255, 255)  # 透かし文字の色を定義します。
angle = 30  # 回転させる度数を定義します。

# 透かしを入れる画像を使って、画像オブジェクトを取得します。
base = Image.open('images/test.png').convert('RGBA')

# テキストを描画する画像オブジェクトを作成します。
# ※後ほど45度回転させたとき、元の画像と同じ大きさにしておくと隙間ができてしまいます。
#  その隙間をなくすために拡大します。
txt = Image.new('RGBA', (base.width * 3, base.height * 3), (255, 255, 255, 0))
draw = ImageDraw.Draw(txt)

# フォントを取得します。
fnt = ImageFont.truetype(font='fonts/Arial Black.ttf', size=fontsize)

# 透かし文字の横幅、縦幅を取得します。
textw, texth = draw.textsize(watermark, font=fnt)

margin_x = int(textw * 1.5)  # 透かし文字間の、X方向のマージンを定義します。
margin_y = int(texth * 3)  # 透かし文字間の、Y方向のマージンを定義します。
for i in range(int(txt.width / margin_x)):  # X方向のループ
    xpos = margin_x * i  # X方向の位置を計算します。
    for j in range(int(txt.height / margin_y)):  # Y方向のループ
        ypos = j * margin_y  # Y方向の位置を計算します。

        # テキストを描画します。
        draw.text((xpos, ypos), watermark, font=fnt, fill=color + (opacity,))

# テキストを描画した画像を回転させます。
txt = txt.rotate(angle)

# 拡大したテキスト画像を、元のサイズで切り取ります。
left, top = int((txt.width - base.width)/ 2), int((txt.height - base.height) / 2)
txt = txt.crop((left, top, left + base.width, top + base.height))

# 画像オブジェクトを重ねます。
out = Image.alpha_composite(base, txt)

# 画像を出力します。
out.save('output/test2.png', 'png', quality=95, optimize=True)

コードの解説

透かし用の画像オブジェクトを拡大

txt = Image.new('RGBA', (base.width * 3, base.height * 3), (255, 255, 255, 0))
draw = ImageDraw.Draw(txt)

ポイントで説明した通り、透かし用の画像オブジェクトの大きさを拡大しています。

透かしを沢山配置

# 透かし文字の横幅、縦幅を取得します。
textw, texth = draw.textsize(watermark, font=fnt)

margin_x = int(textw * 1.5)  # 透かし文字間の、X方向のマージンを定義します。
margin_y = int(texth * 3)  # 透かし文字間の、Y方向のマージンを定義します。
for i in range(int(txt.width / margin_x)):  # X方向のループ
    xpos = margin_x * i  # X方向の位置を計算します。
    for j in range(int(txt.height / margin_y)):  # Y方向のループ
        ypos = j * margin_y  # Y方向の位置を計算します。

        # テキストを描画します。
        draw.text((xpos, ypos), watermark, font=fnt, fill=color + (opacity,))

ここで、透かしを沢山配置しています。margin_xmargin_yは、透かしと透かしの間に空けるスペースの長さを定義しています。そして、その2つのパラメータを使ってX方向・Y方向にループさせ、透かしを描画しています。

透かし用の画像オブジェクトを回転

# テキストを描画した画像を回転させます。
txt = txt.rotate(angle)

透かしを描画した画像オブジェクトを回転させています。

透かし用の画像オブジェクトを切り取り

# 拡大したテキスト画像を、元のサイズで切り取ります。
left, top = int((txt.width - base.width)/ 2), int((txt.height - base.height) / 2)
txt = txt.crop((left, top, left + base.width, top + base.height))

透かしを描画した画像オブジェクトは、大きさが縦横ともに拡大しています。それを元画像と同じ大きさに切り取っています。

作成したコード

今回作成したコードは、https://github.com/yusukemurayama/blog-samples/tree/master/python/watermarkに公開してあります。

参考にしたページ

今回のプログラムを組にあたって、以下のページを参考にさせて頂きました。

この記事が役に立った場合、シェアしていただけると励みになります!!