こんにちは,DSOC R&Dグループ インターン生の内田です. 最近巷でキーボードが話題になっているのに触発されて,ついHHKBを購入してしまいました. ものすごく快適である一方,出先で手が痛くなる問題に悩まされる今日このごろです.
はじめに
弊社で取り扱う名刺画像の入り口には主にスキャナとカメラの2つが存在しており,画像品質はどうしても スキャナ > カメラ となります. 現在カメラ画像に対してHand-Craftedな手法で影の除去や輝度値の引き上げなどを行っています. これを機械学習ベースの手法へ置き換えたいと考えたとき,品質の高いスキャナ画像を正解として学習したいという願望が出てきます.
カメラ画像とスキャナ画像のペア画像データセットを作成する場合,カメラ画像とスキャナ画像の位置合わせが必要となります. 安直には,キーポイントマッチング・ロバスト推定・射影変換を用いて位置を合わせる方法などが考えられます. しかし実際のところ,カメラ画像にはブレやピンボケなどの歪みがあり,写っている名刺が物理的に折れ曲がっていたりもするため,厳密に位置を合わせることはかなり難しいです*1. 位置合わせを頑張るのを半ば諦めて「位置が合ってない状況で学習できないかな~」と考えていた矢先,以前の連載に登場したContextual Bilateral Loss (CoBi)[*2]を思い出しました. CoBiは焦点距離の変化による位置ずれを考慮した誤差関数であり,やりたいこととマッチしている感じがします.
そんなわけで今回は,CoBiの派生元であるContextual Loss[*3][*4]について勉強がてらにまとめたいと思います.
画像変換と誤差関数
画像変換は入力画像をターゲットドメインに変換するタスクの総称*5であり,Image-to-Image TranslationやStyle Transfer,超解像などがこれに含まれます. 近年では画像変換の解法として畳み込みニューラルネットワーク(CNN)がよく用いられ,生成画像とターゲット画像を微分可能な誤差で比較することで学習を行います. ここで用いる誤差関数は次の2つに大きく分類できます*6.
- Pixel-to-Pixel Loss Function
同じ空間座標を持つ画素および特徴同士を比較する誤差関数.L2やL1などの代表的な距離関数に加え,VGG中間特徴を比較するPerceptual Lossなどもこれに該当します. - Global Loss Function
Gram Lossに代表されるような画像の領域および全体を大域的に比較する誤差関数.スタイルやテクスチャ情報をよく捉える事ができると言われます.
これらの誤差関数は多くのタスクに有効ですが,対応できないケースも存在します. 特にPixel-to-Pixel Loss Functionは入出力画像の位置が合っているデータ(Aligned Data)にしか使えません. 画像変換には位置が合っていないデータ(Unaligned Data)を用いる問題設定も多く存在するため,Pixel-to-Pixel Loss Functionが有効な範囲は限定的です. Global Loss Functionを用いれば画像間のAlignmentを無視できますが,大域的な特徴であるがゆえ,画像の内容物を細かく考慮するのは難しくなります.
Contextual Loss
Contextual LossはUnaligned Dataに対して用いる誤差関数であり,Pixel-to-Pixel Loss FunctionとGlobal Loss Functionの中間的な位置づけとして提案されています. Unaligned Dataの画像変換はGANを用いる手法が主流ですが,Contextual Lossを用いれば1つのCNNを訓練するだけで画像変換が可能というメリットもあります. 出力結果について今回あまり議論しないので,結果が気になる方は次のリンクからご覧ください. cgm.technion.ac.il
Contextual Lossのキーアイデアは,画像を高次元点の集合として捉えることです. 座標を持たない高次元点間の類似度を画像類似度とすることで,空間的な位置関係を無視できます. また,各高次元点は局所的な特徴を有しているため,過度に大域的な特徴比較を行うこともありません. このエントリでは,Contextual Lossを構成する類似度(Contextual Similarity)の定義について述べ,位置変化に対する頑健性を実験により確認したいと思います.
以下では生成画像を,ターゲット画像を
とし,キーアイデアに沿って,各画像を高次元点の集合
,
(
はインデックス)とみなします.
基本的に
や
にはVGG特徴や近傍画素値のベクトルを用います.
このとき,基本的には
(
は特徴マップの画素数)ですが,もし
なら大きい方の集合から
点サンプリングしてくるものとします.
Contextual Similarityの定義
画像間のContextual Similarityは,任意のに対して最も類似した
を抽出し,その類似度の平均を取ることで計算されます.
したがって,
,
間のContextual Similarity
は次のように計算されます.
$$ \mathrm{CX}(x, y) = \mathrm{CX}(X, Y) = \frac{1}{N} \sum_j \max_i \mathrm{CX}_{ij}$$
は任意の
,
間の類似度を表しており,コサイン距離
を操作することで計算されます.
のとき,
,
が類似していると考えられ,これを捉えるために
を用いて
を正規化します.
$$\tilde{d}_{ij} = \frac{d_{ij}}{\min_k d_{ik} + \epsilon}$$
ここで
はゼロ除算を防ぐための定数です.
次に
を類似度
に変換します.
$$w_{ij} = \exp \left( \frac{1- \tilde{d}_{ij}}{h} \right)$$
ここで
は帯域幅を表すパラメータで,論文では
を用います*7.
最後に,スケールへの頑健性を獲得するために正規化を行って
とします.
$$\mathrm{CX}_{ij} = w_{ij} / \sum_k w_{ik}$$
の定義は以上になりますが,
は類似度であるため,学習では次のように対数をとって符号を反転して利用します.
$$\mathcal{L}_\mathrm{CX} = - \log(\mathrm{CX}(x, y))$$
簡単な例
式だけでは分かりづらいので,が何を表しているか次の図を用いて説明します.
,
を表しています.
任意の
に特徴空間上で最も近い
が矢印で結ばれており,矢印が短くなれば
の値は大きくなります.
図中(a),(b)はそれぞれ類似,非類似ケースを示していて,類似ケースでは1-to-1のマッチが多いのに対し,非類似ケースでは1-to-manyのマッチとなっています.
1-to-1のマッチが多ければ自ずと
の値は大きくなりますし,
と
の分布が近くなることが直感的に理解できると思います*8.
したがって,Contextual Similarityは 画像特徴分布間の類似度 (の近似)であると論文では述べられています*9.
位置変化への頑健性に関する実験
ここでは,次ののRGB画像
,
のContextual Similarityを実際に計算した後,
の画素を並べ替えて位置変化への頑健性について確認します.
簡単のため特徴にはRGB画素値をそのまま使います.
,
は次のように生成されています.
# 比較する画像の生成 x = np.array([ [231, 76, 60], [46, 204, 113], [52, 152, 219], ]).reshape(1, 3, 3) / 255 y = np.array([ [245, 183, 177], [171, 235, 198], [174, 214, 241], ]).reshape(1, 3, 3) / 255
まず,と
を3次元点の集合
,
として表現します*10.
# 3次元点の集合として表現 X = x.reshape(-1, 3) Y = y.reshape(-1, 3)
次に任意の特徴点ペアのコサイン距離を計算します.
# 正規化 mu = Y.mean(axis=0, keepdims=True) X_centered = X -mu Y_centered = Y -mu X_normalized = X_centered / np.linalg.norm(X_centered, ord=2, axis=1, keepdims=True) Y_normalized = Y_centered / np.linalg.norm(Y_centered, ord=2, axis=1, keepdims=True) # コサイン距離 d = 1 - np.matmul(X_normalized, Y_normalized.transpose()) d_tilde = d / (d.min(axis=1, keepdims=True) + 1e-5) # 列方向に正規化
計算されたは
の行列になっていて,ヒートマップにすると次のようになります.
同じ位置の画素同士の距離が小さくなっているのが確認できます.
を類似度に変換して正規化することで
が計算できます.
こちらも
と同様に,同じ位置の画素同士の類似度が高くなっていることが確認できます.
# 類似度への変換 w = np.exp((1 - d_tilde) / 0.1) # 列方向の和を使って正規化 cx_ij = w / np.sum(w, axis=1, keepdims=True)
は各行の最大値を取り出して平均をとることで計算できます.
cx = np.mean(np.max(cx_ij, axis=0)) # => 0.9878605414928291
以上での値が求まったので,
の画素の順番を入れ替えて
の値がどうなるのか確認します.
from itertools import permutations for i, p in enumerate(permutations([0, 1, 2])): y_ = y[:, p, :] cx = compute_cx(x, y_) print(f'[trial {i}]: {cx}')
出力結果はこんな感じです.
[trial 0]: 0.9878605414928291 [trial 1]: 0.9878605414928291 [trial 2]: 0.9878605414928291 [trial 3]: 0.9878605414928291 [trial 4]: 0.9878605414928291 [trial 5]: 0.9878605414928291
最大値取ってるので当たり前といえば当たり前ですが,全部に同じ値になります.
以上より,の位置変化への頑健性を確認することができました.
まとめ
今回はUnaligned Dataに対する学習で用いるContextual Lossについて学び,実験を通して位置変化に対する頑健性を確認しました. 実験で確認したようにContextual Lossは完全に位置関係を無視するのですが,名刺のペアデータに関して言うと「何となく近くに同じものは写っている」状態なので,位置関係も多少考慮に入れたいところです. 前述のCoBiはContextual Lossに空間的な距離を組み込んだもので,実際に用いるならCoBiを用いるのが現実的に感じます. ただし,Contextual Lossは計算コストが高かったり,完全にUnpairedな問題設定では「平均的な特徴を合成してるだけなのでは?」という意見もあるので,導入には慎重な検証が必要といえると思います.
おまけ
ライブラリとして公開しました.
*1:それだけ1つの研究対象になり得ます.
*2:Zhang, Xuaner, et al. "Zoom to Learn, Learn to Zoom." Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2019.
*3:Mechrez, Roey, Itamar Talmi, and Lihi Zelnik-Manor. "The contextual loss for image transformation with non-aligned data." Proceedings of the European Conference on Computer Vision (ECCV). 2018.
*4:Mechrez, Roey, et al. "Maintaining natural image statistics with the contextual loss." Asian Conference on Computer Vision. Springer, Cham, 2018.
*5:射影変換などの幾何学的変換の意味で使われることも多いです.むしろそっちのが多いかも.
*6:GANの枠組みを用いる手法も多いですが,画像を比較するという意味で前述の誤差関数とは別モノと考えられます.
*7:ここの設定はかなり重要で,うまく設定すればTop1の特徴を強調できますが,失敗すると類似度間に差が出なくなり,最後のの計算に不都合が生じます.
*8:分布が遠くなると端の点にマッチが集まって1-to-manyマッチになります.
*9:自然な画像復元の文脈では,分布間の距離を最小化することが重要です.
*10:厳密には集合じゃないですが…