Lanczos 滤波器,因发明者而得名,在信号处理领域,主要用在增加采样率和低通滤波,在图像处理领域用于图像缩放、旋转,其可以在减小锯齿、锐度、振铃效应( aliasing, sharpness, and minimal ringing)取得最好的平衡。
滤波函数如下:
L ( x ) = { sinc ( x )   sinc ( x / a ) if − a < x < a , 0 otherwise . {\displaystyle L(x)={\begin{cases}\operatorname {sinc} (x)\,\operatorname {sinc} (x/a)&{\text{if}}\ -a<x<a,\\0&{\text{otherwise}}.\end{cases}}} L(x)={sinc(x)sinc(x/a)0if −a<x<a,otherwise.
等价于:
L ( x ) = { 1 if x = 0 , a sin ( π x ) sin ( π x / a ) π 2 x 2 if − a ≤ x < a and x ≠ 0 , 0 otherwise . {\displaystyle L(x)={\begin{cases}1&{\text{if}}\ x=0,\\{\dfrac {a\sin(\pi x)\sin(\pi x/a)}{\pi ^{2}x^{2}}}&{\text{if}}\ -a\leq x<a\ {\text{and}}\ x\neq 0,\\0&{\text{otherwise}}.\end{cases}}} L(x)=⎩⎪⎪⎨⎪⎪⎧1π2x2asin(πx)sin(πx/a)0if x=0,if −a≤x<a and x̸=0,otherwise.
当a取3时,就是Lanczos3滤波器。
插值公式:
S ( x ) = ∑ i = ⌊ x ⌋ − a + 1 ⌊ x ⌋ + a s i L ( x − i ) S(x) = \sum_{i=\lfloor x \rfloor - a + 1}^{\lfloor x \rfloor + a} s_{i} L(x - i) S(x)=i=⌊x⌋−a+1∑⌊x⌋+asiL(x−i)
a是滤波尺寸, ⌊ x ⌋ \lfloor x \rfloor ⌊x⌋是向下取整。
性质:
1、a>0,Lanczos 核函数对每个点都是连续,并且其倒数也是连续,因此 S ( x ) S(x) S(x)连续,其倒数连续;
2、Lanczos 核函数在整数点值为零,除了 x = 0,核函数为1,相当于原始x处信号值不变,仅仅对小数位置进行插值;
二维的情形:
L ( x , y ) = L ( x ) L ( y ) {\displaystyle L(x,y)=L(x)L(y)} L(x,y)=L(x)L(y)
算法实现:
代码实现参考https://github.com/richgel999/imageresampler
#define M_PI 3.14159265358979323846
//sinc函数值
static double sinc(double x)
{
x = (x * M_PI);
if ((x < 0.01f) && (x > -0.01f))
return 1.0f + x*x*(-1.0f / 6.0f + x*x*1.0f / 120.0f);
return sin(x) / x;
}
对于0附近的sinc函数值,采用其泰勒展开式计算:
sinc ( x ) = sin ( x ) x = ∑ n = 0 ∞ ( − x 2 ) n ( 2 n + 1 ) ! \operatorname {sinc} (x)={\frac {\sin(x)}{x}}=\sum _{n=0}^{\infty }{\frac {\left(-x^{2}\right)^{n}}{(2n+1)!}} sinc(x)=xsin(x)=n=0∑∞(2n+1)!(−x2)n
//计算Lanczos3核函数
static float clip(double t)
{
const float eps = .0000125f;
if (fabs(t) < eps)
return 0.0f;
return (float)t;
}
static inline float lancos(float t)
{
if (t < 0.0f)
t = -t;
if (t < 3.0f)
return clip(sinc(t) * sinc(t / 3.0f));
else
return (0.0f);
}
//x方向的插值:
static inline float lancos3_resample_x(uint8_t** arr,int src_w, int src_h, int y, int x,float xscale)
{
float s = 0;
float coef_sum = 0.0f;
float coef;
float pix;
int i;
int l, r;
float c;
float hw;
//对于缩小的情况hw相当于扩大了领域像素个数,这里如果不作此处理,最终缩小图片效果跟最近领域插值法没多少区别,其效果相当于先低通滤波,再插值
if (xscale > 1.0f)
hw = 3.0f;
else
hw = 3.0f / xscale;
c = (float)x / xscale;
l = (int)floor(c - hw);
r = (int)ceil(c + hw);
if (y < 0)y = 0;
if (y >= src_h)y = src_h - 1;
if (xscale > 1.0f)xscale = 1.0f;
for (i = l; i <= r; i++)
{
x = i;
if (i < 0)x = 0;
if (i >= src_w)x = src_w - 1;
pix = arr[y][x];
coef = lancos((c-i)*xscale);
s += pix * coef;
coef_sum += coef;
}
s /= coef_sum;
return s;
}
void img_resize_using_lancos3(uint8_2d* src, uint8_2d* dst)
{
int src_rows, src_cols;
int dst_rows, dst_cols;
int i, j;
if (!src || !dst)return;
uint8_t** src_arr;
uint8_t** dst_arr;
float xratio;
float yratio;
float pix;
int val;
int k;
float hw;
src_arr = src->arr;
dst_arr = dst->arr;
src_rows = src->rows;
src_cols = src->cols;
dst_rows = dst->rows;
dst_cols = dst->cols;
xratio = (float)(dst_cols) / (float)src_cols;
yratio = (float)(dst_rows) / (float)src_rows;
float scale = 0.0f;
if (yratio > 1.0f)
{
hw = 3.0;
scale = 1.0f;
}
else
{
hw = 3.0 / yratio;
scale = yratio;
}
for (i = 0; i < dst_rows; i++)
{
for (j = 0; j < dst_cols; j++)
{
int t, b;
float c;
float s = 0;
float coef_sum = 0.0f;
float coef;
float pix;
c = (float)i / yratio;
t = (int)floor(c - hw);
b = (int)ceil(c + hw);
//先对x方向插值,再进行y方向插值。
for (k = t; k <= b; k++)
{
pix = lancos3_resample_x(src_arr, src_cols,src_rows, k, j,xratio);
coef = lancos((c - k)*scale);
coef_sum += coef;
pix *= coef;
s += pix;
}
val = (int)s / coef_sum;
if (val < 0)val = 0;
if (val > 255)val = 255;
dst_arr[i][j] = val;
}
}
}
测试代码:
void test_lancos3_resize(dai_image* img, float factor)
{
uint8_2d* r = NULL;
uint8_2d* g = NULL;
uint8_2d* b = NULL;
uint8_2d* r1 = NULL;
uint8_2d* g1 = NULL;
uint8_2d* b1 = NULL;
dai_image* img1 = NULL;
if (!img)return;
split_img_data(img, &r, &g, &b);
int w, h;
int w1, h1;
w = img->width;
h = img->height;
w1 = factor*w;
h1 = factor*h;
r1 = create_uint8_2d(h1, w1);
g1 = create_uint8_2d(h1, w1);
b1 = create_uint8_2d(h1, w1);
img_resize_using_lancos3(r, r1);
img_resize_using_lancos3(g, g1);
img_resize_using_lancos3(b, b1);
merge_img_data(r1, g1, b1, &img1);
if (img1)
{
img1->type = EJPEG;
dai_save_image("resize_lancos3.jpg", img1);
dai_destroy_image(&img1);
}
destroy_uint8_2d(&r);
destroy_uint8_2d(&g);
destroy_uint8_2d(&b);
destroy_uint8_2d(&r1);
destroy_uint8_2d(&g1);
destroy_uint8_2d(&b1);
}
另外,缩小之后,图像相对变暗,这时,可以采用gama校正改变亮度。
参考文献:
1、http://www.realitypixels.com/turk/computergraphics/ResamplingFilters.pdf
2、https://en.wikipedia.org/wiki/Lanczos_resampling
3、https://github.com/richgel999/imageresampler