原文来自:博客园 小欣子
图像缩放算法及速度优化——(一)最近邻插值
图像缩放算法及速度优化——(二)双线性插值
————————————————————以下为原文——————————————————
第0节 简介

  图像缩放算法是数字图像处理算法中经常遇到的问题。我们经常会将某种尺寸的图像转换为其他尺寸的图像,如放大或者缩小图像。OpenCV中的Resize() 函数非常方便而且效率非常高。下面是OPENCV提供的cvResize函数原型。

/****************************************************************************************************/
图像大小变换 
void cvResize( const CvArr* src, CvArr* dst, int interpolation=CV_INTER_LINEAR );
src 
输入图像. 
dst 
输出图像. 
interpolation 
插值方法: 
CV_INTER_NN - 最近邻插值, 
CV_INTER_LINEAR - 双线性插值 (缺省使用) 
CV_INTER_AREA - 使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 CV_INTER_NN 方法.. 
CV_INTER_CUBIC - 立方插值. 
函数 cvResize 将图像 src 改变尺寸得到与 dst 同样大小。若设定 ROI,函数将按常规支持 ROI.
/****************************************************************************************************/

  相信使用过Opencv的朋友都知道如何使用此函数。下面根据我自己的理解,用VC++ 来实现图像缩放算法 ,希望大家能从中理解图像缩放算法的原理。
第1节 最近邻插值

  最简单的图像缩放算法就是最近邻插值。顾名思义,就是将目标图像各点的像素值设为源图像中与其最近的点。假设源图像的宽度和高度分别为w0和h0, 缩放后的目标图像的宽度和高度分别为w1和h1, 那么比例就是float fw = float(w0)/w1; float fh = float(h0)/h1; 对于目标图像中的(x,y)点坐标对应着源图像中的(x0, y0)点。其中:x0 = int(x*fw), y0 = int(y*fh)。

  示例1:现在将一张670*503的BMP图像缩放到200*160,代码和效果如下。

void ResizeNear01(CImage &src, CImage &dst)
{
    int w0 = src.GetWidth();
    int h0 = src.GetHeight();

int w1 = dst.GetWidth();
    int h1 = dst.GetHeight();

float fw = float(w0) / w1;
    float fh = float(h0) / h1;

int x0, y0;
    for(int y=0; y<h1; y++)
    {
        y0 = int(y * fh);
        for(int x=0; x<w1; x++)
        {
            x0 = int(x * fw);
            dst.SetPixel(x, y, src.GetPixel(x0, y0));
        }
    }
}

分析:对于此程序,我们将执行此ResizeNear01函数的语句加上一个for循环,使其执行100次,看它的速度怎么样。

#include <time.h>
void CResizeDemoDlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here
    CImage src, dst;
    src.Load(L"d:\\1.bmp");
    dst.Create(200, 160, 24);

clock_t start = clock();
    for(int i=0; i<100; i++)
    {
        ResizeNear01(src, dst);
    }
    float end = float(clock() - start)/CLOCKS_PER_SEC;
    CString str;
    str.Format(L"%6.2f", end);
    MessageBox(str);

dst.Save(L"d:\\rs.jpg");
}

  显示程序执行所用的时间为20.59秒,平均一次需要0.2秒,看起来速度还可以,是因为时间复杂度较低。目标图像的尺寸大小是200*160。

  示例2:示例1中的算法可以改进速度的地方有两个。第一,因为最近邻插值算法求源图像中的坐标是固定的,可以把每个目标每个x和每个y对应的值通过一次循环先求出来,再利进入双重循环。第二,使用指针效率更高,如果使用CImage提供的GetPixel和SetPixel是费时间的。

