畳み込み — 近傍を混ぜる
あるピクセルの値を、自分とその周りの値の重み付き平均で置き換える。重みの並べ方を小さな表(カーネル)で決める。各ピクセルを単独で丸めるのでなく、隣の値を引き込んで決める。
3×3 の 9 マスを全部同じ重みで平均すると一様ぼかしになる。ぼかす対象として、わざと角張った場を配列に書き込む。市松模様と斜めの縞を足したもので、(x + y) & 4 は 4 マスごとに白黒が入れ替わる市松、sin((x + y) * 0.3) が斜めに走る縞。境目がくっきり立っている。
このギザギザを鈍らせる。あるセル (x, y) を中心に、周囲 9 マスの値を全部足して 9 で割る。dx、dy を -1〜1 で回すと、中心とその八方向をなめる。
// (x, y) を中心に 3×3 の 9 マスを平均する
let sum = 0
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
sum += at(x + dx, y + dy)
}
}
const blurred = sum / 9at は配列から 1 マス読む関数。buffer[y * n + x] を引くだけだけれど、画面の縁では隣がない。x が -1 や n を指すと配列の外に落ちる。そこを端の値に丸めて、縁でも 9 マスが揃う。
const at = (x, y) => {
const cx = x < 0 ? 0 : x >= n ? n - 1 : x
const cy = y < 0 ? 0 : y >= n ? n - 1 : y
return buffer[cy * n + cx]
}この平均を全セルに一度かける。結果を buffer に直接書くと、まだ平均していない隣を上書きして読み値が濁る。書き込み先の next を別に持って、全セルを計算し終えてから buffer に入れ替える。仕込んだ市松+縞に、この平均を 1 回だけ通すと、角張った絵より境目がひとまわり甘くなる。
1 回では角が取れきらない。ぼかした結果をもう一度ぼかすと、混ざる範囲が広がってさらに鈍る。平均を関数 blur にまとめて、passes の回数だけ繰り返す。0 にすると市松と縞がそのまま角張って出て、1、2、4 と上げるほど滑らかになる。
カーネルの重みを変えると別の処理になる。9 マスを平均すればぼかし、中心を強く周りを引けば差分(エッジ強調)。ぼかしと微分が同じ枠で書ける。
畳み込み。「カーネルを画像の上で滑らせ、重なったところの積の和を取る」操作。一様な 3×3 平均は重み 1/9 を 9 個並べたカーネルで、ガウシアンぼかしは中心が重く外が軽い釣鐘型のカーネルになる。微分(Sobel など)は片側が正、反対側が負のカーネル。ぼかしも輪郭抽出も同じ畳み込みの枠で、カーネルの中身が違うだけ。3×3 の二次元カーネルは、横方向の 1×3 と縦方向の 3×1 の二段に分けて適用できることがある(分離可能フィルタ)。9 回の掛け算が 3+3 回で済むので速い。