知識がなくても始められる、AIと共にある豊かな毎日。
AIツールレビュー

電子透かしは AI 画像の縮小と圧縮に耐えるのか — TrustMark と invisible-watermark を当サイトの 42 枚に入れて読み直した

ゲンキ

AI で作った画像に「AI 生成」の印を残す方法は、大きく分けて2つある。1つはファイルにメタデータとして書く方法で、C2PA のマニフェストや XMP がこれに当たる。もう1つは、画素そのものに目に見えない形で埋める電子透かしだ。この記事が扱うのは後者、AI 画像の電子透かしである。

学習したモデルで透かしを入れて読む Adobe TrustMark と、学習を使わない計算で透かしを入れる invisible-watermark の dwtDct を、当サイトの記事画像 42 枚に入れた。そのうえで、ブログに載せるまでに起こりがちな縮小・JPEG 圧縮・切り抜き・文字の重ねのあとに、埋めた値が読めるかを数えた。

先に結果を書く。TrustMark(変種 Q、既定の誤り訂正 BCH_5)は、加工なしを含む 11 条件のうち 10 条件で 42 枚すべての値を読み、「幅 768 に縮小してから JPEG の品質 82 で保存する」条件だけが 41 枚だった。透かしがあると判定して違う値を返したことは一度も無く、透かしを入れていない元の 42 枚に「透かしあり」と出たことも無かった。

対照的に、dwtDct は加工なしでも 42 枚中 18 枚しか読めず、幅 1024・768 への縮小、JPEG 圧縮、切り抜き、切り落としのあとは 0 枚だった。幅 300 への縮小では画素数が足りず、ライブラリが受け付けない大きさになった。どれも各条件1回の実行で、加工は WordPress ではなく手元の Pillow で再現している。

この記事は印を消す方法を探すものではない。測ったのは普段の作業で意図せず起きる加工だけで、消すための加工は試していない。実験には記事画像の手元のコピーを使い、公開済みの投稿と画像は変えていない。また、当サイトの記事画像には電子透かしを付けていない。

AI で作った画像の表示に関わる EU の規則の日付と枠組みは、AI 生成の表示と「印」は画像と3Dデータのどこで消えるのか(2026-11-09 公開) と G検定の法律・倫理は AI で現行制度に更新してから覚える — AI 法・基本計画・著作権・EU AI Act の一次資料をシラバスに対応づける(2026-10-04 公開) に書いた。ここでは法的な判断はせず、読者に義務があるかどうかも扱わない。

※本記事にはアフィリエイト広告(PR)が含まれます。

目次
  1. メタデータと電子透かし — 「AI 生成」の印をファイルに書くか、画素に埋めるか
  2. C2PA の soft binding — 電子透かしは消えたマニフェストを探す識別子
  3. 道具を選ぶ — 学習したモデルで入れる TrustMark と、学習を使わない dwtDct
  4. 実験の条件 — 記事画像 42 枚に入れ、11 通りの加工のあとに読む
  5. 結果 — TrustMark は 11 条件で 41〜42 枚、dwtDct は加工なしでも 18 枚
  6. 画質 — PSNR の中央値は TrustMark 44.49 dB、dwtDct 38.89 dB
  7. 結果の読み方 — 縮小・JPEG・切り抜き・文字の重ねで何が起きたか
  8. 読めた識別子は「AI 生成」を示すのか — AI 画像の電子透かしをブログに当てはめる
  9. この実測で言えないこと
  10. まとめ
  11. 参照
忍者AdMax

メタデータと電子透かし — 「AI 生成」の印をファイルに書くか、画素に埋めるか

メタデータと電子透かし — 「AI 生成」の印をファイルに書くか、画素に埋めるか

メタデータは、画像の画素とは別に、ファイルの中の決まった場所に書かれる情報だ。C2PA のマニフェスト、XMP、EXIF、PNG のテキストがこれに当たる。ファイルに残っている限りは読めるが、画像を開いて保存し直す処理がその場所を引き継がなければ消える。当サイトの記事画像の流れで、どの段でメタデータが消えるかは AI 生成の表示と「印」は画像と3Dデータのどこで消えるのか(2026-11-09 公開) で数えた。C2PA のマニフェストを実際に書き込んで読む手順は C2PA で AI 画像に来歴を付けて読む(2026-11-10 公開) で扱っている。

一方、電子透かしは画素の値をわずかに変えて情報を埋める。TrustMark の公式 FAQ は、TrustMark は画素だけを変え、画像のメタデータには触れないと書く。透明度の成分(アルファチャンネル)も透かしの対象外だ。つまり、透かしを入れる処理そのものはメタデータを書き換えない(保存したときにメタデータが残るかは、保存の書き方による)。画素が残っていればファイルを保存し直しても透かしは残りうるが、縮小や圧縮で画素が変われば読めなくなることもある。その「どこまで残るか」を、この記事では数える。

両者の関係は、C2PA の仕様の中で整理されている。C2PA は、画像とマニフェストを結びつける方法を2種類に分ける。hard binding は、ファイルの生のバイト列から計算した値で結びつける方法で、1つのマニフェストに1つまでしか置けない。これに対して soft binding は、透かしや指紋(画像の内容から計算する値)でマニフェストを引き当てる仕組みだ。生のバイト列ではなく画像の内容から計算し、1つのマニフェストに0個以上置ける。電子透かしは、C2PA の中では soft binding の一種として位置づけられている。