//优化后的最近邻插值算法
void ResizeNear02(CImage &src, CImage &dst)
{
    int w0 = src.GetWidth();
    int h0 = src.GetHeight();
    int pitch0 = src.GetPitch();

int w1 = dst.GetWidth();
    int h1 = dst.GetHeight();
    int pitch1 = dst.GetPitch();

float fw = float(w0) / w1;
    float fh = float(h0) / h1;

int *arr_x = new int[w1];
    int *arr_y = new int[h1];
    for(int y=0; y<h1; y++)
    {
        arr_y[y] = int(y*fh);
    }
    for(int x=0; x<w1; x++)
    {
        arr_x[x] = int(x*fw);
    }

BYTE* pSrc = (BYTE*)src.GetBits();
    BYTE* pDst = (BYTE*)dst.GetBits();
    BYTE* p0, *p1;
    for(int y=0; y<h1; y++)
    {
        p0 = pSrc + pitch0 * arr_y[y];
        p1 = pDst + pitch1 * y;
        for(int x=0; x<w1; x++)
        {
            //dst.SetPixel(x, y, src.GetPixel(arr_x[x], arr_y[y]));
            memcpy(p1 + 3*x, p0 + arr_x[x]*3, 3);
        }
    }

delete []arr_x;
    delete []arr_y;
}

  同样执行示例1中的测试程序,让ResizeNear02也循环一百次,在我机器上测试得到的结果是0.05秒,速度提高了400倍,这是一件多么让人兴奋的事情啊。
本人接触图像处理虽然也快一年了,但还是新手,望大家多多指正。

第2节 双线性插值

  双线性插值作为OpenCV中默认使用的图像缩放算法,其效果和速度都是不错的。并且效果也比较稳定,计算复杂度并不算太高。我看了很多网上的算法,自己也没看太懂,下面是从网上找的双线性插值 算法的讲解。
  “图像的双线性插值放大算法中,目标图像中新创造的象素值,是由源图像位置在它附近的2*2区域4个邻近象素的值通过加权平均计算得出的。双线性内插值算法放大后的图像质量较高,不会出现像素值不连续的的情况。然而次算法具有低通滤波器的性质,使高频分量受损,所以可能会使图像轮廓在一定程度上变得模糊。”

    下面还是根据我自己的理解来继续讲述吧,相信读者中有很多高手,希望读者能给予我指点一下,让我也能更明白一些。
    双线性插值 算法和最近邻插值算法比较类似。在最近邻插值算法中,目标图像中的某个点(x,y)是去源图像中找最邻近的一个点(x0, y0)即可。目标图像中的点(x, y)对应于源图像中的点(x0',y0'),x0'、y0'很可能不是整数,而是小数,而最近邻插值算法是找其邻近整型值(int(x0'+0.5f),int(y0'+0.5f))(上篇文章中没有进行四舍五入)。我们现在找x0', y0'所在位置旁边的四个点,根据这四个点与(x0',y0')距离的关系计算目标图像中(x,y)一点的像素值。算法描述如下:

(1)计算源图像与目标图像宽与高的比例
w0 : 表示源图像的宽度
h0 : 表示源图像的高度
w1 : 表示目标图像的宽度
h1 : 表示目标图像的高度
float fw = float(w0-1)/(w1-1);
float fh = float(h0-1)/(h1-1);
(2)针对目标图像的一个点(x, y),计算在源图像中的对应坐标,结果为浮点数。
float x0 = x * fw;
float y0 = y * fh;

int x1 = int(x0);
int x2 = x1 + 1;
int y1 = int(y0);
int y2 = y1+1;

所求的源图像中的四个点坐标为(x1, y1) (x1, y2) (x2, y1) (x2,y2)
(3)求周围四个点所占的权重比值
如上图,
fx1 = x0 - x1;
fx2 = 1.0f - fx1;
fy1 = y0 - y1; 
fy2 = 1.0f - fy1;

float s1 = fx1*fy1;
float s2 = fx2*fy1;
float s3 = fx2*fy2;
float s4 = fx1*fy2;

我们以value(坐标)来代表取得此点的坐标值,则:
value(x0,y0) = value(x2,y2)*s1+value(x1,y2)*s2+value(x1,y1)*s3+value(x2,y1)*s4;

