やあみんな、元気にしてたかな。おじさんはまたも風邪をひいてしまい、先月の風邪で匂いや香りが全くわからなくなったままだというのに泣きっ面に蜂のようなことになってるよ。18℃以下の部屋は脳や心を破壊するという記事も見たので、みんなも部屋は暖かくして過ごすんだぞ。
さて今日はここ数カ月にわたって検討してきた、画像縮小アルゴリズムの、ひとまずな決定版が完成したのでここにメモっておくぞ。カーネル関数は、Lanczos3とかが良いらしいのだけど、よくわからないのでおじさんは今回、ChoppedGaussianというもの勝手にこしらえて採用してみた。先の記事で紹介した平均画素法よりもにじみや欠損が少なく、実装もとてもシンプルになっているのがわかるかと思う。
void image_shrink_chopped_gaussian(LPDWORD src, const int w0, const int h0, const float scale, LPDWORD dst) { int w = w0 * scale; int h = h0 * scale; for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { DWORD& out = dst[i * w + j]; Pix pix_out(0); float xs = j / scale; float xe = (j + 1) / scale; float ys = i / scale; float ye = (i + 1) / scale; // Lanczos3 というかガウシアン。 { float cx = (xs + xe)*.5; float cy = (ys + ye)*.5; float cx0 = floor(cx); float cy0 = floor(cy); float sum_w = 0; const int n_window = 3; for (int ii = -n_window; ii < n_window + 1; ++ii) { for (int jj = -n_window; jj < n_window + 1; ++jj) { int tx = cx0 + jj; int ty = cy0 + ii; float tx2 = tx + .5; float ty2 = ty + .5; if (tx >= 0 && ty >= 0 && tx < w0 && ty < h0) { float rr = (cx - tx2)*(cx - tx2) + (cy - ty2)*(cy - ty2); float ratio = (rr>4) ? 0 : exp(-rr);// これがChoppedGaussian(r); pix_out += Pix(src[ty*w0 + tx])*ratio; sum_w += ratio; } }// jj }// ii pix_out /= sum_w; } // out = pix_out.GetValue(); }// j }//i return; }
Pixクラスについては一つ前の日記を見てくれ。
やっぱり平均画素法はねえ、一見きれいに縮小できたかにみえるんだけど、よくみると色の「にじみ」がひどかったりだね、なにか欠損みたいなものが出ちゃうんだよね。処理も重いので、平均画素法は、どうやらそういう考え方もありますってだけのもので、それを実装したりする意味はずばりゼロなのだね。はい、「平均画素法は重いし、品質もいまいちなのでだめ」これが一つの結論ですね。
じゃ、ま、最後におじさんがどのレベルの違いについて語っているのかを明らかにする画像サンプルを紹介しよう。左と中央とが今回のChoppedGaussianで、右が平均画素法だよ。これは、上記二つのアルゴリズムを使って、もと画像を70%に縮小させた結果を、それぞれ並べて見やすいように引き延ばした結果なわけだけど、全然違いがわからないという人もいれば、全然違うねという人もいるかと思う。
左と中央の違いについて、中央は上記ソースそのままの結果、左はカーネル関数を、rr>2.56でChopOffし、exp(-rr*3)としたもの、つまり、ぼやけるのを嫌がってカーネル関数をより細くしてみた結果だ。しかし、結果的にみると中央のやつが一番きれいだね。左はぼやけ感軽減にはなるけれども、やっぱりその分欠損も出てしまっている気がする。まあ左と中央とは、使い分けなのかな。用途別で。
じゃ今日はこんなところだ。チャオ!