C2PA の soft binding — 電子透かしは消えたマニフェストを探す識別子

C2PA の soft binding — 電子透かしは消えたマニフェストを探す識別子

C2PA 技術仕様(2.5 のページ)の 2.3.14 は、soft binding を「統計的に一意ではない識別子(指紋など)」か「識別される内容に見えない透かしとして埋め込まれた識別子」と定義している。続く 12.2.3 は、その目的を、下にあるビット列が違っていても内容を照合できるようにすることだと書く。たとえばマニフェストが画像から取り除かれても、そのコピーがどこかの保管場所に残っていれば、soft binding を使って画像とマニフェストを照合し直せる、という説明だ。

同じ 12.2.3 は、目的が違うので soft binding を hard binding として使ってはならない、とも書いている。したがって電子透かしは、画像が改ざんされていないことの証明ではない。消えたマニフェストを保管場所(仕様の用語では Manifest Repository)から探すための識別子である。仕様の 2.4.5 はこの保管場所を、マニフェストを置き、内容との結びつきで検索できる場所と定義する。さらに 2.4.1 は、soft binding によって保管場所から見つけられる Content Credential を Durable Content Credential と呼んでいる。

仕様の中での書き方も決まっている。soft binding のアサーションのラベルは c2pa.soft-binding で(16.3.7)、方式の名前(alg)は C2PA が管理する一覧の名前を使うべきだとされる。照合は、方式の名前と値の両方が一致したときに一致とみなす。16.3.7.5 には、soft binding の値などからマニフェストを取り出す Web API(soft binding resolution API)も定められている。

透かしを入れた操作の記録にも決まりがある。action の一覧(16.4.1.1)には、soft binding を作るために透かしを入れた c2pa.watermarked.bound と、soft binding を作らずに入れた c2pa.watermarked.unbound がある。16.4.1.5 は、bound を使うなら soft binding のアサーションもマニフェストに入れると定めており、無ければ検証で assertion.action.softBindingMissing として拒否される(14.10.4.2.4)。

ここで、この実験の範囲をはっきりさせておく。当サイトはマニフェストの保管場所を持っていない。そのため、透かしを読めたとしても、その値で引き当てられるマニフェストは無い。この記事で確かめたのは、画像に埋めた識別子が加工のあとに読めるかまでで、識別子からマニフェストを引き当てる流れは試していない。

方式の一覧は 2026-10-28 のコミット時点で 57 件

C2PA の soft binding の一覧は、GitHub で JSON として公開されている。2026 年 10 月 28 日のコミット時点では 57 件が登録され、種類は透かしが 48 件、指紋が 9 件だった。読み取りの対象に画像を含むものは 32 件ある。件数は当サイトが JSON を集計した値で、一覧は増え続けるので日付を添えておく。

試す道具のうち、開発元自身が一覧に登録しているのは TrustMark だけだ。Adobe が変種 Q・C・P・F の4つを登録しており、Q の登録日は 2024-05-17 である。F は 2026-10-14 の登録で、Python 版 0.9.2 の変種には無い。逆に、Python 版にある変種 B は一覧に無い。Meta の PixelSeal と VideoSeal は、Meta ではなく第三者の実装として載っている。画像向けではほかに、Digimarc や Microsoft の InvisMark の透かし、Adobe の指紋の方式も並ぶ。

これに対して、Google の SynthID と invisible-watermark は一覧に登録が無い。これは「C2PA に対応していない」という意味ではなく、一覧に名前が無いということだ。また一覧への登録は、方式の持ち主の会社に属する人か保守者が変更の提案を出し、C2PA の技術作業部会が承認する手続きで、性能を認定するものではない。なお仕様は、一覧の対象の種類として 3D のモデル(model)も定めているが、この時点で model を含む登録は1件も無かった。

道具を選ぶ — 学習したモデルで入れる TrustMark と、学習を使わない dwtDct

道具を選ぶ — 学習したモデルで入れる TrustMark と、学習を使わない dwtDct

AI 画像の電子透かしを入れる道具を選ぶとき、最初に見たのはライセンスの本体である。当サイトはアフィリエイトを含む収益サイトなので、非商用に限ったライセンスのものは使えない。候補ごとに LICENSE と README を開いて確かめた結果を、先に表にまとめる。

候補コードモデルの重みこの記事での扱い
Adobe TrustMark 0.9.2MITMIT(README が明記)主に使う(変種 Q)
invisible-watermark 0.2.0MITdwtDct は重みを持たない比べる相手に使う(dwtDct)
Meta Watermark AnythingMITMIT の重みと、論文版の CC BY-NC 4.0 の重み使わない
Meta VideoSeal 1.0.1MITMIT今回は試していない
Google SynthID(画像)——自分の画像に入れる道具が公開されていない

TrustMark 0.9.2 — コードもモデルの重みも MIT