如果 对上述运算不够明白 的话,可以这样来求。
我们先要求得(x0, y1) 和(x0,y2)的像素值。
则float value(x0,y1) = value(x1,y1)*fx2 + value(x2,y1)*fx1;
float value(x0,y2) = value(x1,y2)*fx2 + value(x2,y2)*fx1;
注释:离某点越近,离权重越大,故取其与1的差值。
float value(x0,y0) = value(x0,y1)*fy2 + value(x0,y2)*fy1;
验证后与上边公式一样。
(4)求得值后填充到目标图像上就可以了。

为了能让人更容易理解,咱还是使用GetPixel和SetPixel进行取值和赋值。

void ResizeLinear01(CImage& src, CImage& dst)
{
    int w0 = src.GetWidth();
    int h0 = src.GetHeight();
    int pitch0 = src.GetPitch();

int w1 = dst.GetWidth();
    int h1 = dst.GetHeight();
    int pitch1 = dst.GetPitch();

float fw = float(w0) / w1;
    float fh = float(h0) / h1;

int y1,y2, x1,x2, x0,y0;
    float fx1,fx2, fy1, fy2;
    for(int y=0; y<h1; y++)
    {
        y0 = y*fh;
        y1 = int(y0);
        if(y1 == h0-1)    y2 = y1;
        else y2 = y1 + 1;

fy1 = y1-y0; 
        fy2 = 1.0f - fy1;
        for(int x=0; x<w1; x++)
        {
            x0 = x*fw;
            x1 = int(x0);
            if(x1 == w0-1)    x2 = x1;
            else x2 = x1+1;

fx1 = y1-y0;
            fx2 = 1.0f - fx1;

float s1 = fx1*fy1;
            float s2 = fx2*fy1;
            float s3 = fx2*fy2;
            float s4 = fx1*fy2;

COLORREF c1,c2,c3,c4, color;
            c1 = src.GetPixel(x1,y1);
            c2 = src.GetPixel(x2,y1);
            c3 = src.GetPixel(x1,y2);
            c4 = src.GetPixel(x2,y2);
            BYTE r,g,b;
            r = (BYTE)(GetRValue(c1)*s3) + (BYTE)(GetRValue(c2)*s4) + (BYTE)(GetRValue(c3)*s2) + (BYTE)(GetRValue(c4)*s1);
            g = (BYTE)(GetGValue(c1)*s3) + (BYTE)(GetGValue(c2)*s4) + (BYTE)(GetGValue(c3)*s2) + (BYTE)(GetGValue(c4)*s1);
            b = (BYTE)(GetBValue(c1)*s3) + (BYTE)(GetBValue(c2)*s4) + (BYTE)(GetBValue(c3)*s2) + (BYTE)(GetBValue(c4)*s1);

dst.SetPixelRGB(x, y, r, g, b);
        }
    }

}

测试程序仍是将670*503尺寸的图片缩放为200*160。经过测试,上边的算法执行一次就需要0.5秒左右,可以说是非常的慢。如果将其缩放成2000*1600大小的图片,一次就需要50秒。这是非常可怕的,假如现在我们做一个类似PHOTOSHOP的软件,用户想将其扩大若干倍,却要等50秒,估计用户已经没有耐心使用您的软件了。
我们来分析一下怎样可以优化程序。在每一步优化算法后,我们都再用上边同样的程序和步骤进行测试,观察运行所用时间。
(1)改函数调用 为指针操作如第1节。(进行完此步后,程序只需要0.2秒左右,速度提高了250倍,哈哈!)
(2)将x0,y0坐标的计算提取到 循环外,因为第二层循环里的x坐标每次循环都要重复一次,并且是重复的。(仍然是需要0.2秒左右。我们让其循环一百次,再比较下所用时间。使用ResizeLinear02方法用时19.6秒,使用ResizeLinear02方法都是用时17.4秒,看来还是有作用的。)

