[原创]谈谈数字图像的缩放算法
数字图像的缩放,是一个十分有趣的问题,又是一个看似简单,但又有些复杂的问题。许多朋友在具备一定的计算机图形编程的基础知识以后,都可以自己设计出一些简单的位图缩放算法。在计算机图形学和数字图像处理等学科里面,已经详细的研究过了数字图像缩放这个问题,并且已经有了成熟的算法。一些朋友由于没有学习过计算机图形学和数字图像处理,所以凭借自己的想法设计的位图缩放算法存在许多缺陷。在本文中,我将和大家一起来研究这个问题,并且学习前人所总结出来的算法。图像的概念很容易理解,你睁开眼睛,所看到的都是图像了。而一幅画、一张照片,则是现实生活中记录图像的手段和载体。在科学上,我们需要对我们的研究对象建立起数学模型,因此有必要建立起图像的数学模型。一幅图像的数学模型可以简单的定义如下:
+-----------------------------------------------------+
Image = f(x, y);
其中 x, y 为 [0, 1] 上的实数
对于灰度图像 Image 也为 [0, 1] 上的实数
对于彩色图像 Image 则由 R, G, B 三个分量组成
+-----------------------------------------------------+
由于定义域和值域都在 [0, 1] 上,因此被称为连续图像模型
连续图像模型可以精确而完整的刻画所要描述的图像,然而在现实世界中,绝大多数图像都是无法通过这个数学模型进行描述的,因为现实世界中的图像,是不可能通过函数解析式的方法进行描述的。更多的时候,我们只能使用相机将现实图像的一部分信息,保存在胶片上,或者是使用画笔在画纸上绘制出图像。正是由于图像的这个特点,我们所建立的连续的图像模型,对研究图像而言,并没有什么用武之地,而传统的数学研究方法也因此无法用运用到图像上。
为了更加有效的研究和处理图像,我们利用离散数学的理论知识,为现实图像建立起了数字图像模型,并且使用计算这个强大的工具来帮助我们研究和处理图像。数字图像模型的定义如下:
+-----------------------------------------------------+
Image = array(i, j);
通常情况下 i 是大于等于 0, 小于 w 的整数
而 j 是大于等于 0, 小于 h 的整数
array 可以理解为一个矩阵
Image 的取值范围为大于等于 0, 小于等于 255 的整数
对于灰度图像 Image 表示某点的亮度值
对于彩色图像 Image 则由 R, G, B 三个分量组成
+-----------------------------------------------------+
其中的 w, h 通常被称为一幅数字图像的宽度和高度。这里,数字图像的宽度和高度,与一幅数字图像在显示器上实际的宽度和高度,有着一定的对应关系的,这个大家应该都很容易理解。我们所要讨论的数字图像的缩放问题,就是要改变一幅图像的宽度和高度,并且使 array(i, j) 这个矩阵中的数据相应的改变,使得图像按比例的进行缩放。
在数字图像处理上,数字图像的缩放又被称作重采样滤波,这很抽象但是却又深刻揭示出数字图像缩放的本质。当你将重采样滤波这个概念理解了,你会发现位图缩放是如此的简单明了,并会惊奇的发现重采样滤波是如此的神奇而深刻。
现在为了叙述的方便,我们将现实生活中所遇到的图像称为现实图像,将照片等称为物理图像,将连续图像模型成为连续图像,将数字图像模型成为数字图像或位图。那么,我们来看看一幅数字图像的产生过程。
首先,需要使用相机对现实图像进行拍照,产生照片,即物理图像。在物理图像中,仅仅记录了现实图像中的一部分信息。然后要将照片放到扫描仪上进行扫描,这个过程中图像从物理图像变换为了数字图像,在计算机中产生通常所说的位图。在图像从物理图像变换为数字图像这个过程中,最关键的地方就是采样与量化,这两个概念大家也许都非常熟悉,但是我们仍然需要深刻的理解和思考。
假如说,在扫描的过程中,我们将扫描的分辨率设置较大,也就是采样率设置较大,则扫描出来的数字图像的分辨率也较大,也就是图像的宽度和高度都较大,反之,则是变小。让我们再来深刻的理解下重采样滤波这个概念,所谓重采样滤波,指的是根据数字图像,以某种方式重建出物理图像,并且对这个重建出来的物理图像,以所需要的采样率进行重新采样。
让我们来设想这样一个过程,我们有一幅宽高分别为 (w1, h1) 的位图 A,我们想要将其缩放为宽高为 (w2, h2) 的位图 B. 我们可以通过这样的手段来完成缩放,也就是花一千块左右,买一台佳能的彩色激光打印机,将位图 A 用打印机打印出来。然后再买一个惠普的扫描仪,以 (w2, h2) 的扫描分辨率,将前面打印出来的图片扫描到电脑。这样,我们就顺利地完成了位图的缩放。当然,这样的做法成本太高,先后需要花去一千多块的大洋,而且费时又费力,是个理论上可行却不实用的办法。但是,这个办法,足以生动而清晰地说明数字图像缩放的本质和方法。
让我们再来体会一下重采样滤波的深刻吧,即重建物理图像,然后以你所需要的分辨率进行重新采样。在计算机世界里,数字图像是很容易描述的,用一个二维数组就可以简单的描述一幅数字图像。然而,我们怎样才能从已有的数字图像,重建其物理图像呢?好好想想吧,想不出来就只快点准备两千块大洋,去买打印机和扫描仪吧。不是吧大哥,难道我真要去买打印机,赶快揭晓答案。
好吧不开玩笑了,现在揭晓答案。在计算机中,重建物理图像其实是一种计算模型而已,物理图像在计算机世界里面是无法真正的重建的,毕竟计算机是离散系统,而物理图像是连续的事物,计算机无法完整而精确的进行描述。然而我们却可以找到多种的计算模型,来描述我们需要重建的物理图像。需要注意的是,我们找到的是计算模型,然后我们要根据这个计算模型,来进行重新采样。
图像从现实图像到物理图像再到数字图像的变换过程,是一个不可逆的变换过程,在每一次变换过程中,都会丢失掉大量的信息,是不可逆的。如果想要从数字图像重建物理图像,其实是在已有的数字图像数据的基础上,对物理图像做出的一种近似而已。对于已有的数字图像,我们有多种计算模型,来重建物理图像,而这个重建的物理图像,一般都是通过前面所讲的连续图像模型来描述。先介绍最简单的计算模型,最近邻算法。
请大家思考这样一些问题:
1. 我有一张 320 * 240 * 24bit 色的 BMP 图片,对于图片上任一点 (i, j) 我们都可以知道它的 RGB 颜色值,但是如果我想知道 (101.3, 98.6) 点的颜色值,我们该怎么办呢?
2. 我希望将一张 320 * 240 * 24bit 色的 BMP 图片,缩放为 1005 * 754 * 24bit 色的 BMP 图片,该如何进行重新采样呢?
第一个问题,也就是数字图像缩放的第一步,重建物理图像。如果我们采用最近邻算法,对于一个非整数的坐标点,我们选取距离这个点最近的整数坐标点的颜色值,作为其颜色值。也就是说,我们简单的将 (101.3, 98.6) 的颜色值取为点 (101, 99) 的颜色值。解决了这个问题,我们的脑子里面其实就放着一幅连续的图像了,并且对于这个连续图像上的任意一个像素点,我们都可以计算出其颜色值。
第二个问题,则是如何在重建起来的物理图像上进行重采样。所谓采样,就是要取得每个需要采样的颜色值,也就是要用一个二重循环,处理完 1005 * 754 个点,并且计算出每个点的颜色值。简单的代码如下:
+-------------------------------------------------------------------------+
DWORD color;
int i;
int j;
for (j=0; j<754; j++)
{
for (i=0; i<1005; i++)
{
color = resample(i, j); // 计算重采样点的颜色值
putpixel(destbmp, i, j, color); // 将采样点绘制到目的位图上
}
}
+-------------------------------------------------------------------------+
对于每个重采样点的颜色值的计算方法,则是需要根据原始的数字图像、你所选用的计算模型和由计算模型所重建的物理图像来共同决定。对于我们上面的例子,可以先计算出宽高缩放比,进而建立起原始图像和目的图像中像素点的对应关系和变换公式,根据变换公式,计算出目的图像中 (i, j) 点在 原始图像中对应的点的坐标 (x, y),根据重建物理图像的计算模型,计算出原始图像中 (x, y) 点的颜色值 c,然后将 c 作为结果绘制到目的图像中的 (i, j) 上。当你处理完目的图像中的每一个像素点,也就完成了数字图像的缩放了。
对于我们所举的例子,可以写出如下的代码:
+-------------------------------------------------------------------------+
int w1 = 320;
int h1 = 240;
int w2 = 1005;
int h2 = 754;
float xratio = (float) w1 / w2;
float yratio = (float) h1 / h2;
int i;
int j;
float x;
float y;
for (j=0; j<754; j++)
{
for (i=0; i<1005; i++)
{
x = i * xratio;
y = j * yratio;
color = getpixel(srcbmp, (int)(x + 0.5), (int)(y + 0.5));
putpixel(destbmp, i, j, color);
}
}
+-------------------------------------------------------------------------+
其中 | x = i * xratio; y = j * yratio; | 两句代码体现采样的位置,即在重建出来的图像的什么位置进行采样,而 | color = getpixel(srcbmp, (int)(x + 0.5), (int)(y + 0.5)); | 这句代码则体现了采样的结果,即在重建出来的图像的 (x, y) 处的采样的颜色值到底是多少。
请大家认真体会其中的深刻意义,所谓重建物理图像,实际上是无法真正重建的,也就是说这只是一种计算模型,一种方法。而重采样滤波,则是在计算模型的基础上进行的重新采样。数字图像缩放的精妙和深刻之处,就被重采样滤波这样一段话,简短而深刻地概括了。
通过以上的介绍,大家应当明白了数字图像缩放的基本原理和实现方法,并且能够利用最近邻法的计算模型,写出通用的位图缩放程序。可以看到,位图缩放的算法框架其实是非常简单的,关键还是需要理解其中的原理和方法。位图缩放的最近邻算法,在我的图形库中已经实现,大家可以在我的图形库中找到代码进行参考。
接下来介绍更多的重建物理图像的计算模型。除了最近邻点算法之外,还有双线性插值算法和曲面插值算法。其中双线性插值算法比较简单,且易于计算,因此在工程上得到了广泛的运用。而曲面插值可以得到更好的图像效果,但是其插值原理和计算方法都比较复杂,本文不再介绍。
所谓线性插值,大家应该不陌生,举个最简单的例子来说明问题。f(99) = 32, f(100) = 65, 用线性插值法求 f(99.3) 的值。方法如下:
f(100) - f(99) f(100) - f(99.3)
----------------- = --------------------
100 - 99 100 - 99.3
根据以上方程即可求出 f(99.3) 的值。之所以称为线性,是由于三个点都位于一条直线上。将以上的线性插值推广到二维情况,即是我们通常所说的双线性插值。
同样用一个例子说明问题:
f(123, 221) = 231, f(124, 221) = 35
f(123, 222) = 213, f(124, 222) = 86
求 f(123.8, 221.2) 的颜色值。
解,利用一维的线性插值,根据 f(123, 221) 和 f(123, 222) 求得 f(123, 221.2)
利用一维的线性插值,根据 f(124, 221) 和 f(124, 222) 求得 f(124, 221.2)
利用一维的线性插值,根据 f(123, 221.2) 和 f(124, 221.2) 求得 f(123.8, 221.2)
问题得解。
在双线性插值中,大家可以证明先沿 x 坐标计算和先沿 y 坐标计算,最终的结果都是一样的。当然,在实际运用中,大家还需要推算出通用的计算公式,在这里仅仅是以例子说明问题。
最后我们来总结一下吧。
数字图像缩放的基本原理:
1. 根据已有的数字图像重建物理图像
2. 对重建的物理图像以所需要的分辨率重采样
数字图像缩放的基本方法:
1. 建立起重建物理图像的计算模型
2. 建立起目的图像与原始图像的坐标变换关系
3. 计算目的图像中点 (i, j) 在原始图像中对应点的坐标 (x, y)
4. 根据计算模型计算出原始图像中点 (x, y) 处的颜色值 c
5. 将目的图像中点 (i, j) 的颜色值设置为颜色值 c
6. 处理完目的图像上每一个像素点,即可完成缩放
讲了这么多,应该是把数字图像缩放这个问题介绍得比较清楚了。大家关键还是需要理解数字图像的本质,采样和量化的概念,以及根据数字图像重建物理图像和重采样,即重采样滤波。最后再告诉大家两个概念,即数字图像的缩小,被称为降采样滤波,而放大则称为过采样滤波(这个不知道是不是这样叫的,就算我自己发明的叫法吧)。希望大家再看完本文以后,能够有所收获,理解数字图像缩放的原理和方法,并且实现自己的位图缩放程序。
RockCarry
2008-1-22
[[italic] 本帖最后由 RockCarry 于 2008-1-23 17:49 编辑 [/italic]]