TrustMark は Adobe と University of Surrey が開発した透かしで、論文の題は、2023 年の arXiv 版では「TrustMark: Universal Watermarking for Arbitrary Resolution Images」だ。論文はのちに ICCV 2025 で別の題で発表されており、この記事が論文から直接引く記述は、どれも arXiv 版のものである。

LICENSE 本体は MIT で、README は、MIT がリポジトリのコードだけでなく、初回に取得するモデルのファイルにも及ぶと明記している。FAQ も、商用と非商用の両方で使えると書く。

PyPI の最新版は 0.9.2(2026-09-08 の登録)で、配布物はソースの形(sdist)だけだ。Python 3.8.5 以上が要り、主な依存は PyTorch(2.3.1 以上)と torchvision(0.18.1 以上)で、ほかに lightning・omegaconf・einops なども入る。PyPI に登録された 0.9.2 のメタデータには OS の分類が無く、対象の OS は明記されていない(リポジトリの setup.py にだけ「OS Independent」の分類がある)。GPU があれば CUDA か MPS を使い、無ければ CPU で動く。

仕組みの要点を押さえておく。TrustMark は 100 ビットを画像に埋める。そのうち4ビットは版のビットで、2ビットは予約(未使用)、残る2ビットが誤り訂正の方式を示す。既定の BCH_5 では 61 ビットを値に使い、35 ビットを誤り訂正に充てる(61 + 35 + 4 = 100)。BCH_5 は5ビットまでの誤りを訂正できる。ほかに BCH_4(値 68 ビット・訂正用 28 ビット)、BCH_3(75 ビット・21 ビット)、BCH_SUPER(40 ビット・56 ビットで、8ビットまで訂正)があり、読み取り側は方式を自動で判定する。誤りを訂正できないと判定すると、TrustMark は「透かし無し」として空の値を返す。

変種は B・C・P・Q の4つで、ある変種で入れた透かしは別の変種では読めない。既定は変種 Q・強さ 100%・BCH_5 で、公式の設定の説明は、たいていの用途にはこれで足りると書いている。透かしの成分は 256×256 で作り、元の解像度に拡大して画像に合成する。縦横比が 2.0 を超えると中央の正方形だけに入れるが、当サイトの 1280×720(縦横比は約 1.78)なら、変種 Q は画像全体に入る。FAQ も、頑丈さを上げるために透かしを画像全体に分散させると説明している。

FAQ が書く耐性は、オンラインでの再共有・スクリーンショット・印刷のような、編集を目的としない変換の多くに耐えるよう学習した、というものだ。続けて FAQ は、論文のヒストグラムが、Python のライブラリ Kornia にある 18 種類の雑音(四辺の切り抜きで面積の約 20% までを失うものなど)に高い耐性を示すと書き、JPEG 圧縮にも耐えるよう設計したと書く。画像の大きさは、短辺がおよそ 150 px まで確実に扱え、上限は無いとも書く。どれも開発元の説明であり、当サイトの実測はその確かめに当たる。

なお、TrustMark は透かしを取り除くコードもリポジトリに同梱している(公式 FAQ)。この実験では除去の機能を使わず、読み込まない設定(loadRemover=False)で初期化した。Python のほかには、読み取りだけの JavaScript 版(ONNX を使う)、Rust 版、C++ の実行環境、Raspberry Pi AI Kit の HAILO-8L 向けの Python 版もある。この記事で使ったのは Python 版だけだ。

invisible-watermark 0.2.0 — 公式の対象は Linux、使ったのは dwtDct

invisible-watermark は ShieldMnt が公開しているライブラリで、LICENSE 本体は MIT である。PyPI の最新版は 0.2.0(2023-07-06 の wheel)、リポジトリの最後のコミットは 2023-08-25 だ。パッケージの分類では公式の対象は Linux とされ、Ubuntu 18.04 で試したと書かれている。GPU には対応していない。依存には OpenCV・PyTorch・Pillow・PyWavelets・numpy が入る。

方式は dwtDct(既定)・dwtDctSvd・rivaGan の3つがある。README によれば、dwtDct はブロックごとの DCT(離散コサイン変換)の係数のうち、自明でない最大の係数にビットを入れる。既定の強さの設定では、明るさではなく色の成分(YUV の U)にだけ入れる。埋めるビット数は利用者が決め、README の試験では周波数の方式に 64 ビットを使っている。rivaGan は学習したモデル(ONNX)を同梱しているが、上流のモデルのライセンスと学習データの条件をこちらで確かめていないので使わなかった。dwtDct と dwtDctSvd はモデルの重みを持たない。

注意したいのは、開発元自身が弱点を書いていることだ。PyPI のページは、これらの方式は縮小と縦横比の変わる切り抜きには強くないと書く。試験表では「Resize 50%」と「crop 7×5」が周波数の方式で Fail、JPEG 圧縮は Pass になっている。README はさらに、加工をしなくても元の透かしを 100% 正確に読める保証はできないと書き、Web ページのスクリーンショットや背景の色が一様なポスターではうまく働かないことを既知の欠点に挙げている。

もう1つの制約は画像の大きさだ。画素数が 256×256(65,536)未満の画像は、埋め込みでも読み取りでも例外で止まる。この場合は「読めない」ではなく、ライブラリが受け付けないので「実行できない」として別に数えた(判定は、ライブラリを呼ぶ前に同じ条件で行った)。