下面是最终的代码。

void ResizeLinear04(CImage& src, CImage& dst)
{
    int w0 = src.GetWidth();
    int h0 = src.GetHeight();
    int pitch0 = src.GetPitch();

int w1 = dst.GetWidth();
    int h1 = dst.GetHeight();
    int pitch1 = dst.GetPitch();

BYTE* pSrc = (BYTE*)src.GetBits();
    BYTE* pDst = (BYTE*)dst.GetBits();
    BYTE* p0, *p1 = pDst;

float fw = float(w0-1) / (w1-1);
    float fh = float(h0-1) / (h1-1);

float x0, y0;
    int y1, y2, x1, x2;
    float fx1, fx2, fy1, fy2;
    
    int* arr_x1 = new int[w1];
    int* arr_x2 = new int[w1];
    float* arr_fx1 = new float[w1];

for(int x=0; x<w1; x++)
    {
        x0 = x*fw;
        arr_x1[x] = int(x0);
        arr_x2[x] = int(x0+0.5f);
        arr_fx1[x] = x0 - arr_x1[x];
        //TRACE(L"x=%6d; x0=%6.3f; x1=%6d; x2=%6d; fx1=%6.3f;\n", x, x0, arr_x1[x], arr_x2[x], arr_fx1[x]);
    }    
    for(int y=0; y<h1; y++)
    {
        y0 = y*fh;
        y1 = int(y0);
        y2 = int(y0+0.5f);
        fy1 = y0-y1; 
        fy2 = 1.0f - fy1;
        //TRACE(L"y=%6d; y0=%6.3f; y1=%6d; y2=%6d; fy1=%6.3f;\n", y, y0, y1, y2, fy1);
        for(int x=0; x<w1; x++)
        {
            x1 = arr_x1[x];
            x2 = arr_x2[x];
            fx1 = arr_fx1[x];
            fx2 = 1.0f-fx1;

float s1 = fx2*fy2;
            float s2 = fx1*fy2;
            float s3 = fx1*fy1;
            float s4 = fx2*fy1;
            //TRACE(L"s1=%6.3f; s2=%6.3f; s3=%6.3f; s4=%6.3f; sum=%6.3f\n", s1,s2,s3,s4, s1+s2+s3+s4);
            BYTE* p11 = pSrc + pitch0*y1 + 3*x1;
            BYTE* p12 = pSrc + pitch0*y1 + 3*x2;
            BYTE* p21 = pSrc + pitch0*y2 + 3*x1;
            BYTE* p22 = pSrc + pitch0*y2 + 3*x2;
            
            *p1 = BYTE((*p11)*s1 + (*p12)*s2 + (*p21)*s4 + (*p22)*s3);    p1++;    p11++; p12++; p21++; p22++;
            *p1 = BYTE((*p11)*s1 + (*p12)*s2 + (*p21)*s4 + (*p22)*s3);    p1++;    p11++; p12++; p21++; p22++;
            *p1 = BYTE((*p11)*s1 + (*p12)*s2 + (*p21)*s4 + (*p22)*s3);  p1++;

}
        p1 = pDst + y*pitch1;
    }

delete []arr_x1;
    delete []arr_x2;
    delete []arr_fx1;

}

