图像插值算法及其实现

article/2025/9/30 7:58:44

sensor、codec、display device都是基于pixel的,高分辨率图像能呈现更多的detail,由于sensor制造和chip的限制,我们需要用到图像插值(scaler/resize)技术,这种方法代价小,使用方便。同时,该技术还可以放大用户希望看到的感兴趣区域。图像缩放算法往往基于插值实现,常见的图像插值算法包括最近邻插值(Nearest-neighbor)、双线性插值(Bilinear)、双立方插值(bicubic)、lanczos插值、方向插值(Edge-directed interpolation)、example-based插值、深度学习等算法。
插值缩放的原理是基于目标分辨率中的点,将其按照缩放关系对应到源图像中,寻找源图像中的点(不一定是整像素点),然后通过源图像中的相关点插值得到目标点。本篇文章,我们介绍Nearest-neighbor和Bilinear插值的原理及C实现。
插值算法原理如下:
这里写图片描述
1. Nearest-neighbor
最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输出。如下图所示,P为目标图像对应到源图像中的点,Q11、Q12、Q21、Q22是P点周围4个整数点,Q12与P离的最近,因此P点的值等于Q12的值。这里写图片描述
由于图像中像素具有邻域相关性,因此,用这种拷贝的方法会产生明显的锯齿。
2. Bilinear
双线性插值使用周围4个点插值得到输出,双线性插值,是指在xy方法上,都是基于线性距离来插值的。
如图1,目标图像中的一点对应到源图像中点P(x,y),我们先在x方向插值:
这里写图片描述
然后,进行y方向插值:
这里写图片描述
可以验证,先进行y方向插值再进行x方向插值,结果也是一样的。值得一提的是,双线性插值在单个方向上是线性的,但对整幅图像来说是非线性的。

3. C实现
使用VS2010,工程包含三个文件,如下:
这里写图片描述

main.cpp

#include <string.h>
#include <iostream>
#include "resize.h"int main()
{const char *input_file = "D:\\simuTest\\teststream\\00_YUV_data\\01_DIT_title\\data.yuv";		//absolute pathconst char *output_file = "D:\\simuTest\\teststream\\00_YUV_data\\01_DIT_title\\data_out2.yuv";	//absolute path	int src_width = 720;int src_height = 480;int dst_width = 1920;int dst_height = 1080;int resize_type = 1;		//0:nearest, 1:bilinearresize(input_file, src_width, src_height, output_file, dst_width, dst_height, resize_type);return 0;
}

resize.cpp

