🫠

【失敗】勝手に画像を転載してくる輩のためにAIにコード書いてもらって画像に電子透かしを入れようとしたが、うまくいかなかった話【解決策求む】

に公開

お世話になっております!
株式会社ウェイブでCoolmicという英語圏向け漫画配信サイトの開発を行っているささみと申します!
どの漫画サービスも同様だと思いますが、現在Coolmicでは漫画の違法アップロード対策に苦慮しています
今回、違法サイトにアップロードされていたコンテンツの画像からどのユーザーがいつ画像を抜いたかを検知するために、電子透かしの実装にチャレンジしました

※筆者は信号処理や画像処理について一切の知識がないため、明らかにおかしい記述があるかもしれません
※もしあったらコメント欄で教えてください(コメント稼ぎ)

要件

コンテンツの販売サイトである以上、ユーザーが購入したコンテンツの画像が荒くなっている等があると問題です。かといって最初の目的であるユーザー情報の取得ができないのもまた問題です。
また、ユーザー情報を動的に入れなければならないため事前に画像処理ができず、こういうのには不適そうなjavascriptで実装するため、処理時間についても気を使う必要がありました。
今回は以下の要件で開発を行いました(行ったというか、copilotに書いてもらいました)

  1. 画像の質の劣化はユーザーからはわからない程度であること
  2. jpgやwebp圧縮、スクリーンショットからでも情報を抽出できること
  3. 画像一枚あたりの処理時間がミドルスペックのスマートフォンで1秒以内に収まること

方針検討

あらゆるAIチャットボットに質問責めをしてどういった手法があるか聞きました

  1. LSB方式
    • 画像の一部ピクセルの色コードを一つだけずらして情報を保存する方法
      • 例えば#ff0000だった場合#ff0001とかにする
    • 目にはほぼ見えないが、軽微な差であるため圧縮時に消える可能性が高い
  2. 目に見えるレベルの透かしをうすーーーく入れる
    • アルファ値を0.005とかにしてユーザーからは気にならないように
    • あまりアルファ値を下げすぎると圧縮時に消えてしまうため、ある程度の濃さは必要
      • 濃くすると見えちゃうけど......
  3. 全体に満遍なく特定のノイズパターンのようなものを入れる
    • ユーザー情報をハッシュ化したものからノイズを生成し、スクショで消えない程度の濃さで画像に重ねる
    • 多少汚くはなるが全体に満遍なく入るため違和感は少ない
    • ただ、ノイズから情報を逆に抽出することができないため一致するものをなんとかして探す必要がある
  4. DCT変換(離散コサイン変換)、DWT変換(ウェーブレット変換)
    • 人の目には見えづらく、圧縮によって切り捨てられづらい中周波数領域に情報を埋め込む
      • 素人なので説明間違ってたらごめんなさい(怒らないで)
    • 目にも見えないし圧縮耐性もあるが、計算量が非常に多くなるため動的に埋め込むには工夫が必要

今回は圧縮耐性と画像の質を重視してDCT,DWTを使用して実装しました。
Coolmicでは事前にコンテンツ画像をCanvasで描画するようにしていたため、そこに直接この手法を使います

どんな情報を入れようか

なるべく情報量を圧縮して速く・綺麗にしよう!ということで入れる情報は以下のように工夫しました

  1. 識別コード 4bit
    • 事前に決めた0~15までの数 別にいらないかも
  2. ユーザーID 10bit
    • そのまま入れるとかなりの数になるので、下三桁のみを入れる
      • サーバーのログと組み合わせれば流石に同定できるはず
  3. タイムスタンプ 8bit
    • unix時間を何とかして入れようと思ったが流石にきついので、ある日付から何日経過したかを入れました
      • 今回は筆者の誕生日11/14を0とした経過日数にしました(お祝いお待ちしています)

これで合計22bit!まあ悪くないのではないでしょうか 知らんけど

Copilotに書いてもらおう

上記の情報を渡してCopilot(Claude Sonnet 4.5)に書いてもらいました
DCTを使って画像内に4*4の領域でデータを埋め込んだものを複数入れてるみたいなやり方らしいです(もうよくわからないので全部任せました)

結果は.......................

ダメそう

画像は汚くないし時間も一枚一秒以内に収まっているけど、肝心のデータの抽出ができない...
いくつかの画像(コンテンツの画像なので見せられないよ!)で試したところ識別子は問題なく取れるがそれ以外のbitが全て1になってしまう...

筆者の理解も足りないためどこをどうすればいい感じになるか見当もつきません
\(^o^)/オワタ ←懐かしっ

後で調べたところ、漫画の画像は色数が少なくのっぺりしているためこういった情報を埋め込むのには不適なようでした
\(^o^)/オワタ ←懐かしっ

終わりに

こういうかなり専門的なことがAIエージェント任せで実装できるようになったのはかなりすごいなぁと思います
ただ、同時に開発者が機能の内容をしっかり理解していないと直せないので長い目で見ると良くないかもしれませんね.........今回は最初からできなかったので関係ないですが

誰かいい感じの方法あったら教えてください

wwwave's Techblog

Discussion