レンダリングされたキャラクターフレームの半透明ピクセルを削除または不透明化する方法
レンダリングされたキャラクターフレームから半透明ピクセルを削除するには、アルファ値を完全な透明または完全な不透明に変更します。半透明ピクセルをすべて削除するか、表示されているピクセルをすべて不透明化するか、輪郭の濃い部分を残すしきい値を使うかを選びます。
このワークフローは、輪郭をくっきりさせた透明なキャラクターPNGフレームを準備するクリエイター向けです。GIMPでの視覚的な方法と、フレームフォルダー全体に適用できる再現可能なPythonの方法を紹介します。書き出した画像に中間アルファ値が含まれていないことを確認する手順も説明します。
フレームのどこを変更する必要がありますか?
8ビットRGBA画像では、各ピクセルに赤、緑、青、アルファの各成分があります。アルファ値0は完全な透明、255は完全な不透明を意味します。1から254までの値は半透明です。Pillowのドキュメントでは、RGBAは透明度マスクを備えた4つの8ビットチャンネルとして説明されています。Pillow: Concepts
そのため、フレームがほぼ不透明に見えても、半透明ピクセルが多数含まれていることがあります。編集する前に、表示時のサイズと拡大したピクセル表示の両方でコピーを確認してください。その下に明るい背景と暗い背景を一時的に配置し、かすかな外縁ピクセル、欠けた細部、色のにじみがないかを確認します。
完成したアートワークに半透明が必要かどうかを判断してください。半透明のアクセサリーや柔らかな影を不透明または透明のマスクに変えると、見た目が大きく変わります。修正が必要なのがキャラクターの輪郭だけなら、全体に処理を適用する前に、その領域またはレイヤーを分離してください。
元のレンダリング済みフレームは保管してください。しきい値処理では多くのアルファ値が2つの端点にまとめられるため、処理後のファイルから元の透明度を確認することはできません。
半透明ピクセルを削除しますか、それとも不透明化しますか?
フレームシーケンス全体に同じ明確なルールを適用します。以下の表では、Cは保持するアルファ値の最小値で、保持されたピクセルはすべて完全に不透明になります。これらのルールは8ビット入力を対象としています。
| 目指す結果 | ルール | 主な見た目のトレードオフ |
|---|---|---|
| 半透明ピクセルをすべて削除する | アルファ値255のみを保持 | 半透明ピクセルだけでできた細部が消える |
| 表示されているピクセルをすべて不透明化する | アルファ値1~255を保持 | かすかな外縁ピクセルが不透明なシルエットに含まれる |
| 中間的な輪郭を選ぶ | アルファ値がC以上なら保持し、C未満なら破棄 | しきい値によって残るエッジピクセルが決まる |
例として、説明用のアルファ値0, 1, 64, 127, 128, 254, 255を考えてみます。C = 128の場合、出力は0, 0, 0, 0, 255, 255, 255になります。C = 255の場合、最後のピクセルだけが残ります。C = 1の場合、元から表示されていたピクセルがすべて不透明になります。
比較の出発点としてC = 128を使い、実際のキャラクターを確認してください。これは便利な中間値であり、最適な設定として確立された値ではありません。不要なかすかなピクセルが残るなら値を上げ、必要な細部が消えるなら下げます。比較のたびに元のフレームを使ってください。
しきい値を上げるとにじみは消えるものの、重要な細部も欠けてしまう場合、単一の全体しきい値では両方の目的を満たせません。その細部を個別に修正するか、残りの輪郭を処理する前に独立したマスクとして分離してください。
GIMPでアルファ値にしきい値を適用するには?
GIMP 3にはレイヤー → 透明部分 → アルファしきい値…があります。これは1つのレイヤーに作用し、アルファチャンネルが必要です。また、アルファチャンネルをロックが有効になっていると効果がありません。しきい値は0から1までで、しきい値を超える値は不透明になり、しきい値以下の値は透明になります。GIMP: Threshold Alpha
- フレームのコピーを開き、キャラクターレイヤーを選択します。
- アルファチャンネルがあることを確認し、アルファチャンネルをロックを無効にします。
- レイヤー → 透明部分 → アルファしきい値…を開きます。
- 中間値で切る場合はしきい値を0.5、アルファ値が0でないピクセルをすべて不透明化する場合は0に設定してプレビューします。
- 処理を適用する前に、小さな細部と外側の輪郭を確認します。
- 結果を別のPNGとして書き出します。
8ビット入力で半透明ピクセルだけを削除するには、しきい値を254/255、約0.996078に設定します。これにはしきい値1を設定しないでください。説明されている厳密な比較では、アルファ値1もしきい値と等しいため透明になります。GIMP: Threshold Alpha
一時的な確認用背景を、書き出すキャラクターファイルに含めないでください。PNGを書き出した後に開き直し、編集可能なドキュメントだけでなく書き出し結果も確認してください。
すべてのフレームに同じルールを適用するには?
複数のフレームには、画像ごとに個別調整するのではなく、固定のアルファ変換を使います。次の完全なPythonプログラムは、1つのフォルダー内にあるPNGファイルを直接処理し、ファイル名と寸法を保持して、新しいフォルダーに書き出します。
python -m pip install PillowでPillowをインストールし、プログラムをalpha_clean.pyとして保存します。個別の静止した8ビットRGBA PNGフレームを用意してください。パレット画像、高精度レンダリング、その他の画像形式は、このワークフローを使う前に意図的にその形式へ書き出してください。
このプログラムはgetchannelでアルファ値を取り出し、pointでルックアップテーブルを使って変換し、putalphaでアルファ値のみを置き換えます。これらの操作はPillow Imageモジュールのリファレンスに記載されています。
import argparse
from pathlib import Path
from PIL import Image
def threshold_alpha(image, cutoff):
result = image.copy()
table = [255 if value >= cutoff else 0 for value in range(256)]
result.putalpha(image.getchannel("A").point(table))
return result
def partial_count(image):
return sum(image.getchannel("A").histogram()[1:255])
def main():
parser = argparse.ArgumentParser()
parser.add_argument("source", type=Path)
parser.add_argument("destination", type=Path)
parser.add_argument("--cutoff", type=int, default=128)
parser.add_argument("--demo", action="store_true")
args = parser.parse_args()
if not 1 <= args.cutoff <= 255:
parser.error("--cutoff must be between 1 and 255")
if args.demo:
values = [0, 1, 64, 127, 128, 254, 255]
sample = Image.new("RGBA", (len(values), 1))
sample.putdata([(80, 160, 220, a) for a in values])
result = threshold_alpha(sample, args.cutoff)
print([result.getpixel((x, 0))[3] for x in range(len(values))])
return
if not args.source.is_dir():
parser.error("Source must be an existing folder")
if args.destination.exists():
parser.error("Destination must be a new folder")
files = sorted(
p for p in args.source.iterdir()
if p.is_file() and p.suffix.lower() == ".png"
)
if not files:
parser.error("No PNG frames found directly inside source")
args.destination.mkdir(parents=True)
for path in files:
with Image.open(path) as image:
if image.format != "PNG" or image.mode != "RGBA":
raise ValueError(f"{path.name}: expected RGBA PNG")
if getattr(image, "n_frames", 1) != 1:
raise ValueError(f"{path.name}: expected a single frame")
before = partial_count(image)
result = threshold_alpha(image, args.cutoff)
target = args.destination / path.name
result.save(target, format="PNG")
with Image.open(target) as saved:
after = partial_count(saved)
if saved.size != result.size or after != 0:
raise RuntimeError(f"{target.name}: verification failed")
print(f"{path.name}: partial pixels {before} -> {after}")
if __name__ == "__main__":
main()
まず、自己完結したサンプルを実行してください。デモモードでは、2つのフォルダー引数は受け取りますが、どちらのフォルダーも読み込みも作成もしません。
python alpha_clean.py unused_input unused_output --demo --cutoff 128
[0, 0, 0, 0, 255, 255, 255]と出力されます。しきい値1、128、255での変換、変更されないRGB値、PNGの保存と再読み込みの動作は、Pillow 13.0.0を使ったメモリ内のサンプルで確認済みです。
実際のフレームを処理する場合は、入力PNGをframesという名前のフォルダーに置き、次を実行します。
python alpha_clean.py frames frames_clean --cutoff 128
--cutoff 255を使うと半透明ピクセルがすべて削除され、--cutoff 1を使うと表示されているピクセルがすべて不透明になります。比較するたびに、新しい保存先フォルダーを選んでください。
このプログラムは未対応の入力や検証の失敗があると停止します。エラー前に処理されたファイルは保存先に残りますが、そのフォルダーの実行結果は未完了です。入力を修正し、別の新しいフォルダーに再実行してください。このプログラムはサブフォルダーを再帰的に処理せず、アニメーションのタイミングも保持しません。また、あらゆるメタデータを保持するための汎用ツールではありません。
不透明化したエッジが暗く、または薄く見えるのはなぜですか?
アルファのクリーンアップで変わるのは被覆率です。エッジの色は別途確認する必要があります。Microsoftは、RGBとアルファが独立しているストレートアルファと、RGBにアルファがすでに乗算されているプリマルチプライドアルファを区別しています。Microsoft Learn: Premultiplied alpha
説明用の計算例として、アルファ0.25のストレート赤(200, 0, 0)は、プリマルチプライドRGBでは(50, 0, 0)に相当します。このプリマルチプライドカラー値を誤ってストレートRGBとして扱い、アルファを単純に1にすると、暗い赤のピクセルになります。これは、説明されている変換関係から直接導かれます。Microsoft Learn: Premultiplied alpha
Pythonのワークフローは、正しくエンコードされたストレートアルファのRGBA PNG入力を前提としています。誤ってエンコードされた色を診断したり、意図された輪郭色を復元したりすることはできません。ピクセルを不透明化したときににじみが見える場合は、まず元のレンダーや書き出しでのアルファの解釈を確認してください。色自体が以前の背景との合成で汚染されている場合は、きれいな透明レンダーに戻るか、影響を受けたエッジを塗り直してください。
クリーンアップ後のフレームを、明るい背景と暗い背景の両方で比較してください。半透明ピクセルが0個であることは、アルファ値が2値であることを示しますが、エッジの色がキャラクターに合っていることまでは示しません。
書き出しや再生後もクリーンアップ結果を維持するには?
最終的なアルファクリーンアップの前にサイズ変更を行ってください。補間を使ったリサンプリングでは、透明ピクセルと不透明ピクセルの間に中間値が生じることがあります。Pillowの説明によると、バイリニアリサンプリングは補間によって値を計算し、ニアレストネイバーリサンプリングは既存の入力ピクセルを選択します。そのため、2値のアルファチャンネルをニアレストネイバーでサイズ変更すれば、2つの端点値が保たれます。Pillow: Concepts—Filters
意図する見た目がピクセル単位の明確なグリッドであれば、ニアレストネイバーを使ってください。別のビジュアルスタイルにする場合は、希望のフィルターで先にサイズを変更してから、できたフレームにしきい値を適用し、輪郭を再確認してください。その後に拡大縮小や合成を行った場合は、最終出力をもう一度確認してください。
シーケンス全体に同じしきい値を適用して確認します。細い特徴が隣り合うフレーム間でしきい値をまたぐと、個々のフレームは問題なく見えてもちらつくことがあります。その場合は、変化の前後にある元のフレームを確認し、その特徴を一貫するように修正してください。
シーケンスを使用する前に、次の具体的な条件を確認してください。
- 予定されているすべてのフレームが、元のファイル名とキャンバス寸法のまま存在する。
- 保存した各フレームで、アルファ値が1から254までのピクセル数が0である。
- 表示時のサイズで小さな細部が判読できる。
- 明るい背景と暗い背景のプレビューで、不要なにじみが見えない。
- 再生時に、動き全体を通してシルエットに一貫性がある。
キャラクターの意図された形状を保てるアルファルールを選び、一貫して適用し、書き出したシーケンスを確認してください。後から透明度の情報をすべて使って調整できるように、元のレンダーとクリーンアップしたフレームを一緒に保管してください。
