Advertisement

图像缩放的三种方法

阅读量:

用线性插值算法来执行图像缩放

最近我开发了一个简陋的小程序(参考了《个人信息助理之我的相册》一书),用于整理我在用DC相机拍摄的一批照片。其中有个插件具备缩放功能,在目前版本中采用了StretchDraw技术。然而有时效果不尽如人意,因此渴望引入两个更为先进的算法——线性插值法和三次样条法。经过深入研究后发现三次样条算法的计算复杂度过高,并非实用方案。最终决定仅实现线性插值算法版本。

在数字图像处理的基础理论中,在数字计算机上实现几何变换的过程中, 需要对二维空间中的图形进行缩放、平移等操作,这就涉及到将图形上的每一个像素点按照一定的规则移动到新的位置,从而生成缩放后的图形效果

然而,在使用逆向映射法时也会遇到一个问题:源图像坐标通常为非整数值。解决这一问题的方法被称为“重采样滤波器”。从表面上看,这一术语显得非常专业化;实际上它是从电子信号处理领域借用的一个常见术语(在多数情况下,其作用类似于带通滤波器)。理解起来并不复杂:关键是如何确定该非整数坐标的点应取何种颜色值的问题。前面提到的几种方法——最近邻域法、线性插值法以及三次样条插值法——都属于这一类别技术手段。

所谓"最近邻域法"即对非整数坐标进行四舍五入处理,并取其附近的整数点坐标位置上的像素颜色作为目标像素的颜色值。而"线性插值法"则是根据周围四个最邻近像素的颜色信息进行线性组合计算得到目标像素的颜色值,在放大图像时能够有效减少边缘锯齿现象,并显著提升图像质量。不过该方法在应用过程中也会带来一定的负面影响:图像整体呈现出柔和化效果。至于更为复杂的"三次样条插值法"由于其算法原理较为复杂建议读者参考相关数字图像处理教材以获得更深入的理解

进一步探讨坐标变换的相关算法。简单空间变换可通过一种变换矩阵来进行描述:

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

其中x', y'表示目标图像的坐标;u和v是源图像的坐标准确无误地表达了原意,并通过适当的词语替换和句式变化降低了重复率。

虽然采用了一种较为数学化的表达方式(虽然说起来有点抽象),但这种形式却能非常方便地描述多种变换类型(例如平移、旋转和缩放)。其中针对缩放操作而言,则等价于:

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

其中Su,Sv分别是在X轴和Y轴两个方向上的缩放因子,在缩放因子大于1时会实现图像的放大效果显著;在缩放因子介于0到1之间时会呈现出缩小效果;而当缩放因子为负值时会导致图像发生翻转。

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

{ x = u * Su

{ y = v * Sv

就这么简单。_

在完成了上述三个关键步骤之后

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

其中:n代表的是映射后相应点在源图像中Y轴坐标(通常非整数值)下方最近的一行对应的Y轴坐标与其差值;而b则是类似的情况但其对应的坐标系是X轴。PA-PD分别为(u,v)周围四个邻近位置处(左上角、右上角、左下角及右下角)对应的源图像像素颜色值。P即代表(u,v)经过插值算法计算得到的颜色值也就是(x,y)位置的最佳近似色。

这段代码我不打算编写(因为它)运行速度较慢:为了提高效率(需)针对每个像素执行一系列复杂的浮点运算(这一过程)。因此必须对其进行优化处理(以提升性能)。在VCL应用中(一种简便的方法)是利用TBitmap对象的ScanLine属性(按行扫描),从而避免逐像素操作(显著提升性能水平)。这是在VCL图像处理中常见的基础技巧之一。需要注意的是,在涉及几何变换时(这一方法)可能不再适用),通常需要结合其他高级算法来实现更好的效果。

不管怎样来说,在计算机运算中相比整数而言浮点运算所消耗的能量与资源明显更高这是一个不容忽视的问题必须进行优化处理通过观察可知在进行某种数据转换过程中会引入浮点数值而其中参数Su和Sv通常就是用于表示这种转换过程中的数值并且这些数值通常是浮点型数据因此应该重点针对这些参数进行优化处理一般来说 Su 和 Sv 可以表示为分数的形式:

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

设目标图像的宽度与高度分别为Dw与Dh,则源图像的宽度与高度分别记为Sw与Sh(此处需注意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为起点计算的原因,在实际操作中通常会减少1个单位来避免越界访问的问题。GetPixelSize 是通过查询 Pix_fmt 属性来确定每个像素所占的字节数。此代码目前仅支持 24 位或 32 位色的情况(对于15或16位色则需要逐个bit进行处理——如果不进行拆分,在计算过程中可能会出现无法预期的进位或借位问题)。而对于8位及更低的索引颜色来说,则需查阅调色表并重新索引——这一过程同样较为复杂;因此这些情况都不被支持;然而对于8bit灰度图像则可实现支持

经过对比实验,在一台PIII-733的计算机上进行测试分析发现:当处理的目标图像尺寸低于1024×768时(其中使用浮点运算时表现尤为突出),运行速度差异不明显。整体效果令人满意,在无论是缩放缩小还是放大操作下都能获得良好的视觉体验。此外,对比结果显示,在所有测试场景下(尤其是使用浮点运算时)图像质量得到了显著提升。

虽然采用的是整数运算而非浮点运算,在处理图像缩放比例时会遇到一个不容忽视的问题即溢出问题:因为公式中的分母是 dw 乘以 dh 而最终结果应为一个 8-bit 的有符号整型(可表示范围为 -127 到 +127),因此 dw 乘以 dh 的结果必须不超过 2^23-1 即按 2:1 的宽高比限制目标图像的最大分辨率不应超过 4096×2048 pixels。然而这个限制可通过采用无符号整型(增加一位有效数值范围)以及降低数值精度等方式来实现扩展供技术研究者进一步探索

显然这段代码尚未达到最佳优化状态。此外还有许多问题尚未深入探讨。例如抗混叠(anti-aliasing)等技术同样存在。如对这方面感兴趣的朋友可自行查阅相关资料进行深入研究。若你有任何研究成果发现,请随时告知我,并考虑将其应用于我的程序插件开发中。

全部评论 (0)

还没有任何评论哟~