用线性插值算法实现图像缩放

用线性插值算法实现图像缩放

 

猛禽[Mental Studio](个人专栏)(BLOG)

http://mental.mentsu.com

 

在Windows中做过图像方面程序的人应该都知道Windows的GDI有一个API函数:StretchBlt,对应在VCL中是TCanvas类的StretchDraw方法。它可以很简单地实现图像的缩放操作。但问题是它是用了速度最快,最简单但效果也是最差的“最近邻域法”,虽然在大多数情况下,它也够用了,但对于要求较高的情况就不行了。

不久前我做了一个小玩意儿(见《人个信息助理之我的相册》),用于管理我用DC拍的一堆照片,其中有一个插件提供了缩放功能,目前的版本就是用了StretchDraw,有时效果不能令人满意,我一直想加入两个更好的:线性插值法和三次样条法。经过研究发现三次样条法的计算量实在太大,不太实用,所以决定就只做线性插值法的版本了。

从数字图像处理的基本理论,我们可以知道:图像的变形变换就是源图像到目标图像的坐标变换。简单的想法就是把源图像的每个点坐标通过变形运算转为目标图像的相应点的新坐标,但是这样会导致一个问题就是目标点的坐标通常不会是整数,而且像放大操作会导致目标图像中没有被源图像的点映射到,这是所谓“向前映射”方法的缺点。所以一般都是采用“逆向映射”法。

但是逆向映射法同样会出现映射到源图像坐标时不是整数的问题。这里就需要“重采样滤波器”。这个术语看起来很专业,其实不过是因为它借用了电子信号处理中的惯用说法(在大多数情况下,它的功能类似于电子信号处理中的带通滤波器),理解起来也不复杂,就是如何确定这个非整数坐标处的点应该是什么颜色的问题。前面说到的三种方法:最近邻域法,线性插值法和三次样条法都是所谓的“重采样滤波器”。

所谓“最近邻域法”就是把这个非整数坐标作一个四舍五入,取最近的整数点坐标处的点的颜色。而“线性插值法”就是根据周围最接近的几个点(对于平面图像来说,共有四点)的颜色作线性插值计算(对于平面图像来说就是二维线性插值)来估计这点的颜色,在大多数情况下,它的准确度要高于最近邻域法,当然效果也要好得多,最明显的就是在放大时,图像边缘的锯齿比最近邻域法小非常多。当然它同时还带业个问题:就是图像会显得比较柔和。这个滤波器用专业术语来说(呵呵,卖弄一下偶的专业^_^)叫做:带阻性能好,但有带通损失,通带曲线的矩形系数不高。至于三次样条法我就不说了,复杂了一点,可自行参考数字图像处理方面的专业书籍,如本文的参考文献。

再来讨论一下坐标变换的算法。简单的空间变换可以用一个变换矩阵来表示:

[x’,y’,w’]=[u,v,w]*T

其中:x’,y’为目标图像坐标,u,v为源图像坐标,w,w’称为齐次坐标,通常设为1,T为一个3X3的变换矩阵。

这种表示方法虽然很数学化,但是用这种形式可以很方便地表示多种不同的变换,如平移,旋转,缩放等。对于缩放来说,相当于:

          [Su  0  0 ]

[x, y, 1] = [u, v, 1] * | 0  Sv  0 |

          [0   0  1 ]

其中Su,Sv分别是X轴方向和Y轴方向上的缩放率,大于1时放大,大于0小于1时缩小,小于0时反转。

矩阵是不是看上去比较晕?其实把上式按矩阵乘法展开就是:

