博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
resize接口实现之-------双线性插值法解读
阅读量:4298 次
发布时间:2019-05-27

本文共 16481 字,大约阅读时间需要 54 分钟。

1、resize 原理

       OpenCV2.4.9中,cv::resize函数有五种插值算法:最近邻、双线性(默认方式)、双三次、基于像素区域关系、兰索斯插值。

void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
  • src - 原图 
  • dst - 目标图像。当参数dsize不为0时,dst的大小为size;否则,它的大小需要根据src的大小,参数fx和fy决定。dst的类型(type)和src图像相同 
  • dsize - 目标图像大小。

 

下面主要针对双线性插值法。

双线性插值,顾名思义就是在x方向和y方向上进行线性差值.如下图,在节点 

A(x1,y1)和节点B(x2,y2)之间插入节点C(x,y).

我们知道,线性差值,数据的值和距离是成比例的,因此: 

那么节点C的值应该为:

这是一个方向上的插值,而双线性是在二维方向上进行两次差值来求取目标点的数值,一般用于图像 

的放缩.从上面公式可以看到,想要求目标点的值,需要首先确定目标点的坐标,从而确定目标点周围点的权重.

详细推导过程如下图:

2.源码

下面用for循环代替cv::resize函数来说明其详细的插值实现过程。第一种方式(main1)是从opencv源码扣出来的,第二种是自实现的resize功能,读取图片为jpg格式,第三种是读取yuv格式图片。

