Home -> 雑用 -> 雑用メモ -> [5.18 [C#]写真の透かしを除去したい]
2016/02/17 公開
書きっぱなしの文章なので大変読み難い代物となっている。それでも良ければどうぞ。
写真の透かしを除去するためにC#で試行錯誤した記録。
基本的に時系列順なので副題が前後しているが容赦していただきたい。
C#の話の前に前書きを。
透かしとは以下のように画像の上に半透明に被さった文字あるいは記号を指す。ウォーターマークとも呼ばれる。
[画像00]の例では「美瑛」などの文字が透かしとして入っている。これをどうにかして消したい。
画像00: 透かし入り画像の例(https://saltworks.jp/~photo-sports.saltworks.jp/marathon/photo/hm046/images/hm0460349.jpgより)
厳密には、この種の透かしは知覚可能型電子透かしというらしい。
画像に乗せられた透かしを除去しようとする先人たちの取り組みを検索してみたが、大したものは出てこなかった。
唯一何かやってるなー、と感じたのは以下の参考ページ。
明るめの同じ透かしが同じ位置に入った標本が大量にある場合は、それらを加算なり乗算なりで重ねまくると
透かし部分は必ず透過度に応じたある明るさ以上になるので透かしが浮き出てくる。それを加工して透かしを復元する。
透かしを復元できればそれを元に透かしが入る前の画像も復元することができる、という調子らしい。
でも結局やっていることは「ペイントで根性」と大差ない。結構面倒臭そうというかセンスが要求されそうという印象。
また、この方法で復元した透かし画像は透過度を調整する必要があり、周縁部も不完全という欠点がある。
さらに、透かしが単色ではなく白と黒の2色が使われている今回の場合、この方法ではとてつもなく面倒な作業を行わねばならない。
ところが幸いなことに[画像00]には透かしが入っていない縮小版(サムネイル)が存在する。
画像01: [画像00]のサムネイル
これをうまく使えば、プログラムにより半自動で透かしの復元および透かし除去ができそうだ。
プログラムで画像を扱うためには、その性質を定性的かつ定量的に議論する必要がある。
まず、画像に透かしを被せると画素の色がどう変化するのかを"何となく"理解していきたい。
簡単のために透かし画像は無彩色であると仮定する。
このとき、直感的にもそうであるように、透かしが不透明でない限り理想的には透かしを重ねる前後で色相は変わらない。
(ただし実際には後述のように量子化誤差や圧縮による擾乱が存在し、状況によってはこれがかなり効いてくる。)
画像02: 有彩色に無彩色を重ねたときの色の変化
つまりこの仮定の下では、透かしが入った画像の特定画素の色から元の色を復元する際、透かし除去後の色相は既に明らかであるため
彩度と明度のみが未知数であり、これから議論するのは1つの画素につき自由度2の決定系ということになる。
では、具体的に式を追うことにより、どのような計算を行えばよいのか確認していこう。
画像上の座標における画素値をとし、これに上の同座標の画素値を
指定された透過情報により重ねてできる新たな色を上の同じ座標にマッピングする。
いま、ある座標に着目してであり、これにを
不透明度()で加算した結果をにマッピングするとき、以下の関係が成り立つ。
――(1.1)
上式における未知数はおよびの2つのみなので、が有彩色であれば
上式は2変数に対して方程式が3つ存在する過剰決定系であり、その最適解は最小二乗解である。
画像、上の座標における任意の原色成分をそれぞれ、と表すことにすると、は
およびによって一意に定まるの関数(すなわちモデル関数)と誤差を用いて
――(1.2)
と表すことができる。また(1.1)より(1.3)のように定めることができる。
――(1.3)
ここで(1.4)の変数変換を考える。
――(1.4)
すると、(1.3)は(1.5)に示す通り一次関数で表すことができる。
――(1.5)
すなわち、およびを求めることは(1.5)に示すモデル関数について係数の最適解を求めることと等価であり、
(1.5)についてはごく一般的な1次関数による最小二乗近似を用いることができるので、以下によって値を定められる。
――(1.6)
ある画像のある画素について着目したとき、原色成分の値のペアは3組できるので、標本画像数の3倍のデータが得られる。
これを元にして(1.6)により係数を計算すれば、フィルタの明度および不透明度が計算できる。
前準備において、フィルタが掛かる前の画像が手元にある時はフィルタが復元できることが示された。
では、今回のように透かしが入る前の画像が存在せず、透かしを除去した状態の画像が欲しい場合はどうしたらよいのか。
今回は、先に紹介した通り透かしの入っていない縮小版があるため、これを用いる。
とはいえ解像度が元画像に比べてかなり低いので色々と不具合が生じる。そこで、以下の仮定を置くことにより、
縮小版を用いて得られた結果の正確さがある程度保証されるものとして議論を進める。
注意しなければならないのは、この仮定は正しいとは限らないということである。実際にこの仮定が妥当かどうかは後に検証を行う。
この仮定により、誤差の大きさに対して標本数が十分に多い場合はをそれなりに正確に求められる。
実際には、まず(1.6)により係数を計算し、以下の関数によりを求めればよい。
――(1.7)
(1.7)における不定の扱いだが、(1.4)からわかるようにこのときフィルタの不透明度100%であり元の色を特定できない。
実装自体は大したことのないごく普通の画像処理である。しかし何点か注意しなければならないことがある。
ここまでは、十分な数の標本画像が手元に存在するという前提で議論を進めてきた。実際にはそれらを予め用意しておくという手順を
追加する必要がある。ひとまず下記の条件で揃えた画像を標本とする。これだけの枚数があればそこそこの精度は出るだろう。
(当然ながらこの時点では統計学的に十分な精度が保証されるかどうかはわからない。)
標本の枚数が多いので処理する画素数も多くなる。既に有名なようにBitmap.GetPixel()
とBitmap.SetPixel()
は非常に遅いので
別の方法で処理するべきである。
また、縮小版はそのまま拡大すると透かし入りの画像と微妙にずれるので、これも補正して処理する必要がある。
画像03: 縮小版を拡大したものと透かし入りの画像のずれ
画像04: 縮小版を拡大するときに適切に補正を施したもの
以上に注意しつつ色々と作っていく。また、に対するの誤差が正規分布に従うかどうか、仮に正規分布に従わない場合、
最小二乗法が適切な解を与えるのかどうかを確かめるために、ある座標に着目してその誤差の分布を記録する。
まずは美瑛の「美」の中央上寄りあたりについて検証する。この点は[画像05]において水色の○で示した。
透かしが入っていないことが明らかな領域は計算対象から除外する。領域の境界は[画像05]において桃色の四角形で示した。
画像05: 透かしの範囲選択と座標の指定
これまでに述べた計算を全ての画素について行うと、フィルタとして[画像06]のようなものが得られる。計算時間は30秒弱。
高速化の余地も残っているが所詮は.NETなのでここを改良してもそれほど意味はない。
[画像06]は実際には計算上生じたいくつかの不具合を誤魔化しているので、正確な透かし画像ではない。これはあくまでもイメージである。
画像06: 得られた透かし
実際の透かし除去は、最小二乗法で得られた係数をそのまま用いてモデル関数の逆関数による計算によって行う。
この時点で[画像07]のような除去結果が得られる。
画像07: 透かしの除去結果
一見してかなり良好な結果にみえるだろう。実際に、べた塗りが少ない画像ならばこれでも全く問題ない。
しかし、よく見ると透かし除去処理を行った部分は色のコントラストがごく僅かに落ちてしまっている。
空のように明度の高い領域に被る部分では明度の変化が明らかである。
安いディスプレイでも下から覗き込んだりすれば、先ほど桃色の枠で囲った領域の跡が確認できるだろう。
画像08: 透かしの除去結果を補正したもの
ここからは、上で述べたような現象が生じる原因を考えて対策していく。
まず、先の理論によるフィッティングは正しく機能しているのか。座標(150,75)について横軸、縦軸でプロットすると下図のようになる。
この座標はちょうど美瑛の「美」の字の真上に位置し、フィルタにより白みがかるため以下のような相関が得られる。
これだけを見ると、割と正しく動作しているように見受けられる。
画像09: 座標(150,75)におけるフィルタ前後の画素値の相関
では、期待値は実際の画素値に対してどのような分布を取っているのか。同座標について横軸を残差、縦軸を確率としてプロットすると
[画像10]のようになる。橙色の線は、統計的に求めた期待値と分散を元に描いた正規分布であり、縦軸は確率密度を表す。
見ての通り、残差の分布は正規分布ではフィッティングできない。正規分布に比べて裾が広いため、
計算して出てくる分散の値は見た目よりも大きくなってしまう。また平均値はほぼ0だが最頻値は-1となっている。
これが割と重要で、フィルタ画像から元画像を推定すると、この座標では多くの場合においておよそ1だけ明度が落ちることを意味する。
画像10: 座標(150,75)における残差と確率
文字上以外の点ではどうなるだろうか。美瑛の「美」の上あたりに相当する座標(150,60)についても統計を取ったところ
[画像11]のようになった。この結果を見るときに注意しなければならないのは、このグラフの概形が意味するところは
「元画像にフィルタを重ねてフィルタ済みの出力を得るときに、入力の明度によって出力の明度がどう変化するか」、すなわち
「入力からフィルタ済みの出力を得るために設定されたトーンカーブ」であるということ。
画像11: 座標(150,60)におけるフィルタ前後の画素値の相関
上の結果をもう少し噛み砕くと、今のままだとフィルタ後にはコントラストが上がるということであり、
逆に言えば透かし画像から除去結果を得るとコントラストが下がってしまうということである。
これは上のグラフが理想的には(0,0)と(255,255)を結ぶ直線にならなければならないことからもわかる。
このようになってしまう原因として以下の3点が考えられる。
では、どのようにこれを補正するのか。まずは正確なずれを知る必要があるのでこれを求める。
ひとまず、透かしに近く本来は不透明度0%となる領域として、[画像12]中の緑の枠で囲った領域を選んで検証する。
画像12: 透かしに近く不透明度0%となるべき領域
フィルタ前後の画素値の相関は[画像13]の通り。理想的な直線とは(119.6, 119.6)を中心としてズレを生じている。
このことは、縮小・拡大を経ると平均的に見てRGB(119.6, 119.6, 119.6)
あたりに近づくことを意味する。
画像13: フィルタ前後の画素値の理想的な関係と実際の相関
ここまでに述べた方法で得られる値を、補正後の値をとすると、[画像13]のグラフは縦軸が、横軸がであり、
傾きおよび切片をそれぞれ、とすると、(1.8)の関係が成り立つ。
――(1.8)
また、なので(1.9)の関係が成り立つ。
――(1.9)
今まで(1.7)を用いていた代わりに(1.9)を用いれば、尤もらしい出力が期待できる。
一番最初に縮小版を拡大する時にコントラストを弄ってしまうという方法もあるが、これだと補正係数を先に求めなければ
ならないため実装が面倒になってしまう上に、コントラストを弄った時点でRGBの各値が整数に丸められてしまい誤差の原因になる、
といったことが予想されるため保留。
また一応述べておくが(1.9)自体はフィルタ除去前と除去後の画素値のマッピングを各座標について与えるだけであり、
元画像に乗せられた透かし画像がどのようなものだったのかについては言及していない。
実装といっても、必要な定数を追加で解析時に求めるようにして、画像生成時に(1.9)を用いるようにするだけ。
これで[画像14]のように良好な結果が得られるようになった。
画像14: (1.9)による透かしの除去結果
[画像08]と同様に補正を掛けても[画像08]とは異なり不具合は見られなくなった。
画像15: [画像08]と同様に透かしの除去結果を補正したもの
という訳で目標としていた透かしの除去が概ね達成された。
式(1.9)は先に述べたように単体では透かし自体の情報は持っていない。しかし実際に画像処理ソフトウェアでの使用を想定すると
フィルタ前後の値のマッピングを与えるよりも透過情報を持った透かし画像を与える方が現実的である。
実際にどんなフィルタが掛けられていたのか確認したいというだけでも動機としては十分である。
補正係数が必要となる原理より明らかになので、(1.9)を変形すると(1.10)が得られる。
――(1.10)
(1.3)より、(1.4)と同様の関係として(1.11)が考えられる。
――(1.11)
これを整理すると(1.12)が得られる。
――(1.12)
よって、これを用いることで補正結果を適用した透かし画像を生成することができる。
生成結果は[画像16]の通りである。これと[画像06]とは似て非なるものである。
画像16: 補正係数を適用して生成した透かし画像
また、これができるからといって何か嬉しいことがある訳ではないが、[画像17]のように適当な画像に同じ透かしを追加することもできる。
画像17: 適当な画像に透かしを適用した結果
ここまでに述べた内容だけを見ると既に目的を完全に達成したかのように見えるが、実際にはまだ問題が残っている。
[画像18]の例では、美瑛の「瑛」の字の右側部分の輪郭が浮き出てしまっている。
画像18: 透かしが除去しきれない例
[画像18]だけだと遠目には分からないかもしれないが、1次微分(Gradient)を取ることにより輪郭を抽出すると[画像19]が得られる。
やはり容易に抽出可能な程度で跡が残ってしまっているので、これでは透かしを完全に除去できたとは言えない。
画像19: 除去しきれなかった透かしを顕在化させたもの
この問題が生じる原因は主に以下の通りである。
JPEGの圧縮原理については以下のページに分かりやすく書いてある。
という訳で、たまたまモデルになってもらった3269番さんがたまたま真っ赤な服を着ていたために無視できない問題となってしまった。
問題を整理すると、純粋な赤に近い色は縮小版においては透かし入り画像に比べて暗めになってしまうため、これを元にして
透かし除去の仕組みを構成すると、透かし入り画像に比べて透かし除去済み画像は純粋な赤に近い色は元よりも明るくなってしまう、
ということである。これは透かし入り画像と縮小版とで比べた時に、赤色に近い色であることが原因で及ぼされる色の劣化の程度の差が
大きい座標、すなわち透かし文字上(特に文字輪郭部分)において顕在化するため、このように文字の輪郭が浮き出てしまった。
では、どのように対策を施せばよいのか。
想定する補正関数は以下の要件を満たす必要がある。
そこで、付け焼刃ではあるが(1.13)のような関数を考える。
――(1.13)
(1.13)は4つの要素からそれぞれ独立して決定される数の積である。
ゲインおよび閾値は定数であり、適切に設定をする必要がある。余弦類似度およびシグモイド関数は(1.14)により定義する。
――(1.14)
とりあえずは画素値のうち赤色成分のみを対象とし、新たな画素値を(1.15)により決定する。
――(1.15)
勾配は適当なアルゴリズムを選択して計算する。
4つ目の成分は不透明度により決定され、[画像20]のように閾値より大きい場合は正、小さい場合は負となる。
これにより輝度を大きくするか小さくするかを分ける。
ただし[画像20]ではゲインおよび閾値を適当に設定している。
画像20: 不透明度により決定される成分の特性
2つ目・3つ目の成分はそれぞれ勾配・余弦類似度により決定され、両方の値が大きい時のみ補正関数が有効に働くような特性を持つ。
不透明度が閾値から十分に離れているとき、補正の特性は殆どこの2成分で決まる。
画像21: 勾配および余弦類似度により決定される成分の特性
このような補正関数を考えるとき、ゲインや閾値といった定数をどのように決定するかが問題となる。
理想的には何らかの効用関数が最大となるように最適な大きさを決定するべきだが、まずは手作業により調整を行った。
画像22: 赤色劣化を軽減した結果
定数を適当に調整して補正を行った結果、透かしの跡が補正前に比べて目立たなくなった。
画像23: 補正関数による処理前後の出力比較
勾配を取り輪郭を抽出すると、他の部分に殆ど変更を加えることなく透かしの跡を目立たなくすることができたことがわかる。
すなわち先に定義した補正関数はある程度有効に働いたということになる。
画像24: 補正関数による処理前後の出力を1次微分したものの比較
では、よりよい結果を得るために各定数をどのように決定するか。
一番簡単な方法は、色々な値を代入してみて勾配が最小となる組を探す、という感じだろう。
しかし7変数もあるのであらゆる組み合わせを総当たりで検証しようとするとメモリを大量に食ってしまいVisualStudioに怒られてしまう。
真面目に解くと複雑な問題に関して最適解を得たいとき、遺伝的アルゴリズム(GA)がよく用いられる。
これは進化的アルゴリズムの1つで、評価関数を用いて適応度を計算することが可能な問題であれば
だいたいどんな問題でも最適解かそれに近い何かが得られるという便利なアルゴリズムである。
遺伝的アルゴリズム、とだけ聞くと何か先進的なものであるかのように感じてしまうかもしれないが、中身は実に単純である
初期値を適当に設定し、それらを交叉させたり突然変異させたりして次世代を作り、評価の高いものが多く残るように淘汰する。
あとは交叉・突然変異・淘汰を繰り返すだけである。エリート(評価の高い個体)を暫定的な解とみなすと、
これは世代交代を繰り返す度に最適解に漸近する。ただし、個体数・交叉率・突然変異率が適切でなければならない。
中身といっても実のところこれは表向きであり、裏には色々と理論があるようだか今回は触れないことにする。
ここで問題となるのが、評価関数・個体数・交叉率・突然変異率である。まず、評価関数は(1.16)により定義する。
――(1.16)
この値が大きくなるようなパラメータは適応度が高い組み合わせとなる。はユーザが決める定数で、
勾配は少ないが人が見て不自然な結果となるような組み合わせを淘汰するためのものである。これを大きくし過ぎると
閾値が大きくなりすぎたりゲインが小さくなりすぎたりするため、大きさは0.2程度に調整する。
この評価関数を用いて、個体数100、交叉率90%、突然変異率1%で世代交代を50回行ったところ[画像25]のような結果が得られた。
画像25: 遺伝的アルゴリズムにより決定された定数を用いて補正したものと補正前との比較
補正前よりは透かしが目立たなくなったが、予想していたほどの効果は見られなかった。
これは補正関数あるいは評価関数の設計が不適当であることが原因と考えられる。
何らかのスケーリングを施すことである程度の改善が期待できるが、手動で決定したときの結果を超えられる見込みはないので
そこまではやらないことにする。
一応赤色劣化の補正はある程度までなら自動で行うことが可能であることを示すことができたので、この章はこれでよしということでご勘弁願いたい。
以上より、縮小版でもいいので透かしが入っていない画像と組になるような標本を十分な量だけ用意することができれば、
画像から透かしを除去することは技術的には十分に可能であることがわかった。
実際には標本が用意できないような場合も多いので、このときは別の方法を検討しなければならない。
また標本については透かしが同じ位置に入っていることが前提となっているという点も注意が必要。
画像99: 行った操作の概要
恐らくここまでの説明はかなり分かり辛いものだっただろうと考え、概略を[画像99]の通り作成してみた。
実際には複数の標本を用いて推定を行っているためここまで単純ではないが、要旨は理解できるだろう。
また標本数が統計学的に十分かどうかについては一切の検討を行わなかったが、出力にノイズが殆ど乗っていないので十分だろう。
もう少し標本数は少なくてもいいかもしれないが、計算結果は使い回せるので1回の計算に多少時間が掛かったとしても問題にはならない。
同様の画像から透かしを除去したもの。これらの画像は検証の結果を示すために掲載したものであり、他意はない。
一部の画像はGIMPを用いて後処理を施しているが、素人が数分間弄っただけなので基本的にはC#側で透かしをほぼ100%除去できたと考えてよい。
透かしがどれほど綺麗に除去されうるかを以下から確認して頂きたい。
ついでにGIMPの練習をした。電脳はさみが思ったよりも無能だったのでほぼ手動で切り抜くことになった。
背景はスタイルシートで適当な色になっているが画像自体は透過PNGになっている。
PCならマウスオーバで背景色が切り替わるので、これにより切り抜きの精度がどの程度か容易に確認することができる。
切り抜き透過PNG | 最小枠透過PNG |
---|---|
▲hm0040406: 2011年 ゴール地点 11:00~11:30 | |
▲hm0460345: 2012年クォーターマラソン ペンション星ヶ丘 10:30~10:45 | |
▲hm0460347: 2012年クォーターマラソン ペンション星ヶ丘 10:30~10:45 | |
▲hm0460348: 2012年クォーターマラソン ペンション星ヶ丘 10:30~10:45 | |
▲hm0460349: 2012年クォーターマラソン ペンション星ヶ丘 10:30~10:45 | |
▲hm0320054: 2012年ハーフ、クオーター、ワンエイツマラソン ゴール地点 11:00~11:15 | |
▲hm0320218: 2012年ハーフ、クオーター、ワンエイツマラソン ゴール地点 11:00~11:15 |
今回の検証と同じようなことは既に世界のどこかで何回も行われていることだろう(未確認)。
しかし少なくとも国内の記事を探してみた範囲では見つからなかったので、画像に透かしを入れようと考えている方は
それが復元可能な状態であるかどうかに留意する必要があることを今後は念頭に置いておいてほしい。
また逆に透かしを除去してみたいという方はこの手法を試してみるのもよいだろう。
一日あれば書けるし、メモリが許せばPHPでも画像処理ができる時代なので、好きな言語で書いてみてほしい。
今は先人たちが作成した画像処理用の便利なライブラリも沢山転がっているので、そういうものを漁ってみると
もしかしたらもっと先進的な手法が見つかるかもしれない。
この記事を書いたのは[画像00]の透かしを除去しようと思ったのがきっかけである。
びえいヘルシーマラソン2012 PHOTO WEB 様からは検証用に多くの素材をお借りした。
事後報告になってしまい申し訳ないが、この場で感謝の意を表したい。
ついでに、びえいヘルシーマラソンの画像に陽を当ててくださった各氏との出会いに感謝。
これらの検証(コーディング)には Visual Studio Community 2015 を用いた。
このような優れた開発環境を無償で利用できるからこその検証である。という訳でMicrosoft社に感謝。
付録1の画像の一部、および付録2の画像においては加工にあたり画像編集ソフトウェア GIMP を用いた。The GIMP Team の皆様に感謝。
管理人Twitter: @su_te_ak/◆mmft4k9vgtL6
要望等はTwitterへ
Home -> 雑用 -> 雑用メモ -> [5.18 [C#]写真の透かしを除去したい]
ここ以降は鯖が勝手に付加するやつです