AIに「透明背景で」と頼んだPNGは透明ではなかった。市松模様ごと焼き込まれていた

「背景は透明で」と頼んで返ってきたPNGの左上の画素は、RGB(208,210,208)でした。透明ではなく、灰色です。
ステッカーのような小さなデジタル商品は、買った人がスライドや動画に重ねて使います。背景が透けていなければ商品として成立しません。そこで画像生成AIに、透明背景のステッカーをそのまま頼んでみました。
使ったのはOpenRouter経由のgoogle/gemini-3.1-flash-image、Hermes Agentの画像生成ツールです。比べたのは2通りで、どちらも正方形の1枚だけです。何度も試した平均ではありません。確認にはImageMagick 7.1.1-43と、Pillow 11.1.0を入れたPython 3.13.5を使いました。
返ってきたのは「透明に見える絵」
最初のプロンプトです。
A single flat-design sticker of a cute orange cat holding a coffee mug, white die-cut border around the cat, transparent background, PNG with alpha channel, no text
返ってきたPNGを画像として開くと、猫の後ろに、見慣れた灰色と白の市松模様が見えます。画像編集ソフトで透明部分に表示される、あの柄です。
ファイルはrequest-transparent.pngとして保存し、アルファチャンネルの有無を調べました。アルファチャンネルとは、画素ごとの「どれだけ透けるか」を持つ層です。PNGのカラータイプでいえば、透けを持つのは6(RGBA)で、持たないのは2(RGB)です。確認用に、次のスクリプトをalpha-report.pyとして置きます。
alpha-report.py の全文
import sys
from PIL import Image
for name in sys.argv[1:]:
im = Image.open(name)
rgba = im.convert("RGBA")
w, h = rgba.size
alpha = rgba.getchannel("A")
clear = sum(1 for a in alpha.getdata() if a == 0)
visible = [p for p in rgba.getdata() if p[3] > 0]
pink = sum(1 for r, g, b, a in visible if r - g > 60 and b - g > 60)
gray = sum(1 for r, g, b, a in visible
if max(r, g, b) - min(r, g, b) < 8 and 150 < r < 240)
print(f"{name}: mode={im.mode} alpha={alpha.getextrema()} "
f"透明{clear / (w * h):.1%} マゼンタ寄り{pink} 灰色寄り{gray}")
scratch="$TMPDIR/sticker-alpha"
mkdir -p "$scratch"
# 生成した2枚と、上のalpha-report.pyを作業フォルダへ移す
mv request-transparent.png request-magenta.png alpha-report.py "$scratch"/
cd "$scratch"
python3 alpha-report.py request-transparent.png
magick request-transparent.png -format "左上 %[pixel:p{0,0}] / 左下 %[pixel:p{0,1023}]\n" info:
request-transparent.png: mode=RGB alpha=(255, 255) 透明0.0% マゼンタ寄り0 灰色寄り688575
左上 srgb(208,210,208) / 左下 srgb(229,232,229)
モードはRGBで、アルファの最小値も最大値も255。透ける画素は0%です。灰色寄りの画素は68万8575個です。測った2点も208前後と229前後で色が違い、見た目どおり、市松模様そのものが絵として描かれていたようです。
このモデルは「透明背景」を、透明を表す柄として描いたようです。プロンプトには「PNG with alpha channel」とも書きましたが、それでもRGBのPNGが返りました。
灰色を抜く。5%では足りず、15%で抜けた
返ってきた絵を使うなら、灰色を透明に置き換えます。ImageMagickの-fuzzは、指定した色に近い色を同じ色とみなす幅です。-draw 'color 0,0 floodfill'は、左上の画素から同じ色とみなせる範囲を塗りつぶします。ここでは塗る色をnoneにして、透明にしました。
magick request-transparent.png -alpha set -fuzz 5% -fill none -draw 'color 0,0 floodfill' attempt-a.png
magick request-transparent.png -alpha set -fuzz 15% -fill none -draw 'color 0,0 floodfill' attempt-b.png
python3 alpha-report.py attempt-a.png attempt-b.png
attempt-a.png: mode=RGBA alpha=(0, 255) 透明32.9% マゼンタ寄り0 灰色寄り344007
attempt-b.png: mode=RGBA alpha=(0, 255) 透明65.0% マゼンタ寄り0 灰色寄り6718
5%では、市松の片方の色しか抜けませんでした。透明は32.9%で、灰色寄りが34万個も残っています。別の背景に重ねると、猫の周りに市松が穴あきで残っていました。
15%にすると、2色とも抜けました。透明は65.0%。ここまでは成功に見えました。
ところが灰色寄りが6718個、まだ残っています。別の背景に重ねて縁を拡大したら、白い縁の外側に、灰色のギザギザが並んでいました。市松の升目の影のような形で、重ねるとはっきり目立ちます。
fuzzをさらに上げる手もあります。ただ、市松の明るいほうは229前後で、白い縁との差はわずかです。上げすぎると縁まで削れると考え、この方向は止めました。
背景色を先に決めて、後で抜く
発想を変えました。最初から、抜きやすい背景を描かせます。単色のマゼンタ(#FF00FF)なら、猫や白い縁には使われません。境目がはっきりするはずです。
A single flat-design sticker of a cute orange cat holding a coffee mug, thick white die-cut border around the cat, placed on a perfectly flat solid pure magenta background (#FF00FF), uniform color with no gradient, no shadow, no checkerboard pattern, no text
返ってきたrequest-magenta.pngの左上と左下の2点を測ります。
magick request-magenta.png -format "左上 %[pixel:p{0,0}] / 左下 %[pixel:p{0,1023}]\n" info:
左上 srgb(251,6,250) / 左下 srgb(252,26,238)
#FF00FFを指定したのに、返ってきた左上は(251,6,250)でした。左下は(252,26,238)。完全な単色にはなりません。fuzzを2通りにして抜き、報告を並べます。
magick request-magenta.png -alpha set -fuzz 12% -fill none -draw 'color 0,0 floodfill' key-12.png
magick request-magenta.png -alpha set -fuzz 30% -fill none -draw 'color 0,0 floodfill' key-30.png
python3 alpha-report.py key-12.png key-30.png
key-12.png: mode=RGBA alpha=(0, 255) 透明74.7% マゼンタ寄り7707 灰色寄り10
key-30.png: mode=RGBA alpha=(0, 255) 透明75.3% マゼンタ寄り1276 灰色寄り10
どちらも背景は抜けています。ただ、縁にマゼンタが細い輪郭として残りました。12%で7707画素、30%で1276画素です。fuzzを広げれば減るものの、ゼロにはなりません。別の背景に重ねて拡大すると、ピンクの線も見えました。
最後に、不透明部分を2画素だけ内側へ削りました。-morphology Erode Disk:2は、アルファの縁を半径2の円の分だけ縮める操作です。
magick request-magenta.png -alpha set -fuzz 30% -fill none -draw 'color 0,0 floodfill' -channel A -morphology Erode Disk:2 +channel key-30-erode2.png
python3 alpha-report.py key-30-erode2.png
key-30-erode2.png: mode=RGBA alpha=(0, 255) 透明75.7% マゼンタ寄り1 灰色寄り10
マゼンタ寄りが1個まで減りました。同じように背景に重ねても、ピンクの輪郭は見えません。市松を抜く方法より、こちらのほうが縁はきれいです。
納得していないところ
きれいになったとはいえ、安心はできません。
まず、縮めた分だけ、白い縁が薄くなりました。ステッカーらしい厚みを残したい絵では、最初のプロンプトで縁をもっと太く頼む必要があります。
次に、縁がくっきり切れたままで、半透明でなめらかにつながっていません。拡大すると階段状のギザギザが見えます。等倍では気になりません。拡大して使う商品なら、別の処理が要ります。
それと、左上から塗り広げる方法は、絵に囲まれた穴を抜けません。猫の腕とマグカップの間に背景が丸く残るような絵なら、その部分はマゼンタのままです。今回の1枚に、そういう穴はありませんでした。穴の確認は別に要ります。
もうひとつ。マゼンタ版の猫は、最初の猫と別の絵でした。ひげが描かれ、マグカップは青灰色です。同じ猫を、背景だけ変えて作り直すことはできませんでした。シリーズ物を作るなら、ここが次の壁。
2枚の生成にかかったのは、合わせて約0.134ドルです。1枚あたり約0.067ドル。
次に変えること
「透明背景」とは頼まず、単色の背景を指定して後から抜く。まずこれを標準にします。買った人に渡すファイルには、アルファの最小値が0であること、四隅が透明であることの2点を検査に入れます。mode=RGBと出た時点で、そのファイルは納品しません。
半透明の縁をどう作るか。次はそこを試します。


