こちらの記事からの派生です。
補間フィルタ
ビットマップ画像のリサイズ/縮小拡大は、各ピクセルの仕切り直しと考える事もできます。(主流は点光源の波の組み合わせですが、ここでは分かりやすいグリッドの方で説明します。
| src | resample | dst | |
|---|---|---|---|
| 拡大 | ![]() |
![]() |
![]() |
| 縮小 | ![]() |
![]() |
![]() |
近傍の色を参考にこの白い隙間を埋める処理方式によって、リサイズ処理の結果が変わってきます。
色を埋める処理を補間フィルタ、その具体的なアルゴリズムを補間メソッドと呼びます。
レガシーな印象もありますが今でもよく使われる代表的な補間メソッドを紹介します。
補間カーネル
拡大でも縮小でも周囲のピクセルを適度な割合で混ぜて補間を行います。その混ぜ方は1次元の重み付け数列で表現できて、補間カーネルと呼びます。参考までに代表的なメソッドを並べます。
| Nearest-Neighbor | Bi-Linear |
|---|---|
![]() |
![]() |
| Bi-Cubic (b:1/3,c:1/3, mitchell) | Lanczos (lobe:4, lanczos4) |
![]() |
![]() |
形状から、Nearest を Point フィルタ、Bi-Linear を Triangle フィルタ、Tent フィルタ、又は 2D だと Cone フィルタと呼ぶ事もあります。ちなみに、ImageMagick のフィルタ名は、この point, triangle を採用しています。
これら以外にも沢山の補間メソッドが提唱されています。
Nearest-Neighbor
実用的な補間メソッドとして最も単純で最速です。隣からピクセルをコピーしてくるだけです。
| src | resample | enlarge | interpolate | dst |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
実用的には、画像ビューア(ブラウザ含む)のウィンドウ枠をマウスで掴みグリグリ画像のサイズを変える時だけ Nearest-Neighbor で素早く表示し、マウスが止まってサイズが変わくなった時に改めて重たいけど画質が良い補間メソッドで表示し直す。といった使われ方をよくされます。
あと、色が変わらないといった稀有な特徴があり、GIF や PNG8 等のパレット画像のリサイズでは、この補間方式が使われる事も多いです。
分かりやすい欠点として、Nearest-Neighbor は拡大するにつれ、ピクセルが四角いまま膨らみドット絵のような表示になります。ジャギーといった表現をよくします。
逆に縮小する時はピクセルをところどころ読み飛ばすので、運次第で見た目と全然違う色、いわゆる偽色に変わったり、特定パターンによる模様が現れたりします。
Bi-Linear はそれらの問題をある程度解決します。
Bi-Linear
空白ピクセルの上下左右に1つしか色のピクセルが存在しない場合はそれをコピーしますが、左右又は上下にある場合はその二つの平均 (a+b)/2、上下左右4つある場合はその4つを平均 (a+b+c+d)/4 、といった具合に色を混ぜます。
| src | resample | enlarge | interpolate | dst |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
Bi-Linear の欠点として、ジャギーが軽減される代わりに全体的にピンボケたように見えます。
また、テントの形をした補間フィルタは C1 連続でない為に、部分部分急激な色の変化をもたらし偽の輪郭が発生する事があります。
Bi-Cubic B,Cパラメータ
Bi-Linear に無かった C1 連続を導入して不自然さをある程度軽減します。隣の更に隣のピクセルまで参照します。
![]() |
|---|
| 転載元) https://en.wikipedia.org/wiki/Bicubic_interpolation |
三次式の計算で処理はそこそこ重たいですが、"質"の良い補間メソッドです。
2つの自由度があり、B (Basis?),C(Cardinal) パラメータを組み合わせる事で、色んなフィルタを作り出せます。Cubic Family という呼び方もされます。
代表的な Cubic フィルタを並べます。
| Hermite B:0, C:0 | General B:1, C:0 |
|---|---|
![]() |
![]() |
| Catmull-Rom B:0, C:1/2 | Mitchell B:1/3, C:1/3 |
![]() |
![]() |
なお、実験にて Mitchell がバランスが良いとされてます。
![]() |
|---|
| 転載元) http://www.imagemagick.org/Usage/filter/#mitchell - 論文 |
計算方法
まず B,C を元に3次方程式の係数を算出します。
function cubicBCcoefficient(b, c) {
var p = 2 - 1.5*b - c;
var q = -3 + 2*b + c;
var r = 0;
var s = 1 - (1/3)*b;
var t = -(1/6)*b - c;
var u = b + 5*c;
var v = -2*b - 8*c;
var w = (4/3)*b + 4*c;
return [p, q, r, s, t, u, v, w];
}
この係数を使って Cubic の数列を計算できます。
function cubicBC(x, coeff) {
var [p, q, r, s, t, u, v, w] = coeff;
var y = 0;
var ax = Math.abs(x);
if (ax < 1) {
//y = p*(ax*ax*ax) + q*(ax*ax) + r*(ax) + s;
y = ((p*ax + q)*ax + r)*ax + s;
} else if (ax < 2) {
//y = t*(ax*ax*ax) + u*(ax*ax) + v*(ax) + w;
y = ((t*ax + u)*ax + v)*ax + w;
}
return y;
}
Lanczos lobeパラメータ
信号処理で大変便利な Sinc 窓というものがあります。
![]() |
|---|
| (c) https://en.wikipedia.org/wiki/Sinc_filter |
これは永遠に横(x軸方向)に広がる関数なので、更に
を計算することで、波の広がりを一定区間に抑えたのが Lanczos 窓です。
以下のグラフは分母の n が 2,3,4 の Lanczos 窓です。
| lobe | グラフ |
|---|---|
| lobe:2 | ![]() |
| lobe:3 | ![]() |
| lobe:4 | ![]() |
n でどこ区間まで波を広げるのか調整できます。パラメータ名として Lobe がよく使われるのと、n:3 を lanczos3, n:4 を lanczos4 のように呼称する事もあります。
Sinc は LPF(低域通過フィルタ)の性質もある為、Lanczos は縮小リサイズに向いています。ただし、sin関数を多用するのと実用的にカーネル窓を広めにとるので大変重たいメソッドです。
Lobe 値を増やすほど良い結果を期待できますが、その分処理が重たくなります。よく使われるのは Lobe:3 か 4 です。
計算方法
sinc は sin を使います。
function sinc(x) {
var pi_x = Math.PI * x;
return Math.sin(pi_x) / pi_x;
}
sinc 関数は無限に広がるので、そのまま使うと補間カーネルが無限の大きさを持ってしまいます。lanczos はこれを一定幅で抑える工夫をします。この一定区間に収まる事をコンパクトサポートといった表現をします。
function lanczos(x, lobe) {
if (x === 0) {
return 0;
}
if (Math.abs(x) < lobe) {
return sinc(x) * sinc(x/lobe);
}
return 0;
}
sinc を2回呼ぶので結果的に sin を2回使います。sin はまともに計算すると大変重たい関数です。
Pixel Mixing
これは上記の補間メソッドとは一風変わっていて、グリッドを仕切り直した時に、ピクセルの面積比で足し混みます。
| src | 3x3=>2x2 |
|---|---|
![]() |
![]() |
左上のピクセルの混ぜ方
| src | A | B | C | D | (A4 + B2 + B2 + D1) / 9 | dst |
|---|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |


































