Pythonで画像に透かし・ウォーターマークを自動で入れる方法をコード付きで解説

この記事では、Pythonを使って画像に透かしを自動で追加する方法をわかりやすく解説します。

写真や画像をインターネットに公開する際、著作権を守るために「透かし(ウォーターマーク)」を追加することは重要です。

特に、InstagramやTwitterなどのSNSで簡単に写真が共有される現代では、画像の無断使用や盗用を防ぐ手段として透かしを自動的に追加できる方法が求められています。

手作業で画像に透かしを追加する手間を省き、効率的に著作権保護を行うことができるので、ブログ運営者や写真家、デザイナーにとって大きな時間の節約になります。

また、自社ロゴやテキストを画像に一括で適用することで、画像がトリミングされた際にも確実に透かしが機能するようにすることが可能です。

透かしによる画像保護で、ブランドの認知度を高め、画像の盗難や不正使用を防ぐための最適な方法をご紹介します。

1. スクリプトの概要

こんなことができるよ
  • 用意したPNG画像を、右下に自動で配置
  • 配置場所の明るさを計算し、黒or白のロゴを自動選択
  • 画像サイズに合わせてロゴのサイズを自動調整
  • JPEG、PNG、BMPなどの一般的な画像形式に対応

このスクリプトは、画像に透かしを自動で追加できます。

また、環境構築用のinstall.batファイルを用意しており、簡単に必要な環境をセットアップできます。

ソフトウェアのダウンロードは以下に案内しております。

2. 前提条件

このスクリプトを使用するには、以下の環境が必要です。

  • Windows環境(バッチファイルのため)
  • Python 3.x がインストールされていること
  • 必要なPythonライブラリ:Pillow、NumPy

3. 使い方

ステップ1:環境のセットアップ

  • Pythonのインストール:Python 3.x未インストールの場合は公式サイトからダウンロードしてインストールします。
  • 環境構築の実行:「Auto-Watermark-by-kia.zip」を解凍し、「install.bat」をダブルクリックして実行します。
  • スクリプトとロゴ画像の配置:解凍後は以下のようなディレクトリ構成になっています。
    ├── watermark.py
    ├── watermark.bat
    ├── install.bat
    ├── logo
    │   ├── logo-black.png
    │   └── logo-white.png
        

ステップ2: 透かし用透過ロゴ画像の準備

透かしに使用するロゴを以下の要件で準備してください。

  • ロゴ画像の準備:埋め込みたい画像を「logo-black.png」と「logo-white.png」として「logo」フォルダに上書き保存で配置する
  • 黒と白の両方のバージョンを用意する
  • 背景を透過したPNG形式で保存する
  • 画像サイズは横幅200px以上最大幅728px程度を推奨

準備したロゴファイルを「logo-black.png」と「logo-white.png」にリネームし、スクリプト内の「logo」フォルダに上書き保存してください。

ステップ3:ウォーターマークを画像に合成

  • 画像ファイルの準備:ウォーターマークを追加したい画像を用意します。
  • ショートカットを作成:watermark.batのショートカットを作成し、好きな場所に移動させて、実行できます。
  • 実行方法:watermark.bat(ショートカットでもOK)の上に画像ファイルをドラッグ&ドロップして実行します。
  • 複数ファイルの処理:複数の画像を一度にドラッグ&ドロップすることで一括処理が可能です。

ステップ4:結果の確認

  • 出力ファイル:元の画像ファイルと同じ場所に「_watermarked」が付いたファイルが生成されます。
  • 例:「sample.jpg」を処理すると「sample_watermarked.jpg」が生成されます。

4. スクリプトの詳細説明

watermark.py

以下が透かしを追加するメインのPythonスクリプトです。

透かしのサイズ・位置を変更する場合の設定