使わなかった候補 — Watermark Anything・SynthID・VideoSeal

Meta の Watermark Anything は、コードと、SA-1B で学習した新しい重みが MIT で、こちらは商用に使える。しかし論文で評価した COCO の重みは CC BY-NC 4.0 で、当サイトは収益サイトなので使わない。CC BY-NC 4.0 は NonCommercial を、主として商業上の利益や金銭的な対価を目的としないことと定義し(§1(i))、許諾を非商用の目的に限っている(§2(a)(1)(A))。加えて、リポジトリは 2026-08-01 にアーカイブされ、読み取り専用になった。保守されない道具は今回の比較から外した。

Google の SynthID は書き分けが要る。オープンソースで公開されているのは文章用の SynthID Text で、ライセンスは Apache 2.0、開発元は本番での利用を想定しないと書いている。画像の SynthID は、Google と提携先の生成物に、生成した時点で入るものだ。自分の画像に入れる道具は、公式ページ・ブログ・リポジトリのどこにも案内が無かった。

確かめる側の手段としては、Gemini・検索・Chrome が SynthID の透かしを確認し、2026-10-07 には SynthID Detector が英語で一般公開された。Google のブログは、Google と OpenAI・NVIDIA・Kakao などの提携先の AI で作ったかを、画像・動画・音声について確かめられると書いている。

文章に入る透かしの例としては、Claude の生成テキストの透かしに Claude Fable 5.1 で何が変わったのか — 同じモデルの二つの名前と、キャッシュ読み取りが4分の1になった意味(2026-09-21 公開) で触れた。この記事が扱うのは画像の透かしだけである。

Meta の VideoSeal 1.0.1 も、コードとモデルが MIT で使える。ただし依存が重く(decord や、版を固定した timm など)、今回は入れていない。試していないので、結果も比べていない。

TrustMark も invisible-watermark も Python のライブラリとして配られている。手元で試すなら、ファイルの一括処理を Python で書く基本を押さえておくと、42 枚のような枚数でもすぐに回せる。

実験の条件 — 記事画像 42 枚に入れ、11 通りの加工のあとに読む

実験の条件 — 記事画像 42 枚に入れ、11 通りの加工のあとに読む

入力は、2026-09-28〜2026-11-08 の各記事のカバー画像(images/cover.png)42 枚である。どれも当サイトがローカルの画像生成(Z-Image-Turbo、Apache 2.0)で作った 1280×720 の PNG で、選び方は日付の範囲だけにした。挿絵をローカルで生成する仕組みと、モデルのライセンスの読み方は、挿絵をローカル画像生成でまかなう — ComfyUIとFLUXのライセンスを読み違えないために(2026-08-27 公開) に書いた。

埋めた値は、1枚ごとに記事の日付から作った。TrustMark には文字列で sw と記事の月日(例: sw1102、6 文字)を、invisible-watermark には8バイトで sw・月日・!! を入れた(64 ビット)。透かしを入れた画像はいったん PNG に保存し、読み直したものを加工の出発点にした。当サイトの画像は PNG で、PNG は画素を変えずに保存できるので、保存で透かしが変わらない状態から始めるためだ。

加工は次の 11 条件で、各画像に各条件を1回ずつ当てた。縮小の幅は、WordPress の初期設定の縮小サイズのうち縦横比を保つ3つ(large は 1024、medium_large は幅 768、medium は 300 に収める)に合わせ、横長の画像がそれぞれの幅になるように縮めた。初期設定には正方形に切り抜く thumbnail(150×150)もあるが、これは試していない。JPEG の品質 82 は、WordPress が JPEG を書き出すときの既定値である。ただし WordPress は、既定では PNG を PNG のまま縮小し、JPEG には変えない。当サイトの記事画像は PNG なので、JPEG の条件は、画像を JPEG にして上げる場合や、ほかの場所で JPEG に変換される場合を想定したものになる。また、加工は WordPress そのものではなく、手元の Pillow で再現したものだ。

条件内容加工後の大きさ
加工なし透かしを入れた PNG をそのまま読む1280×720
幅 1024縦横比を保って縮小(Lanczos)1024×576
幅 768同上768×432
幅 300同上300×169
JPEG 82品質 82 の JPEG に保存して読み直す1280×720
JPEG 70品質 70 の JPEG1280×720
JPEG 50品質 50 の JPEG1280×720
幅 768+JPEG 82幅 768 に縮小してから品質 82 の JPEG768×432
中央 90%中央を縦横それぞれ 90% で切り抜く1152×648
右端 10% 切り落とし右端の 10% を切り落とす1152×720
文字の重ね当サイトの comfyui/generate.py の apply_text_overlay で、下端に高さ 10% の帯(黒 60%)と文字(幅の 3.5%)を重ねる1280×720

縮小後の高さは元の縦横比から出した(720 × 1024 ÷ 1280 = 576、720 × 768 ÷ 1280 = 432、720 × 300 ÷ 1280 = 168.75 を丸めて 169)。中央の切り抜きは 1280 × 0.9 = 1152、720 × 0.9 = 648 で、右端の切り落としは 1280 − 1280 × 0.1 = 1152 である。文字の重ねは当サイトの画像生成のスクリプトにある機能だが、いまの記事画像では使っていない。

