🖼️

透過画像を復元したいときの話

2022/03/06に公開

状況

まず、何らかの透過画像が存在するとします。

元画像の例

実際にはこの画像は持っていないとします。ただし、これを白背景に貼りつけた画像と、黒背景に貼りつけた画像は得られたとします。

白背景の例 黒背景の例

これらから、元画像を(ある程度の精度で)復元することを考えます。

前提知識

透過画像の合成の計算方法について触れます。Alpha compositing (Wikipedia) にある A over B の合成方法を用います。たぶんメジャーなので。

各画素・RGB の各値に対して、それぞれ独立に行います。

画素 A = (x _A, \alpha _A) を画素 B = (x _B, \alpha _B) の上に合成したものを、画素 C = (x _C, \alpha _C) とします。x には r, g, b がそれぞれ入ります。なお、x\alpha[0, 255] の整数値ではなく [0, 1] の実数値を取るとします。

このとき、(x _C, \alpha _C) は次のように計算されます。

\begin{cases} \alpha _C = \alpha _A + \alpha _B\cdot (1-\alpha _A); \\ x _C = \frac{1}{\alpha _C}(x _A\cdot\alpha _A + x _B\cdot\alpha _B\cdot(1-\alpha _A)). \end{cases}

考察

白背景と黒背景の画像に関して、それぞれ wb を添字にして表すとします。

白背景の画素 B _w(1, 1)、黒背景の画素 B _b(0, 1) であり、\alpha _{C _w} = \alpha _{C _b} = 1 です。
よって、以下が得られます。

\begin{cases} \begin{aligned} x _{C _b} &= x _A\cdot\alpha _A+ 0\cdot 1\cdot (1-\alpha _A) \\ &= x _A\cdot\alpha _A; \end{aligned}\\ \begin{aligned} x _{C _w} &= x _A\cdot\alpha _A+ 1\cdot 1\cdot (1-\alpha _A) \\ &= x _A\cdot\alpha _A + (1-\alpha _A). \end{aligned} \end{cases}

よって、x _{C _w} = x _{C _b} + (1-\alpha _A) なので、\alpha _A = 1 + x _{C _b} - x _{C _w} を得ます。ここから、x _A = x _{C _b}/\alpha _A もわかります。

ここで、\alpha _A = 0 のケースには注意が必要です。「R の値は 0 だが G の値は正」といった状況で、R の値に基づいて \alpha を計算しようとするとうまくいきません。とりあえず、RGB のうち \alpha を最大にするものを採用します。それでも \alpha=0 の場合は、(x _A, \alpha _A) = (0, 0) とします。

これに基づいて復元が可能です。

復元画像の例

合成の際に(整数に丸めるため)誤差が生じますが、人間[1]の目にわかる程度の差はなさげです。背景は実際には白と黒ではなくても可能そうです(が、誤差が大きくなりそうな気がします)。

ビット数が 8 程度なら、全通り作っておいて表引きしてもよさそうな気もします。16 ビットある場合は表引きでは大変そうです。

Rust で書くとこんな感じ。

fn transparent(white: [u8; 4], black: [u8; 4]) -> [u8; 4] {
    let alpha = (0..3).map(|i| 255 - (white[i] - black[i])).max().unwrap();
    if alpha == 0 {
        return [0; 4];
    }
    
    let mut res = [0, 0, 0, alpha];
    for i in 0..3 {
        res[i] = ((255 * black[i] as u16) / alpha as u16) as u8;
    }
    res
}

おわり

あまり役に立つ機会はなさそう。

ポエム

実は、プログラミング始めたての頃(たぶん 7–8 年くらい前)に書いたものがこれでした[2]。久々に思い出したのでまとめました。あなたは始めたての頃に書いたものを覚えていますか?

最近は(実務を除いては)競プロくらいしかしていなくて、当時の方がいろいろやっていたのではないかという気もしています。

脚注
  1. 少なくとも自分。 ↩︎

  2. 他にも、フォントのファイルからグリフを抽出するのを書いたりしていました。変わった趣味だね。 ↩︎

GitHubで編集を提案

Discussion