人物の切り抜きマスクを BiRefNet で作り直す:新旧を並べて比べた記録
に公開
OS標準の人物切り抜きで作ったマスクを rembg の BiRefNet(birefnet-portrait)で作り直し、新旧を並べて比べました。8枚中7枚で改善、1枚は悪化して戻した手順と、縁のぼかしや手直しの要点を記録します。
人物を台紙から浮かせて見せるカードの試作で、人物の切り抜きマスクを作り直しました。最初に使っていたのは macOS 標準の人物切り抜き(Vision)です。これを rembg 経由の BiRefNet(birefnet-portrait)に替え、新旧のマスクを並べて比べました。結果は8枚中7枚で新しい方が良く、1枚だけ悪化したので元に戻しました。
- 切り抜き専用のモデルに替えると、髪の毛や袖の縁がなめらかになった
- 「全部替える」ではなく「新旧を並べて枚ごとに判定する」にしたので、悪化した1枚を拾えた
- 元のマスクは別名で必ず残す。戻せないと比較そのものが成り立たない
- 縁は埋め込む時に少しぼかす(ぼかしの強さは 0.8)

何を作りたかったか
作っているのは、人物の写真を主役にした「動くカード」です。人物の後ろに別の背景を置き、手前の人物だけを少し浮かせたり、枠の外へ飛び出させたりします。この表現は、人物だけを切り抜いたマスクの品質にそのまま左右されます。縁が荒いと、背景が透けて見えたり、髪の周りに白い輪郭が残ったりして、せっかくの動きが安っぽく見えてしまいます。
実際、動きを一通り作って第三者の目で採点してもらうと、切り抜きの縁は毎回のように指摘が残る項目でした。点を上げる手として私が選んだのが「切り抜きを専用モデルで作り直す」ことと、「きれいに見える写真だけを固定で使う」ことの2つです。この記事は前者の記録です。
最初のやり方と、何が駄目だったか
最初のマスクは macOS の Vision フレームワークの人物切り抜きで作っていました。手軽で速く、ほとんどの写真ではそれなりに使えます。ただ、次のような弱点が出ました。
- 髪の毛の細い束や袖の端が、ぎざぎざになったり欠けたりする
- 人物のすぐそばにある物(手に持った物など)のまわりで、輪郭が人物側へにじむ
- 髪につながった背景の一部を、人物として取り込んでしまうことがある
どれも「仕様どおりに動いた結果」で、設定で直せるものではありませんでした。切り抜きを目的に作られたモデルに替える方が早いと判断しました。
直した方法:BiRefNet で全写真を作り直す
rembg は Python の切り抜きライブラリで、モデルを差し替えられます。そのうち birefnet-portrait は人物向けの BiRefNet です。次が実際に使ったスクリプトの要点です(写真の名前は一般的なものに置き換えています)。
from PIL import Image
from rembg import new_session, remove
MODEL = 'birefnet-portrait'
session = new_session(MODEL)
for name in PHOTOS:
src = S / (name + '-bg.jpg') # 元の写真
mask = S / (name + '-mask.png') # いま使っているマスク
keep = S / (name + '-mask-vision.png') # 旧マスクの保管先
if not keep.exists():
shutil.copy(mask, keep) # 旧マスクを別名で残す
im = Image.open(src).convert('RGB')
m = remove(im, session=session, only_mask=True) \
.convert('L').resize(im.size, Image.LANCZOS)
m.save(mask)
ポイントは3つあります。
new_sessionはループの外で1回だけ作ります。モデルの読み込みは重く、写真ごとに作ると時間が無駄になります。remove(..., only_mask=True)で、切り抜いた画像ではなく白黒のマスクだけを受け取ります。写真そのものは変えず、マスクの差し替えだけで済ませたかったためです。- 旧マスクを
-mask-vision.pngとして先にコピーします。if not keep.exists()で、すでに保管済みなら上書きしません。スクリプトを2回走らせても、いちばん元のマスクが失われません。
実際のスクリプトには、前に手で直したことのある1枚について「手直し前のいちばん元のもの」を保管先へ入れる分岐もあります。新旧の比較では「何が旧なのか」が曖昧になりやすいので、保管の規則を先に決めておくのが大事でした。
さらに、新旧でどれだけ人物の面積が違うかを数字でも出しています。
a = sum(1 for v in m.getdata() if v > 128) / (im.width * im.height)
b = sum(1 for v in old.getdata() if v > 128) / (im.width * im.height)
print(f'{name}: 人物の割合 新 {a:.3f} / 旧 {b:.3f}')
これは良し悪しを決める指標ではありません。新旧の面積が極端に違う写真を見つけて、先に目で確認するための目印です。判定そのものは次の節のとおり、並べて見て行いました。
新旧を並べて比べた結果
マスクを替えただけでは「良くなった」と言えません。数字で測れるのは面積くらいで、縁の自然さは見るしかないからです。そこで、同じ写真の新旧の切り抜きを並べた画像を作り、別のエージェントに「どちらが良いか」を枚ごとに判定させました。
結果は次のとおりです。
- 8枚中7枚で、新しい BiRefNet 版が良い
- そのうち1枚は、手に持った物のまわりのにじみが消えた
- 1枚(水色のフリルの衣装)だけは悪化したので、元の Vision 版に戻した
悪化の理由は記録に残っていません。ここは推測せず、「この1枚はモデルが苦手な形だったのだろう」とも書かないでおきます。分かっているのは、全部を一律に新しい方へ替えていたら、この1枚は品質が下がっていたという事実です。枚ごとの比較をしたことの価値はここにあります。
戻す作業は、保管しておいた -mask-vision.png を -mask.png へコピーするだけです。先に旧マスクを別名で残す、という最初の判断がここで効きました。
別の人物向けのスクリプト
写真の組が増えてからは、写真の名前を引数で渡せる版も作りました。やることは同じで、保管先と呼び出しだけが違います。
session = new_session("birefnet-portrait")
for key in sys.argv[1:]:
src = FG / f"{key}-bg.jpg"
mask = FG / f"{key}-mask.png"
keep = FG / f"{key}-mask-vision.png"
if not src.exists():
print("skip", key); continue
shutil.copy(mask, keep) # いまのマスクを残す
im = Image.open(src).convert("RGB")
m = remove(im, session=session, only_mask=True) \
.convert("L").resize(im.size)
m.save(mask)
こちらは shutil.copy(mask, keep) を無条件に行います。引数で渡した組を1回だけ処理する想定なので、保管に「あるなら上書きしない」の条件は付けていません。同じ組を2回流すと保管先が BiRefNet 版で上書きされるので、気をつける必要があります。これは記録にあるスクリプトを読んで気づいた点で、実際に事故が起きたわけではありません。
縁のぼかしと手直し
マスクは、人物を台紙へ埋め込む時に少しぼかしています。ぼかしの強さは 0.8 です。縁をぼかすのは、切り抜きの境目が「貼り付けた」ように見えるのを和らげるためです。このぼかしを入れる処理はスクリプトとは別の場所にあり、この記事では値だけを記録から引いています。
モデルを替えても、全部がきれいになるわけではありません。写真によっては、手で直しました。
- 髪につながった背景の葉が、人物として取り込まれていた写真は、その葉の部分を消した
- 体の下のあたりが、テーブルの上の物で平らに切れて見えた写真は、その部分を下へ向けて消した
手直し前の BiRefNet のマスクは別名(-mask-birefnet.png)で残してあります。あとから「手直ししすぎたかも」と思った時に、素の出力へ戻れるようにするためです。
測った結果と、残った弱点
切り抜きを作り直し、使う写真も固定したあとの採点は、同じ基準・同じ5枚で 7.3 → 6.8 → 6.9 でした。直した内容に対して点が上下しており、絶対点は回ごとに 0.3 ほどぶれます。点だけで「BiRefNet で何点上がった」とは言えない、というのが正直なところです。
そのため次からは、点ではなく「前の版と並べて、どちらが良いか」で測る運用に変えました。この記事の新旧比較が、その運用の最初の例です。
残る弱点もあります。
- 悪化した1枚は元に戻しただけで、原因は調べていない
- 手直しは写真ごとの手作業で、自動化していない
- 8枚という数は小さく、BiRefNet が一般に Vision より良いとは言えない。この写真の組での結果にとどまる
まとめ
人物の切り抜きマスクを BiRefNet で作り直したところ、8枚中7枚で縁が良くなり、1枚は悪化して戻しました。要点は、旧マスクを別名で必ず残すこと、新旧を並べて枚ごとに判定すること、点の揺れが大きい採点は前後比較に置き換えることです。
マスクの上に載せる「頭や腕が写真の縁で切れた絵を継ぎ足す」方法は 縁で切れた絵を継ぐ記事 に、カードゲーム側の作り方は ゲームの数値調整の記事 にあります。