「読めた」の定義は実行の前に決めた。

結果TrustMarkinvisible-watermark(dwtDct)
読めた「透かしあり」を返し、読んだ文字列が埋めた値と完全に一致読んだ8バイトが埋めた値と完全に一致
読めない「透かし無し」を返す8バイトのどこかが違う
誤った値「透かしあり」を返したが値が違う(読めないに含め、件数を別に書く)—
実行できない—加工後の画像が 65,536 画素未満で、ライブラリが受け付けない

dwtDct については、64 ビットのうち一致したビットの割合(以下、ビット一致率)も記録した。論文や公式 FAQ はビットの正解率で性能を書くが、この記事の主な数は、1枚の値が丸ごと読めたかどうかである。

これとは別に、透かしを入れていない元の 42 枚を加工なしで読ませる陰性対照も、各1回行った(11 条件の実行の後に追加した)。透かしの無い画像に「透かしあり」と出ないか、埋めた値の形の値が出ないかを見るためだ。

実行環境は Windows 11 Pro の PC(Intel Core Ultra 7 265F、RAM 63.7 GB、RTX 5070 Ti 16 GB)で、Python 3.13.17 の仮想環境に trustmark 0.9.2 と invisible-watermark 0.2.0 を入れた。PyTorch は CPU 版(2.15.0+cpu)で、GPU は使っていない。TrustMark は変種 Q・既定の BCH_5 で初期化した。呼び出しの要点は次のとおりだ。

tm = TrustMark(verbose=False, model_type="Q", loadRemover=False)
marked = tm.encode(orig, "sw1102", MODE="text")
secret, present, schema = tm.decode(after, MODE="text")
# 読めた: present が真で、secret が埋めた文字列と完全に一致

enc = WatermarkEncoder()
enc.set_watermark("bytes", b"sw1102!!")
marked_bgr = enc.encode(bgr, "dwtDct")
got = WatermarkDecoder("bytes", 64).decode(after_bgr, "dwtDct")
# 読めた: got が埋めた8バイトと完全に一致

Windows で TrustMark を入れるときは PYTHONUTF8=1 が要った

TrustMark も invisible-watermark も、Windows で動くとは公式に書いていない。実際、この PC では TrustMark の導入で一度つまずいた。TrustMark の setup.py は README を既定の文字コードで読むため、日本語版 Windows(既定の文字コードが cp932)では次の例外で止まった。

UnicodeDecodeError: 'cp932' codec can't decode byte 0x94

環境変数 PYTHONUTF8=1 を付けて入れ直すと入り、入れた後は Windows で動いた。この実行で入れたときのコマンドは次のとおりで(Git Bash)、PyTorch は先に CPU 版を入れておいた。PowerShell で同じことをするなら、先に $env:PYTHONUTF8 = '1' を設定する。

PYTHONUTF8=1 <仮想環境の python> -m pip install trustmark==0.9.2 invisible-watermark==0.2.0

invisible-watermark は公式の対象が Linux だが、配布物は OS を選ばない形式(py3-none-any)の wheel なので入り、この PC でも 42 枚の処理が最後まで走った。

TrustMark のモデルの重みは初回に取得する。ログインは要らず、変種 Q の読み取り側のファイル(decoder_Q.ckpt)は 47,652,460 バイトだった。初回は取得と読み込みで 124.2 秒かかり、2回目以降の読み込みは 0.7 秒だった。CPU で試しに1枚を処理すると、埋め込みは 0.12 秒、読み取りは 0.06 秒である。

結果 — TrustMark は 11 条件で 41〜42 枚、dwtDct は加工なしでも 18 枚

結果 — TrustMark は 11 条件で 41〜42 枚、dwtDct は加工なしでも 18 枚
加工TrustMark 読めたTrustMark 誤った値dwtDct 読めたdwtDct 実行できないdwtDct ビット一致率の平均
加工なし42/42018/4200.7615
幅 102442/4200/4200.4773
幅 76842/4200/4200.5089
幅 30042/420—42—
JPEG 8242/4200/4200.5454
JPEG 7042/4200/4200.5365
JPEG 5042/4200/4200.5342
幅 768+JPEG 8241/4200/4200.5164
中央 90%42/4200/4200.6049
右端 10% 切り落とし42/4200/4200.6395
文字の重ね42/42019/4200.7705

TrustMark は、11 条件のうち 10 条件で 42 枚すべての値を読んだ。読めなかったのは「幅 768+JPEG 82」の1枚だけで、そのときも「透かし無し」を返しており、違う値を読んだのではない。全体で見ると、42 枚 × 11 条件 = 462 回の読み取りのうち、「透かしあり」と判定して違う値を返した回は 0 だった。

これに対して dwtDct は、加工なしの時点で 42 枚中 18 枚しか読めなかった。幅 1024・768 への縮小、JPEG 圧縮、切り抜き、切り落としのあとは、どの条件でも 0 枚だ。幅 300 は 300×169=50,700 画素で 65,536 画素に満たず、42 枚とも実行できなかった。文字の重ねのあとは 19 枚で、加工なしの 18 枚とほぼ同じだった。