图像缩放算法及速度优化相关推荐

  1. 图像缩放算法(中篇)

    图像缩放算法(中篇) ================================= 转载别人的,但是这篇文章写得确实太好了,所以想分享出来. 原文地址:http://blog.chinaunix ...

  2. 图形图像处理 —— 图像缩放算法

    转自:http://blog.chinaunix.net/space.php?uid=22915173&do=blog&id=2185545 摘要:首先给出一个基本的图像缩放算法,然后 ...

  3. 图像缩放算法(下篇)

    图像缩放算法(下篇) ================================= 转载别人的,但是这篇文章写得确实太好了,所以想分享出来. 原文地址:http://blog.chinaunix ...

  4. 几个图像缩放算法的比较

    几个图像缩放算法的比较 前段时间由于项目的需求,需要实现图像的缩放功能,期间查找了不少关于图像缩放算法的资料,现把自己的心得整理一下. 由于研究生期间没有选修过图像处理方面的课程,所以对图像缩放的原理 ...

  5. 计算机视觉-图像缩放算法-cuda实现

    一.CUDA CUDA是显卡厂商NVIDIA(英伟达)推出的运算平台,能够将数据数据复制到GPU,在GPU中进行计算,然后再返回给CPU端.CUDA将GPU称为设备侧或者Device,将CPU称为Ho ...

  6. [图像]图像缩放算法-双线性内插法

    原创文章,欢迎转载.转载请注明:转载自 祥的博客 原文链接:http://blog.csdn.net/humanking7/article/details/45014879 简介: 图像缩放算法–双线 ...

  7. 基于FPGA 的图像缩放算法设计

    介绍双线性插值算法来实现图像缩放,FPGA 硬件实现方法,包括图像数据缓冲单元.插值系数生成单元以及插值计算单元等. 图像是人类感知世界的视觉基础,是人类获取信息.表达信息的重要手段.现在研究较多的是 ...

  8. 图像缩放算法_技术专栏|基于无人机LK光流算法的适用性及其优化方法探究

    点击上方蓝字关注我们 问题描述 ◆ ◆ ◆ 一般的LK光流算法存在一个比较明显的局限性.我们知道,一般的LK光流算法必须包含三个假设: (1)亮度恒定: (2)时间连续或者运动是小运动: (3)空间一 ...

  9. 图像缩放算法_opencv缩放算法

    1.opencv插值介绍 opencv提供resize函数用来做图像缩放,该函数有6个参数: (1)输入图像,Mat型 (2)输出图像,Mat型 (3)输出图像大小,可用cv::Size(out_im ...

最新文章

  1. 【Vegas原创】Oracle每日export的脚本(Windows版)
  2. 树上边分治-求任意两点路径的总和
  3. 驱动开发中使用安全字符串函数
  4. 因为这两天比较忙,所以没有及时把要发表的东西写来.废话少说:我前面把两个简单的滚动说了下.接下来介绍第三种集合循环滚动....
  5. 正则学习小结(1)-基础
  6. Windows中EFS加密及解密应用
  7. 虚拟机NAT模式联网
  8. mysql mha reference_MySQL MHA配置常见问题
  9. 最常用的网络应用工具之寻线仪
  10. [Java] 蓝桥杯ALGO-147 算法训练 4-3水仙花数
  11. 日志平台查询异常,没有打印异常信息
  12. Imagenet的中英对应分类
  13. 关于‘go list‘ failed with: error obtaining VCS status error obtaining VCS status: exit status 128问题的解决
  14. python对月饼数据进行可视化,看看哪家最划算
  15. [WinError 206] 文件名或扩展名太长(组策略值修改 解除windows文件名 字符长度限制)
  16. php每四位隔开,php数字每三位加逗号的功能函数
  17. webview性能优化—webview预创建
  18. Android Studio百度地图仿QQ发说说选择位置功能
  19. matlab 计算连杆长度,matlab机器人运动学计算
  20. 【Active Learning - 13】总结与展望 参考文献的整理与分享(The End...)

热门文章

  1. 跟我一起使用 compose 做一个跨平台的黑白棋游戏(4)移植到compose-jb实现跨平台
  2. Flowable完整安装使用流程
  3. 机器下棋史:人造的智能,战胜了造智能的人
  4. ASCII码与16进制转换对照表
  5. AAAI 2017论文简析:利用可拍照移动设备感知空气质量---Crowdsensing Air Quality with Camera-enabled Mobile Devices
  6. 虚拟机redis连接windows本地
  7. python pyecharts学习笔记 散点图、气泡图
  8. 论文写作课堂总结3:慎用的单词短语
  9. Logistic函数求导
  10. php 执行时间超时