これは、触って掴んだものを、腰を据えて確かめるための場所だよ。Gradus や PixLab で手を動かして「なんとなく分かった」を、ここで言葉と数式に落としていく。順に読んでもいいし、気になった章だけ拾ってもいい。
道しるべ:画像=数の格子 から始めて、微分(エッジ)、統計(しきい値)、色(測る・まとめる・直す) へ。連続の数学を、格子の上でどう近似しているか、が通奏低音だね。
連続した景色を、格子の上の数字に置き換える。すべての出発点だね。
カメラの前に広がっているのは、本来どこまでも滑らかな光の分布だよ。位置 $(x,y)$ ごとに明るさが決まる関数 $f(x,y)$ ――これを連続関数だと思っておこう。ところがセンサーも画面も、点が有限個しかない。だから連続の $f$ を、格子点でつまみ食いして並べ直す。それが標本化(サンプリング)だ。
間隔 $\Delta$ の格子で拾うと、画像は
$$ f[m,n] = f(m\Delta,\; n\Delta), \qquad m,n \in \mathbb{Z} $$という二次元の数列=行列になる。さらに、拾った明るさも連続値のままでは置けないから、$0$〜$255$ のような有限段階に丸める。これが量子化だね。位置を離散にするのが標本化、値を離散にするのが量子化。画像が「数の格子」だというのは、この二段階の離散化の結果なんだ。
写真をドット絵に落とすとき、各ブロックから代表値を一つ選ぶ。素朴には「ブロックの真ん中の画素」を採ればよさそうに見える。でもそれは危うい。ブロック $B$ の中に細かい模様があると、中央の一点はたまたまの値を拾ってしまう。正しくは、ブロック全体を平均する:
$$ \bar f(B) = \frac{1}{|B|}\sum_{(x,y)\in B} f(x,y) $$これは一様な重みの畳み込み=ボックスフィルタで、面積平均とも言う。平均してから間引くと、ブロック内の情報が一点に押し潰されず、なだらかに残る。
理由は標本化定理(ナイキスト)にある。信号に含まれる最高周波数を $f_{\max}$ とすると、それを取りこぼさず再現するには標本化周波数 $f_s$ が
$$ f_s > 2\,f_{\max} $$でなければならない。格子が粗い($f_s$ が小さい)のに細かい模様(高い周波数)があると、高周波が低周波の「偽物」に化けて現れる。これがエイリアシング――シャツの縞がモアレになったり、回る車輪が逆回転して見えたりするあれだね。中央一点のサンプリングはこの偽物をそのまま拾う。先に平均(ローパス)して高周波を削ってから間引けば、偽物の素を断てる。だから「ちゃんとした縮小」は必ず平均を挟むんだ。
エッジもぼかしも、正体は「隣との関係」。連続の微分を、格子の上では差分で近似する。
明るさが急に変わる場所が、輪郭(エッジ)だ。「急に変わる」を数学にすると微分になる。連続なら
$$ \frac{df}{dx} = \lim_{h\to 0}\frac{f(x+h)-f(x)}{h} $$だけど、格子の上には $h\to 0$ がない。いちばん細かい刻みは「隣のマス」、つまり $h=1$ だ。そこで極限を取らずに止めると、微分は差分になる。
いちばん素朴なのが前進差分。中心差分はより対称で、誤差が小さい:
$$ \Delta f[n] = f[n]-f[n-1], \qquad \delta f[n] = \frac{f[n+1]-f[n-1]}{2} $$もう一段、傾きの変化(曲がり具合)を見たいなら2階差分。これは2階微分 $f''$ の近似だ:
$$ \Delta^2 f[n] = f[n-1]-2f[n]+f[n+1] $$平らな所では $\Delta f \approx 0$、明るさが急に立ち上がる所で $|\Delta f|$ が大きくなる。エッジは「差分が大きい場所」として浮かび上がる、ということだね。
二次元では、横方向と縦方向、二つの偏微分をまとめた勾配を考える:
$$ \nabla f = \left(\frac{\partial f}{\partial x},\; \frac{\partial f}{\partial y}\right), \qquad |\nabla f| = \sqrt{f_x^{2}+f_y^{2}} $$$|\nabla f|$ がエッジの強さだ。これを格子の上で計算する道具が畳み込み。カーネル $h$ を画像 $f$ に重ねて、ずらしながら積和を取る:
$$ (f * h)[m,n] = \sum_{i}\sum_{j} h[i,j]\, f[m-i,\,n-j] $$カーネルの中身を選ぶことで、畳み込みは「差分を取る演算子」になる。たとえば横方向の差分を、上下に少しぼかしながら取るのがSobelだ:
$$ h_x = \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix} $$真ん中の行 $[-1\;0\;1]$ がまさに「右−左」の中心差分で、上下の行はそれを平滑化している。だから Sobel は「ノイズに少し強い横微分」なんだ。エッジ検出が結局は微分だ、というのはこういうことだね。
2階微分を二次元にするとラプラシアンになる:
$$ \nabla^2 f = \frac{\partial^2 f}{\partial x^2}+\frac{\partial^2 f}{\partial y^2} \;\approx\; f[m{-}1,n]+f[m{+}1,n]+f[m,n{-}1]+f[m,n{+}1]-4f[m,n] $$1次元の $\,f[n-1]-2f[n]+f[n+1]\,$ を縦横に足したものだ。ラプラシアンはエッジで符号が反転する――立ち上がりの手前で正、奥で負になり、ちょうど境目でゼロを横切る。このゼロ交差を拾うと、細い輪郭線が得られる。
微分が「差を取る」なら、ぼかしは「ならす(平均する)」だ。一様な平均(ボックス)でもぼけるけれど、自然なのはガウスぼかし。中心ほど重い釣鐘型の重みで畳み込む:
$$ G(x,y) = \frac{1}{2\pi\sigma^2}\exp\!\left(-\frac{x^2+y^2}{2\sigma^2}\right) $$ガウスが特別なのは、$x$ と $y$ に分解できて計算が軽いこと(分離可能)、リンギング(不自然な縞)が出ないこと、そして $\sigma$ を連続に変えると「だんだんぼける」一本のスケール空間になること。$\sigma$ が大きいほど、削れる高周波の範囲=ぼけの強さが増す。
微分とは毛色が変わる。値そのものではなく「集まり方」から境目を決める。
明るさで白黒に分けたい――たとえば文字を背景から切り出すとき。しきい値 $t$ を決めて、$t$ 以下を黒、$t$ より明るいを白にする。問題は「$t$ をどう選ぶか」だ。手で決めるのではなく、画像自身のヒストグラム(明るさの分布)から自動で決めたい。
明るさ $k\,(=0,\dots,255)$ の画素数を $h(k)$、総画素数を $N$ とする。確率分布は $p(k)=h(k)/N$ だね。しきい値 $t$ で二つのクラス(暗 $C_0$:$k\le t$、明 $C_1$:$k>t$)に分けると、各クラスの重み(割合)と平均は
$$ \omega_0(t)=\sum_{k\le t} p(k), \quad \omega_1(t)=\sum_{k>t} p(k)=1-\omega_0 $$ $$ \mu_0(t)=\frac{1}{\omega_0}\sum_{k\le t} k\,p(k), \quad \mu_1(t)=\frac{1}{\omega_1}\sum_{k>t} k\,p(k) $$大津(Otsu)の方法は、「二つの山がいちばん離れて見える $t$」を選ぶ。その「離れ具合」を測るのがクラス間分散だ:
$$ \sigma_B^2(t) = \omega_0\,\omega_1\,(\mu_0-\mu_1)^2 $$これを最大にする $t$ が、求めるしきい値:
$$ t^\star = \arg\max_{t}\; \sigma_B^2(t) $$$\omega_0\omega_1$ は「両方のクラスがそれなりの大きさを持つ」ことを、$(\mu_0-\mu_1)^2$ は「平均が大きく離れている」ことを評価している。だから $\sigma_B^2$ が最大になるのは、画素がきれいに二つの塊に分かれる谷の位置になる。
全体の分散 $\sigma_T^2$ は $t$ に依らない定数で、次のように分解できる:
$$ \sigma_T^2 = \underbrace{\sigma_W^2(t)}_{\text{クラス内分散}} + \underbrace{\sigma_B^2(t)}_{\text{クラス間分散}} $$$\sigma_T^2$ が定数だから、クラス間分散 $\sigma_B^2$ を最大にすることは、クラス内分散 $\sigma_W^2$(各クラスのまとまりの悪さ)を最小にすることと同じだ。つまり大津は「各グループが内側でまとまり、グループ同士は離れる」分け方を選んでいる。クラスタリングの考え方を、1次元のしきい値に落とした形だね。計算も軽くて、$t$ を $0$ から $255$ まで一度なめれば済む。
RGB の数字は、そのままでは「光の量」でも「見た目の近さ」でもない。橋を架ける。
色を正しく扱うには、二つの落とし穴を知っておく必要がある。ひとつはガンマ、もうひとつは知覚の非一様だ。順に行こう。
画面に出る sRGB の値 $c\in[0,1]$ は、物理的な光の強さ(リニア光)$c_\text{lin}$ をそのまま表してはいない。次のようにガンマ符号化されている:
$$ c_\text{lin} = \begin{cases} \dfrac{c}{12.92} & c \le 0.04045 \\[6pt] \left(\dfrac{c+0.055}{1.055}\right)^{2.4} & c > 0.04045 \end{cases} $$なぜこんな変換をするのか。人間の明るさ知覚は暗部に敏感で対数的だから、限られたビット数を暗部に厚く配るとバンディングが減る。符号化はそのための工夫なんだ。問題は、光を足し合わせる計算(平均・ぼかし・WB)は、リニア光で行わないと物理的に正しくないこと。sRGB の値のまま平均すると、たとえば赤と緑を混ぜたとき、リニアでの正しい混色より暗く濁った色になる。だから「ガンマを戻す → 計算する → 符号化し直す」が色の作法だね。
リニア RGB は、等色実験に基づく線形変換で絶対的な色空間 XYZ に移せる(行列を一回かけるだけ)。けれど XYZ も「数値の近さ=見た目の近さ」ではない。そこを均したのが CIELAB だ。$X,Y,Z$ を白色点 $X_n,Y_n,Z_n$ で割って $f(t)=t^{1/3}$(小さい所は線形)に通し、
$$ L^* = 116\,f\!\left(\tfrac{Y}{Y_n}\right)-16,\quad a^*=500\big[f(\tfrac{X}{X_n})-f(\tfrac{Y}{Y_n})\big],\quad b^*=200\big[f(\tfrac{Y}{Y_n})-f(\tfrac{Z}{Z_n})\big] $$$L^*$ が明度、$a^*$ が赤–緑、$b^*$ が黄–青の軸。立方根が効いていて、Lab 上では「同じ距離なら、どの色でも同じくらい違って見える」ように近似してある。
だから、二色の「見た目の違い」は Lab 上のユークリッド距離で測れる。これが色差 $\Delta E$:
$$ \Delta E_{ab}^* = \sqrt{(\Delta L^*)^2+(\Delta a^*)^2+(\Delta b^*)^2} $$目安として $\Delta E \approx 1$ が「かろうじて識別できる差」、$\Delta E \gtrsim 2\text{–}3$ で「並べれば分かる」くらい。RGB のユークリッド距離が小さくても、$\Delta E$ では遠い(=見た目は別物)ことがある。色を「人の目の尺度」で測りたいなら Lab、というわけだ。(より人の知覚に合わせた $\Delta E_{00}$ という改良版もあるけれど、考え方の核は同じだよ。)
第4章の「色の測り方」を道具に、色を減らし、色かぶりを直す。
使う色を $K$ 個に減らすのは、色空間という3次元の点群を $K$ 個の代表点で置き換えるベクトル量子化だ。代表色 $\{c_1,\dots,c_K\}$ と、各画素 $x_i$ の割り当て先を選んで、全体の歪みを最小にしたい:
$$ J = \sum_{i} \min_{k}\; \lVert x_i - c_k \rVert^2 $$これを解く定番が k-means。二段階を繰り返す。①各画素を、いちばん近い代表色に割り当てる。②各代表色を、自分に割り当てられた画素の平均で更新する。割り当てが動かなくなったら収束だ。①と②はどちらも $J$ を下げるので、反復は必ず止まる(局所最適だけどね)。
ここで効くのが第4章だ。距離 $\lVert x_i-c_k\rVert$ をRGBで測るか、Labで測るかで結果が変わる。Lab で測ると「人の目に近い色どうし」がまとまり、暗部の微妙な差も自然に分かれる。RGB だと数値上は近いのに見た目が離れた色が同じ代表色に潰れることがある。減色の品質は、距離の尺度で決まるんだ。
写真には光源の色が乗る。夕日なら全体が赤く、曇天なら青く。これを打ち消すのがホワイトバランス(色順応)だ。モデルは フォン・クリース――各チャンネルを独立にスケールするだけ、という潔いもの:
$$ R' = g_R\,R,\quad G' = g_G\,G,\quad B' = g_B\,B $$推定した照明の色を $(\ell_R,\ell_G,\ell_B)$ とすると、その色が無彩色(グレー)に写るようにゲインを決める。グレーの目標を照明の明るさ $Y_\ell$ に取れば
$$ g_c = \frac{Y_\ell}{\ell_c}\quad(c=R,G,B) $$青が足りない暖色かぶりなら $\ell_B$ が小さく、$g_B$ が大きくなって青を持ち上げる。これもリニア光でやるのが作法だよ(第4章)。
残る問題は $(\ell_R,\ell_G,\ell_B)$ の推定だ。前提の置き方で三つの定番がある:
・グレーワールド:画面全体の平均は本来グレーのはず → 平均色を照明とみなす。
・白色点(ホワイトパッチ):いちばん明るい所は本来白のはず → 明るい画素の平均を照明とみなす。
・中立画素:彩度の低い(色みの薄い)画素だけを集めて平均する。
どれも「シーンについての仮定」だから万能ではない。被写体がもともと緑だらけだとグレーワールドは行き過ぎるし、白い物が無ければ白色点は外す。仮定を理解した上で使い分ける、というのが現実的なところだね。
ここまでで「標本化 → 微分 → 統計 → 色」の地図を、言葉と数式で一周したね。あとは手を動かすたびに、この地図のどこにいるかを思い出せれば、たいていの画像処理は迷子にならずに読めると思う。