{ x = u * Su

{ y = v * Sv

就这么简单。^_^

有了上面三个方面的准备,就可以开始编写代码实现了。思路很简单:首先用两重循环遍历目标图像的每个点坐标,通过上面的变换式(注意:因为是用逆向映射,相应的变换式应该是:u = x / Su 和v = y / Sv)取得源坐标。因为源坐标不是整数坐标,需要进行二维线性插值运算:

P = n*b*PA + n * ( 1 – b )*PB + ( 1 – n ) * b * PC + ( 1 – n ) * ( 1 – b ) * PD

其中:n为v(映射后相应点在源图像中的Y轴坐标,一般不是整数)下面最接近的行的Y轴坐标与v的差;同样b也类似,不过它是X轴坐标。PA-PD分别是(u,v)点周围最接近的四个(左上,右上,左下,右下)源图像点的颜色(用TCanvas的Pixels属性)。P为(u,v)点的插值颜色,即(x,y)点的近似颜色。

这段代码我就不写的,因为它的效率实在太低:要对目标图像的每一个点的RGB进行上面那一串复杂的浮点运算。所以一定要进行优化。对于VCL应用来说,有个比较简单的优化方法就是用TBitmap的ScanLine属性,按行进行处理,可以避免Pixels的像素级操作,对性能可以有很大的改善。这已经是算是用VCL进行图像处理的基本优化常识了。不过这个方法并不总是管用的,比如作图像旋转的时候,这时需要更多的技巧。

无论如何,浮点运算的开销都是比整数大很多的,这个也是一定要优化掉的。从上面可以看出,浮点数是在变换时引入的,而变换参数Su,Sv通常就是浮点数,所以就从它下手优化。一般来说,Su,Sv可以表示成分数的形式:

Su = ( double )Dw / Sw; Sv = ( double )Dh / Sh

其中Dw, Dh为目标图像的宽度和高度,Sw, Sh为源图像的宽度和高度(因为都是整数,为求得浮点结果,需要进行类型转换)。

将新的Su, Sv代入前面的变换公式和插值公式,可以导出新的插值公式:

因为:

b = 1 – x * Sw % Dw / ( double )Dw;  n = 1 – y * Sh % Dh / ( double )Dh

设:

B = Dw – x * Sw % Dw; N = Dh – y * Sh % Dh

则:

b = B / ( double )Dw; n = N / ( double )Dh

用整数的B,N代替浮点的b, n,转换插值公式:

P = ( B * N * ( PA – PB – PC + PD ) + Dw * N * PB + DH * B * PC + ( Dw * Dh – Dh * B – Dw * N ) * PD ) / ( double )( Dw * Dh )

这里最终结果P是浮点数,对其四舍五入即可得到结果。为完全消除浮点数,可以用这样的方法进行四舍五入:

P = ( B * N … * PD + Dw * Dh / 2 ) / ( Dw * Dh )

这样,P就直接是四舍五入后的整数值,全部的计算都是整数运算了。

简单优化后的代码如下:

int __fastcall TResizeDlg::Stretch_Linear(Graphics::TBitmap * aDest, Graphics::TBitmap * aSrc)

{

    int sw = aSrc->Width - 1, sh = aSrc->Height - 1, dw = aDest->Width - 1, dh = aDest->Height - 1;

    int B, N, x, y;

    int nPixelSize = GetPixelSize( aDest->PixelFormat );

    BYTE * pLinePrev, *pLineNext;

    BYTE * pDest;

    BYTE * pA, *pB, *pC, *pD;

    for ( int i = 0; i <= dh; ++i )

    {

        pDest = ( BYTE * )aDest->ScanLine[i];

        y = i * sh / dh;

        N = dh - i * sh % dh;

        pLinePrev = ( BYTE * )aSrc->ScanLine[y++];

        pLineNext = ( N == dh ) ? pLinePrev : ( BYTE * )aSrc->ScanLine[y];

        for ( int j = 0; j <= dw; ++j )

        {

            x = j * sw / dw * nPixelSize;

            B = dw - j * sw % dw;

            pA = pLinePrev + x;

            pB = pA + nPixelSize;

            pC = pLineNext + x;

            pD = pC + nPixelSize;

            if ( B == dw )

            {

                pB = pA;

                pD = pC;

            }

            for ( int k = 0; k < nPixelSize; ++k )

                *pDest++ = ( BYTE )( int )(

                    ( B * N * ( *pA++ - *pB - *pC + *pD ) + dw * N * *pB++

                    + dh * B * *pC++ + ( dw * dh - dh * B - dw * N ) * *pD++

                    + dw * dh / 2 ) / ( dw * dh )

                );

        }

    }

    return 0;

}

应该说还是比较简洁的。因为宽度高度都是从0开始算,所以要减一,GetPixelSize是根据PixelFormat属性来判断每个像素有多少字节,此代码只支持24或32位色的情况(对于15或16位色需要按位拆开—因为不拆开的话会在计算中出现不期望的进位或借位,导致图像颜色混乱—处理较麻烦;对于8位及8位以下索引色需要查调色板,并且需要重索引,也很麻烦,所以都不支持;但8位灰度图像可以支持)。另外代码中加入一些在图像边缘时防止访问越界的代码。

通过比较,在PIII-733的机器上,目标图像小于1024x768的情况下,基本感觉不出速度比StretchDraw有明显的慢(用浮点时感觉比较明显)。效果也相当令人满意,不论是缩小还是放大,图像质量比StretchDraw方法有明显提高。

不过由于采用了整数运算,有一个问题必须加以重视,那就是溢出的问题:由于式中的分母是dw * dh,而结果应该是一个Byte即8位二进制数,有符号整数最大可表示31位二进制数,所以dw * dh的值不能超过23位二进制数,即按2:1的宽高比计算目标图像分辨率不能超过4096*2048。当然这个也是可以通过用无符号数(可以增加一位)及降低计算精度等方法来实现扩展的,有兴趣的朋友可以自己试试。

当然这段代码还远没有优化到极致,而且还有很多问题没有深入研究,比如抗混叠(anti-aliasing)等,有兴趣的朋友可以自行参考相关书籍研究,如果你有什么研究成果,非常欢迎你为我的程序编写插件实现。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/453972.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蒙特卡洛分析 pmp_PMP基础名词介绍 | 59. 实施定量风险分析

点击上方蓝字关注我们你好&#xff0c;这是“兔子研习社”为管理新手推出的“PMP基础名词介绍”系列内容。如果你正打算转到管理岗位&#xff0c;或者想要学习国际通行的项目管理知识&#xff0c;那恭喜你&#xff0c;这里满满的干货会让你不虚此行。实施定量风险分析是就已识别…

深度学习案例之基于 CNN 的 MNIST 手写数字识别

一、模型结构 本文只涉及利用Tensorflow实现CNN的手写数字识别,CNN的内容请参考:卷积神经网络(CNN) MNIST数据集的格式与数据预处理代码input_data.py的讲解请参考 :Tutorial (2) 二、实验代码 # -*- coding:utf-8 -*- """Time : Author: Feng LepengFile …

怎样获取linux命令帮助?

获得命令使用帮助&#xff1a;内部命令&#xff1a;help COMMAND外部命令&#xff1a;COMMAND --help &#xff08;大多数命令有help选项&#xff09;命令手册&#xff1a;manualman [章节号] COMMAND其中man数据库是分章节的&#xff0c;相同的COMMAND出现在不同的章节表示…

编译安装 zbar 时两次 make 带来的惊喜

为了装 php 的条形码扩展模块 php-zbarcode&#xff0c;先装了一天的 ImageMagick 和 zbar。也许和我装的 Ubuntu 17.10 的有版本兼容问题吧&#xff0c;总之什么毛病都有&#xff0c;apt 不行&#xff0c;PPA 源也不行&#xff0c;编译安装还有几处源代码出错&#xff0c;装不…

python数组的乘法_在Python中乘法非常大的2D数组

我必须在Python中将非常大的2D数组乘以大约100次.每个矩阵由3200032000元素组成.我正在使用np.dot(X,Y),但是每次乘法都需要很长时间…在我的代码实例下面&#xff1a;import numpy as npX Nonefor i in range(100)multiplying Trueif X None:X generate_large_2darray()mu…

0阶指数哥伦布编码

指数哥伦布编码 规定语法元素的编解码模式的描述符如下&#xff1a; 比特串&#xff1a; b(8):任意形式的8比特字节&#xff08;就是为了说明语法元素是为8个比特&#xff0c;没有语法上的含义&#xff09; f(n):n位固定模式比特串&#xff08;其值固定&#xff0c;如forbidde…

TensorFolw 报错

1、报错1&#xff1a;ValueError: Only call softmax_cross_entropy_with_logits with named arguments (labels..., logits..., ...) 提示出错如下&#xff1a; Traceback (most recent call last):File "/MNIST/softmax.py", line 12, in <module>cross_en…

CentOS7种搭建FTP服务器

安装vsftpd 首先要查看你是否安装vsftp [rootlocalhost /]# rpm -q vsftpd vsftpd-3.0.2-10.el7.x86_64 #显示也就安装成功了&#xff01; 如果没有则安装vsftpd [rootlocalhost/]# yum install -y vsftpd 完成后再检查一遍 [rootlocalhost /]# whereis vsftpd vsf…

js循环

顺序——要加分号结束 分支&#xff1a;让程序根据条件不同执行不同的代码 if else语句用来做分支的 if&#xff08;条件&#xff09;{代码} if&#xff08;条件&#xff09;{代码}else{代码} else if&#xff08;条件&#xff09;{代码} if是嵌套。 switch...case&#xff1…

x264函数调用关系图

1 encoder 2 slice write 3 analyse FFMPEG中MPEG-2编解码函数调用关系图 1 Encoder &#xff08;函数调用从左到右&#xff0c;下同&#xff1b;图片显示不全时&#xff0c;请下载显示&#xff09; 2 P帧运动估计流程图 3 B帧运动估计流程图 4 decoder ffmpeg的mpeg2编码I帧代…

Tensorflow 加载预训练模型和保存模型

使用tensorflow过程中&#xff0c;训练结束后我们需要用到模型文件。有时候&#xff0c;我们可能也需要用到别人训练好的模型&#xff0c;并在这个基础上再次训练。这时候我们需要掌握如何操作这些模型数据。看完本文&#xff0c;相信你一定会有收获&#xff01; 一、Tensorfl…

在 ActiveReports 中嵌入 Spread 控件

Spread 是一款很出色的表格控件&#xff0c;Spread 可以使开发人员把具有兼容 Microsoft Excel 的电子表格添加到程序中。ActiveReports 提供了一个非常灵活的、简单的报表环境。下面将展示怎样在 ActiveReports 中使用 Spread for WinForm。和其他三方控件一样&#xff0c;Spr…

sort()函数、C++

Sort&#xff08;&#xff09;函数是c一种排序方法之一&#xff0c;它使用的排序方法是类似于快排的方法&#xff0c;时间复杂度为n*log2(n) &#xff08;1&#xff09;Sort函数包含在头文件为#include<algorithm>的c标准库中。 II&#xff09;Sort函数有三个参数&#x…

python waitkey_python中VideoCapture(),read(),waitKey()的使用

有以下程序import cv2cap cv2.VideoCapture(0)while cap.isOpened():ret,frame cap.read()cv2.imshow(frame,frame)c cv2.waitKey(1)if c 27:breakcap.release()cv2.destroyAllWindows()说明&#xff1a;程序段里&#xff0c;1、cv2.VideoCapture()函数&#xff1a;cap cv…

深度学习案例之 验证码识别

本项目介绍利用深度学习技术&#xff08;tensorflow&#xff09;&#xff0c;来识别验证码&#xff08;4位验证码&#xff0c;具体的验证码的长度可以自己生成&#xff0c;可以在自己进行训练&#xff09; 程序分为四个部分 1、生成验证码的程序&#xff0c;可生成数字字母大…

windows下使用pthread库

最近在看《C多核高级编程》这本书&#xff0c;收集了些有用的东西&#xff0c;方便在windows下使用POSIX标准进行Pthread开发&#xff0c;有利于跨平台。 -------------------------------------------------- windows下使用pthread库时间:2010-01-27 07:41来源:罗索工作室 作…

day 05 多行输出与多行注释、字符串的格式化输出、预设创建者和日期

msg"hello1 hello2 hello3 " print(msg) 显示结果为&#xff1a; # " "只能进行单行的字符串 多行字符串用 ,前面设置变量&#xff0c;可以用 表示多行 msghello1 hello2 hello3print(msg) 显示结果为&#xff1a; 当然如果没有设置变量&#xff0c;…

python数值计算guess_【python】猜数字game,旨在提高初学者对Python循环结构的使用...

import random #引入生成随机数的模块需求&#xff1a;程序设定生成 1-20 之间的一个随机数&#xff0c;让用户猜日期&#xff1a;2019-10-21作者&#xff1a;xiaoxiaohui目的&#xff1a;猜数字game&#xff0c;旨在提高初学者对Python 变量类型以及循环结构的使用。secretNu…

调试九法-总体规则

调试规则规则1 理解系统规则2 制造失败规则3 不要想&#xff0c;而要看规则4 分而治之规则5 一次只改一个地方规则6 保持审计跟踪规则7 检查插头规则8 获得全新观点规则9 如果你不修复bug&#xff0c;它将依然存在转载于:https://www.cnblogs.com/uetucci/p/7987805.html

深度学习之循环神经网络(Recurrent Neural Network,RNN)

递归神经网络和循环神经网络 循环神经网络&#xff08;recurrent neural network&#xff09;&#xff1a;时间上的展开&#xff0c;处理的是序列结构的信息&#xff0c;是有环图递归神经网络&#xff08;recursive neural network&#xff09;&#xff1a;空间上的展开&#…