UnityでTextureに目で見えないウォーターマークを埋め込んでみる 

# はじめに

こんにちは、[QualiArts Advent Calendar 2022](https://qiita.com/advent-calendar/2022/qualiarts)、12日目の記事になります。
本日はUnityの環境において、Textureに目で見えないウォーターマークを埋め込む実験について書きます。
実装例は https://github.com/thammin/unity-texture-watermark に上げています。

環境：
Unity 2021.3.0f1

# ウォーターマーク（電子透かし）

主に、画像ファイルの著作権の保護などの目的に使われています。
今回はQRコードを埋め込んだらどうなるかを試していきます。

![picture1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/22c7915f-62c9-10bc-13a2-cb81e519e861.png)

実現の方法は様々ですが、その中に元画像のピクセルにデータを埋め込む手法があります。
処理後の画像は、jpeg変換などによって発生するデータロスもある程度回復できるように、今回は[ウェーブレット変換](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A7%E3%83%BC%E3%83%96%E3%83%AC%E3%83%83%E3%83%88%E5%A4%89%E6%8F%9B)と[離散コサイン変換](https://ja.wikipedia.org/wiki/%E9%9B%A2%E6%95%A3%E3%82%B3%E3%82%B5%E3%82%A4%E3%83%B3%E5%A4%89%E6%8F%9B)を利用して埋め込みます。

アルゴリズムのフローは下記を参考しました。
・https://github.com/mchall/HiddenWatermark
・https://github.com/ShieldMnt/invisible-watermark
・https://github.com/accord-net/framework

# ウェーブレット変換の概要

![Jpeg2000_2-level_wavelet_transform-lichtenstein.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/f4635ae3-85aa-80e5-a6a9-797403ac649b.png)

ウェーブレット変換は、画像の色を隣接した値の差分と和を求めて、再帰的に行うと上記の画像[^1]のようになります。

![DWT 2 interations](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/233027c5-0eb7-f219-945b-05fe6b422082.png)

LL = 横 Low 周波数 + 縦 Low 周波数
LH = 横 Low 周波数 + 縦 High 周波数
HL = 横 High 周波数 + 縦 Low 周波数
HH = 横 High 周波数 + 縦 High 周波数

左上の画像は横と縦の低い周波数で構成されています。
今回はウェーブレット変換を2回行った、LL2のところにデータを埋め込みます。

# 離散コサイン変換

![DCT](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/23d70626-b881-e4e7-c696-467f8ea46ee4.png)

離散コサイン変換は、上記の画像[^2]のように離散信号を周波数領域へ変換する方法の一つであります。
特徴として、パーワーのほとんどが左上の低い周波数領域に集中します。
今回はウェーブレット変換した画像をさらにブロックを分割して、離散コサイン変換された後の真ん中にデータを埋め込みます。

# 全体的な処理順

前提：
・対象画像（1024x1024）
・ウェーターマーク画像（64x64)
・作業用の色配列（1024x1024）

エンコード順：
1. 作業用の色配列をYuvに変換
2. 作業用の色配列をウェーブレット変換を2回行なって、LL2の領域を抽出する
3. ウェーターマーク画像のピクセル毎に、LL2の領域からさらにブロック（4x4）を切り出して、離散コサイン変換を行う
4. ブロックの中間領域にウェーターマークのピクセルを埋め込む
   埋め込み方法：白ならデータを正方面に増幅、黒ならデータを負方面に増幅
5. 逆離散コサイン変換を行う
6. 逆ウェーブレット変換を2回行う
7. 作業用の色配列をRgbに変換
8. 対象画像に作業用のRgbに変換されたデータを加算する

デコード順：
1. 対象画像をYuvに変換
2. 対象画像をウェーブレット変換を2回行って、LL2の領域を抽出する
3. LL2の領域から順番にブロック（4x4）を切り出して、離散コサイン変換を行う
4. ブロック中間領域からデータを判定する
   判定方法：データが0以上なら白、以下なら黒
5. 判定したデータを画像として書き出す

上記の実装はこちらで確認できます。
https://github.com/thammin/unity-texture-watermark/blob/master/Assets/Watermark.cs

# Unity上の実行例

![Unity](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/4ef84e88-6ab7-b64a-a53e-cb3e30ef7572.png)
INPUT = 元画像
OUTPUT = 埋め込んだ後の画像
WATERMARK = 埋め込みたいQRコード画像
EMBED WATERMARK = INPUTに加算するデータ（可視化のためにグレースケール調整入っています）
RECOVERED WATERMARK = 埋め込んだ後の画像から抽出したQRコード画像

実際に実行したら、肉眼でOUTPUT画像から大きいの変更がない事を確認できて、
QRコードも無事に抽出できました。

試しにOUTPUTをJPEG変換して、抽出できるかも確認しましょう

```c#
outputTexture.EncodeToJPG(jpegQuality);
```

![Screen Shot 2022-12-12 at 19.33.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532685/e34d5590-569c-2867-9f5f-c66e4e024932.png)

Quality 15まで落とすと、ノイズが増えてきたのも確認できました。

# 結論

なんで画像にQRコードを見えないように埋め込むのはさておき、
ウェーブレット変換と離散コサイン変換を利用することで、
ある程度の画質が落とされても復帰できる事を確認できました。

以上、12日目の記事でした。引き続き今後のカレンダー投稿を宜しくお願いします。

[^1]: By Alessio Damato - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=2118889

[^2]: Alessio Damato - I made it by myself, CC 表示-継承 3.0, https://commons.wikimedia.org/w/index.php?curid=781346による