壁紙アプリのアセットフォルダを開いて、手が止まりました。
数百枚の PNG が並んでいます。Gemini で生成したもの、以前に自分で描いたもの、素材を組み合わせて書き出したもの。どれがどれなのか、ファイル名からは判別できません。
そのとき最初に思いついたのが「SynthID で確かめればいい」でした。Google の生成物には電子透かしが入っているはずだから、一枚ずつ通せば仕分けできる。そう考えて調べ始めて、この発想そのものが間違っていたことに気づきます。
検出結果は、白黒がつく形では返ってこないのです。
問い: 手元の画像が Gemini 由来かどうか、SynthID で判別できるか
先に結論を書きます。部分的にしかできません。しかもその「部分」は、思っている側とは逆側です。
判別したいときに欲しいのは「これは AI 生成ではない」という確認の方であることが多いのですが、SynthID が確実に答えられるのは「これは Google AI 由来である」という側だけです。この非対称性を理解しないまま検出を運用に組み込むと、静かに間違った台帳ができあがります。
順を追って見ていきます。
SynthID は、ファイルの付属情報ではなく画像そのものに入る
まず前提から整理します。生成物の来歴を示す仕組みは、大きく二系統あります。
| 方式 | どこに入るか | スクリーンショットで | 持てる情報量 |
|---|---|---|---|
| C2PA(Content Credentials) | ファイルのメタデータ | 消える | 多い(生成元・日時・編集履歴) |
| SynthID | 画素そのもの | 残る | ほとんどない(有無に近い) |
C2PA はファイルに添えられた署名付きの記録です。誰が、いつ、どのモデルで作ったかまで載せられますが、再エンコードやスクリーンショット一発で剥がれます。
SynthID は画像の見た目を変えずに画素側へ信号を埋め込みます。人間の目には分かりません。スクリーンショットを撮っても、SNS の圧縮を通しても、信号は画像と一緒に運ばれます。代わりに、運べる情報はほとんどありません。「Google AI の透かしがある」以上のことは、透かし自身は語らないと考えておくのが安全です。
2026年5月に Google と OpenAI がこの二層を組み合わせる方針を打ち出したのは、両者の壊れ方がきれいに補い合うからです。C2PA が剥がれた画像でも SynthID で AI 由来を拾える。SynthID しか残っていない画像でも、C2PA が生きていれば詳細が読める。片方だけでは埋まらない穴を、もう片方が塞ぎます。
私自身、この二層構造を知るまでは「透かしが入っているなら全部それで済むはず」と思っていました。実際には、透かしは情報を運ぶ器ではなく、印そのものです。
「検出された」と「検出されなかった」は、重さが違う
ここが本題です。Gemini アプリに画像をアップロードして「これは Google AI で作られたものですか」と尋ねると、検出結果が返ってきます。その二つの結果は、対称ではありません。
| 結果 | 言えること | 言えないこと |
|---|---|---|
| 透かしが検出された | 画像の全部または一部が Google AI で作られた、または編集された | どのモデルか・いつか・どの部分か |
| 透かしが検出されなかった | Google AI 由来だと積極的には言えない | AI 生成でない、とは言えない |
検出されなかった画像には、少なくとも三つの可能性が残ります。
一つ目は、本当に人間が作ったもの。二つ目は、他社の AI が作ったもの(SynthID は Google の仕組みなので、原則として他社の生成物には入りません)。三つ目は、Google AI 由来だが加工の過程で透かしが弱って読めなくなったものです。
三つ目が厄介です。自分のパイプラインを通した画像が、まさにここに落ちる可能性があります。
つまり検出は、陽性のときだけ強く、陰性のときは何も確定させません。医療の検査に近い構造で、「陰性だから健康」とは言えないのと同じです。ここを取り違えると、「検出されなかった=自作」と分類した台帳ができあがり、しかもその誤りは目に見えません。
私はこの点で、判定を「三値」で持つべきだと考えるようになりました。確認済み(陽性)/ 不明(陰性)/ 記録ありの三つです。陰性を「自作」に丸めないことが、この仕組みと付き合う最低条件になります。
どこまでの加工なら、透かしは残るのか
では三つ目の可能性はどれくらい現実的なのか。ここは自分の書き出し工程と直接ぶつかる話です。
公開されている説明を整理すると、おおよそ次のような傾向になります。
| 加工 | 透かしの傾向 |
|---|---|
| 中程度までの JPEG 圧縮 | 残りやすい |
| リサイズ(元の半分程度まで) | 残りやすい |
| 軽いトリミング・色空間変換 | 残りやすい(信号が画像全体に分散しているため) |
| 明るさ・コントラストの軽い調整 | 残りやすい |
| 極端な圧縮・強いフィルタ | 弱る・消えることがある |
| スタイル転送・image-to-image での作り直し | 消えることがある |
| content-aware fill などの大幅な描き換え | 消えることがある |
数値の境目は環境と実装で動くため、断定はできません。個人開発で確かめられるのは「自分の工程を通したあと、どうなるか」だけです。
私の壁紙アプリの場合、生成した画像は端末の実解像度に合わせてリサイズし、WebP へ再エンコードして配信します。上の表で言えば残りやすい側の操作が中心です。ただし「残りやすい」は保証ではありません。実際、私が過去に使っていた書き出しスクリプトには、軽量化のために強めのシャープネスとノイズ低減をかける工程が挟まっていました。この手の処理が透かしにどう効くかは、表からは読み取れません。
だからこそ、書き出し工程を透かしの都合で設計し直すのは筋が悪いと考えています。画質と配信サイズのために選んだ工程を、検出のために曲げることになるからです。透かしはあくまで第三者が後から確かめるための仕組みであって、自分の資産管理の土台ではありません。
個人開発では、検出に頼らず生成の瞬間に記録する
ここまで整理して、最初の「一枚ずつ通して仕分けする」という発想が筋違いだったと分かりました。
判別したい情報は、生成した瞬間には完全に分かっているのです。どのモデルで、どのプロンプトで、いつ作ったか。それを捨てて、あとから確率的な検出で復元しようとしていたわけです。決定的に分かることを、確率的な仕組みに投げていた。順序が逆でした。
最小の対処は、生成と同時にサイドカーの JSON を一枚置くことです。
import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
MODEL_ID = "gemini-3.1-flash-lite-image" # 生成に使った実際のモデルIDを入れる
def write_sidecar(image_path: Path, prompt: str, model_id: str = MODEL_ID) -> Path:
"""画像の隣に来歴JSONを置く。生成直後に必ず呼ぶ。"""
image_bytes = image_path.read_bytes()
record = {
"file": image_path.name,
# 出力の同一性を後から照合するためのハッシュ
"sha256": hashlib.sha256(image_bytes).hexdigest(),
# プロンプト全文ではなくハッシュ。全文が必要な用途なら別途保管する
"prompt_sha256": hashlib.sha256(prompt.encode("utf-8")).hexdigest(),
"model_id": model_id,
"generated_at": datetime.now(timezone.utc).isoformat(),
# 「陰性=自作」に丸めないための三値。ここは常に confirmed
"origin": "confirmed_generated",
}
sidecar = image_path.with_suffix(image_path.suffix + ".json")
sidecar.write_text(json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
return sidecarorigin を単なる真偽値にしていない点が、この記事で一番言いたかったところです。生成時に自分で書いた記録は confirmed_generated。過去のアセットで記録が無いものは unknown であって、not_generated ではありません。検出が陰性だったとしても、unknown のままにしておきます。
なぜこう書くかというと、あとから台帳を見返したときに「調べていないから不明」と「調べたが分からなかった」と「作ったと分かっている」を区別できないと、判断のやり直しが効かなくなるからです。真偽値に潰した瞬間、失われた情報は二度と戻りません。
すでに大量のアセットが手元にあり、記録が無い場合は、SynthID の検出を補助として使えます。陽性が出たものだけを confirmed_generated に引き上げる。陰性のものは unknown のまま置いておく。検出を「引き上げる方向にだけ使う」と決めておけば、非対称性と喧嘩せずに済みます。
来歴の記録をもう一段しっかり設計したい場合は、Gemini 生成物の来歴を残す — 再現と監査のためのプロベナンス設計 が土台になります。8月17日の画像生成モデル停止を控えて「どのアセットが再生成できて、どれが凍結資産なのか」を切り分ける必要がある方は、停止するモデルで作った画像は、二度と同じものが作れない — 再生成可能性を台帳で管理する の方が具体的です。
なお、透かしが入るのは画像だけではありません。音声側の扱いは Lyria 3 Pro API 実装ガイド — テキスト・画像からプロ品質のフルレングス楽曲を生成する で触れています。
今日、手元で確かめられること
もし生成画像をアプリやサービスに載せているなら、今日できる確認は一つです。
自分の書き出しパイプラインの最終出力を数枚、Gemini アプリにアップロードして、透かしが検出されるか見てみる。
陽性が出れば、その工程は透かしを保つ側だと分かります。陰性が出たとしても、それは工程が悪いという話ではなく、「あなたのアセットは第三者から Google AI 由来だと確認できない状態にある」という事実が分かるだけです。どちらの結果でも、次に何を記録すべきかは同じ場所に着地します。
検出が答えを出してくれるのを待つのではなく、答えを持っている側が先に書き留めておく。地味ですが、数ヶ月後にアセットフォルダを開いたときに効いてくるのは、そちらの方でした。
お読みいただきありがとうございました。