【クイックアクション】線画イラストを切り抜く方法 その3

課題

Macのクイックアクションには純正で「背景を削除」があるのですが、どうも精度が今ひとつです。

画像ファイルの上で、右クリック→クイックアクション、「背景を削除」を選びます。非常に便利です。

普通の写真、動物や人物の映った普通の写真からの、背景削除は綺麗に出来ます。

ところが、線だけで描いたイラストをよく使うのですが、その背景削除は、なかなか上手にいきません。下のように、白塗りは不正確、線も不正確です。

ポイント解説:

ということで、これまで、線画イラストを切り抜く方法の、その1、その2と紹介してきましたが、今回は、その3です。Macを使う人にとっては、一番、使いやすいかもしれません。

まず最初に、ChatGPTにスクリプトの記述を依頼します。

以下が、依頼文(プロンプト)です。

プロンプト
MacのAutomator,Python、シェルスクリプト、その他必要な言語を使って、下記フローの記述をお願いします。

1 MacのPDFまたは画像ファイル(pdf png jpg jpeg gif)の上で右クリックしてクイックアクションを起動する。クイックアクション名を”PDFまたは画像から切り抜き画像の生成”とする。
2 PDFファイルはJPG画像に変換して、他はそのままで、画像1とする。
3 画像1から、輪郭線と線だけを元の色(黒色)、不透明で抽出し、それ以外は、完全に透明にした画像(線と輪郭線だけの透過PNG画像)を作成する。注意点として、線の中は中空にせず線の色で塗り埋めること。これをPNG画像2とする。
4 PNG画像2から、線によって閉じた輪郭線(外周線)を検出して、その輪郭線の内側を白塗りにして、白塗り以外の部分を透明にして、PNG画像3を生成する。
5 PNG画像3を下にして、PNG画像2を上にして、重ねて、PNG画像4を生成する。PNG画像4が完成した画像となる。
6 PNG画像4、を読み込んだファイルと同じフォルダに書き込む。ファイル名は”読み込んだファイルのファイル名から拡張子を除いた部分”&”_cropped.png”とする。
7 画像1、PNG画像2、PNG画像3、は削除する。
以上
スクリプト

何回か修正、微調整を行なって、正しく処理されるようになったスクリプトの記述は以下です。Automator、シェルスクリプト、Python3、を利用しています。

Mac Automatorの設定条件:
クイックアクション
ワークフローが受け取る現在の項目:ファイルまたはフォルダ
検索対象:Finder.app
シェル:/bin/bash
入力の引き渡し方法:引数として
「シェルスクリプトを実行」を挿入

下欄のスクリプトをコピペしてください。
#!/bin/bash

FILE_PATH=”$1″
EXTENSION=”${FILE_PATH##*.}”
BASENAME=$(basename “$FILE_PATH” .${EXTENSION})
DIRNAME=$(dirname “$FILE_PATH”)

cd “$DIRNAME”

# JPEG変換(PDFのみ)
if [[ “$EXTENSION” == “pdf” ]]; then
    /usr/local/bin/pdftoppm -jpeg -r 300 “$FILE_PATH” “${BASENAME}_page”
    mv “${BASENAME}_page-1.jpg” “${BASENAME}_page.jpg”
    IMAGE_NAME=”${BASENAME}_page.jpg”
elif [[ “$EXTENSION” == “gif” ]]; then
    # GIF → JPEG(最初のフレーム)に変換
    IMAGE_NAME=”${BASENAME}_frame.jpg”
    /usr/local/bin/convert “$FILE_PATH[0]” “$IMAGE_NAME”
else
    # その他の画像はそのまま使う
    cp “$FILE_PATH” “${BASENAME}_page.jpg”
    IMAGE_NAME=”${BASENAME}_page.jpg”
fi

# Bash変数を Python に渡すため export
export BASENAME
export DIRNAME
export IMAGE_NAME

/Users/xxxxxx/python_scripts/venv_pdfcrop/bin/python3 <<“EOF”
import cv2
import numpy as np
from PIL import Image
import os

basename = os.environ.get(“BASENAME”)
dirpath = os.environ.get(“DIRNAME”)
jpg_path = os.path.join(dirpath, os.environ.get(“IMAGE_NAME”))
img = cv2.imread(jpg_path, cv2.IMREAD_COLOR)

# === PNGやJPEGからの入力の場合は人工的に解像度アップ ===
# PDF由来なら元から300dpiあるが、画像は低解像度の可能性があるためスケーリング
img = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LINEAR)

# === 線の抽出(濃い部分全体を抽出)===
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)

# 線を適度に太らせて塗り潰す(85%程度に抑える)
kernel = np.ones((3,3), np.uint8)
filled = cv2.dilate(thresh, kernel, iterations=1)

# RGBA画像を生成し、黒で完全に塗りつぶし、不透明に
rgba = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8)
rgba[:, :, 0:3] = 0
rgba[:, :, 3] = filled
png2_path = os.path.join(dirpath, f”{basename}_lineonly.png”)
cv2.imwrite(png2_path, rgba)

# === 外周内側白塗り(PNG画像3)===
contours, _ = cv2.findContours(filled, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mask = np.zeros_like(gray)
cv2.drawContours(mask, contours, -1, 255, -1)

white_fill = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8)
white_fill[:, :, 0:3] = 255
white_fill[:, :, 3] = mask
png3_path = os.path.join(dirpath, f”{basename}_filled.png”)
cv2.imwrite(png3_path, white_fill)

# === 合成してPNG画像4を作成 ===
img2 = Image.open(png2_path).convert(“RGBA”)
img3 = Image.open(png3_path).convert(“RGBA”)
combined = Image.alpha_composite(img3, img2)

png4_path = os.path.join(dirpath, f”{basename}_cropped.png”)
combined.save(png4_path)

# === 中間ファイル削除 ===
os.remove(jpg_path)
os.remove(png2_path)
os.remove(png3_path)
EOF

結果

このスクリプトを実行します。
PDFまたは画像ファイルの上で右クリックして、クイックアクションから、選択します。

ファイル名の末尾に、_cropped.pngと入ったファイルが出来るので、これをクリックして確認します。


私の個人的な設定ですが、スクリーンショットはPDFで保存するようにしています。その理由は、PDFファイルですと図形や文字などを追記することができ、その追記も後から修正ができるからです。そのため、画像ファイルでもPDFでも、切り抜きができるようにしたかったのです。

以上です。