👓

【技術検証】Gemini 2.5 Flash × C2PA × 複数モデルで構築する「堅牢なAI画像真贋判定システム」のアーキテクチャ

に公開
2025/12/21

1. はじめに:なぜ「目には目を」なのか

「その画像、本当に人間が描きましたか?」

最近の生成AIの進化は凄まじく、パッと見では「AIっぽい」と感じても、確信を持てないことが増えました。なんとなく怪しい、でも証拠がない。そんな「人間の目が真実を見抜けなくなっていく悔しさ」を感じたことはないでしょうか?

そこで私は開発しました。人間の目で判断できないなら、AIを使って判断すればいい。
コンセプトは目には目を。AIにはAIを。

この記事では、私が開発したAI画像真贋判定システム月ねこAI判定局 Evidence Lensの裏側にある、安定性と信頼性を担保するための技術的アーキテクチャについて解説します。

2. 技術スタックとディレクトリ構成

本システムは、I/Oバウンドな並列処理(外部APIの多重呼び出し)に有利な Node.js (TypeScript) をバックエンドに採用しています。

🛠 Tech Stack

  • Runtime: Node.js v20 (Express)
  • AI Inference: Hugging Face Inference API (Multiple Models)
  • Provenance: C2PA (c2pa-node / WASM) [cite: 156]
  • Logic Engine: Gemini 2.5 Flash (JSON Mode) [cite: 158]

📂 Directory Structure

実務を意識し、責務ごとにレイヤーを分割しています。

src/
├── client/              # React Frontend
├── server/
│   ├── index.ts         # Entry Point
│   ├── services/
│   │   ├── gemini.ts    # Gemini API Wrapper (JSON Schema)
│   │   ├── hf.ts        # Hugging Face Inference Logic
│   │   └── c2pa.ts      # C2PA Verification (WASM)
│   ├── utils/
│   │   ├── normalizer.ts # スコア正規化ロジック
│   │   └── timeout.ts    # Promise.race用ヘルパー
│   └── types/
│       └── analysis.ts  # 共通型定義
└── package.json

3. アーキテクチャ図解(Mermaid)

ここがシステムの心臓部です。並列処理とタイムアウト制御 縮退運転の挙動を可視化しました。

🔄 非同期処理のシーケンス図

ユーザーの待ち時間を最小化するため、Promise.all で全モデルを並列に叩きつつ、C2PA解析のみ Promise.race で厳密にタイムアウトさせています。

🧠 判定ロジックのフローチャート

異なるモデルのスコアをどう統合しているかのロジック図です。C2PA(来歴情報)が見つかった場合、スコアをオーバーライドして「確定」させるフローを入れています。

4. 実装のDeep Dive:型安全と正規化

エンジニアとして譲れないポイントは型安全性とデータの正規化です。

1. 厳格な型定義 (TypeScript)

Geminiに生成させるJSONの構造を、TypeScriptのinterfaceとして定義し、プロンプトにも反映させています。

// src/server/types/analysis.ts

export interface AnalysisResult {
  label: "AI_DOMINANT" | "HUMAN_LIKELY" | "UNCERTAIN";
  confidence: number; // 0-100
  evidence: {
    c2pa_present: boolean;
    c2pa_signer?: string;
    model_scores: {
      ateeqq: number;
      umm_maybe: number;
    };
  };
  reasoning: string; // 月ねこ解説
  highlight_area?: string; // 注目すべき領域(例:指、背景)
}

2. スコア正規化の実装

Hugging Faceのモデルは出力ラベルが統一されていません(FAKE, Artificial, Label_0 など)。これを統一的に扱うための正規化関数です。

// src/server/utils/normalizer.ts

type HFResponse = Array<{ label: string; score: number }>;

export const normalizeScore = (response: HFResponse): number => {
  // "AI" や "Fake" を示すラベルを検索
  const aiLabel = response.find(r => 
    /fake|ai|artificial|generated/i.test(r.label)
  );

  if (aiLabel) {
    return Math.round(aiLabel.score * 100);
  }

  // "Real" や "Human" の場合は (1 - score) を返す
  const humanLabel = response.find(r => 
    /real|human/i.test(r.label)
  );
  
  if (humanLabel) {
    return Math.round((1 - humanLabel.score) * 100);
  }

  return 50; // フォールバック値
};

3. Geminiによる構造化出力の強制

Gemini 1.5/2.5 Flash は response_mime_type: 'application/json' を指定することで、確実なJSONを返します。これを活用し、LLMを「おしゃべりBot」ではなく**「推論エンジン」**として組み込みます。

// src/server/services/gemini.ts

import { GoogleGenerativeAI } from "@google/generative-ai";

export const generateReasoning = async (data: AnalysisData) => {
  const model = genAI.getGenerativeModel({ 
    model: "gemini-2.5-flash",
    generationConfig: { 
      responseMimeType: "application/json" // 👈 これが重要
    } 
  });

  const prompt = `
    You are "Moon Cat AI", an image forensics expert.
    Analyze the following data and output JSON ONLY.
    
    Data: ${JSON.stringify(data)}
    
    Required JSON Structure:
    {
      "reasoning": "Explain strictly based on the score in Japanese. Tone: Professional yet slightly playful (cat-like).",
      "highlight_area": "Suggest where to look (e.g., hands, background)"
    }
  `;

  const result = await model.generateContent(prompt);
  return JSON.parse(result.response.text());
};

5. 実務的なTips:縮退運転の設計

WebAssembly (WASM) に依存する c2pa-node は、環境によって初期化に数秒かかることがあります。
今回は Promise.race を使い、1000ms以内にC2PA検証が終わらなければタイムアウトさせ、画像解析スコアのみで結果を返す実装にしました。

// src/server/index.ts (抜粋)

const c2paTask = c2paService.verify(imageBuffer);
const timeoutTask = new Promise((resolve) => setTimeout(() => resolve(null), 1000));

// どちらか早い方を採用(タイムアウトなら null が返る)
const c2paResult = await Promise.race([c2paTask, timeoutTask]);

if (!c2paResult) {
  console.warn("⚠️ C2PA Verification Timed Out - Continuing with visual analysis only");
}

6. まとめ

「月ねこAI判定局」の開発を通じて、以下の知見が得られました。
アンサンブル学習は正規化が命: 外部APIを使うときは、まず出力を揃えるアダプターを書く。
外部依存は信用しない: WASMや重いAPIは Promise.race でタイムアウト制御し、システム全体を道連れにしない。

LLMはチャット以外で輝く: 複雑な条件分岐(if文)の代わりにGeminiを使うと、柔軟で人間味のあるロジックが書ける。

このプロジェクトは現在GitHubでは非公開ですが、技術的なコア部分は上記の通りです。 「AIで作られたものを、AIで見抜く」。この技術的ないたちごっこは今後も続きますが、エンジニアリングで「真実」を守る戦いは、とてもエキサイティングです。

🔗 参考文献
Google Gemini API Docs
https://ai.google.dev/
Content Credentials (C2PA)
https://contentcredentials.org/
Hugging Face Inference API
https://huggingface.co/docs/inference-providers/index

https://github.com/nekoai-lab/aidriven_evidencelens

Discussion