課題
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でも、切り抜きができるようにしたかったのです。
以上です。