ビット一致率の平均を見ると、縮小と JPEG のあとは 0.4773〜0.5454 にとどまる。64 ビットをでたらめに当てても、平均では半分ほどが一致する(論文も、当て推量の正解率を 50% と書いている)。言い換えれば、縮小や JPEG のあとの dwtDct は、ビットの単位で見ても当て推量に近い値しか返していない。切り抜きと切り落としは 0.6049 と 0.6395 で少し高いが、値が丸ごと一致した画像は無かった。

透かしを入れていない 42 枚 — 陰性対照

透かしの無い元の 42 枚を加工なしで読ませると、TrustMark が「透かしあり」と出したのは 0/42 だった。11 条件で違う値を読んだ回が 0 だったこととあわせると、この 42 枚では、TrustMark は透かしの無い画像に「あり」と出ず、透かしのある画像で違う値を返すこともなかった(どちらも各1回)。

dwtDct の側は事情が違う。dwtDct には透かしの有無を判定する仕組みが無く、透かしの無い画像にも毎回8バイトを返す。42 枚で返った値は 13 種類で、埋めた値の形(sw で始まり !! で終わる)になったものは 0/42 だった。つまり dwtDct では、値が返ったこと自体は透かしがある証拠にならない。返った値が、入れた側が決めた形と合うかを読む側が確かめて、初めて「読めた」と言える。

画質 — PSNR の中央値は TrustMark 44.49 dB、dwtDct 38.89 dB

画質 — PSNR の中央値は TrustMark 44.49 dB、dwtDct 38.89 dB

透かしを入れる前と後の画像の差は、PSNR(ピーク信号対雑音比)で測った。PSNR は、2枚の画像の画素の差の二乗を平均した値(平均二乗誤差)から次の式で出す。値が大きいほど、透かしを入れた後の画像が元の画像に近い。

PSNR(dB)= 10 × log10(255² ÷ 平均二乗誤差)

42 枚の中央値は、TrustMark が 44.49 dB、dwtDct が 38.89 dB だった。TrustMark の公式の設定の説明は、変種 Q の典型的な PSNR を 43〜45 dB と書いており、この実行の中央値もその範囲に入った。同じ指標で比べると、dwtDct の中央値は TrustMark より 44.49 − 38.89 = 5.60 dB 低い。PSNR は平均二乗誤差の対数で決まるので、この差を平均二乗誤差の比に直すと、10 の(5.60 ÷ 10)乗 ≒ 3.6 倍に当たる(中央値どうしから出した目安)。

つまり、この実行では dwtDct のほうが元の画像からの変化が大きいのに、加工への耐性は低かった。なお dwtDct は既定で色の成分(U)にだけ入れ、PSNR は RGB の3色をまとめて計算しているので、この PSNR は、どの成分をどれだけ変えたかまでは表さない。また PSNR は画素の差の数値であって、人が見て違いが分かるかは、この実行では確かめていない。

PSNR のような指標を条件ごとに並べて比べる作業は、表を集計する型を持っておくと速い。Python のデータ分析を題材ごとに練習できる本で、集計と可視化の手順を身につけておきたい。

結果の読み方 — 縮小・JPEG・切り抜き・文字の重ねで何が起きたか

結果の読み方 — 縮小・JPEG・切り抜き・文字の重ねで何が起きたか

縮小 — 幅 300 でも TrustMark は 42 枚、dwtDct は幅 1024 で 0 枚

TrustMark は、幅 1024・768・300 のどれでも 42 枚を読んだ。幅 300 のときの短辺は 169 px で、FAQ が書く下限(短辺およそ 150 px)をわずかに上回る。論文(2023 年の arXiv 版)のモデルは 256×256 の画像を入力にとり、学習と評価では、その画像に 0.8〜1.0 の範囲のランダムな縮小(縦横比 3/4〜4/3)と、256 のうち 244 の切り抜きを加えると書く。一方、TrustMark の Python 版は、読み取りの前に画像をいつもモデルの解像度(変種 Q では 245×245)に縮める。そのため、元の幅 1280 からの縮小の割合を、論文の範囲と直接は比べられない。

また、論文のモデルは MIRFlickR の画像で学習したもので、配布されているモデルは Adobe Stock の画像で学習したと FAQ は書く。配布モデルがどの加工をどの強さで加えて学習されたかは、この記事では確かめていない。論文の条件から配布モデルの結果を予想することはできず、論文の数値を配布モデルの性能として読むこともできない。この実行で言えるのは、配布モデルの変種 Q が、当サイトの 42 枚では幅 300 まで読めたということだけだ。

一方、dwtDct は幅 1024 への縮小(1024 ÷ 1280 = 0.8 倍)でも 0 枚だった。開発元が縮小に強くないと書き、試験表で「Resize 50%」を Fail としているのと同じ向きの結果である。この実行では、加工なしで読めた 18 枚も含めて、0.8 倍の縮小で1枚も読めなくなった。

JPEG 圧縮 — dwtDct は開発元の試験表と結果が分かれた

