图像缩放 - 超越 GDI+






4.90/5 (91投票s)
本文讨论了 GDI+ 滤镜的一个小缺点,并展示了一个用于高质量图像缩放的类。

引言
即使是 GDI+ 的方法也不是最全面的。常用的图像缩放方法包括:最近邻、双线性或双三次插值。这些是某些“标准”的一部分,但专家和发烧友知道,这些滤镜无法提供像其他滤镜那样精美的图像。在某些软件产品(尤其是专门用于天文和医学成像的软件)中,我们可以看到名称奇怪的滤镜,如 Hermite、Mitchell 或 Lanczos。这些滤镜参考图像的频率频谱或边缘进行工作。最令人不快的伪影之一是 Moiré 效应,当详细图像缩小缩放时会出现。为了防止这些伪影、增强边缘并减少像素化,设计了更复杂的滤镜。
尽管 GDI+ 滤镜在 Moiré 图案方面存在很大问题,它们会在图像边缘产生一些伪影(这不是错误,可以消除,稍后会讨论)。本文介绍了一个具有更精确滤镜的图像重采样类。
背景
第一个提到的伪影是由于混叠引起的 Moiré 图案。当在缩放图像之前不进行模糊处理就缩小图像时,就会出现这种伪影,高频部分会在新图像中产生图案。从旧图像中采样会在新图像中创建新的频率。当使用低半径的滤镜时(此滤镜包含来自旧图像的采样周围的相邻像素太少),这种效果最明显。这些示例显示了所谓的最近邻滤镜(左侧第一张图)产生的混叠伪影。
使用 GDI+低滤镜(第二张图 - 滤镜半径为 1)可以获得更好的质量。高质量双三次滤镜(半径 2)提供更好的质量。最后,这是通过 Lanczos8 滤镜(半径 8)从 ResamplingService
类滤波后输出的图像。此滤镜的半径是前者的三倍,因此提供了这些滤镜中最高质量的图像(屋顶瓦片更清晰可见)。




原始屋顶图像可以在维基百科的“Moiré 图案”文章中找到。很难看清,但最后一张图像比第三张更清晰,但在 GDI+ 重采样版本中存在另一个更明显的缺陷。


使用 DrawImage
方法重写中的 ImageAttributes
对象可以轻松解决此问题(不要忘记将其处置)。
System.Drawing.Imaging.ImageAttributes attr =
new System.Drawing.Imaging.ImageAttributes();
attr.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
//...
attr.Dispose();
另一种解决方案是将 Graphics 对象的 PixelOffsetMode
属性设置为适当的值。
Graphics grfx = Graphics.FromImage(imgInput);
grfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
使用 GDI+ 滤镜进行图像重采样
图像缩放(或重采样)是所有栅格图像处理工具最常见的功能之一。如果您决定使用 GDI+ 来缩放您的图像,您可以从多种滤镜中进行选择。所谓的最近邻(System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor)滤镜是最快的,但它会在图像拉伸时产生块状伪影,因为此滤镜只是将旧图像的采样复制到新图像(它们之间没有插值)。相反,当降采样因子为 2 时,每隔一个像素就会丢失。
使用一些插值样条可以获得更好的结果。当图像放大时,像素之间的空间必须用一些新的像素值填充。新像素的值可以计算为最近像素及其邻居的加权平均值(权重由滤镜给出)。最简单的插值/重采样滤镜之一是线性滤镜(InterpolationMode.Low)。邻近像素的权重是线性函数。如果您想获得“最佳”质量,请使用高质量双三次(InterpolationMode.HighQualityBicubic)。此插值在每个新像素值中包含更多邻居,并且权重分布更有效(函数是三次样条)。
缩放图像的插值是通过图像滤波,或图像(强度函数)与卷积核之间的卷积来完成的。如果您想更详细地了解卷积的工作原理,请参阅此网站上 Christian Grauss 的精彩文章。他还解释了双线性插值。
使用自定义滤镜进行图像重采样
现在将目光转向 ResamplingService.cs。此代码文件包含几个用于精确重采样的类。最重要的类是 ResamplingService
,其方法提供了所有功能。
有一个类代表常见的重采样滤镜。
public abstract class ResamplingFilter
运行重采样时,也会创建一个新的 ResamplingFilter
实例。滤镜取决于 ResamplingService
对象的 Filter 属性。
一些人提到这个库的旧版本无法对带 alpha 通道的图像进行重采样。因此,我根据这些反馈使其更加通用,主要是通过使用 ushort
数组(GDI+ Bitmap 除外)。此方法的优点如下。要重采样 GDI+ Bitmap,必须添加一些功能来提供 Bitmap 和 ushort[][,]
类型之间的转换(转换例程是针对每像素 32 位带 alpha 进行的,但可以轻松扩展以支持任意颜色格式)。
使用 ResamplingService
进行重采样在速度上有一个缺点(不是实时),但您将获得精美的结果。