#include 
#include
#include
#include
using namespace cv;using namespace std;typedef unsigned char uchar;#define MAX_IMAGE_WIDTH 1280#define MAX_IMAGE_HEIGHT 720int dst_width = 704;int dst_hight = 480;const int INTER_RESIZE_COEF_BITS = 11;const int INTER_RESIZE_COEF_SCALE = 1 << INTER_RESIZE_COEF_BITS;const int INTER_REMAP_COEF_BITS = 15;static const int MAX_ESIZE = 16;const int INTER_REMAP_COEF_SCALE = 1 << INTER_REMAP_COEF_BITS;typedef void(*ResizeFunc)(const Mat& src, Mat& dst, const int* xofs, const void* alpha, const int* yofs, const void* beta, int xmin, int xmax, int ksize);typedef uchar value_type;typedef int buf_type;typedef short alpha_type;typedef uchar T;typedef int WT;typedef short AT;//typedef uchar value_type;//typedef int buf_type;//typedef short alpha_type;//template
;static inline int clip(int x, int a, int b){ return x >= a ? (x < b ? x : b - 1) : a;}void hresize(const uchar** src, int** dst, int count, const int* xofs, const short* alpha, int swidth, int dwidth, int cn, int xmin, int xmax){ int ONE = 2048; int dx, k; //VecOp vecOp; int dx0 = 0;//vecOp((const uchar**)src, (uchar**)dst, count, xofs, (const uchar*)alpha, swidth, dwidth, cn, xmin, xmax); for (k = 0; k <= count - 2; k++) { const T *S0 = src[k], *S1 = src[k + 1]; WT *D0 = dst[k], *D1 = dst[k + 1]; for (dx = dx0; dx < xmax; dx++) { int sx = xofs[dx]; WT a0 = alpha[dx * 2], a1 = alpha[dx * 2 + 1]; WT t0 = S0[sx] * a0 + S0[sx + cn] * a1; WT t1 = S1[sx] * a0 + S1[sx + cn] * a1; D0[dx] = t0; D1[dx] = t1; } for (; dx < dwidth; dx++) { int sx = xofs[dx]; D0[dx] = WT(S0[sx] * ONE); D1[dx] = WT(S1[sx] * ONE); } } for (; k < count; k++) { const T *S = src[k]; WT *D = dst[k]; for (dx = 0; dx < xmax; dx++) { int sx = xofs[dx]; D[dx] = S[sx] * alpha[dx * 2] + S[sx + cn] * alpha[dx * 2 + 1]; } for (; dx < dwidth; dx++) D[dx] = WT(S[xofs[dx]] * ONE); }}void vresize(const buf_type** src, value_type* dst, const alpha_type* beta, int width){ alpha_type b0 = beta[0], b1 = beta[1]; const buf_type *S0 = src[0], *S1 = src[1]; //VResizeLinearVec_32s8u vecOp; int x = 0;//vecOp((const uchar**)src, (uchar*)dst, (const uchar*)beta, width);#if CV_ENABLE_UNROLLED for (; x <= width - 4; x += 4) { dst[x + 0] = uchar((((b0 * (S0[x + 0] >> 4)) >> 16) + ((b1 * (S1[x + 0] >> 4)) >> 16) + 2) >> 2); dst[x + 1] = uchar((((b0 * (S0[x + 1] >> 4)) >> 16) + ((b1 * (S1[x + 1] >> 4)) >> 16) + 2) >> 2); dst[x + 2] = uchar((((b0 * (S0[x + 2] >> 4)) >> 16) + ((b1 * (S1[x + 2] >> 4)) >> 16) + 2) >> 2); dst[x + 3] = uchar((((b0 * (S0[x + 3] >> 4)) >> 16) + ((b1 * (S1[x + 3] >> 4)) >> 16) + 2) >> 2); }#endif for (; x < width; x++) dst[x] = uchar((((b0 * (S0[x] >> 4)) >> 16) + ((b1 * (S1[x] >> 4)) >> 16) + 2) >> 2);}void invoker(const Mat& src, Mat &dst, const int *xofs, const int *yofs, const alpha_type* alpha, const alpha_type* _beta, const Size& ssize, const Size &dsize, int ksize, int xmin, int xmax, Range& range){ int dy, cn = src.channels(); //HResize hresize; //VResize vresize; int bufstep = (int)alignSize(dsize.width, 16); AutoBuffer
_buffer(bufstep*ksize); const value_type* srows[MAX_ESIZE] = { 0 }; buf_type* rows[MAX_ESIZE] = { 0 }; int prev_sy[MAX_ESIZE]; for (int k = 0; k < ksize; k++) { prev_sy[k] = -1; rows[k] = (buf_type*)_buffer + bufstep*k; } const alpha_type* beta = _beta + ksize * range.start; for (dy = range.start; dy < range.end; dy++, beta += ksize) { int sy0 = yofs[dy], k0 = ksize, k1 = 0, ksize2 = ksize / 2; for (int k = 0; k < ksize; k++) { int sy = clip(sy0 - ksize2 + 1 + k, 0, ssize.height); for (k1 = std::max(k1, k); k1 < ksize; k1++) { if (sy == prev_sy[k1]) // if the sy-th row has been computed already, reuse it. { if (k1 > k) memcpy(rows[k], rows[k1], bufstep*sizeof(rows[0][0])); break; } } if (k1 == ksize) k0 = std::min(k0, k); // remember the first row that needs to be computed srows[k] = (value_type*)(src.data + src.step*sy); prev_sy[k] = sy; } if (k0 < ksize) hresize((const value_type**)(srows + k0), (buf_type**)(rows + k0), ksize - k0, xofs, (const alpha_type*)(alpha), ssize.width, dsize.width, cn, xmin, xmax); vresize((const buf_type**)rows, (value_type*)(dst.data + dst.step*dy), beta, dsize.width); int x = 2; }}static void resizeGeneric_(const Mat& src, Mat& dst, const int* xofs, const void* _alpha, const int* yofs, const void* _beta, int xmin, int xmax, int ksize){ typedef alpha_type AT; const AT* beta = (const AT*)_beta; Size ssize = src.size(), dsize = dst.size(); int cn = src.channels(); ssize.width *= cn; dsize.width *= cn; xmin *= cn; xmax *= cn; // image resize is a separable operation. In case of not too strong Range range(0, dsize.height); //resizeGeneric_Invoker
invoker(src, dst, xofs, yofs, (const AT*)_alpha, beta, //ssize, dsize, ksize, xmin, xmax); //parallel_for_(range, invoker, dst.total() / (double)(1 << 16)); invoker(src, dst, xofs, yofs, (const AT*)_alpha, beta, ssize, dsize, ksize, xmin, xmax, range);}void Resize___(InputArray _src, OutputArray _dst, Size dsize, double inv_scale_x, double inv_scale_y, int interpolation){ Mat src = _src.getMat(); Size ssize = src.size(); CV_Assert(ssize.area() > 0); CV_Assert(dsize.area() || (inv_scale_x > 0 && inv_scale_y > 0)); if (!dsize.area()) { dsize = Size(saturate_cast
(src.cols*inv_scale_x), saturate_cast
(src.rows*inv_scale_y)); CV_Assert(dsize.area()); } else { inv_scale_x = (double)dsize.width / src.cols; inv_scale_y = (double)dsize.height / src.rows; } _dst.create(dsize, src.type()); Mat dst = _dst.getMat(); int depth = src.depth(), cn = src.channels(); double scale_x = 1. / inv_scale_x, scale_y = 1. / inv_scale_y; int k, sx, sy, dx, dy; { int iscale_x = saturate_cast
(scale_x); int iscale_y = saturate_cast
(scale_y); bool is_area_fast = std::abs(scale_x - iscale_x) < DBL_EPSILON && std::abs(scale_y - iscale_y) < DBL_EPSILON; // in case of scale_x && scale_y is equal to 2 // INTER_AREA (fast) also is equal to INTER_LINEAR if (interpolation == INTER_LINEAR && is_area_fast && iscale_x == 2 && iscale_y == 2) { interpolation = INTER_AREA; } } int xmin = 0, xmax = dsize.width, width = dsize.width*cn; bool area_mode = interpolation == INTER_AREA; bool fixpt = depth == CV_8U; float fx, fy; //ResizeFunc func = 0; int ksize = 0, ksize2; ksize = 2; //func = linear_tab[depth]; ksize2 = ksize / 2; //CV_Assert(func != 0); AutoBuffer
_buffer((width + dsize.height)*(sizeof(int)+sizeof(float)*ksize)); int* xofs = (int*)(uchar*)_buffer; int* yofs = xofs + width; float* alpha = (float*)(yofs + dsize.height); short* ialpha = (short*)alpha; float* beta = alpha + width*ksize; short* ibeta = ialpha + width*ksize; float cbuf[MAX_ESIZE]; for (dx = 0; dx < dsize.width; dx++) { if (!area_mode) { fx = (float)((dx + 0.5)*scale_x - 0.5); sx = cvFloor(fx); fx -= sx; } else { sx = cvFloor(dx*scale_x); fx = (float)((dx + 1) - (sx + 1)*inv_scale_x); fx = fx <= 0 ? 0.f : fx - cvFloor(fx); } if (sx < ksize2 - 1) { xmin = dx + 1; if (sx < 0) fx = 0, sx = 0; } if (sx + ksize2 >= ssize.width) { xmax = std::min(xmax, dx); if (sx >= ssize.width - 1) fx = 0, sx = ssize.width - 1; } for (k = 0, sx *= cn; k < cn; k++) xofs[dx*cn + k] = sx + k; cbuf[0] = 1.f - fx; cbuf[1] = fx; if (fixpt) { for (k = 0; k < ksize; k++) ialpha[dx*cn*ksize + k] = saturate_cast
(cbuf[k] * INTER_RESIZE_COEF_SCALE); for (; k < cn*ksize; k++) ialpha[dx*cn*ksize + k] = ialpha[dx*cn*ksize + k - ksize]; } else { for (k = 0; k < ksize; k++) alpha[dx*cn*ksize + k] = cbuf[k]; for (; k < cn*ksize; k++) alpha[dx*cn*ksize + k] = alpha[dx*cn*ksize + k - ksize]; } } for (dy = 0; dy < dsize.height; dy++) { if (!area_mode) { fy = (float)((dy + 0.5)*scale_y - 0.5); sy = cvFloor(fy); fy -= sy; } else { sy = cvFloor(dy*scale_y); fy = (float)((dy + 1) - (sy + 1)*inv_scale_y); fy = fy <= 0 ? 0.f : fy - cvFloor(fy); } yofs[dy] = sy; cbuf[0] = 1.f - fy; cbuf[1] = fy; if (fixpt) { for (k = 0; k < ksize; k++) ibeta[dy*ksize + k] = saturate_cast
(cbuf[k] * INTER_RESIZE_COEF_SCALE); } else { for (k = 0; k < ksize; k++) beta[dy*ksize + k] = cbuf[k]; } } resizeGeneric_(src, dst, xofs, fixpt ? (void*)ialpha : (void*)alpha, yofs, fixpt ? (void*)ibeta : (void*)beta, xmin, xmax, ksize);}int main1(){ string strfile = "img.jpg"; Mat src = cv::imread(strfile); int imageWidth = src.cols; int imageHeight = src.rows; cvtColor(src, src, CV_BGR2GRAY); Size dsize(dst_width, dst_hight); Mat dst; //cv::resize(src, dst, dsize); //imwrite("resize0.jpg", dst); clock_t start, finish; double totaltime; start = clock(); int interpolation = INTER_LINEAR;//双线性插值(默认方法) Resize___(src, dst, dsize, 0, 0, interpolation); finish = clock(); totaltime = (double)(finish - start); cout << "\n此程序1的运行时间为" << totaltime << "毫秒!" << endl; imwrite("xuxin1.jpg", dst);return 1;}int main2(){ cv::Mat matSrc, matDst1, matDst2; matSrc = cv::imread("img.jpg");//lena output cvtColor(matSrc, matSrc, CV_BGR2GRAY); int nChannle = matSrc.channels(); matDst1 = cv::Mat(cv::Size(dst_width, dst_hight), matSrc.type(), cv::Scalar::all(0));//(800, 1000) matDst2 = cv::Mat(matDst1.size(), matSrc.type(), cv::Scalar::all(0)); double scale_x = (double)matSrc.cols / matDst1.cols; double scale_y = (double)matSrc.rows / matDst1.rows; clock_t start, finish; double totaltime; start = clock(); uchar* dataDst = matDst1.data; int stepDst = matDst1.step; uchar* dataSrc = matSrc.data; int stepSrc = matSrc.step; int iWidthSrc = matSrc.cols; int iHiehgtSrc = matSrc.rows; for (int j = 0; j < matDst1.rows; ++j) { float fy = (float)((j + 0.5) * scale_y - 0.5); int sy = cvFloor(fy); fy -= sy; sy = std::min(sy, iHiehgtSrc - 2); sy = std::max(0, sy); short cbufy[2]; cbufy[0] = cv::saturate_cast
((1.f - fy) * 2048); cbufy[1] = 2048 - cbufy[0]; //cout << fy << "," << sy << "," << cbufy[0] << "," << cbufy[1] << endl; for (int i = 0; i < matDst1.cols; ++i) { float fx = (float)((i + 0.5) * scale_x - 0.5); int sx = cvFloor(fx); fx -= sx; if (sx < 0) { fx = 0, sx = 0; } if (sx >= iWidthSrc - 1) { fx = 0, sx = iWidthSrc - 2; } short cbufx[2]; cbufx[0] = cv::saturate_cast
((1.f - fx) * 2048); cbufx[1] = 2048 - cbufx[0]; int charnnel = matSrc.channels(); for (int k = 0; k < matSrc.channels(); ++k) { *(dataDst + j*stepDst + nChannle * i + k) = (*(dataSrc + sy*stepSrc + nChannle * sx + k) * cbufx[0] * cbufy[0] + *(dataSrc + (sy + 1)*stepSrc + nChannle * sx + k) * cbufx[0] * cbufy[1] + *(dataSrc + sy*stepSrc + nChannle * (sx + 1) + k) * cbufx[1] * cbufy[0] + *(dataSrc + (sy + 1)*stepSrc + nChannle * (sx + 1) + k) * cbufx[1] * cbufy[1]) >> 22; } } } finish = clock(); totaltime = (double)(finish - start); cout << "\n此程序2的运行时间为" << totaltime << "毫秒!" << endl; cv::imwrite("xuxin2.jpg", matDst1); //cv::resize(matSrc, matDst2, matDst1.size()); //cv::imwrite("linear_2.jpg", matDst2); return 1;}double Mymin(double a, double b){ double minValue = a; if (minValue > b) { minValue = b; } return minValue;}double Mymax(double a, double b){ double maxValue = a; if (maxValue < b) { maxValue = b; } return maxValue;}int lpr_read_file(uchar *pU8, FILE* pFp, int width, int height){ int y; (void)fgetc(pFp); if (feof(pFp)) { //printf("end of file!\n"); fseek(pFp, 0, SEEK_SET); } else { fseek(pFp, -1, SEEK_CUR); } if (pU8 == NULL) { //printf("\n ===== pU8 is NULL. \n"); return -1; } //printf("==== pst image:[%dx%d] === \n", height, width); //pU8 = pstImg->pu8VirAddr[0]; for (y = 0; y < height; y++) { if (1 != fread(pU8, width, 1, pFp)) { //printf("Read file fail\n"); return -1; } pU8 += width; } return 0;}int lpr_write_file(uchar *pU8, FILE* pFp, int width, int height){ int y; if ((pU8 == NULL) || (pFp == NULL)) { printf("\n ==== pU8 or pFp is NULL. \n"); return -1; } //pU8 = pstImg->pu8VirAddr[0]; for (y = 0; y < height; y++) { if (width != fwrite(pU8, 1, width, pFp)) { printf("Write file fail\n"); return -1; } pU8 += width; } return 0;}int main3(){ // 1、读取数据 FILE* pFp = fopen("ff_720.yuv", "rb"); if (!pFp) { return 0; } uchar *dataSrc = (uchar *)malloc(MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT); memset(dataSrc, 255, MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT); lpr_read_file(dataSrc, pFp, MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT); // 2、resize处理 int imageSize = dst_width * dst_hight; uchar * dataDst = (uchar *)malloc(imageSize); memset(dataDst, 0, imageSize); int stepDst = dst_width; // 图像宽度 int stepSrc = MAX_IMAGE_WIDTH; int iWidthSrc = MAX_IMAGE_WIDTH; int iHiehgtSrc = MAX_IMAGE_HEIGHT; double scale_x = (double)MAX_IMAGE_WIDTH / dst_width; // 计算比例因子 double scale_y = (double)MAX_IMAGE_HEIGHT / dst_hight; clock_t start, finish; double totaltime; start = clock(); for (int j = 0; j < dst_hight; ++j) { float fy = (float)((j + 0.5) * scale_y - 0.5); // Q映射到A中的坐标的时,做了0.5的偏移.这是为了保证图像缩放以后重心一致 int sy = (int)fy; fy -= sy; // 获取小数部分 sy 为坐标整数部分,fy为小数部分 sy = Mymin(sy, iHiehgtSrc - 2); //cout << "sy = " << sy << endl; sy = Mymax(0, sy); short cbufy[2]; cbufy[0] = (short)((1.f - fy) * 2048); cbufy[1] = 2048 - cbufy[0]; //std::cout <<"fy = "<< fy << ",sy = " << sy << ",cbufy[0] = " << cbufy[0] << ",cbufy[1] = " << cbufy[1] << std::endl; for (int i = 0; i < dst_width; ++i) { float fx = (float)((i + 0.5) * scale_x - 0.5); int sx = (int)fx; fx -= sx; if (sx < 0) { fx = 0, sx = 0; } if (sx >= iWidthSrc - 1) { fx = 0, sx = iWidthSrc - 2; } short cbufx[2]; cbufx[0] = (short)((1.f - fx) * 2048); cbufx[1] = 2048 - cbufx[0]; //std::cout << "sx = " << sx << ",sy = " << sy << ",cbufx[0] = " << cbufx[0] << ",cbufx[1] = " << cbufx[1] << ",cbufy[0] = " << cbufy[0] << ",cbufy[1] = " << cbufy[1] << std::endl; *(dataDst + j*stepDst + i) = (*(dataSrc + sy*stepSrc + sx) * cbufx[0] * cbufy[0] + *(dataSrc + (sy + 1)*stepSrc + sx) * cbufx[0] * cbufy[1] + *(dataSrc + sy*stepSrc + (sx + 1)) * cbufx[1] * cbufy[0] + *(dataSrc + (sy + 1)*stepSrc + (sx + 1)) * cbufx[1] * cbufy[1]) >> 22; } } finish = clock(); totaltime = (double)(finish - start); cout << "\n此程序3的运行时间为" << totaltime << "毫秒!" << endl; // 3、保存图片 FILE* pFp_write = fopen("ff720_linux_resize704_480.yuv", "wb"); lpr_write_file(dataDst, pFp_write, dst_width, dst_hight); // 4、释放 delete dataSrc; dataSrc = NULL; delete dataDst; dataDst = NULL; fclose(pFp); fclose(pFp_write); return 1;}int main(){ main1(); //main2(); main3(); return 1;}

结果:

 

3,加速以及优化策略

 单纯按照上文实现的插值算法只能勉强完成插值的功能,速度和效果都不会理想,在具体代码实现的时候有些小技巧。参考OpenCV源码以及网上博客整理如下两点: 

  • 源图像和目标图像几何中心的对齐。 
  • 将浮点运算转换成整数运算

3.1 源图像和目标图像几何中心的对齐  

 方法:在计算源图像的虚拟浮点坐标的时候,一般情况: 

  srcX=dstX* (srcWidth/dstWidth) , 
  srcY = dstY * (srcHeight/dstHeight) 
  中心对齐(OpenCV也是如此): 
  SrcX=(dstX+0.5)* (srcWidth/dstWidth) -0.5 
  SrcY=(dstY+0.5) * (srcHeight/dstHeight)-0.5 
  原理: 
双线性插值算法及需要注意事项这篇博客解释说“如果选择右上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。”我有点保持疑问。 
  将公式变形: 
  srcX=dstX* (srcWidth/dstWidth)+0.5*(srcWidth/dstWidth-1) 
  相当于我们在原始的浮点坐标上加上了0.5*(srcWidth/dstWidth-1)这样一个控制因子,这项的符号可正可负,与srcWidth/dstWidth的比值也就是当前插值是扩大还是缩小图像有关,有什么作用呢?看一个例子:假设源图像是3*3,中心点坐标(1,1)目标图像是9*9,中心点坐标(4,4),我们在进行插值映射的时候,尽可能希望均匀的用到源图像的像素信息,最直观的就是(4,4)映射到(1,1)现在直接计算srcX=4*3/9=1.3333!=1,也就是我们在插值的时候所利用的像素集中在图像的右下方,而不是均匀分布整个图像。现在考虑中心点对齐,srcX=(4+0.5)*3/9-0.5=1,刚好满足我们的要求。

3.2 将浮点运算转换成整数运算

  参考图像处理界双线性插值算法的优化 

  直接进行计算的话,由于计算的srcX和srcY 都是浮点数,后续会进行大量的乘法,而图像数据量又大,速度不会理想,解决思路是: 
  浮点运算→→整数运算→→”<<左右移按位运算”。 
  放大的主要对象是u,v这些浮点数,OpenCV选择的放大倍数是2048“如何取这个合适的放大倍数呢,要从三个方面考虑, 
  第一:精度问题,如果这个数取得过小,那么经过计算后可能会导致结果出现较大的误差。 
  第二,这个数不能太大,太大会导致计算过程超过长整形所能表达的范围。 
  第三:速度考虑。假如放大倍数取为12,那么算式在最后的结果中应该需要除以12*12=144,但是如果取为16,则最后的除数为16*16=256,这个数字好,我们可以用右移来实现,而右移要比普通的整除快多了。”我们利用左移11位操作就可以达到放大目的。

 

按照网上一些博客上写的,源图像和目标图像的原点(0,0)均选择左上角,然后根据插值公式计算目标图像每点像素,假设你需要将一幅5x5的图像缩小成3x3,那么源图像和目标图像各个像素之间的对应关系如下:

        从图中可以很明显的看到,如果选择左上角为原点(0,0),那么最右边和最下边的像素实际上并没有参与计算,而且目标图像的每个像素点计算出的灰度值也相对于源图像偏左偏上。 

        那么,让坐标加1或者选择右下角为原点怎么样呢?很不幸,还是一样的效果,不过这次得到的图像将偏右偏下。 
最好的方法就是,两个图像的几何中心重合,并且目标图像的每个像素之间都是等间隔的,并且都和两边有一定的边距,这也是matlab和openCV的做法。如下图: 

如果你不懂我上面说的什么,没关系,只要在计算对应坐标的时候改为以下公式即可,

int x=(i+0.5)*m/a-0.5

int y=(j+0.5)*n/b-0.5

代替

int x=i*m/a

int y=j*n/b

利用上述公式,将得到正确的双线性插值结果。

 

 

转载地址:http://wmsws.baihongyu.com/

你可能感兴趣的文章
Python+Selenium练习篇之22-获取页面元素大小
查看>>
Python+Selenium练习篇之23-组合键-全选文字
查看>>
Python+Selenium练习篇之24-组合键-退格键删除文字
查看>>
Python+Selenium练习篇之25-鼠标右键
查看>>
Python+Selenium练习篇之26-执行JavaScript
查看>>
Python+Selenium练习篇之27-多窗口之间切换
查看>>
Python+Selenium练习篇之28-处理iframe切换
查看>>
Python+Selenium练习篇之29-处理Alert弹窗
查看>>
Python+Selenium练习篇之30-获取当前页面全部图片信息
查看>>
Python+Selenium练习篇之31-获取页面元素的href属性
查看>>
Python+Selenium练习篇之32-如何截图并保存
查看>>
Python+Selenium中级篇之0-设计自动化测试框架的前提技能介绍
查看>>
Python+Selenium中级篇之1-Python IDE工具-PyCharm的安装和简单使用
查看>>
Python+Selenium中级篇之2-Python中类/函数/模块的简单介绍和方法调用
查看>>
Python+Selenium中级篇之3-二次封装Selenium中几个方法
查看>>
Python+Selenium中级篇之4-封装一个自己的类-浏览器引擎类
查看>>
Python+Selenium中级篇之5-Python读取配置文件内容
查看>>
Python+Selenium中级篇之6-Python获取系统时间和格式化时间显示
查看>>
Python+Selenium中级篇之7-Python中字符串切割操作
查看>>
Python+Selenium中级篇之8-Python自定义封装一个简单的Log类
查看>>