图像二次线性插值--C实现

1、图像缩放二次线性插值
原理:详细参见https://en.wikipedia.org/wiki/Bilinear_interpolation
在数学上,二次线性插值是2个变量函数在方形网格上线性插值的一个扩展,其主要做法是先在一个方向上进行线性插值,然后在另一个方向上进行线性插值,对整体二次线性插值对两变量函数插值而言不是线性的。
如图:
图像二次线性插值--C实现_第1张图片
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)x2x1x2xf(Q11)+x2x1xx1f(Q21),x2x1x2xf(Q12)+x2x1xx1f(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)y2y1y2yf(x,y1)+y2y1yy1f(x,y2)=y2y1y2y(x2x1x2xf(Q11)+x2x1xx1f(Q21))+y2y1yy1(x2x1x2xf(Q12)+x2x1xx1f(Q22))=(x2x1)(y2y1)1(f(Q11)(x2x)(y2y)+f(Q21)(xx1)(y2y)+f(Q12)(x2x)(yy1)+f(Q22)(xx1)(yy1))=(x2x1)(y2y1)1[x2xxx1][f(Q11)f(Q21)f(Q12)f(Q22)][y2yyy1].f(x,y)y2y1y2yf(x,y1)+y2y1yy1f(x,y2)=y2y1y2y(x2x1x2xf(Q11)+x2x1xx1f(Q21))+y2y1yy1(x2x1x2xf(Q12)+x2x1xx1f(Q22))=(x2x1)(y2y1)1(f(Q11)(x2x)(y2y)+f(Q21)(xx1)(y2y)+f(Q12)(x2x)(yy1)+f(Q22)(xx1)(yy1))=(x2x1)(y2y1)1[x2xxx1][f(Q11)f(Q21)f(Q12)f(Q22)][y2yyy1].

在算法实现中,由于像素间的距离为单位长度,所以邻近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)(1x)(1y)+f(1,0)x(1y)+f(0,1)(1x)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)[1xx][f(0,0)f(1,0)f(0,1)f(1,1)][1yy].

直观的几何描述如下图:
图像二次线性插值--C实现_第2张图片
黑点处的值乘以正方行面积等于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
图像二次线性插值--C实现_第3张图片
图像二次线性插值--C实现_第4张图片
好了,二次线性插值就介绍到这,若有错误疏漏之处,欢迎指正,代码的算法主要来自于维基百科。

你可能感兴趣的:(图像处理)