1、图像缩放二次线性插值
原理:详细参见https://en.wikipedia.org/wiki/Bilinear_interpolation
在数学上,二次线性插值是2个变量函数在方形网格上线性插值的一个扩展,其主要做法是先在一个方向上进行线性插值,然后在另一个方向上进行线性插值,对整体二次线性插值对两变量函数插值而言不是线性的。
如图:
P点为要插值的点,邻近4个点Q11 = (x1, y1), Q12 = (x1, y2), Q21 = (x2, y1), and Q22 = (x2, y2)像素值知道,可以先由Q12、Q22插值得到R2,Q11、Q21插值得到R2,再由R2、R1插值得到P;
x方向插值,公式如下:
f ( x , y 1 ) ≈ x 2 − x x 2 − x 1 f ( Q 11 ) + x − x 1 x 2 − x 1 f ( Q 21 ) , f ( x , y 2 ) ≈ x 2 − x x 2 − x 1 f ( Q 12 ) + x − x 1 x 2 − x 1 f ( Q 22 ) . {\displaystyle {\begin{aligned}f(x,y_{1})&\approx {\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{11})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{21}),\\f(x,y_{2})&\approx {\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{12})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{22}).\end{aligned}}} f(x,y1)f(x,y2)≈x2−x1x2−xf(Q11)+x2−x1x−x1f(Q21),≈x2−x1x2−xf(Q12)+x2−x1x−x1f(Q22).
然后y方向插值,将x方向公式代入,公式如下:
f ( x , y ) ≈ y 2 − y y 2 − y 1 f ( x , y 1 ) + y − y 1 y 2 − y 1 f ( x , y 2 ) = y 2 − y y 2 − y 1 ( x 2 − x x 2 − x 1 f ( Q 11 ) + x − x 1 x 2 − x 1 f ( Q 21 ) ) + y − y 1 y 2 − y 1 ( x 2 − x x 2 − x 1 f ( Q 12 ) + x − x 1 x 2 − x 1 f ( Q 22 ) ) = 1 ( x 2 − x 1 ) ( y 2 − y 1 ) ( f ( Q 11 ) ( x 2 − x ) ( y 2 − y ) + f ( Q 21 ) ( x − x 1 ) ( y 2 − y ) + f ( Q 12 ) ( x 2 − x ) ( y − y 1 ) + f ( Q 22 ) ( x − x 1 ) ( y − y 1 ) ) = 1 ( x 2 − x 1 ) ( y 2 − y 1 ) [ x 2 − x x − x 1 ] [ f ( Q 11 ) f ( Q 12 ) f ( Q 21 ) f ( Q 22 ) ] [ y 2 − y y − y 1 ] . f ( x , y ) ≈ y 2 − y y 2 − y 1 f ( x , y 1 ) + y − y 1 y 2 − y 1 f ( x , y 2 ) = y 2 − y y 2 − y 1 ( x 2 − x x 2 − x 1 f ( Q 11 ) + x − x 1 x 2 − x 1 f ( Q 21 ) ) + y − y 1 y 2 − y 1 ( x 2 − x x 2 − x 1 f ( Q 12 ) + x − x 1 x 2 − x 1 f ( Q 22 ) ) = 1 ( x 2 − x 1 ) ( y 2 − y 1 ) ( f ( Q 11 ) ( x 2 − x ) ( y 2 − y ) + f ( Q 21 ) ( x − x 1 ) ( y 2 − y ) + f ( Q 12 ) ( x 2 − x ) ( y − y 1 ) + f ( Q 22 ) ( x − x 1 ) ( y − y 1 ) ) = 1 ( x 2 − x 1 ) ( y 2 − y 1 ) [ x 2 − x x − x 1 ] [ f ( Q 11 ) f ( Q 12 ) f ( Q 21 ) f ( Q 22 ) ] [ y 2 − y y − y 1 ] . {\displaystyle {\begin{aligned}f(x,y)&\approx {\frac {y_{2}-y}{y_{2}-y_{1}}}f(x,y_{1})+{\frac {y-y_{1}}{y_{2}-y_{1}}}f(x,y_{2})\\&={\frac {y_{2}-y}{y_{2}-y_{1}}}\left({\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{11})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{21})\right)+{\frac {y-y_{1}}{y_{2}-y_{1}}}\left({\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{12})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{22})\right)\\&={\frac {1}{(x_{2}-x_{1})(y_{2}-y_{1})}}{\big (}f(Q_{11})(x_{2}-x)(y_{2}-y)+f(Q_{21})(x-x_{1})(y_{2}-y)+f(Q_{12})(x_{2}-x)(y-y_{1})+f(Q_{22})(x-x_{1})(y-y_{1}){\big )}\\&={\frac {1}{(x_{2}-x_{1})(y_{2}-y_{1})}}{\begin{bmatrix}x_{2}-x&x-x_{1}\end{bmatrix}}{\begin{bmatrix}f(Q_{11})&f(Q_{12})\\f(Q_{21})&f(Q_{22})\end{bmatrix}}{\begin{bmatrix}y_{2}-y\\y-y_{1}\end{bmatrix}}.\end{aligned}}} {\displaystyle {\begin{aligned}f(x,y)&\approx {\frac {y_{2}-y}{y_{2}-y_{1}}}f(x,y_{1})+{\frac {y-y_{1}}{y_{2}-y_{1}}}f(x,y_{2})\\&={\frac {y_{2}-y}{y_{2}-y_{1}}}\left({\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{11})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{21})\right)+{\frac {y-y_{1}}{y_{2}-y_{1}}}\left({\frac {x_{2}-x}{x_{2}-x_{1}}}f(Q_{12})+{\frac {x-x_{1}}{x_{2}-x_{1}}}f(Q_{22})\right)\\&={\frac {1}{(x_{2}-x_{1})(y_{2}-y_{1})}}{\big (}f(Q_{11})(x_{2}-x)(y_{2}-y)+f(Q_{21})(x-x_{1})(y_{2}-y)+f(Q_{12})(x_{2}-x)(y-y_{1})+f(Q_{22})(x-x_{1})(y-y_{1}){\big )}\\&={\frac {1}{(x_{2}-x_{1})(y_{2}-y_{1})}}{\begin{bmatrix}x_{2}-x&x-x_{1}\end{bmatrix}}{\begin{bmatrix}f(Q_{11})&f(Q_{12})\\f(Q_{21})&f(Q_{22})\end{bmatrix}}{\begin{bmatrix}y_{2}-y\\y-y_{1}\end{bmatrix}}.\end{aligned}}} f(x,y)≈y2−y1y2−yf(x,y1)+y2−y1y−y1f(x,y2)=y2−y1y2−y(x2−x1x2−xf(Q11)+x2−x1x−x1f(Q21))+y2−y1y−y1(x2−x1x2−xf(Q12)+x2−x1x−x1f(Q22))=(x2−x1)(y2−y1)1(f(Q11)(x2−x)(y2−y)+f(Q21)(x−x1)(y2−y)+f(Q12)(x2−x)(y−y1)+f(Q22)(x−x1)(y−y1))=(x2−x1)(y2−y1)1[x2−xx−x1][f(Q11)f(Q21)f(Q12)f(Q22)][y2−yy−y1].f(x,y)≈y2−y1y2−yf(x,y1)+y2−y1y−y1f(x,y2)=y2−y1y2−y(x2−x1x2−xf(Q11)+x2−x1x−x1f(Q21))+y2−y1y−y1(x2−x1x2−xf(Q12)+x2−x1x−x1f(Q22))=(x2−x1)(y2−y1)1(f(Q11)(x2−x)(y2−y)+f(Q21)(x−x1)(y2−y)+f(Q12)(x2−x)(y−y1)+f(Q22)(x−x1)(y−y1))=(x2−x1)(y2−y1)1[x2−xx−x1][f(Q11)f(Q21)f(Q12)f(Q22)][y2−yy−y1].
在算法实现中,由于像素间的距离为单位长度,所以邻近4个像素的关系采用(0,0),(0,1),(1,0),(1,1)表示更方便,插值点p坐标(x,y)在[0,1],因此上面公式变为:
f ( x , y ) ≈ f ( 0 , 0 ) ( 1 − x ) ( 1 − y ) + f ( 1 , 0 ) x ( 1 − y ) + f ( 0 , 1 ) ( 1 − x ) y + f ( 1 , 1 ) x y , {\displaystyle f(x,y)\approx f(0,0)(1-x)(1-y)+f(1,0)x(1-y)+f(0,1)(1-x)y+f(1,1)xy,} f(x,y)≈f(0,0)(1−x)(1−y)+f(1,0)x(1−y)+f(0,1)(1−x)y+f(1,1)xy,
写成矩阵形式:
f ( x , y ) ≈ [ 1 − x x ] [ f ( 0 , 0 ) f ( 0 , 1 ) f ( 1 , 0 ) f ( 1 , 1 ) ] [ 1 − y y ] . f(x,y)\approx {\begin{bmatrix}1-x&x\end{bmatrix}}{\begin{bmatrix}f(0,0)&f(0,1)\\f(1,0)&f(1,1)\end{bmatrix}}{\begin{bmatrix}1-y\\y\end{bmatrix}}. f(x,y)≈[1−xx][f(0,0)f(1,0)f(0,1)f(1,1)][1−yy].
直观的几何描述如下图:
黑点处的值乘以正方行面积等于4个彩色点乘以对应颜色矩形面积的和,正方形面积为单位面积。
C代码实现:
基础的数据结构见上一篇博文,https://blog.csdn.net/MikeDai/article/details/82975582中的基础数据结构。
void img_resize_using_bilinear(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 x, y;
float u, v;
float a, b, c, d;
int val;
int col, row;
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)(src_cols-1) / (float)dst_cols;
yratio = (float)(src_rows-1) / (float)dst_rows;
for (i = 0; i < dst_rows;i++)
{
for (j = 0; j < dst_cols; j++)
{
x = xratio*j;
y = yratio*i;
row = (int)y;
col = (int)x;
u = x - col;
v = y - row;
a = src_arr[row][col];
b = src_arr[row][col + 1];
c = src_arr[row + 1][col];
d = src_arr[row + 1][col + 1];
assert(row + 1 < src_rows && col + 1 < src_cols);
val = (int)(a*(1 - u)*(1 - v) + b*(1 - v)*u + c*(1 - u)*v + d*u*v);
if (val < 0)val = 0;
if (val > 255)val = 255;
dst_arr[i][j] = val;
}
}
}
测试代码:
先封装jpeg、png、bmp图像读取、保存库。
#ifndef __IMG_H
#define __IMG_H
#if defined (IMG_HAS_DLL)
# if defined (IMG_BUILD_DLL)
# define IMG_API __declspec (dllexport)
# else
# define IMG_API __declspec (dllimport)
# endif
#else
# define IMG_API
#endif /* IMG_HAS_DLL */
#include
typedef enum image_type_t
{
EJPEG = 0,
EPNG,
EBMP,
EUNKNOWN,
EEMPTY,
ENOFILE
}dai_image_type;
typedef enum image_color_space_t
{
ECOLOR_SPACE_UNKNOWN, /* error/unspecified */
ECOLOR_SPACE_GRAYSCALE, /* monochrome */
ECOLOR_SPACE_RGB, /* red/green/blue */
ECOLOR_SPACE_YCbCr, /* Y/Cb/Cr (also known as YUV) */
ECOLOR_SPACE_CMYK, /* C/M/Y/K */
ECOLOR_SPACE_YCCK /* Y/Cb/Cr/K */
}dai_image_color_space;
typedef struct dai_image_t
{
int width;
int height;
int nb_component;
int type;
int quality;
dai_image_color_space color_space;
uint8_t* data;
uint32_t compress_data_size;
uint8_t* compress_data;
}dai_image;
IMG_API int dai_save_image(char* name, dai_image* img);
IMG_API dai_image* dai_load_image(char* file_name);
IMG_API dai_image* dai_load_image_data(uint8_t* buf, uint32_t size);
IMG_API int dai_save_image_data(dai_image* img);
IMG_API dai_image* dai_create_image();
IMG_API void dai_destroy_image(dai_image** _img);
#endif
分拆RGB24为3个通道的图像:
void split_img_data(dai_image* img, uint8_2d** _r, uint8_2d** _g, uint8_2d** _b)
{
uint8_2d* r = NULL;
uint8_2d* g = NULL;
uint8_2d* b = NULL;
if (!img)return;
int rows, cols;
int i, j;
uint8_t** rarr;
uint8_t** garr;
uint8_t** barr;
rows = img->height;
cols = img->width;
r = create_uint8_2d(rows, cols);
g = create_uint8_2d(rows, cols);
b = create_uint8_2d(rows, cols);
rarr = r->arr;
garr = g->arr;
barr = b->arr;
uint32_t row_bytes = (cols*img->nb_component + 3)&~3;
uint32_t off = 0;
uint8_t* data = NULL;
for (i = 0; i < rows; i++)
{
data = img->data + off;
for (j = 0; j < cols; j++)
{
barr[i][j] = *data++;
garr[i][j] = *data++;
rarr[i][j] = *data++;
}
off += row_bytes;
}
*_r = r;
*_g = g;
*_b = b;
}
合并R、G、B通道为RGB24图像:
void merge_img_data(uint8_2d* r, uint8_2d* g, uint8_2d* b, dai_image** _img)
{
dai_image* img = NULL;
img = dai_create_image();
if (!img || !r || !g || !b)return;
if (r->cols != g->cols || r->rows != g->rows)return;
if (g->cols != b->cols || g->rows != b->rows)return;
int rows, cols;
int i, j;
uint8_t** rarr;
uint8_t** garr;
uint8_t** barr;
rarr = r->arr;
garr = g->arr;
barr = b->arr;
rows = r->rows;
cols = r->cols;
uint32_t row_bytes = (cols * 3 + 3)&~3;
uint8_t* data = (uint8_t*)calloc(row_bytes*rows, sizeof(uint8_t));
uint8_t* ptr;
uint32_t off = 0;
for (i = 0; i < rows; i++)
{
ptr = data + off;
for (j = 0; j < cols; j++)
{
*ptr++ = barr[i][j];
*ptr++ = garr[i][j];
*ptr++ = rarr[i][j];
}
off += row_bytes;
}
img->width = cols;
img->height = rows;
img->data = data;
img->nb_component = 3;
*_img = img;
}
测试二次线性插值算法:
void test_bilinear_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_bilinear(r, r1);
img_resize_using_bilinear(g, g1);
img_resize_using_bilinear(b, b1);
merge_img_data(r1, g1, b1, &img1);
if (img1)
{
img1->type = EJPEG;
dai_save_image("resize_blinear.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);
}
最后,上图,分别为原始图和放大图,放大为3.0
好了,二次线性插值就介绍到这,若有错误疏漏之处,欢迎指正,代码的算法主要来自于维基百科。