TrustMark は、品質 82・70・50 のどれでも 42 枚を読んだ。FAQ は JPEG 圧縮に耐えるよう設計したと書いており、それと食い違わない結果である。論文の補足資料には JPEG の強さの表もあるが、配布モデルがどの強さで学習されたかは書かれていないので、ここでは引かない。

dwtDct は、品質 82 でも 0 枚だった。こちらは開発元の試験表(JPEG 圧縮は Pass)と結果が分かれる。開発元の試験とこの実行では画像も条件も違い、なぜ分かれたかはこの実行では切り分けていない。少なくとも当サイトの記事画像では、加工なしで読めた画像も含めて、品質 82 の JPEG のあとに読めた画像は無かった。

縮小と JPEG の組み合わせ — TrustMark が1枚だけ読めなかった

単独の「幅 768」と「JPEG 82」ではどちらも 42 枚だった TrustMark が、両方を続けて当てると 41 枚になった。とはいえ各条件1回の実行で、差は1枚である。この数から「組み合わせると何割読めなくなる」とは言えない。読めなかった1枚についても TrustMark は「透かし無し」を返しており、違う値を読んではいない。

切り抜きと切り落とし — 面積の 19% を落としても TrustMark は 42 枚

中央 90% の切り抜きでは、残る面積は 0.9 × 0.9 = 0.81 で、面積の 19% を落とすことになる。FAQ が論文の評価の値として引く「四辺の切り抜きで面積の約 20% まで」に近い強さだが、これは論文のモデルの評価で、配布モデルの目安には使えない。この条件で、TrustMark は 42 枚を読んだ。右端 10% の切り落としは 1152×720 で、縦横比が約 1.78 から 1152 ÷ 720 = 1.6 に変わる。こちらも 42 枚だった。

dwtDct はどちらも 0 枚だった。開発元は「縦横比の変わる切り抜き」に強くないと書くが、この実行では縦横比を変えない中央の切り抜きでも読めなかった。ビット一致率の平均は 0.6049 と 0.6395 で、縮小や JPEG のあとより高いものの、値が丸ごと一致した画像は1枚も無い。

文字の重ね — 下端 10% の帯では、どちらも加工なしとほぼ同じ

文字の重ねは、画像の下端 10% に帯と文字を描き、PNG のまま保存し直す加工である。TrustMark は 42 枚を読み、dwtDct は 19 枚で、枚数は加工なしの 18 枚とほぼ同じだった(加工なしで読めた1枚が読めず、読めなかった2枚が読めた)。画素が変わるのは帯の部分だけで、JPEG のような圧縮も加わらないので、どちらの方式にとっても加工なしに近い条件だったと考えられる。ただし、そう確かめたわけではなく、帯の高さや色を変えた場合も試していない。

dwtDct が加工なしで 24 枚を読めなかったこと

加工なしで読めなかったのは 24 枚(42 − 18)だった。保存の影響かを切り分けるため、本番で読めなかった5枚と読めた3枚の計 8 枚で、埋め込んだ直後の配列をそのまま読む方法と、PNG に保存して読み直す方法を比べた(このとき埋めた値は、本番とは別の swtest!!)。8 枚とも2つの方法で結果は同じで、少なくともこの 8 枚では、保存は原因ではなかった。読めなかった残りの画像では、この比較をしていない。読めなかった 24 枚のうち 13 枚は、8バイトすべてが 0xff という値を返した。README が、加工をしなくても 100% 正確に読める保証はできないと書くとおり、画像によって方式が効かない場合がある。どんな画像で効かないのかは、この実行では分析していない。

読めた識別子は「AI 生成」を示すのか — AI 画像の電子透かしをブログに当てはめる

読めた識別子は「AI 生成」を示すのか — AI 画像の電子透かしをブログに当てはめる

この実行の範囲で言えば、WordPress の初期設定の縮小サイズのうち縦横比を保つ3つの幅と、JPEG の既定の品質 82 を Pillow で再現した加工のあとでも、TrustMark の値は 41〜42 枚で読めた(正方形に切り抜く thumbnail は試していない)。保存し直しでメタデータが消える場合でも、画素に埋めた識別子は残りうる、ということだ。対照的に dwtDct は、加工なしの時点で 42 枚中 24 枚が読めず、ブログに載せる画像の印としては、この実行では使えなかった。

とはいえ、読めた値そのものは「AI 生成」を意味しない。この実験で埋めたのは sw1102 のような当サイトが決めた短い文字列で、その意味を知っているのは入れた側だけだ。C2PA の仕組みの中で透かしが役に立つのは、soft binding としてマニフェストに書かれ、そのマニフェストが保管場所に置かれていて、読んだ値から引き当てられるときである。当サイトはその保管場所を持たないので、この実験で読めた値から引き当てられるものは無い。

透かしの役割を整理すると、次の3点になる。

  • 透かしは、マニフェストが消えた画像から、保管場所にあるマニフェストを探すための識別子である(仕様 12.2.3)
  • 透かしは改ざんが無いことの証明にはならず、soft binding を hard binding の代わりにしてはならない(同)
  • C2PA で透かしを soft binding として記録するなら、c2pa.watermarked.bound の action と c2pa.soft-binding のアサーションを組で入れる(16.4.1.5)

