网站开发要什么流程,怎么做网站不被发现,wordpress付费查看vip购买查看,渭南网站开发用线性插值算法实现图像缩放 猛禽[Mental Studio](个人专栏)(BLOG) http://mental.mentsu.com 在Windows中做过图像方面程序的人应该都知道Windows的GDI有一个API函数#xff1a;StretchBlt#xff0c;对应在VCL中是TCanvas类的StretchDraw方法。它可以很简单地实现图像的缩放… 用线性插值算法实现图像缩放 猛禽[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’称为齐次坐标通常设为1T为一个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 用整数的BN代替浮点的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等有兴趣的朋友可以自行参考相关书籍研究如果你有什么研究成果非常欢迎你为我的程序编写插件实现。