#include "resize.h"int clip3(int data, int min, int max)
{return (data > max) ? max : ((data < min) ? min : data);if(data > max)return max;else if(data > min)return data;elsereturn min;
}//bilinear takes 4 pixels (2×2) into account
/*
* 函数名:	bilinearHorScaler
* 说明:	水平方向双线性插值
* 参数:
*/
void bilinearHorScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{double resizeX = (double)dst_width / src_width;for(int ver = 0; ver < dst_height; ++ver){for(int hor = 0; hor < dst_width; ++hor){double srcCoorX = hor / resizeX;double weight1 = srcCoorX - (double)((int)srcCoorX);double weight2 = (double)((int)(srcCoorX + 1)) - srcCoorX;double dstValue = *(src_image + src_width * ver + clip3((int)srcCoorX, 0, src_width - 1)) * weight2 + *(src_image + src_width * ver + clip3((int)(srcCoorX + 1), 0, src_width - 1)) * weight1;*(dst_image + dst_width * ver + hor) = clip3((uint8)dstValue, 0, 255);}}
}/*
* 函数名:	bilinearVerScaler
* 说明:	垂直方向双线性插值
* 参数:
*/
void bilinearVerScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{double resizeY = (double)dst_height / src_height;for(int ver = 0; ver < dst_height; ++ver){for(int hor = 0; hor < dst_width; ++hor){double srcCoorY = ver / resizeY;double weight1 = srcCoorY - (double)((int)srcCoorY);double weight2 = (double)((int)(srcCoorY + 1)) - srcCoorY;double dstValue = *(src_image + src_width * clip3((int)srcCoorY, 0, src_height - 1) + hor) * weight2 + *(src_image + src_width * clip3((int)(srcCoorY + 1), 0, src_height - 1) + hor) * weight1;*(dst_image + dst_width * ver + hor) = clip3((uint8)dstValue, 0, 255);}}
}/*
* 函数名:	yuv420p_NearestScaler
* 说明:	最近邻插值
* 参数:
*/
void nearestScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height)
{double resizeX = (double)dst_width /src_width;			//水平缩放系数double resizeY = (double)dst_height / src_height;			//垂直缩放系数int srcX = 0;int srcY = 0;for(int ver = 0; ver < dst_height; ++ver) {for(int hor = 0; hor < dst_width; ++hor) {srcX = clip3(int(hor/resizeX + 0.5), 0, src_width - 1);srcY = clip3(int(ver/resizeY + 0.5), 0, src_height - 1);*(dst_image + dst_width * ver + hor) = *(src_image + src_width * srcY + srcX);}}
}void resize(const char *input_file, int src_width, int src_height, const char *output_file, int dst_width, int dst_height, int resize_type)
{//define and init src bufferint *src_y = new int[src_width * src_height];int *src_cb = new int[src_width * src_height / 4];int *src_cr = new int[src_width * src_height / 4];memset(src_y, 0, sizeof(int) * src_width * src_height);memset(src_cb, 0, sizeof(int) * src_width * src_height / 4);memset(src_cr, 0, sizeof(int) * src_width * src_height / 4);//define and init dst bufferint *dst_y = new int[dst_width * dst_height];int *dst_cb = new int[dst_width * dst_height / 4];int *dst_cr = new int[dst_width * dst_height / 4];memset(dst_y, 0, sizeof(int) * dst_width * dst_height);memset(dst_cb, 0, sizeof(int) * dst_width * dst_height / 4);memset(dst_cr, 0, sizeof(int) * dst_width * dst_height / 4);//define and init mid bufferint *mid_y = new int[dst_width * src_height];int *mid_cb = new int[dst_width * src_height / 4];int *mid_cr = new int[dst_width * src_height / 4];memset(mid_y, 0, sizeof(int) * dst_width * src_height);memset(mid_cb, 0, sizeof(int) * dst_width * src_height / 4);memset(mid_cr, 0, sizeof(int) * dst_width * src_height / 4);uint8 *data_in_8bit = new uint8[src_width * src_height * 3 / 2];memset(data_in_8bit, 0, sizeof(uint8) * src_width * src_height * 3 / 2);uint8 *data_out_8bit = new uint8[dst_width * dst_height * 3 / 2];memset(data_out_8bit, 0, sizeof(uint8) * dst_width * dst_height * 3 / 2);FILE *fp_in = fopen(input_file,"rb");if(NULL == fp_in){//exit(0);printf("open file failure");}FILE *fp_out = fopen(output_file, "wb+");//data readfread(data_in_8bit, sizeof(uint8), src_width * src_height * 3 / 2, fp_in);//Y componentfor(int ver = 0; ver < src_height; ver++){for(int hor =0; hor < src_width; hor++){src_y[ver * src_width + hor] = data_in_8bit[ver * src_width + hor];}}//c component YUV420Pfor(int ver = 0; ver < src_height / 2; ver++){for(int hor =0; hor < src_width / 2; hor++){src_cb[ver * (src_width / 2) + hor] = data_in_8bit[src_height * src_width + ver * src_width / 2 + hor];src_cr[ver * (src_width / 2) + hor] = data_in_8bit[src_height * src_width + src_height * src_width / 4 + ver * src_width / 2 + hor];}}//resizeif(0 == resize_type){nearestScaler(src_y, dst_y, src_width, src_height, dst_width, dst_height);nearestScaler(src_cb, dst_cb, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);nearestScaler(src_cr, dst_cr, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);}else if(1 == resize_type){bilinearHorScaler(src_y, mid_y, src_width, src_height, dst_width, src_height);bilinearHorScaler(src_cb, mid_cb, src_width / 2, src_height / 2, dst_width / 2, src_height / 2);bilinearHorScaler(src_cr, mid_cr, src_width / 2, src_height / 2, dst_width / 2, src_height / 2);bilinearVerScaler(mid_y, dst_y, dst_width, src_height, dst_width, dst_height);bilinearVerScaler(mid_cb, dst_cb, dst_width / 2, src_height / 2, dst_width / 2, dst_height / 2);bilinearVerScaler(mid_cr, dst_cr, dst_width / 2, src_height / 2, dst_width / 2, dst_height / 2);}	else{nearestScaler(src_y, dst_y, src_width, src_height, dst_width, dst_height);nearestScaler(src_cb, dst_cb, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);nearestScaler(src_cr, dst_cr, src_width / 2, src_height / 2, dst_width / 2, dst_height / 2);}//data writefor(int ver = 0; ver < dst_height; ver++){for(int hor =0; hor < dst_width; hor++){data_out_8bit[ver * dst_width + hor] = clip3(dst_y[ver * dst_width + hor], 0, 255);}}for(int ver = 0; ver < dst_height / 2; ver++){for(int hor = 0; hor < dst_width / 2; hor++){data_out_8bit[dst_height * dst_width + ver * dst_width / 2 + hor] = clip3(dst_cb[ver * (dst_width / 2) + hor], 0, 255);data_out_8bit[dst_height * dst_width + dst_height * dst_width / 4 + ver * dst_width / 2 + hor] = clip3(dst_cr[ver * (dst_width / 2) + hor], 0, 255);}}fwrite(data_out_8bit, sizeof(uint8), dst_width * dst_height * 3 / 2, fp_out);delete [] src_y;delete [] src_cb;delete [] src_cr;delete [] dst_y;delete [] dst_cb;delete [] dst_cr;delete [] mid_y;delete [] mid_cb;delete [] mid_cr;delete [] data_in_8bit;delete [] data_out_8bit;fclose(fp_in);fclose(fp_out);}

resize.h

#ifndef RESIZE_H
#define RESIZE_H#include <stdio.h>
#include <string.h>typedef unsigned char uint8;
typedef unsigned short uint16;int clip3(int data, int min, int max);
void bilinearHorScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void bilinearVerScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void nearestScaler(int *src_image, int *dst_image, int src_width, int src_height, int dst_width, int dst_height);
void resize(const char *input_file, int src_width, int src_height, const char *output_file, int dst_width, int dst_height, int resize_type);#endif

效果比较
将720x480分辨率图像放大到1080p,1:1截取局部画面如下,左边是最近邻放大的效果,右边是双线性效果,可以看到,双线性放大的锯齿要明显比最近邻小。
这里写图片描述

Matlab
常用的matlab缩放方法有两种,如下

  1. B = imresize(A, scale, method) B = imresize(A, 0.5, ‘bicubic’)使用双立方插值将宽高各缩小1/2
  2. B = imresize(A, outputSize, method) B = imresize(A, [1080,1920], ‘bilinear’)使用双线性插值缩放到1920x1080分辨率
    这里写图片描述

Opencv
常用的opencv中resize调用方法也有两种

  1. dsize=0,指定fx和fy,此时目标图像大小会自动计算出,dsize=Size(round(fxsrc.cols),round(fysrc,rows))
resize(src, dst, Size(0, 0), 0.5, 0.5, 2);		//缩小为原来的1/2,使用双立方插值(2)
  1. fx和fy为0,指定dsize
resize(src, dst, Size(1024,1024), 0, 0, 1);		//缩放到1024x1024分辨率,使用双线性插值(1)

Opencv提供5种插值方法有5种:最近邻、双线性、双立方、面积关联、兰佐斯。
Resize函数声名及插值方式玫举定义:

CV_EXPORTS_W void resize( InputArray src, OutputArray dst,Size dsize, double fx=0, double fy=0,int interpolation=INTER_LINEAR );enum
{INTER_NEAREST=CV_INTER_NN, //!< nearest neighbor interpolationINTER_LINEAR=CV_INTER_LINEAR, //!< bilinear interpolationINTER_CUBIC=CV_INTER_CUBIC, //!< bicubic interpolationINTER_AREA=CV_INTER_AREA, //!< area-based (or super) interpolationINTER_LANCZOS4=CV_INTER_LANCZOS4, //!< Lanczos interpolation over 8x8 neighborhoodINTER_MAX=7,WARP_INVERSE_MAP=CV_WARP_INVERSE_MAP
};enum
{CV_INTER_NN        =0,CV_INTER_LINEAR    =1,CV_INTER_CUBIC     =2,CV_INTER_AREA      =3,CV_INTER_LANCZOS4  =4
};

参考
[1] https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation
[2] https://en.wikipedia.org/wiki/Bilinear_interpolation


http://chatgpt.dhexx.cn/article/lBbsfHYq.shtml

相关文章

插值算法(数学建模学习)

本系列参考清风老师的数学建模课程 插值算法 一、算法介绍 &#xff08;一&#xff09;算法引入 对于数据量少到不足以去分析问题&#xff0c;而必须生成一些合理的数据的情况要用到插值算法。 &#xff08;二&#xff09;算法详解 &#xff08;1&#xff09;定义 设函数 …

MATLAB-插值算法汇总

前言 数模比赛中常常需要对数据进行分析&#xff0c;当数据不足时就需要补充数据&#xff0c;所用到的方法就是插值法。本文汇总了一些常用的插值算法。 Hermite插值 埃尔米特插值(Hermite)会在给定的节点处&#xff0c;要求插值多项式的函数值与原函数值相同&#xff0c;同时…

第三讲 插值算法

数模比赛中&#xff0c;常常需要根据已知的函数点进行数据&#xff0c;模型的处理和分析&#xff0c;而有时候现有的数据是极少的&#xff0c;不足以支撑分析的进行&#xff0c;这时就需要使用一些数学的方法&#xff0c;“模拟产生”一些新的但又比较靠谱的值来满足需求&#…

opencv中插值算法详解

导读 做图像处理的同学应该经常都会用到图像的缩放&#xff0c;我们都知道图片存储的时候其实就是一个矩阵&#xff0c;所以在对图像进行缩放操作的时候&#xff0c;也就是在对矩阵进行操作&#xff0c;如果想要将图片放大&#xff0c;这里我们就需要用到过采样算法来扩大矩阵…

几种插值算法对比

1.拉格朗日插值 2.牛顿插值 3.分段线性插值 4. 分段三次埃尔米特插值 5.样条插值函数 6.五种样条函数比较 所以&#xff0c; 7. 五种插值方法的实际应用

转载:一文讲解图像插值算法原理

最近在研究插值算法&#xff0c;看到这篇CSDN博主Datawhale学习介绍的博文&#xff0c;觉得介绍得挺不错&#xff0c;转载过来。原文地址&#xff1a;https://blog.csdn.net/Datawhale/article/details/105697264 寄语&#xff1a;本文梳理了最近邻插值法、双线性插值法和三次…

插值算法

插值&#xff0c;通俗来说当在一个离散的事件中&#xff0c;想知道某一个位置确定的值时&#xff0c;就可以利用插值方式计算得到&#xff0c;即利用已知数据估计未知位置数值。插值的方式有很多&#xff0c;下面介绍几种常用的插值方式。 一、最近邻插值(Nearest Neighbour …

几种插值算法对比研究

[研究内容] 目前比较常用的几种插值算法 [正文] 目前比较常用的插值算法有这么几种&#xff1a;最邻近插值&#xff0c;双线性二次插值&#xff0c;三次插值&#xff0c; Lanczos插值等等&#xff0c;今天我们来对比一下这几种插值效果的优劣。 1&#xff0c;最邻近插值 最…

【3.0】 常见的插值算法

插值算法的概念一维插值问题一般插值多项式的原理拉格朗日插值法牛顿插值法埃尔米特插值法分段 三次埃尔米特插值和分段三次样条插值&#xff08;常用&#xff0c;附代码&#xff09; 一、插值算法的概念 数学建模比赛中&#xff0c;常常需要根据已知的函数点进行数据、模型的…

常用的三种插值算法

在做数字图像处理时&#xff0c;经常会碰到小数象素坐标的取值问题&#xff0c;这时就需要依据邻近象素的值来对该坐标进行插值。比如做图像的几何校正&#xff0c;也会碰到同样的问题。 1、最近邻插值法&#xff08;Nearest Neighbour Interpolation&#xff09; 这是最简单的…

数学建模常见算法:插值算法

目录 一、插值的定义 二、拉格朗日多项式插值&#xff08;Lagrange插值&#xff09; 三、龙格现象&#xff08;Runge phenomenon&#xff09; 四、牛顿插值&#xff08;Newton&#xff09; 五、分段线性插值 六、埃尔米特插值(Hermite 插值) 七、三次样条插值 八、插值…

图像处理之-----插值算法

插值算法是图像处理中最基本的算法&#xff0c;首先我们先了解一下什么是插值算法&#xff0c;以及插值算法在图像处理过程中的应用。 1、什么是插值 Interpolation is a method of constructing new data points within the range of a discrete set of known data points. …

操作系统 读者写者问题的实现(C++ 读者优先、写者优先)

通过信号量机制和相应的系统调用&#xff0c;用于线程的互斥和同步&#xff0c;实现读者写者问题。利用信号量机制&#xff0c;实现读者写者问题。 在windows 10环境下&#xff0c;创建一个控制台进程&#xff0c;此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程…

信号量机制实现读者写者问题(思路剖析+Java代码实现+验证)

写在前面&#xff1a; Java中&#xff1a; 我们用这样的代码新建一个信号量&#xff1a;Semaphore mutex new Semaphore(1);P操作(wait)的代码为&#xff1a;mutex.acquire();V操作(signal)的代码为&#xff1a;mutex.release(); 本文章的内容&#xff1a; 读者写者问题&#…

Java实现读者写者问题--读者优先

作者&#xff1a;凌杰林 简介 临界资源&#xff1a;同一时间只能由一个进程访问的资源 临界区&#xff1a;访问临界资源的代码段 读者写者问题&#xff1a;存在一个多个进程共享的数据区&#xff08;临界资源&#xff09;&#xff0c;该数据区可以是一个文件或者一块内存空间…

操作系统实验:读者写者问题

一&#xff0e;实验目的&#xff1a; 通过实现读者写者问题理解进程及信号量的概念 二&#xff0e;实验要求&#xff1a; 创建一个控制台进程&#xff0c;此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求进行读写操作。用信号量机制分…

信号量机制——读者-写者问题

信号量机制——读者-写者问题 问题描述 一个共享数据区&#xff0c;有若干个进程负责对其进行读入工作&#xff0c;若干个进程负责对其进行写入工作。 允许多个进程同时读数据互斥读数据若有进程写数据&#xff0c;不允许读者读数据 对照生活中的购票系统&#xff1a; 一个…

操作系统:读者写者问题

操作系统&#xff1a;读者写者问题 问题&#xff1a;一、读者-写者问题: 读者优先二、读者-写者问题&#xff1a;写进程优先 三、读者写者问题的应用---独木桥问题类型1.一次只能过一辆车类型2.一次能过多辆车 四、总结附代码&#xff1a;//读者优先//写者优先//公平竞争 教材原…

四、操作系统——读者写者问题(详解)

一、问题描述&#xff1a; 二、需要满足的条件&#xff1a; 写进程与写进程之间必须互斥的写入数据&#xff08;因为如果两个写进程同时对共享数据中的区域A中的数据进行写操作的话&#xff0c;会导致数据错误覆盖的问题&#xff09;写进程与读进程之间必须互斥的访问共享数据…

锁机制:读者写者问题 Linux C

最近碰到一些锁机制的问题&#xff0c;想起大三的时候做过的一个小课设&#xff0c;记录复习一下。 问题描述&#xff1a; 一个数据文件可以被多个进程共享&#xff0c;其中&#xff0c;有些进程要求读&#xff08;reader进程&#xff09;&#xff0c;而另一些进程要求对数据…