このスクリプトでは、冒頭の設定項目を変更することで、ウォーターマークのサイズ、位置、透過率、明るさの閾値を自由に調整できます。

  • ウォーターマークのサイズ(%指定):画像サイズに対するウォーターマークの大きさを割合で指定します。ロゴの「高さ」と「幅」の短い方を基準にします。
  • 明るさの閾値:ウォーターマークを配置する領域の平均輝度がこの値以上の場合は「黒いロゴ」を、それ未満の場合は「白いロゴ」を使用します。輝度の範囲は0(最も暗い)から255(最も明るい)です。
  • 透過率:黒いロゴと白いロゴの透過率をそれぞれ指定できます。値は0から1の間で設定し、0が完全に不透明、1が完全に透明を意味します。
  • ウォーターマークの位置(%指定):ウォーターマークの右下の位置を、画像の幅と高さに対する割合で指定します。値を調整することで、ウォーターマークの表示位置を自由に変更できます。例えば、0.95と設定すると、画像の右端から5%内側、下端から5%上の位置に配置されます。
  • ウォーターマーク画像のパス:使用する黒いロゴと白いロゴの画像ファイルのパスを指定します。
import os
import sys
import logging
import argparse
from PIL import Image
import numpy as np

########################################################################
# ユーザーが変更可能な設定

# ウォーターマークの大きさ(短い方の辺に対する割合 %指定)
LOGO_SCALE = 0.08  # 例:0.08は短い方の辺の8%

# 明るさの閾値(0~255)
BRIGHTNESS_THRESHOLD = 110  # ロゴの色を切り替える明るさの基準値(例:110)

# 透過率(0から1の間)
OPACITY_DARK_LOGO = 0.5  # 黒いロゴの透過率(1で完全透明、0で完全不透明)
OPACITY_LIGHT_LOGO = 0.4  # 白いロゴの透過率(1で完全透明、0で完全不透明)

# ウォーターマークの位置(%指定)
ANCHOR_X_RATIO = 0.95  # ウォーターマークの右下の位置を指定(幅の割合、例:0.95は右から5%内側)
ANCHOR_Y_RATIO = 0.95  # ウォーターマークの右下の位置を指定(高さの割合、例:0.95は下から5%上)

# ウォーターマーク画像のパス
BLACK_WATERMARK_PATH = r"logo\logo-black.png"
WHITE_WATERMARK_PATH = r"logo\logo-white.png"

# ログファイルのパス
LOG_FILE_PATH = "watermark_log.txt"

# ユーザーが変更可能な設定 ここまで
########################################################################

def setup_logging():
    """ログの設定を行う"""
    logging.basicConfig(
        filename=LOG_FILE_PATH,
        filemode='w',
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
    )

def load_logo_images():
    """ウォーターマーク画像を読み込む"""
    try:
        black_logo = Image.open(BLACK_WATERMARK_PATH).convert('RGBA')
    except FileNotFoundError:
        logging.error(f"黒いロゴ画像が見つかりません: {BLACK_WATERMARK_PATH}")
        sys.exit(1)
    except Exception as e:
        logging.error(f"黒いロゴ画像の読み込み中にエラーが発生しました: {e}")
        sys.exit(1)

    try:
        white_logo = Image.open(WHITE_WATERMARK_PATH).convert('RGBA')
    except FileNotFoundError:
        logging.error(f"白いロゴ画像が見つかりません: {WHITE_WATERMARK_PATH}")
        sys.exit(1)
    except Exception as e:
        logging.error(f"白いロゴ画像の読み込み中にエラーが発生しました: {e}")
        sys.exit(1)

    return black_logo, white_logo

def is_image_file(file_path):
    """ファイルが画像かどうかをPillowで判定する"""
    try:
        with Image.open(file_path) as img:
            img.verify()  # 画像ファイルが破損していないかをチェック
        return True
    except (IOError, SyntaxError) as e:
        logging.error(f"画像形式の判定中にエラーが発生しました: {file_path}")
        logging.error(f"エラーの詳細: {e}")
        return False

def calculate_average_brightness(region):
    """指定した領域の平均輝度を計算する"""
    region_rgb = region.convert('RGB')
    pixels = np.array(region_rgb)
    luminance = 0.2126 * pixels[:, :, 0] + 0.7152 * pixels[:, :, 1] + 0.0722 * pixels[:, :, 2]
    average_brightness = luminance.mean()
    return average_brightness