他人の画像を確かめる側に立つと、事情はまた違う。Google の説明では、Google やその提携先の生成物には生成の時点で SynthID が入り、Gemini・検索・Chrome や SynthID Detector で確かめられる。ただし SynthID は、自分で作った画像に自分で入れる道具としては使えない。その点で、自分の画像に入れて自分で読める TrustMark とは立場が違う。AI 画像の電子透かしを自分で入れるなら、何を埋め、どこでその意味を引けるようにするかまでを決めておかないと、読めた値は手がかりにならない。

電子透かしは、生成 AI の出力を見分けるための対策の一つにすぎない。生成 AI のリスクと対策を体系的にまとめた教科書で、透かしが受け持つ範囲と受け持たない範囲を見ておくとよい。

¥1,650 (2026/09/22 15:04時点 | Amazon調べ)

この実測で言えないこと

この実測で言えないこと
  • 各条件1回、42 枚の実行である。どれも当サイトが同じ画像生成モデルで作った 1280×720 の PNG で、選び方は日付の範囲だけだ。写真や、別のモデル・別の大きさの画像で同じ結果になるとは言えない
  • 加工は手元の Pillow で再現した。WordPress が実際に縮小・圧縮した画像ではない。縮小の方式(Lanczos)や JPEG の書き出しの細部が、サーバーの画像処理と同じとは限らない
  • 正方形に切り抜く縮小は試していない。WordPress の初期設定の縮小サイズには、正方形に切り抜く thumbnail(150×150)もある。この実行の縮小は縦横比を保つものだけで、切り抜きと切り落としは中央 90% と右端 10% だけである
  • 陰性対照は、透かしの無い 42 枚を加工なしで読んだ各1回だけ。TrustMark の「透かしあり」は 0/42 だったが、透かしの無い画像を縮小や圧縮したあとに「あり」と出ることがあるかは数えていない。dwtDct は透かしの無い画像にも毎回値を返すので、値が返ったかどうかでは有無を判断できない
  • 論文や公式 FAQ の数値とは並べて比べない。公式 FAQ によれば、既定の変種 Q は強い雑音の下でもビットの正解率が 96% を超えうるとされる。しかしこの記事は1枚の値が丸ごと読めたかで数えており、指標が違う。論文のモデルと配布モデルは、学習データも違う
  • 使ったのは TrustMark の変種 Q と dwtDct だけ。TrustMark の P・B・C、dwtDctSvd・rivaGan、VideoSeal などは試していない
  • 消すための加工は試していない。強いノイズ・回転・生成し直しのような、印を消す目的の加工に対してどうなるかは、この記事の範囲の外にある
  • 識別子からマニフェストを引き当てる流れは試していない。当サイトはマニフェストの保管場所を持たない
  • 画質は PSNR の数値だけで、人が見て違いが分かるかは確かめていない。処理の時間は、CPU で試しに1枚を処理した値である

まとめ

まとめ

AI 画像の電子透かしは、メタデータとは別の場所、つまり画素に「AI 生成」の印を置く方法である。当サイトの記事画像 42 枚に入れ、ブログで起こりがちな加工を Pillow で再現したこの実行では、学習したモデルで入れる TrustMark(変種 Q)は 11 条件のうち 10 条件で 42 枚、残る1条件で 41 枚の値を読み、違う値を返したことは無かった。透かしの無い 42 枚に「あり」と出たことも無い。PSNR の中央値は 44.49 dB で、公式の典型値の範囲に入った。

学習を使わない dwtDct は、加工なしで 18 枚、幅 1024・768 への縮小・JPEG・切り抜き・切り落としのあとは 0 枚で、幅 300 の画像はライブラリが受け付けない大きさだった。縮小に弱いこと、加工なしでも確実には読めないことは、開発元が PyPI のページと README に書いているとおりの向きだった。JPEG 圧縮については、開発元の試験表と結果が分かれた。

ただし、C2PA の仕組みの中で、読めた識別子が「AI 生成」の記録につながるのは、soft binding としてマニフェストに結びつけ、保管場所から引き当てられるときだ。透かしは改ざんが無いことの証明ではなく、仕様も soft binding を hard binding の代わりにしてはならないと書く。画像に印を残すなら、ファイルに書く C2PA のマニフェストと画素に埋める電子透かしが、それぞれ何を担い、どこで消えうるかを分けて考える必要がある。C2PA を書いて読む手順は C2PA で AI 画像に来歴を付けて読む(2026-11-10 公開)、当サイトの流れでメタデータがどこで消えるかは AI 生成の表示と「印」は画像と3Dデータのどこで消えるのか(2026-11-09 公開) で扱った。

参照

ブラウザだけでできる本格的なAI画像生成【ConoHa AI Canvas】
ABOUT ME
swiftwand
swiftwand
AIを使って、毎日の生活をもっと快適にするアイデアや将来像を発信しています。 初心者にもわかりやすく、すぐに取り入れられる実践的な情報をお届けします。 Sharing ideas and visions for a better daily life with AI. Practical tips that anyone can start using right away.
記事URLをコピーしました