I.图像处理-双线性插值(转载)
图像的缩放很好理解,就是图像的放大和缩小。传统的绘画工具中,有一种叫做“放大尺”的绘画工具,画家常用它来放大图画。当然,在计算机上,我们不再需要用放大尺去放大或缩小图像了,把这个工作交给程序来完成就可以了。下面就来讲讲计算机怎么来放大缩小图象;在本文中,我们所说的图像都是指点阵图,也就是用一个像素矩阵来描述图像的方法,对于另一种图像:用函数来描述图像的矢量图,不在本文讨论之列。 越是简单的模型越适合用来举例子,我们就举个简单的图像:3X3 的256级灰度图,也就是高为3个象素,宽也是3个象素的图像,每个象素的取值可以是 0-255,代表该像素的亮度,255代表最亮,也就是白色,0代表最暗,即黑色。假如图像的象素矩阵如下图所示(这个原始图把它叫做源图,Source): 234 38 22 67 44 12 89 65 63
---------------------->X | | | | | ∨Y
如果想把这副图放大为 4X4大小的图像,那么该怎么做呢?那么第一步肯定想到的是先把4X4的矩阵先画出来再说,好了矩阵画出来了,如下所示,当然,矩阵的每个像素都是未知数,等待着我们去填充(这个将要被填充的图的叫做目标图,Destination): ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 然后要往这个空的矩阵里面填值是从源图中来,先填写目标图最左上角的象素,坐标为(0,0),那么该坐标对应源图中的坐标可以由如下公式得出: srcX=dstX* (srcWidth/dstWidth) , srcY = dstY * (srcHeight/dstHeight) 套用公式,就可以找到对应的原图的坐标(0*(3/4),0*(3/4))=>(0*0.75,0*0.75)=>(0,0) ,找到了源图的对应坐标,就可以把源图中坐标为(0,0)处的234象素值填进去目标图的(0,0)这个位置了。
接下来,如法炮制,寻找目标图中坐标为(1,0)的象素对应源图中的坐标,套用公式: (1*0.75,0*0.75)=>(0.75,0) 结果发现,得到的坐标里面竟然有小数,这可怎么办?计算机里的图像可是数字图像,象素就是最小单位了,象素的坐标都是整数,从来没有小数坐标。这时候采用的一种策略就是采用四舍五入的方法(也可以采用直接舍掉小数位的方法),把非整数坐标转换成整数,好,那么按照四舍五入的方法就得到坐标(1,0),完整的运算过程就是这样的: (1*0.75,0*0.75)=>(0.75,0)=>(1,0) 那么就可以再填一个象素到目标矩阵中了,同样是把源图中坐标为(1,0)处的像素值38填入目标图中的坐标。 依次填完每个象素,一幅放大后的图像就诞生了,像素矩阵如下所示: 234 38 22 22 67 44 12 12 89 65 63 63 89 65 63 63
这种放大图像的方法叫做最临近插值算法,这是一种最基本、最简单的图像缩放算法,效果也是最不好的,放大后的图像有很严重的马赛克,缩小后的图像有很严重的失真;效果不好的根源就是其简单的最临近插值方法引入了严重的图像失真,比如,当由目标图的坐标反推得到的源图的的坐标是一个浮点数的时候,采用了四舍五入的方法,直接采用了和这个浮点数最接近的象素的值,这种方法是很不科学的,当推得坐标值为 0.75的时候,不应该就简单的取为1,既然是0.75,比1要小0.25 ,比0要大0.75 ,那么目标象素值其实应该根据这个源图中虚拟的点四周的四个真实的点来按照一定的规律计算出来的,这样才能达到更好的缩放效果。双线型内插值算法就是一种比较好的图像缩放算法,它充分的利用了源图中虚拟点四周的四个真实存在的像素值来共同决定目标图中的一个像素值,因此缩放效果比简单的最邻近插值要好很多。
双线性内插值算法描述如下: 对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即: f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j)+ uvf(i+1,j+1) 公式1 其中f(i,j)表示源图像(i,j)处的的像素值,以此类推。
比如,象刚才的例子,现在假如目标图的象素坐标为(1,1),那么反推得到的对应于源图的坐标是(0.75 , 0.75), 这其实只是一个概念上的虚拟象素,实际在源图中并不存在这样一个象素,那么目标图的象素(1,1)的取值不能够由这个虚拟象素来决定,而只能由源图的这四个象素共同决定:(0,0)(0,1)(1,0)(1,1),而由于(0.75,0.75)离(1,1)要更近一些,那么(1,1)所起的决定作用更大一些,这从公式1中的系数uv=0.75×0.75就可以体现出来,而(0.75,0.75)离(0,0)最远,所以(0,0)所起的决定作用就要小一些,公式中系数为(1-u)(1-v)=0.25×0.25也体现出了这一特点;
最邻近插值和双向性内插值缩放图片的效果对比:
原始图片
最邻近插值放大图片
双线型内插值放大图片
常用的插值方法有:最近邻插值、双线性插值、三次卷积法。
在做数字图像处理时,经常会碰到小数象素坐标的取值问题,这时就需要依据邻近象素的值来对该坐标进行插值。比如:做地图投影转换,对目标图像的一个象素进行坐标变换到源图像上对应的点时,变换出来的对应的坐标是一个小数,再比如做图像的几何校正,也会碰到同样的问题。以下是对常用的三种数字图像插值方法进行介绍。
1、最邻近元法
这是最简单的一种插值方法,不需要计算,在待求象素的四邻象素中,将距离待求象素最近的邻象素灰度赋给待求象素。设i+u, j+v(i, j为正整数, u, v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰度的值 f(i+u, j+v) 如下图所示:
如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。
最邻近元法计算量较小,但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。
2、双线性内插法
双线性内插法是利用待求象素四个邻象素的灰度在两个方向上作线性内插,如下图所示:
对于 (i, j+v),f(i, j) 到 f(i, j+1) 的灰度变化为线性关系,则有:
f(i, j+v) = [f(i, j+1)- f(i, j)] * v + f(i, j)
同理对于 (i+1, j+v) 则有:
f(i+1,j+v) = [f(i+1, j+1) - f(i+1, j)] * v + f(i+1, j)
从f(i, j+v) 到 f(i+1, j+v) 的灰度变化也为线性关系,由此可推导出待求象素灰度的计算式如下:
f(i+u,j+v) = (1-u) * (1-v) * f(i, j) + (1-u) * v * f(i, j+1) + u * (1-v) * f(i+1, j)+ u * v * f(i+1, j+1)
双线性内插法的计算比最邻近点法复杂,计算量较大,但没有灰度不连续的缺点,结果基本令人满意。它具有低通滤波性质,使高频分量受损,图像轮廓可能会有一点模糊。
3、三次内插法
该方法利用三次多项式S(x)求逼近理论上最佳插值函数sin(x)/x, 其数学表达式为:
待求像素(x, y)的灰度值由其周围16个灰度值加权内插得到,如下图:
待求像素的灰度计算式如下:
f(x, y) = f(i+u, j+v) = ABC
其中:
三次曲线插值方法计算量较大,但插值后的图像效果最好。
II.图像处理-双立方插值(转载)
双立方插值计算涉及到16个像素点,其中(i’, j’)表示待计算像素点在源图像中的包含
小数部分的像素坐标,dx表示X方向的小数坐标,dy表示Y方向的小数坐标。具体
可以看下图:
根据上述图示与双立方插值的数学表达式可以看出,双立方插值本质上图像16个像素点
权重卷积之和作为新的像素值。
其中R(x)表示插值表达式,可以根据需要选择的表达式不同。常见有基于三角取值、Bell
分布表达、B样条曲线表达式。
1. 基于三角形采样数学公式为
最简单的线性分布,代码实现如下:
[java] view plain copy private double triangleInterpolation( double f ) { f = f / 2.0; if( f < 0.0 ) { return ( f + 1.0 ); } else { return ( 1.0 - f ); } } 2.基于Bell分布采样的数学公式如下:
Bell分布采样数学公式基于三次卷积计算实现。代码实现如下:
[java] view plain copy private double bellInterpolation( double x ) { double f = ( x / 2.0 ) * 1.5; if( f > -1.5 && f < -0.5 ) { return( 0.5 * Math.pow(f + 1.5, 2.0)); } else if( f > -0.5 && f < 0.5 ) { return 3.0 / 4.0 - ( f * f ); } else if( ( f > 0.5 && f < 1.5 ) ) { return( 0.5 * Math.pow(f - 1.5, 2.0)); } return 0.0; } 3.基于B样条曲线采样的数学公式如下:
是一种基于多项式的四次卷积的采样计算,代码如下:
[java] view plain copy private double bspLineInterpolation( double f ) { if( f < 0.0 ) { f = -f; } if( f >= 0.0 && f <= 1.0 ) { return ( 2.0 / 3.0 ) + ( 0.5 ) * ( f* f * f ) - (f*f); } else if( f > 1.0 && f <= 2.0 ) { return 1.0 / 6.0 * Math.pow( ( 2.0 - f ), 3.0 ); } return 1.0; } 实现图像双立方插值的完整源代码如下: [java] view plain copy package com.gloomyfish.zoom.study; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import com.gloomyfish.filter.study.AbstractBufferedImageOp; public class BicubicInterpolationFilter extends AbstractBufferedImageOp { public final static int TRIANGLE__INTERPOLATION = 1; public final static int BELL__INTERPOLATION = 2; public final static int BSPLINE__INTERPOLATION = 4; public final static int CATMULLROOM__INTERPOLATION = 8; public final static double B = 0.0; public final static double C = 0.5; // constant private int destH; // zoom height private int destW; // zoom width private int type; public BicubicInterpolationFilter() { this.type = BSPLINE__INTERPOLATION; } public void setType(int type) { this.type = type; } public void setDestHeight(int destH) { this.destH = destH; } public void setDestWidth(int destW) { this.destW = destW; } private double bellInterpolation( double x ) { double f = ( x / 2.0 ) * 1.5; if( f > -1.5 && f < -0.5 ) { return( 0.5 * Math.pow(f + 1.5, 2.0)); } else if( f > -0.5 && f < 0.5 ) { return 3.0 / 4.0 - ( f * f ); } else if( ( f > 0.5 && f < 1.5 ) ) { return( 0.5 * Math.pow(f - 1.5, 2.0)); } return 0.0; } private double bspLineInterpolation( double f ) { if( f < 0.0 ) { f = -f; } if( f >= 0.0 && f <= 1.0 ) { return ( 2.0 / 3.0 ) + ( 0.5 ) * ( f* f * f ) - (f*f); } else if( f > 1.0 && f <= 2.0 ) { return 1.0 / 6.0 * Math.pow( ( 2.0 - f ), 3.0 ); } return 1.0; } private double triangleInterpolation( double f ) { f = f / 2.0; if( f < 0.0 ) { return ( f + 1.0 ); } else { return ( 1.0 - f ); } } private double CatMullRomInterpolation( double f ) { if( f < 0.0 ) { f = Math.abs(f); } if( f < 1.0 ) { return ( ( 12 - 9 * B - 6 * C ) * ( f * f * f ) + ( -18 + 12 * B + 6 *C ) * ( f * f ) + ( 6 - 2 * B ) ) / 6.0; } else if( f >= 1.0 && f < 2.0 ) { return ( ( -B - 6 * C ) * ( f * f * f ) + ( 6 * B + 30 * C ) * ( f *f ) + ( - ( 12 * B ) - 48 * C ) * f + 8 * B + 24 * C)/ 6.0; } else { return 0.0; } } @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { int width = src.getWidth(); int height = src.getHeight(); if (dest == null) dest = createCompatibleDestImage(src, null); int[] inPixels = new int[width * height]; int[] outPixels = new int[destH * destW]; getRGB(src, 0, 0, width, height, inPixels); float rowRatio = ((float) height) / ((float) destH); float colRatio = ((float) width) / ((float) destW); int index = 0; for (int row = 0; row < destH; row++) { int ta = 0, tr = 0, tg = 0, tb = 0; double srcRow = ((float) row) * rowRatio; // 获取整数部分坐标 row Index double j = Math.floor(srcRow); // 获取行的小数部分坐标 double t = srcRow - j; for (int col = 0; col < destW; col++) { double srcCol = ((float) col) * colRatio; // 获取整数部分坐标 column Index double k = Math.floor(srcCol); // 获取列的小数部分坐标 double u = srcCol - k; double[] rgbData = new double[3]; double rgbCoffeData = 0.0; for(int m=-1; m<3; m++) { for(int n=-1; n<3; n++) { int[] rgb = getPixel(j+m, k+n, width, height, inPixels); double f1 = 0.0d; double f2 = 0.0d; if(type == TRIANGLE__INTERPOLATION) { f1 = triangleInterpolation( ((double) m ) - t ); f2 = triangleInterpolation ( -(( (double) n ) - u ) ); } else if(type == BELL__INTERPOLATION) { f1 = bellInterpolation( ((double) m ) - t ); f2 = bellInterpolation ( -(( (double) n ) - u ) ); } else if(type == BSPLINE__INTERPOLATION) { f1 = bspLineInterpolation( ((double) m ) - t ); f2 = bspLineInterpolation ( -(( (double) n ) - u ) ); } else { f1 = CatMullRomInterpolation( ((double) m ) - t ); f2 = CatMullRomInterpolation ( -(( (double) n ) - u ) ); } // sum of weight rgbCoffeData += f2*f1; // sum of the RGB values rgbData[0] += rgb[0] * f2 * f1; rgbData[1] += rgb[1] * f2 * f1; rgbData[2] += rgb[2] * f2 * f1; } } ta = 255; // get Red/green/blue value for sample pixel tr = (int) (rgbData[0]/rgbCoffeData); tg = (int) (rgbData[1]/rgbCoffeData); tb = (int) (rgbData[2]/rgbCoffeData); index = row * destW + col; outPixels[index] = (ta << 24) | (clamp(tr) << 16) | (clamp(tg) << 8) | clamp(tb); } } setRGB(dest, 0, 0, destW, destH, outPixels); return dest; } public int clamp(int value) { return value > 255 ? 255 : (value < 0 ? 0 : value); } private int[] getPixel(double j, double k, int width, int height, int[] inPixels) { int row = (int) j; int col = (int) k; if (row >= height) { row = height - 1; } if (row < 0) { row = 0; } if (col < 0) { col = 0; } if (col >= width) { col = width - 1; } int index = row * width + col; int[] rgb = new int[3]; rgb[0] = (inPixels[index] >> 16) & 0xff; rgb[1] = (inPixels[index] >> 8) & 0xff; rgb[2] = inPixels[index] & 0xff; return rgb; } public BufferedImage createCompatibleDestImage( BufferedImage src, ColorModel dstCM) { if ( dstCM == null ) dstCM = src.getColorModel(); return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(destW, destH), dstCM.isAlphaPremultiplied(), null); } } 运行效果:原图双立方插值放大以后:
总结:
基于这里三种方法实现的双立方插值以后图片跟原图像相比,都有一定模糊
这里时候可以通过后续处理实现图像锐化与对比度提升即可得到Sharpen版本
当然也可以通过寻找更加合适的R(x)函数来实现双立方卷积插值过程时保留
图像边缘与对比度。
III.图像处理-OpenCV中resize函数五种插值算法的实现过程
(转载)
最新版OpenCV2.4.7中,cv::resize函数有五种插值算法:最近邻、双线性、双三次、基于像素区域关系、兰索斯插值。下面用for循环代替cv::resize函数来说明其详细的插值实现过程,其中部分代码摘自于cv::resize函数中的源代码。
每种插值算法的前部分代码是相同的,如下:
[cpp] view plain copy cv::Mat matSrc, matDst1, matDst2; matSrc = cv::imread("lena.jpg", 2 | 4); matDst1 = cv::Mat(cv::Size(800, 1000), matSrc.type(), cv::Scalar::all(0)); 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;1、最近邻:公式,
[cpp] view plain copy for (int i = 0; i < matDst1.cols; ++i) { int sx = cvFloor(i * scale_x); sx = std::min(sx, matSrc.cols - 1); for (int j = 0; j < matDst1.rows; ++j) { int sy = cvFloor(j * scale_y); sy = std::min(sy, matSrc.rows - 1); matDst1.at<cv::Vec3b>(j, i) = matSrc.at<cv::Vec3b>(sy, sx); } } cv::imwrite("nearest_1.jpg", matDst1); cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 0); cv::imwrite("nearest_2.jpg", matDst2); 2、双线性:由相邻的四像素(2*2)计算得出,公式,
[cpp] view plain copy 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<short>((1.f - fy) * 2048); cbufy[1] = 2048 - cbufy[0]; 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<short>((1.f - fx) * 2048); cbufx[1] = 2048 - cbufx[0]; for (int k = 0; k < matSrc.channels(); ++k) { *(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22; } } } cv::imwrite("linear_1.jpg", matDst1); cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 1); cv::imwrite("linear_2.jpg", matDst2); 3、双三次:由相邻的4*4像素计算得出,公式类似于双线性 [cpp] view plain copy int iscale_x = cv::saturate_cast<int>(scale_x); int iscale_y = cv::saturate_cast<int>(scale_y); 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, matSrc.rows - 3); sy = std::max(1, sy); const float A = -0.75f; float coeffsY[4]; coeffsY[0] = ((A*(fy + 1) - 5*A)*(fy + 1) + 8*A)*(fy + 1) - 4*A; coeffsY[1] = ((A + 2)*fy - (A + 3))*fy*fy + 1; coeffsY[2] = ((A + 2)*(1 - fy) - (A + 3))*(1 - fy)*(1 - fy) + 1; coeffsY[3] = 1.f - coeffsY[0] - coeffsY[1] - coeffsY[2]; short cbufY[4]; cbufY[0] = cv::saturate_cast<short>(coeffsY[0] * 2048); cbufY[1] = cv::saturate_cast<short>(coeffsY[1] * 2048); cbufY[2] = cv::saturate_cast<short>(coeffsY[2] * 2048); cbufY[3] = cv::saturate_cast<short>(coeffsY[3] * 2048); 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 < 1) { fx = 0, sx = 1; } if (sx >= matSrc.cols - 3) { fx = 0, sx = matSrc.cols - 3; } float coeffsX[4]; coeffsX[0] = ((A*(fx + 1) - 5*A)*(fx + 1) + 8*A)*(fx + 1) - 4*A; coeffsX[1] = ((A + 2)*fx - (A + 3))*fx*fx + 1; coeffsX[2] = ((A + 2)*(1 - fx) - (A + 3))*(1 - fx)*(1 - fx) + 1; coeffsX[3] = 1.f - coeffsX[0] - coeffsX[1] - coeffsX[2]; short cbufX[4]; cbufX[0] = cv::saturate_cast<short>(coeffsX[0] * 2048); cbufX[1] = cv::saturate_cast<short>(coeffsX[1] * 2048); cbufX[2] = cv::saturate_cast<short>(coeffsX[2] * 2048); cbufX[3] = cv::saturate_cast<short>(coeffsX[3] * 2048); for (int k = 0; k < matSrc.channels(); ++k) { matDst1.at<cv::Vec3b>(j, i)[k] = abs((matSrc.at<cv::Vec3b>(sy-1, sx-1)[k] * cbufX[0] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx-1)[k] * cbufX[0] * cbufY[1] + matSrc.at<cv::Vec3b>(sy+1, sx-1)[k] * cbufX[0] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx-1)[k] * cbufX[0] * cbufY[3] + matSrc.at<cv::Vec3b>(sy-1, sx)[k] * cbufX[1] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx)[k] * cbufX[1] * cbufY[1] + matSrc.at<cv::Vec3b>(sy+1, sx)[k] * cbufX[1] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx)[k] * cbufX[1] * cbufY[3] + matSrc.at<cv::Vec3b>(sy-1, sx+1)[k] * cbufX[2] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx+1)[k] * cbufX[2] * cbufY[1] + matSrc.at<cv::Vec3b>(sy+1, sx+1)[k] * cbufX[2] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx+1)[k] * cbufX[2] * cbufY[3] + matSrc.at<cv::Vec3b>(sy-1, sx+2)[k] * cbufX[3] * cbufY[0] + matSrc.at<cv::Vec3b>(sy, sx+2)[k] * cbufX[3] * cbufY[1] + matSrc.at<cv::Vec3b>(sy+1, sx+2)[k] * cbufX[3] * cbufY[2] + matSrc.at<cv::Vec3b>(sy+2, sx+2)[k] * cbufX[3] * cbufY[3] ) >> 22); } } } cv::imwrite("cubic_1.jpg", matDst1); cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 2); cv::imwrite("cubic_2.jpg", matDst2); 4、基于像素区域关系:共分三种情况,图像放大时类似于双线性插值,图像缩小(x轴、y轴同时缩小)又分两种情况,此情况下可以避免波纹出现。 [cpp] view plain copy cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 3); cv::imwrite("area_2.jpg", matDst2); double inv_scale_x = 1. / scale_x; double inv_scale_y = 1. / scale_y; int iscale_x = cv::saturate_cast<int>(scale_x); int iscale_y = cv::saturate_cast<int>(scale_y); bool is_area_fast = std::abs(scale_x - iscale_x) < DBL_EPSILON && std::abs(scale_y - iscale_y) < DBL_EPSILON; if (scale_x >= 1 && scale_y >= 1) //zoom out { if (is_area_fast) //integer multiples { for (int j = 0; j < matDst1.rows; ++j) { int sy = j * scale_y; for (int i = 0; i < matDst1.cols; ++i) { int sx = i * scale_x; matDst1.at<cv::Vec3b>(j, i) = matSrc.at<cv::Vec3b>(sy, sx); } } cv::imwrite("area_1.jpg", matDst1); return 0; } for (int j = 0; j < matDst1.rows; ++j) { double fsy1 = j * scale_y; double fsy2 = fsy1 + scale_y; double cellHeight = cv::min(scale_y, matSrc.rows - fsy1); int sy1 = cvCeil(fsy1), sy2 = cvFloor(fsy2); sy2 = std::min(sy2, matSrc.rows - 1); sy1 = std::min(sy1, sy2); float cbufy[2]; cbufy[0] = (float)((sy1 - fsy1) / cellHeight); cbufy[1] = (float)(std::min(std::min(fsy2 - sy2, 1.), cellHeight) / cellHeight); for (int i = 0; i < matDst1.cols; ++i) { double fsx1 = i * scale_x; double fsx2 = fsx1 + scale_x; double cellWidth = std::min(scale_x, matSrc.cols - fsx1); int sx1 = cvCeil(fsx1), sx2 = cvFloor(fsx2); sx2 = std::min(sx2, matSrc.cols - 1); sx1 = std::min(sx1, sx2); float cbufx[2]; cbufx[0] = (float)((sx1 - fsx1) / cellWidth); cbufx[1] = (float)(std::min(std::min(fsx2 - sx2, 1.), cellWidth) / cellWidth); for (int k = 0; k < matSrc.channels(); ++k) { matDst1.at<cv::Vec3b>(j, i)[k] = (uchar)(matSrc.at<cv::Vec3b>(sy1, sx1)[k] * cbufx[0] * cbufy[0] + matSrc.at<cv::Vec3b>(sy1 + 1, sx1)[k] * cbufx[0] * cbufy[1] + matSrc.at<cv::Vec3b>(sy1, sx1 + 1)[k] * cbufx[1] * cbufy[0] + matSrc.at<cv::Vec3b>(sy1 + 1, sx1 + 1)[k] * cbufx[1] * cbufy[1]); } } } cv::imwrite("area_1.jpg", matDst1); return 0; } //zoom in,it is emulated using some variant of bilinear interpolation for (int j = 0; j < matDst1.rows; ++j) { int sy = cvFloor(j * scale_y); float fy = (float)((j + 1) - (sy + 1) * inv_scale_y); fy = fy <= 0 ? 0.f : fy - cvFloor(fy); short cbufy[2]; cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048); cbufy[1] = 2048 - cbufy[0]; for (int i = 0; i < matDst1.cols; ++i) { int sx = cvFloor(i * scale_x); float fx = (float)((i + 1) - (sx + 1) * inv_scale_x); fx = fx < 0 ? 0.f : fx - cvFloor(fx); if (sx < 0) { fx = 0, sx = 0; } if (sx >= matSrc.cols - 1) { fx = 0, sx = matSrc.cols - 2; } short cbufx[2]; cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048); cbufx[1] = 2048 - cbufx[0]; for (int k = 0; k < matSrc.channels(); ++k) { matDst1.at<cv::Vec3b>(j, i)[k] = (matSrc.at<cv::Vec3b>(sy, sx)[k] * cbufx[0] * cbufy[0] + matSrc.at<cv::Vec3b>(sy + 1, sx)[k] * cbufx[0] * cbufy[1] + matSrc.at<cv::Vec3b>(sy, sx + 1)[k] * cbufx[1] * cbufy[0] + matSrc.at<cv::Vec3b>(sy + 1, sx + 1)[k] * cbufx[1] * cbufy[1]) >> 22; } } } cv::imwrite("area_1.jpg", matDst1); 5、兰索斯插值:由相邻的8*8像素计算得出,公式类似于双线性 [cpp] view plain copy int iscale_x = cv::saturate_cast<int>(scale_x); int iscale_y = cv::saturate_cast<int>(scale_y); 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, matSrc.rows - 5); sy = std::max(3, sy); const double s45 = 0.70710678118654752440084436210485; const double cs[][2] = {{1, 0}, {-s45, -s45}, {0, 1}, {s45, -s45}, {-1, 0}, {s45, s45}, {0, -1}, {-s45, s45}}; float coeffsY[8]; if (fy < FLT_EPSILON) { for (int t = 0; t < 8; t++) coeffsY[t] = 0; coeffsY[3] = 1; } else { float sum = 0; double y0 = -(fy + 3) * CV_PI * 0.25, s0 = sin(y0), c0 = cos(y0); for (int t = 0; t < 8; ++t) { double dy = -(fy + 3 -t) * CV_PI * 0.25; coeffsY[t] = (float)((cs[t][0] * s0 + cs[t][1] * c0) / (dy * dy)); sum += coeffsY[t]; } sum = 1.f / sum; for (int t = 0; t < 8; ++t) coeffsY[t] *= sum; } short cbufY[8]; cbufY[0] = cv::saturate_cast<short>(coeffsY[0] * 2048); cbufY[1] = cv::saturate_cast<short>(coeffsY[1] * 2048); cbufY[2] = cv::saturate_cast<short>(coeffsY[2] * 2048); cbufY[3] = cv::saturate_cast<short>(coeffsY[3] * 2048); cbufY[4] = cv::saturate_cast<short>(coeffsY[4] * 2048); cbufY[5] = cv::saturate_cast<short>(coeffsY[5] * 2048); cbufY[6] = cv::saturate_cast<short>(coeffsY[6] * 2048); cbufY[7] = cv::saturate_cast<short>(coeffsY[7] * 2048); 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 < 3) { fx = 0, sx = 3; } if (sx >= matSrc.cols - 5) { fx = 0, sx = matSrc.cols - 5; } float coeffsX[8]; if (fx < FLT_EPSILON) { for ( int t = 0; t < 8; t++ ) coeffsX[t] = 0; coeffsX[3] = 1; } else { float sum = 0; double x0 = -(fx + 3) * CV_PI * 0.25, s0 = sin(x0), c0 = cos(x0); for (int t = 0; t < 8; ++t) { double dx = -(fx + 3 -t) * CV_PI * 0.25; coeffsX[t] = (float)((cs[t][0] * s0 + cs[t][1] * c0) / (dx * dx)); sum += coeffsX[t]; } sum = 1.f / sum; for (int t = 0; t < 8; ++t) coeffsX[t] *= sum; } short cbufX[8]; cbufX[0] = cv::saturate_cast<short>(coeffsX[0] * 2048); cbufX[1] = cv::saturate_cast<short>(coeffsX[1] * 2048); cbufX[2] = cv::saturate_cast<short>(coeffsX[2] * 2048); cbufX[3] = cv::saturate_cast<short>(coeffsX[3] * 2048); cbufX[4] = cv::saturate_cast<short>(coeffsX[4] * 2048); cbufX[5] = cv::saturate_cast<short>(coeffsX[5] * 2048); cbufX[6] = cv::saturate_cast<short>(coeffsX[6] * 2048); cbufX[7] = cv::saturate_cast<short>(coeffsX[7] * 2048); for (int k = 0; k < matSrc.channels(); ++k) { matDst1.at<cv::Vec3b>(j, i)[k] = abs((matSrc.at<cv::Vec3b>(sy-3, sx-3)[k] * cbufX[0] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx-3)[k] * cbufX[0] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx-3)[k] * cbufX[0] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx-3)[k] * cbufX[0] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx-3)[k] * cbufX[0] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx-3)[k] * cbufX[0] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx-3)[k] * cbufX[0] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx-3)[k] * cbufX[0] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx-2)[k] * cbufX[1] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx-2)[k] * cbufX[1] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx-2)[k] * cbufX[1] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx-2)[k] * cbufX[1] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx-2)[k] * cbufX[1] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx-2)[k] * cbufX[1] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx-2)[k] * cbufX[1] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx-2)[k] * cbufX[1] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx-1)[k] * cbufX[2] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx-1)[k] * cbufX[2] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx-1)[k] * cbufX[2] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx-1)[k] * cbufX[2] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx-1)[k] * cbufX[2] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx-1)[k] * cbufX[2] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx-1)[k] * cbufX[2] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx-1)[k] * cbufX[2] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx)[k] * cbufX[3] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx)[k] * cbufX[3] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx)[k] * cbufX[3] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx)[k] * cbufX[3] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx)[k] * cbufX[3] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx)[k] * cbufX[3] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx)[k] * cbufX[3] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx)[k] * cbufX[3] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx+1)[k] * cbufX[4] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx+1)[k] * cbufX[4] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx+1)[k] * cbufX[4] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx+1)[k] * cbufX[4] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx+1)[k] * cbufX[4] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx+1)[k] * cbufX[4] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx+1)[k] * cbufX[4] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx+1)[k] * cbufX[4] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx+2)[k] * cbufX[5] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx+2)[k] * cbufX[5] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx+2)[k] * cbufX[5] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx+2)[k] * cbufX[5] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx+2)[k] * cbufX[5] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx+2)[k] * cbufX[5] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx+2)[k] * cbufX[5] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx+2)[k] * cbufX[5] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx+3)[k] * cbufX[6] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx+3)[k] * cbufX[6] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx+3)[k] * cbufX[6] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx+3)[k] * cbufX[6] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx+3)[k] * cbufX[6] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx+3)[k] * cbufX[6] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx+3)[k] * cbufX[6] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx+3)[k] * cbufX[6] * cbufY[7] + matSrc.at<cv::Vec3b>(sy-3, sx+4)[k] * cbufX[7] * cbufY[0] + matSrc.at<cv::Vec3b>(sy-2, sx+4)[k] * cbufX[7] * cbufY[1] + matSrc.at<cv::Vec3b>(sy-1, sx+4)[k] * cbufX[7] * cbufY[2] + matSrc.at<cv::Vec3b>(sy, sx+4)[k] * cbufX[7] * cbufY[3] + matSrc.at<cv::Vec3b>(sy+1, sx+4)[k] * cbufX[7] * cbufY[4] + matSrc.at<cv::Vec3b>(sy+2, sx+4)[k] * cbufX[7] * cbufY[5] + matSrc.at<cv::Vec3b>(sy+3, sx+4)[k] * cbufX[7] * cbufY[6] + matSrc.at<cv::Vec3b>(sy+4, sx+4)[k] * cbufX[7] * cbufY[7] ) >> 22);// 4194304 } } } cv::imwrite("Lanczos_1.jpg", matDst1); cv::resize(matSrc, matDst2, matDst1.size(), 0, 0, 4); cv::imwrite("Lanczos_2.jpg", matDst2);
以上代码的实现结果与 cv::resize 函数相同,但是执行效率非常低,只是为了详细说明插值过程。 OpenCV 中默认采用 C++ Concurrency 进行优化加速,你也可以采用 TBB 、 OpenMP 等进行优化加速。