def adjust_logo_opacity(logo_image, opacity_value):
    """ロゴの不透明度を調整する"""
    opacity = int(255 * (1 - opacity_value))
    alpha = logo_image.split()[3]
    alpha = alpha.point(lambda p: p * (opacity / 255))
    logo_image.putalpha(alpha)
    return logo_image

def process_image(image_path, black_logo, white_logo):
    """1枚の画像にウォーターマークを追加する"""
    if not os.path.isfile(image_path) or not is_image_file(image_path):
        logging.warning(f"対応していないファイル形式、またはファイルが存在しません: {image_path}")
        return

    try:
        with Image.open(image_path) as im:
            # 画像をRGBAモードに変換(透過情報を扱うため)
            im = im.convert('RGBA')
            width, height = im.size

            # 画像の短い辺を基準にウォーターマークのサイズを決定
            shorter_side = min(width, height)
            logo_size = int(shorter_side * LOGO_SCALE)  # 短い方の辺を基準にウォーターマークのサイズを決定

            # ロゴのアスペクト比を維持してサイズを計算
            aspect_ratio = black_logo.width / black_logo.height
            logo_width = logo_size
            logo_height = int(logo_width / aspect_ratio)

            # ロゴをリサイズ(PNGのまま)
            black_logo_resized = black_logo.resize((logo_width, logo_height), resample=Image.LANCZOS)
            white_logo_resized = white_logo.resize((logo_width, logo_height), resample=Image.LANCZOS)

            # 埋め込み位置の計算
            anchor_x = int(width * ANCHOR_X_RATIO)
            anchor_y = int(height * ANCHOR_Y_RATIO)
            x = anchor_x - logo_width
            y = anchor_y - logo_height
            x = max(0, x)
            y = max(0, y)

            # 埋め込み位置の画像を切り取り
            region = im.crop((x, y, x + logo_width, y + logo_height))

            # 輝度の計算
            average_brightness = calculate_average_brightness(region)

            # ロゴの選択
            if average_brightness >= BRIGHTNESS_THRESHOLD:
                logo_to_use = black_logo_resized
                opacity_value = OPACITY_DARK_LOGO
                logging.info("Selected Logo: Black")
            else:
                logo_to_use = white_logo_resized
                opacity_value = OPACITY_LIGHT_LOGO
                logging.info("Selected Logo: White")

            # ロゴの不透明度を適用
            logo_to_use = adjust_logo_opacity(logo_to_use, opacity_value)

            # ウォーターマーク用のレイヤーを作成
            watermark_layer = Image.new('RGBA', im.size, (0, 0, 0, 0))
            watermark_layer.paste(logo_to_use, (x, y), logo_to_use)

            # 画像とウォーターマークを合成
            combined = Image.alpha_composite(im, watermark_layer)

            # 出力ファイルのパスと拡張子を取得
            dir_name, file_name = os.path.split(image_path)
            base_name, ext = os.path.splitext(file_name)
            output_filename = f"{base_name}_watermarked{ext}"
            output_path = os.path.join(dir_name, output_filename)

            # 画像を保存
            if ext.lower() in ['.png', '.gif', '.tiff', '.tif', '.webp']:
                combined.save(output_path)
            else:
                combined_rgb = combined.convert('RGB')
                combined_rgb.save(output_path, quality=95)

            logging.info(f"Processed and saved: {output_path}")

    except Exception as e:
        logging.error(f"画像処理中にエラーが発生しました: {image_path}")
        logging.error(f"エラーの詳細: {e}")

def main():
    # ログの設定
    setup_logging()

    # コマンドライン引数の処理
    parser = argparse.ArgumentParser(description='画像にウォーターマークを追加するスクリプト')
    parser.add_argument('images', nargs='+', help='処理する画像ファイルのパス')
    args = parser.parse_args()

    # ウォーターマーク画像の読み込み
    black_logo, white_logo = load_logo_images()

    for image_path in args.images:
        process_image(image_path, black_logo, white_logo)

if __name__ == "__main__":
    main()

