Part 1: GANから生成AIへ — 移行の理由と方法
![]()
はじめに
本記事は、GANを用いた仮想試着システムの構築を記録したMETA FITシリーズの続編です。前シリーズはPart 5で、体型の多様性、処理速度、衣服の忠実度における根本的な限界を認めて終わりました。
この新シリーズでは、GANベースのパイプラインから生成AIへの移行を3回に分けて記録します。技術的な判断、実装の詳細、そして実際の結果がどうだったかを述べます。
Part 1ではなぜ移行したかを扱います。生成AIが仮想試着を変え始めた背景、それを自分のシステムで試す動機、そしてGPUパイプライン全体をAPI呼び出しに置き換えたアーキテクチャの再設計について解説します。
ソースコードはすべてgithub.com/matu79go/metafitで公開しています。
生成AIが仮想試着を変えた
移行の最大の動機は、生成AIの登場によって仮想試着の実現方法が根本的に変わりつつあることでした。
Googleは2024年にAIを活用した仮想試着機能をGoogle Shoppingに導入しています。ユーザーが商品ページで服を選ぶと、多様な体型のモデルがその服を着た画像を生成AIがリアルタイムで生成する。かつて私が何年もかけてGANで構築しようとしていた機能が、APIの向こう側で実用化されていたのです。
これを見て、疑問が生まれました。同じ技術を使えば、自分が構築したPASTA-GAN++パイプラインを丸ごと置き換えられるのではないか。 GANが抱えていた体型多様性の問題、処理速度の問題、インフラの問題——それらすべてが解消されるかもしれない。
この技術検証が本プロジェクトの出発点です。
ライセンスの問題:移行を後押しした副次的理由
技術的な関心に加え、ライセンスの問題も移行を後押ししました。
PASTA-GAN++システムのコードベースを精査したところ、主要5コンポーネントが非商用ライセンスだったことが判明しました。
| コンポーネント | ライセンス | パイプラインでの役割 |
|---|---|---|
| PASTA-GAN++ | 非商用研究限定 | 試着画像生成の中核 |
| StyleGAN2 (NVIDIA) | NVIDIA Source Code License-NC | ジェネレーター基盤 (torch_utils/, dnnlib/) |
| OpenPose (CMU) | 学術非商用 | ポーズ検出(18点) |
| PF-AFN | 非商用研究限定 | ワーピングモジュール |
| FlowNet2 (Freiburg) | 研究専用 | オプティカルフロー推定 |
商用ライセンスの選択肢がある成分もありました。例えばOpenPoseはCMU FlintBox経由で年間約$25,000で利用可能でしたが、すべてのコンポーネントのライセンスを個別に取得するコストと複雑さは現実的ではありませんでした。
より重要な点として、ライセンス制約は個別コンポーネントだけでなくパイプライン全体に適用されます。OpenPoseを前処理として使い、その結果を商用ライセンスのモデルに渡す場合でも、OpenPoseの商用利用に該当します。
パターンA(問題なし):
写真 → Gemini API → 試着結果
→ Geminiのライセンスのみ適用
パターンB(違反):
写真 → OpenPose(キーポイント検出) → Gemini API → 試着結果
→ OpenPoseの非商用ライセンスに違反
商用利用可能な残りのコンポーネント——Graphonomy(MIT)、OpenCV(Apache 2.0)、PyTorch(BSD)——は有用ですが、パイプラインの中核を成す非商用コンポーネントなしでは機能しません。
生成AIへの技術的関心とライセンスの制約——この2つの理由が重なり、コンポーネント単位の改善ではなく、アプローチ全体を変える判断に至りました。
2つの選択肢:GeminiとVertex AI
Googleの生成AI製品群を調査した結果、仮想試着に使える2つの異なるアプローチが見つかりました。
Gemini画像生成(Nano Banana)
Gemini APIの画像生成機能——内部名称Nano Banana——は汎用的な画像編集モデルです。画像とテキストプロンプトを入力として受け取り、変換された画像を出力します。
仮想試着への応用では、人物画像と衣服画像に加え、望む変換を記述したプロンプトを送信します。モデルが指示を解釈し、結果を生成します。
from google import genai
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
response = client.models.generate_content(
model="gemini-3-pro-image-preview",
contents=[prompt, person_image, clothing_image],
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
),
)
強み: 極めて柔軟。プロンプトで正確に何をすべきかを指定できます。ある人物から服を抽出して別の人物に着せる、特定の体型特徴を保持する、背景を維持するなど。試着専用の学習済みモデルは不要です。
制約: 結果はプロンプトエンジニアリングに大きく依存します。ドレスのような特徴的なデザインでは、モデルが衣服を独自に解釈してしまうことがあります。
Vertex AI Virtual Try-On
Googleは仮想試着専用モデル(virtual-try-on-001)もVertex AIを通じて提供しています。Geminiの汎用アプローチとは異なり、商品画像を人物写真にフィッティングすることに特化したモデルです。
# Vertex AI REST API呼び出し
payload = {
"instances": [{
"personImage": {
"image": {"bytesBase64Encoded": person_b64}
},
"productImages": [{
"image": {"bytesBase64Encoded": clothing_b64}
}]
}],
"parameters": {"sampleCount": 1}
}
強み: 商品画像に対する忠実度が高い。色の正確さ、衣服の構造、プロポーションが適切に保持されます。ECサイト向けに設計されています。
制約: 人物間の着せ替え(transfer)には対応していません。白背景の平置き商品画像を前提として設計されています。GCPプロジェクトのセットアップとサービスアカウント認証が必要です。
ハイブリッド戦略
2つのエンジンは補完的な強みを持っています。
| シナリオ | 最適エンジン | 理由 |
|---|---|---|
| ECサイト:商品を顧客に着せて見せる | Vertex AI VTO | 商品→人物に特化、最高の忠実度 |
| 「あの人と同じ服を着たい」 | Gemini(Nano Banana) | 人物画像から服を抽出できる唯一の選択肢 |
| 異性間・体型差のある着せ替え | Gemini(Nano Banana) | プロンプトで体型保持を制御可能 |
どちらかを選ぶのではなく、入力の種類と用途に応じてエンジンを使い分ける構成が最適です。
実装:パイプラインからAPI呼び出しへ
旧アーキテクチャ
前シリーズのPart 3とPart 4で解説したPASTA-GAN++パイプラインは、複数の逐次処理が必要でした。
入力画像
→ OpenPose: 18箇所の骨格キーポイント抽出 → JSON
→ Graphonomy: 20クラスの人体セグメンテーション → PNG
→ PASTA-GAN++:
→ style_encoding(衣服, 保持マスク) → スタイルベクトル
→ const_encoding(ポーズテンソル) → ポーズ特徴
→ mapping(z, スタイル) → wベクトル
→ synthesis(w, ポーズ, 衣服特徴) → 出力画像
各段階が独自のモデル、独自の前処理要件、独自の障害モードを持っていました。test.pyの推論コードがその複雑さを示しています。
# StyleGAN2ベースのジェネレーターをロード
with dnnlib.util.open_url(config["network"]) as f:
G = legacy.load_network_pkl(f)["G_ema"].to(device)
# 各サンプルをフルパイプラインで処理
for data in dataloader:
image, clothes, pose = data[0], data[1], data[2]
norm_img, norm_img_lower = data[4], data[5]
retain_mask, skin_average = data[10], data[11]
# 衣服の外観からスタイルエンコーディング
gen_c, cat_feat_list = G.style_encoding(
torch.cat([norm_img, norm_img_lower], dim=1),
retain_mask
)
# 骨格からポーズエンコーディング
gen_z = torch.randn(1, G.z_dim, device=device)
ws = G.mapping(gen_z, gen_c)
pose_feat = G.const_encoding(pose)
# 最終合成
gen_imgs = G.synthesis(ws, pose_feat, cat_feat_list, ...)
これにはNVIDIA CUDA対応のDocker、約4GBのGPUメモリ、事前計算済みのキーポイントとセグメンテーションマスク、320×512ピクセルに正規化された画像が必要でした。
新アーキテクチャ
try_on_test.pyのGeminiベースの実装は、パイプライン全体を1回のAPI呼び出しに集約しています。
def run_tryon(person_path, clothing_path, prompt, mode, use_preprocess):
load_dotenv()
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
# 画像をAPI互換の形式でロード
person_part = load_image_as_part(person_path)
clothing_part = load_image_as_part(clothing_path)
contents = [prompt, person_part, clothing_part]
response = client.models.generate_content(
model="gemini-3-pro-image-preview",
contents=contents,
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
),
)
# 生成画像の抽出と保存
for part in response.candidates[0].content.parts:
if part.inline_data:
image_bytes = part.inline_data.data
# ファイルに保存...
GPUなし。Dockerなし。前処理パイプラインなし。ポーズの理解、人体のセグメンテーション、衣服の抽出、画像合成——すべてをモデルが内部で処理します。
2つのモード、1つのインターフェース
スクリプトはプロンプトの切り替えにより2つのモードをサポートしています。
clothingモード——商品画像を人物に適用:
PROMPT_CLOTHING = """You are a virtual fitting AI model.
Given the person image and the clothing product image,
generate a new image of THE SAME PERSON wearing THE GIVEN CLOTHING.
The person's face MUST remain EXACTLY identical to the input.
Do NOT regenerate or modify the face in any way.
Preserve the exact same: body shape, pose, background, hair, accessories.
Only change the clothing to match the provided product image."""
transferモード——ある人物の服を抽出して別の人物に着せる:
PROMPT_TRANSFER = """You are a virtual fitting AI model.
The first image is the TARGET person.
The second image is the SOURCE person wearing the clothes to transfer.
Extract only the clothing design, color, pattern, and style from
the SOURCE person, and generate a new image of the TARGET person
wearing those exact clothes.
The TARGET person's face MUST remain EXACTLY identical.
Do NOT regenerate or modify the face in any way.
Maintain the TARGET person's body shape and pose exactly."""
transferモードは特に重要です。PASTA-GAN++の機能(OpenPose+Graphonomy+学習済みGANが必要だった処理)を、テキストプロンプトと2枚の画像だけで再現しています。
MediaPipeの実験:不要だった複雑さ
初期の仮説として、明示的な体の情報——キーポイントや顔のランドマーク——を提供すれば結果が改善するのではないかと考えました。実装ではオプションのMediaPipe前処理を追加しました。
def preprocess_images(person_path, clothing_path, mode):
# MediaPipe顔ランドマーク検出
face_result = face_detector.detect(mp_image)
if face_result.face_landmarks:
landmarks = face_result.face_landmarks[0]
# 正規化座標から顔のバウンディングボックスを抽出
face_left = min(lm.x for lm in landmarks)
face_top = min(lm.y for lm in landmarks)
# ... 参照用に顔をクロップ
# MediaPipeポーズランドマーク検出
pose_result = pose_detector.detect(mp_image)
if pose_result.pose_landmarks:
# 13箇所の体のプロポーションを抽出
shoulder_width = abs(landmarks[11].x - landmarks[12].x)
hip_width = abs(landmarks[23].x - landmarks[24].x)
# ... 体の測定値を計算
この関数は被写体の体のプロポーションを記述した補足プロンプトと、追加参照用にクロップした顔画像を生成しました。
テストの結果、これは不要であることが判明しました。高解像度の入力画像(1000px以上)があれば、Gemini単体でも前処理を加えたバージョンと同等かそれ以上の結果が得られました。MediaPipeのステップはレイテンシと複雑さを追加するだけで、品質の意味ある改善にはつながりませんでした。
これは重要な発見でした。生成AIモデルはすでに人体の解剖学を十分に理解しており、明示的なポーズ/体型情報を追加しても品質は向上しません。前シリーズのPart 4で扱ったポーズ推定や人体パーシングの分野全体が、パイプラインの必須要素ではなくオプションの補助情報になったのです。
顔の復元:解決済みの問題
画像生成で常に懸念されるのが顔の保持です。初期テストでは、特に低解像度の入力で顔の品質が劣化するケースがありました。実装には顔復元の後処理が含まれています。
def postprocess_face_restore(original_path, generated_path):
# 両画像で顔を検出
orig_face = detect_face_region(original_image)
gen_face = detect_face_region(generated_image)
# LAB色空間での色調補正
orig_lab = cv2.cvtColor(orig_crop, cv2.COLOR_BGR2LAB)
gen_lab = cv2.cvtColor(gen_crop, cv2.COLOR_BGR2LAB)
for ch in range(3):
gen_ch = gen_lab[:,:,ch].astype(float)
gen_ch = (gen_ch - gen_ch.mean()) / (gen_ch.std() + 1e-6)
gen_ch = gen_ch * orig_lab[:,:,ch].std() + orig_lab[:,:,ch].mean()
result_lab[:,:,ch] = np.clip(gen_ch, 0, 255)
# 楕円形フェザーマスクによるシームレスなブレンド
feather_size = max(face_h, face_w) // 3
mask = create_elliptical_mask(face_h, face_w)
mask = cv2.GaussianBlur(mask, (feather_size*2+1, feather_size*2+1), 0)
# 元の顔を生成画像にブレンド
result = original_face * mask + generated_face * (1 - mask)
しかし、これも高解像度の入力では不要でした。プロンプトベースのアプローチ(「顔は絶対に変えるな」)は、入力画像の解像度が十分であれば機能しました。
一貫したパターンが見えてきました。解像度が品質の最大要因です。低解像度の画像(320px、PASTA-GAN++が動作していたサイズ)には補助処理が必要です。高解像度の画像(1000px以上)にはAPI呼び出し以外何も必要ありません。
Vertex AI VTO:専用モデルという選択肢
商品画像からの試着には、Vertex AIが専用モデルを提供しています。compare_vto.pyとtest_vertex_vto.pyの実装はサービスアカウント認証を用いたREST APIを使用しています。
def get_vertex_token():
credentials = service_account.Credentials.from_service_account_file(
os.getenv("GOOGLE_APPLICATION_CREDENTIALS"),
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
credentials.refresh(google.auth.transport.requests.Request())
return credentials.token
def run_vertex_vto(person_path, clothing_path):
token = get_vertex_token()
endpoint = (
f"https://{LOCATION}-aiplatform.googleapis.com/v1/"
f"projects/{PROJECT_ID}/locations/{LOCATION}/"
f"publishers/google/models/virtual-try-on-001:predict"
)
payload = {
"instances": [{
"personImage": {"image": {"bytesBase64Encoded": person_b64}},
"productImages": [{"image": {"bytesBase64Encoded": clothing_b64}}]
}],
"parameters": {"sampleCount": 1}
}
response = requests.post(
endpoint,
headers={"Authorization": f"Bearer {token}"},
json=payload,
timeout=120
)
実務上の注意点として、GCP認証のセットアップではバージョン非互換への対処が必要でした。gcloud auth application-default loginは古いgcloud CLIバージョンでスコープエラーが発生し、gcloud components updateは停止してしまいました。解決策は、Vertex AIユーザーロールを持つ専用サービスアカウント(metafit-vto)を作成し、そのJSONキーファイルを直接使用することでした。
変化の全体像:Before/After
| 観点 | PASTA-GAN++(以前) | Gemini + Vertex AI(以後) |
|---|---|---|
| インフラ | Docker + NVIDIA GPU | APIキー |
| 処理パイプライン | 3段階(OpenPose → Graphonomy → GAN) | 1回のAPI呼び出し |
| 画像解像度 | 固定320×512 | 任意の解像度 |
| 体型多様性 | 学習データに少ない体型で劣化 | あらゆる体型に対応 |
| 商用ライセンス | 非商用5コンポーネント | 完全商用可能 |
| 処理コスト | GPU計算リソース | API課金(約$0.02-0.04/枚) |
| コード複雑度 | パイプライン制御で約500行 | API連携で約100行 |
| transferモード | 学習済みGANモデルが必要 | プロンプトベース、学習不要 |
次回予告
Part 1ではこの移行の理由と方法を扱いました。Part 2では、Nano Bananaの16テストケースにわたる体系的な検証を記録します。ノイズのある画像での初期実験から高解像度のアクションポーズまで、そして前処理ではなく解像度こそが品質の鍵であるという発見について述べます。
META FIT GenAIシリーズ:
- Part 1: GANから生成AIへ(この記事)
- Part 2: Nano Banana Virtual Try-On — 16テストケース
- Part 3: 3エンジン対決