在极端情况下,我们可以将四次放大的结果进行比较。结果分别使用 GDI+ HQ Bicubic 滤镜(左)、Paint Shop PRO 8 Smart Size(中)和当前实现的 Lanczos8 滤镜。
关于滤镜
我实现了一个包含不同重采样滤镜的集合。其中一些滤镜提供的结果甚至优于专业成像应用程序中的滤镜。ResamplingService
支持这些滤镜(您可以通过继承 ResamplingFilter
类并在基类的 Create
方法中添加新的滤镜创建来添加您自己的滤镜)。以下是 ResamplingFilters
枚举成员的简要描述。
Box | 放大时等同于最近邻,降采样时平均像素。 |
三角形 | 等同于低;该函数因其形状可以称为 Tent 函数。 |
Hermite | 使用 Hermite 插值的三次样条。 |
Bell | 试图在减少块状伪影和模糊图像之间取得折衷。 |
CubicBSpline | 最模糊的滤镜(三次 Bezier 样条)- 已知样本只是该曲线的“磁铁”。 |
Lanczos3 | 窗口化 sinc 函数(sin(x)/x)- 质量有希望,但可能会出现振铃伪影。 |
Mitchell | 另一个折衷方案,但对于放大非常出色。 |
余弦 | 尝试用余弦函数(偶函数)替换高阶多项式的曲线。 |
CatmullRom | Catmull-Rom 曲线,用于第一个图像扭曲算法(您看过《终结者 2》吗?) |
Quadratic | 性能优化的滤镜 - 结果与 B-Spline 类似,但只使用二次函数。 |
QuadraticBSpline | 二次 Bezier 样条修改。 |
CubicConvolution | 示例中使用的滤镜,其权重分布可以增强图像边缘。 |
Lanczos8 | 也是 sinc 函数,但窗口更大,此函数包含最大的邻域。 |
Using the Code
项目演示解决方案非常简单易懂。您需要做的就是通过将 ResamplingService.cs 复制到项目目录并在项目中使用它,只需要几行代码。
ResamplingService resamplingService = new ResamplingService();
resamplingService.Filter = ResamplingFilters.CubicBSpline;
ushort[][,] input = ConvertBitmapToArray((Bitmap)imgInput);
ushort[][,] output = resamplingService.Resample(input, nSize.Width,
nSize.Height);
Image imgCustom = (Image)ConvertArrayToBitmap(output);
此代码将创建一个 ResamplingService
实例,设置其 Filter 属性以使用 CubicBSpline
滤镜,将输入图像转换为数组,将数组位图重采样到新数组,最后将输出数组转换为图像。请注意,示例代码中的转换方法仅适用于像素格式为 32bpp 带 alpha(4 个通道)的位图。
超越传统方法
所介绍的滤镜(GDI+ 或其他)都无法保留边缘、添加人工细节或在图像上做更多的事情。有人可能会问:“那么那些使用边缘增强扩散、自适应样条或分形来获得良好视觉质量的昂贵的放大软件呢?”
事实是,这些技术是授权的,但并非未知。任何人都可以设计某种自适应滤镜(实际的内核取决于当前的像素邻域)。但这些滤镜对于实时应用(电视、DVD、游戏等)来说太慢了。是的,当然,这对于摄影师来说非常实用。他们可以保留质量而不是性能。
我对样条方法不太熟悉,但我对分形进行了一些研究,因为它不仅可以保留图像边缘,还可以为自然图像添加美观的细节。这些示例是使用非平凡的分形分辨率增强技术制作的。
我所在学院的许多学生对数字摄影感兴趣,因此分形库将免费提供给学术用途。这比市场上非常昂贵的专业放大软件要好。学术版和专业版之间的唯一区别是质量与速度的比例。PRO 软件速度快,结果有妥协,科学方法需要牺牲几十分钟来放大一张中等大小的图像,但质量提升是显而易见的(这里是 400% 放大)。
秋季林木图像的原始裁剪

Photoshop 的双三次滤镜

Genuine Fractals PrintPro - 现有的顶级放大软件

我们实现的基于分形的放大

结论
希望您对重采样主题有了简要的了解。我们讨论了如何使用自己的滤镜。要获取更多信息,请尝试查找一些技术文章。您可以找到几种其他重采样方法的实现,但要注意它们的属性。我测试了我的类在许多怪异的图像尺寸(例如 1x2397 来自 191x2 像素)上的表现,并消除了边缘的所有伪影,修复了我在其他来源中看到的错误权重分布。
一位程序员向我展示了他的专门用于重采样的 GUI。我发现并修复了一个严重的问题,该问题导致在以奇数因子缩小图像大小时,所有图像上都会出现黑白条纹 - 这当然是由于权重分布错误造成的。这在他开发商业组件前的-周。我认为提供的类还可以,但如果您发现任何问题或有任何关于如何优化此常见滤波算法的想法,我将很乐意与您讨论。
最新版本中的更改
代码经过修改,更具可读性(更多私有访问修饰符、全局变量减少、更多空行、缩进和 XML 注释)。该库使用 ushort[][,]
,而不是 Bitmap。为什么是 ushort[][,]
而不是 byte[,]
?我选择此类型是因为它可以包含几乎所有可想象颜色格式的位图(从黑白图像到每通道 16 位图像加 alpha),并且所有颜色格式都可以以相同的方式处理。ushort
的交错数组(ushort
范围是四个字节或 0-65535)可以包含任意数量的通道。选择交错数组而不是仅 ushort[,]
的原因在于,重采样过程会预先计算像素权重,然后用相同的权重过滤每个通道,因此这种解决方案在彩色图像上提供了更好的性能。代码现在是 100% 受管的(没有 unsafe 代码块)。库的使用方式也已更改,并且滤镜被枚举以使其更透明。