Comments
BoxフィルタとNearest-Neighborを同じ物として紹介されていますが、両者は別物です。
この図は、Boxフィルタのグラフです。
順を追って説明します。
まず、本記事のような文脈で紹介されるグラフは、
-filter_half_width <= x <= +filter_half_width(実装によっては不等号の決め方が変わり、
<=ではなく<を選ぶこともあります)の範囲について図示されたものです。
ここで、
filter_half_widthは、そのフィルタ関数において、yが「重みとして意味のある値」の値域になるように、実装者が恣意的にxの定義域を決めるために与える定数です。(多くのケースでは、y != 0となる最も狭いxの範囲が選ばれる。)(※補足:この考え方は重要で、任意の定義域が与えられるということは、理論上は例えば
-2000 < x < +2000となるような極端なカーネル幅を持つフィルタ関数も考案可能です。)結果的に、
2 * filter_half_widthが「テクセル幅が1/sizeのときのカーネル幅」と同じ意味になります。今、「テクセル幅が1/sizeのとき」と書きました。つまり、画像を拡大または縮小する場合は、src画像のテクセル幅は
1/src.size、dst画像のテクセル幅は1/dst.sizeとなり、同じになりませんので、フィルタカーネルの幅もそれに合わせて伸長します。このアルゴリズムは画像処理の古典的大著・Graphics Gems III (1992) 収録の記事 [1.2] General Filtered Image Rescaling (Dale Schumacher) で論じられたのが有名です。
一般的なOSSを含め、現代に存在する画像処理の実装は、基本的にすべてこれの派生形か改良版を使っていると捉えて構わないと思います。(ソースコードレベルで似通っているケースが多いです。)
例として、300x300 の画像を 100x100 に縮小する場合を考えてみます。スケールは
0.3333...になります。このとき、もしも愚直にフィルタ関数を適用してしまうと、「スケールが1.0であるとみなしたときのカーネル」 すなわち 「300x300 の画像を 300x300 にズームするときのカーネル」 が得られ、それを無理やり 100x100 に詰め込んだ出力が得られることになります。当然これでは望ましい結果は得られません。上記のアルゴリズムでは、この問題を、フィルタカーネルを
fscaleで伸長することで解決しようと試みています。しかし、考え方の方向性は合っているのですが、この実装には理論的な瑕疵があります。フィルタ関数は単位区間についての定義域と値域の関係性を示したものに過ぎないため、任意のxの範囲について取得したyを合計しても、その合計値(≒ウェイトの合計値)はなにか特定の範囲に収まることが保証されません。結果的に、フィルタ関数の形状またはスケールに依存して合計値が変化することになり、求める結果は得られません。よって、どのズーム率でも同等の効果を得るためにはウェイトをノーマライズする必要があり、モダンな実装ではその辺りの対策が盛り込まれています。(実装の詳細は割愛させて頂きます。)
まとめると、
ということは念頭に置いておく必要があります。
前置きが大変長くなりましたが、今回の記事についての指摘は次のようになります。
以上で解説したように、フィルタカーネルの幅は、スケールに合わせて伸長します。つまり、Box filter の幅もスケールされますので、たとえば0.2倍のリサイズであれば、1/0.2 すなわち 5倍の幅についてサンプリングし、そのうえでノーマライズするのが正しい実装です。これは、「最も近傍のピクセルの値を取る」というNearest-Neighbor法とは明らかに異なります。
Box filter はグラフの見た目だけ見ると矩形でスパッと切れているのでNearestっぽいと感じる気持ちもわからなくもないのですが(私も初めての頃は勘違いしていました)、しかし実際は加重平均を得る処理になります。
一方の Nearest-Neighbor は、定義から「最も近いピクセルの値を採用する」というアルゴリズムなので、実際には座標値を四捨五入したピクセルの値を愚直に採用します(あるいは、GPUに組み込まれているハードウェアのnearestフィルタにテクスチャを丸投げするだけで済まします)。これを同じようなグラフで表すと Box filterのような形状にはならず、点 (0, 1) のようなイメージになります。(ここでは簡単のためそう説明しているだけで、実際には「点」をノーマライズすることは不可能なので独立した処理が必要です)
あ。。ImageMagick だと NN は Point フィルタと呼んで、Box はサポート窓の扱いが違うと区別していますね。
大変手間のかけた丁寧な指摘、ありがとうございます。早めに記事を修正します!
対応ありがとうございます。
差し出がましいようですが注意点を挙げさせて頂きますと、私が解説した理屈は 「カーネル幅の伸長処理」 と 「ノーマライズ処理」 が正しいことを前提としています。
仮に、 カーネルを伸長せずに Box filter (filter_half_width = 0.5) を検討すると、
floor(x - 0.5) <= i <= ceil(x + 0.5)(ただし 0 <= x < src.width)
は 「xの周囲 1~2px をサンプリングする」 という効果になり、当然それは 「xの周囲 0px をサンプリングする」Nearest-Neighbor と限りなく近い見た目 が出力されることになります。(推測ですがyoyaさんも過去にそういうウソの解説を読まれてしまったのではないでしょうか?)
具体例を挙げると、有名なイラストツールCLIP STUDIOもBicubicがNearest-Neighborみたいな見た目で掛かるので同種のミスをしていますし、他にもStackOverflowにあるGLSLのコードなど……。
ImageMagickは私が言うところの「正しい」系統の実装で、カーネル幅の伸長処理も行われていますし、ノーマライズも行われていますし、yoyaさんが仰るようにenum値の時点でPointとBoxが別々になっているので、特に問題ないと思われます。
https://github.com/ImageMagick/ImageMagick/blob/de5f368ee961855112d29ef8929f3df8433bc1e5/MagickCore/resample.c#L550-L621
取り急ぎ文言の方を修正しました!
グラフはまた後で見直します。
Let's comment your feelings that are more than good