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

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と出た時点で、そのファイルは納品しません。

半透明の縁をどう作るか。次はそこを試します。

出典

この記事をシェア