現在開発中の画像エフェクト系のソフトウェアで画像の拡大機能を今までOpenCVを使ってとりあえずで済ませていたのですが、画像の型が限定されてしまうのが嫌だったので、いい加減自分で作ろうと思い、Bilinearで画像を拡大するテンプレート関数を書いてみましたのでソースコードを載せます。
アルゴリズムは単純なのですぐに書けると思いきや、拡大した結果がだいたいPhotoshopと同じになるものはすぐにできたものの、微妙に位置やスケールが違ったので、それが同じになるまではかなり苦労しました。拡大前の(0,0)座標が拡大後も(0,0)になるように考えていたのが誤りで、紙に書いて整理していてようやくそうでないことに気づきました。先入観とは恐ろしい。こんな単純なプログラムに一日もかかってしまうとは。まぁ、なんとか完成してよかったです。
ただ、頭がこんがらがったときにわかりやすくするために、無駄なことをやっているので、高速にするためにはもっと整理しないといけませんが、とにかく自分で書いたものを使っているというのが後々の最適化において重要なことになってくるはず。とりあえず、速度的に不満が出るまではこのコードを使っていこうと思います。
アルゴリズムは単純なのですぐに書けると思いきや、拡大した結果がだいたいPhotoshopと同じになるものはすぐにできたものの、微妙に位置やスケールが違ったので、それが同じになるまではかなり苦労しました。拡大前の(0,0)座標が拡大後も(0,0)になるように考えていたのが誤りで、紙に書いて整理していてようやくそうでないことに気づきました。先入観とは恐ろしい。こんな単純なプログラムに一日もかかってしまうとは。まぁ、なんとか完成してよかったです。
ただ、頭がこんがらがったときにわかりやすくするために、無駄なことをやっているので、高速にするためにはもっと整理しないといけませんが、とにかく自分で書いたものを使っているというのが後々の最適化において重要なことになってくるはず。とりあえず、速度的に不満が出るまではこのコードを使っていこうと思います。
template<typename T> void wsp::img::ScaleImageBilinear(
const T *in_src, int src_width, int src_height, int num_channels,
T *o_dst, int dst_width, int dst_height
){
float x_src_step = 1.0/(float)(src_width);
float y_src_step = 1.0/(float)(src_height);
float x_dst_step = 1.0/(float)(dst_width);
float y_dst_step = 1.0/(float)(dst_height);
float zero_xpos_src = x_src_step/2.0;
float zero_xpos_dst = x_dst_step/2.0;
float zero_ypos_src = y_src_step/2.0;
float zero_ypos_dst = y_dst_step/2.0;
{
int x, y, c;
int i_s0, i_s1, i_s2, i_s3, i_d;
int yd, xd, ys, xs;
float x_coef0, x_coef1, y_coef0, y_coef1;
float x_src_f, y_src_f, x_dst_f, y_dst_f;
y_src_f=zero_ypos_src;
y_dst_f=zero_ypos_dst;
for(yd=0, ys=0; yd<dst_height; ++yd, y_dst_f+=y_dst_step){
if(y_src_f+y_src_step <= y_dst_f){
y_src_f+=y_src_step;
++ys;
}
y_coef1 = (y_dst_f-y_src_f)/y_src_step;
y_coef0 = 1.0f - y_coef1;
x_dst_f=zero_xpos_dst;
x_src_f=zero_xpos_src;
for(xd=0, xs=0; xd<dst_width; ++xd, x_dst_f+=x_dst_step){
if(x_src_f+x_src_step <= x_dst_f){
x_src_f+=x_src_step;
++xs;
}
if( zero_xpos_src<=x_dst_f && x_dst_f<1.0f-zero_xpos_src
&& zero_ypos_src<=y_dst_f && y_dst_f<1.0f-zero_ypos_src)
{
x_coef1 = (x_dst_f-x_src_f)/x_src_step;
x_coef0 = 1.0f - x_coef1;
i_d = (yd*dst_width+xd)*num_channels;
i_s0 = ((ys)*src_width+(xs))*num_channels;
i_s1 = i_s0 + num_channels;
i_s2 = i_s0 + src_width*num_channels;
i_s3 = i_s1 + src_width*num_channels;
for(c=0; c<num_channels; ++c){
//** compute linear interpolation
o_dst[i_d+c] = static_cast<T>(
(in_src[i_s0+c]*x_coef0 + in_src[i_s1+c]*x_coef1)*y_coef0
+ (in_src[i_s2+c]*x_coef0 + in_src[i_s3+c]*x_coef1)*y_coef1
);
}
}
}
}
}
//** copy corner
{
int x, y, c, i_dst0, i_src0, i_dst1, i_src1;
int x_copy_top, x_copy_tail, y_copy_top, y_copy_tail;
float y_dst_f, x_dst_f;
//** compute position of source of copy
for(y_copy_top=0, y_dst_f=zero_ypos_dst; y_dst_f<zero_ypos_src; ++y_copy_top, y_dst_f+=y_dst_step){}
for(x_copy_top=0, x_dst_f=zero_xpos_dst; x_dst_f<zero_xpos_src; ++x_copy_top, x_dst_f+=x_dst_step){}
x_copy_tail = dst_width - 1 - x_copy_top;
y_copy_tail = dst_height - 1 - y_copy_top;
printf("ytop:%d, xtop:%d, ytail:%d, xtail:%d\n", y_copy_top, x_copy_top, y_copy_tail, x_copy_tail);
//** copy horizontal lines in corner
y_dst_f = zero_ypos_dst;
for(y=0; y_dst_f<zero_ypos_src; ++y, y_dst_f+=y_dst_step){
for(x=0; x<dst_width; ++x){
i_dst0 = (y*dst_width+x)*num_channels;
i_src0 = (y_copy_top*dst_width+x)*num_channels;
i_dst1 = ((dst_height-y-1)*dst_width+x)*num_channels;
i_src1 = (y_copy_tail*dst_width+x)*num_channels;
for(c=0; c<num_channels; ++c){
//** copy top x corner
o_dst[i_dst0+c] = o_dst[i_src0+c];
//** copy bottom x corner
o_dst[i_dst1+c] = o_dst[i_src1+c];
}
}
}
//** copy vertical lines in corner
x_dst_f = zero_xpos_dst;
for(x=0; x_dst_f<zero_xpos_src; ++x, x_dst_f+=x_dst_step){
for(y=0; y<dst_height; ++y){
i_dst0 = (y*dst_width+x)*num_channels;
i_src0 = (y*dst_width+x_copy_top)*num_channels;
i_dst1 = ((y+1)*dst_width-x-1)*num_channels;
i_src1 = (y*dst_width+x_copy_tail)*num_channels;
for(c=0; c<num_channels; ++c){
//** copy left y corner
o_dst[i_dst0+c] = o_dst[i_src0+c];
//** copy right y corner
o_dst[i_dst1+c] = o_dst[i_src1+c];
}
}
}
}
}