コード解説:

  • ライブラリのインポート:画像処理にPillow、数値計算にNumPyを使用しています。
  • ウォーターマーク画像の読み込み:指定したパスから黒と白のロゴ画像をRGBAモードで読み込みます。RGBAモードにすることで、透過情報を扱うことができます。
  • 画像ファイルの取得:コマンドライン引数から処理対象の画像ファイルのリストを取得します。これにより、複数の画像を一度に処理することが可能です。
  • 画像のサイズに応じたロゴのリサイズ:画像の縦横比を判定し、設定した割合に基づいてロゴのサイズを調整します。縦長画像では高さ、横長画像では幅を基準にロゴのサイズを計算し、アスペクト比を維持します。
  • ウォーターマークの位置計算:設定した位置の割合に基づいて、ウォーターマークを配置する位置を計算します。右下の位置を基準に、ロゴのサイズを考慮して左上の位置を求めます。位置が画像の範囲外にならないように調整します。
  • 明るさの計算:ウォーターマークを配置する領域の平均輝度を計算します。その値が明るさの閾値以上かどうかで、黒いロゴと白いロゴのどちらを使用するかを決定します。
  • 透明度の設定:選択したロゴに対して、設定した透過率を適用します。アルファチャンネルを操作することで、ロゴの透明度を調整し、画像に自然に重ね合わせます。
  • ウォーターマークの合成:元の画像とウォーターマークを合成します。透過情報を持つウォーターマークを正しく重ねるために、アルファ合成を使用しています。
  • 画像の保存:処理後の画像を、元のファイル名に「_watermarked」を付加して保存します。画像形式に応じて、適切なモードで保存します。例えば、JPEG形式の場合はRGBモードに変換し、品質を指定して保存します。

install.bat

環境構築用のバッチファイルです。仮想環境(venv)を作成し、必要なライブラリをインストールします。

初回ダウンロード時のみ実行する必要があり、インストール後は削除して構いません。


@echo off
chcp 65001 >nul
setlocal

REM 仮想環境を作成
python -m venv venv

REM 仮想環境をアクティベート
call venv\Scripts\activate.bat

REM 必要なライブラリをインストール
pip install --upgrade pip
pip install Pillow numpy

echo 環境構築が完了しました。
pause

コード解説:

  • 仮想環境の作成:Pythonのvenvモジュールを使って仮想環境を作成します。
  • ライブラリのインストール:PillowとNumPyをpipでインストールします。

watermark.bat

以下がPythonスクリプトを実行するためのバッチファイルです。


@echo off
chcp 65001 >nul
setlocal

REM 仮想環境をアクティベート
call venv\Scripts\activate.bat

REM スクリプトのパスを設定(バッチファイルと同じディレクトリに置く場合)
set SCRIPT_PATH=%~dp0watermark.py

REM 引数を収集
set args=
:loop
if "%~1"=="" goto after_loop
set args=%args% "%~1"
shift
goto loop
:after_loop

REM スクリプトを実行
python "%SCRIPT_PATH%" %args%

REM エラーレベルをチェック
if %ERRORLEVEL% neq 0 (
    echo エラーが発生しました。
    pause
)

コード解説:

  • 仮想環境のアクティベート:install.batで作成した仮想環境をアクティベートします。
  • スクリプトの実行:コマンドライン引数で指定した画像ファイルに対してPythonスクリプトを実行します。
  • エラーハンドリング:エラーが発生した場合はメッセージを表示します。

5. まとめ

このスクリプトを使うことで、画像に対する透かしの追加が簡単に自動化できます。明るさに応じたロゴの選択や、画像サイズに合わせたロゴのリサイズなど、細かい調整も不要です。ぜひ一度試してみてください。

ご質問やフィードバックがございましたら、以下のコメント欄からお気軽にお寄せください。

最後までお読みいただきありがとうございました。このスクリプトが皆様のお役に立てれば幸いです。

コメントする

ABOUTこの記事をかいた人

Web、Python、生成AI、投資、行動経済学の知見を活かし、みんなの生活を豊かにするための情報発信を心がけています。日々の選択を少し変えるだけで、ちょっと笑顔が増えるかも。一緒に学び、成長しながら、より良い人生にしていきましょう。