C#如何将非常大的位图保存为JPEG(或任何其他压缩格式)

9

我目前正在处理非常大的图像,这些图像基本上是通过拼接许多较小的图像(例如全景或照片马赛克软件)生成的。为了避免内存溢出异常(内存中仅包含如何排列较小图像的“地图”),我编写了一些代码,使用BinaryWriter和LockBits将这些图像逐行保存为位图。目前为止,一切顺利。

问题是现在我也想将这些图像保存为Jpegs(或PNGs)。由于我对c#还比较陌生,我目前只能想到两种方法:

1)类似于位图保存过程。生成一些jpeg标题并逐行保存大图像,在压缩之前进行某种方式的压缩。不过我不知道如何进行压缩。

2)将已保存的位图流式传输到内存中,并将其保存为编码jpeg。

由于第二种方法似乎更容易,所以我尝试了以下内容:

FileStream fsr =
    new FileStream("input.bmp", FileMode.Open, FileAccess.Read);
FileStream fsw =
    new FileStream("output.jpg", FileMode.CreateNew, FileAccess.Write);

EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] =
    new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 80L);

Bitmap bmp = new Bitmap(fsr);
bmp.Save(fsw, GetEncoder(ImageFormat.Jpeg), encoderParameters);
bmp.Dispose();

问题在于保存方法首先尝试将位图完全加载到内存中,导致了内存不足异常。我非常希望能够得到任何解决或规避此问题的建议!

1
在这个时代还出现了OutOfMemoryException?你的位图有多大? - zmbq
2
是的,你很容易遇到内存问题。我正在调整大约10K张图像的大小,并且经常遇到“OutOfMemoryException”错误,尽管我已经正确释放了它们。 - DarthVader
1
从位图中OOM非常可疑。这个类会为几乎所有出错的情况(包括错误参数等)抛出内存异常。 - H H
也许这个 .NET封装器 可以帮助你使用 FreeImage 库。 - Magnus
你的方法适用于小位图吗?先测试代码。 - H H
显示剩余3条评论
3个回答

1
请注意,JPEG压缩有一些重要的非平凡缺点:
1)JPEG是有损压缩,因此您将无法再现相同的图像。这可能完全可以接受,但如果您需要精确地再现源图像,则PNG是更好的选择。
2)JPEG对齐在8像素边界上。如果您拼接大小不是8的倍数的图像,无论是宽度还是高度,您将在图像边界处得到一些强烈的伪影。
考虑您的用例,我建议不要将图像拼接在一起,并在每个较小的图像上使用标准压缩算法,例如PNG或Zlib。您仍然可以将结果压缩的流存储到单个文件中。优点是您可以直接“跳转”到要提取的小图像的位置,这将节省*很多*内存。
缺点是您无法“可视化预览”所有较小的图像成为一个大图像(您需要创建一个程序来实现此用途)。

首先,非常感谢。将代码改为以8x8像素块而不是逐行保存大图像应该不会有问题。此外,由于将其保存为JPEG文件而导致的轻微信息丢失并不重要。目前我的问题是,我绝对不知道如何分块地编写JPEG(或PNG)文件。 - mgulde
如果您想坚持使用“大图像”方法,我建议将其保存为8行一组的带。然后,您将在内存中获得一个宽度为x 8像素的位图,这将减少与完整图像相比的内存需求。 - Cyan
我刚修改了代码,现在正在将图像分为8行进行保存。但是仍然存在一个问题,我不知道如何将其保存为jpeg格式:对于bmp文件,我编写了标头,然后只需逐个流传ARGB字节值。我需要一些函数,可以编写jpeg标头,然后 - 这就是我认为比较棘手的部分 - 将这些条带编码为jpeg格式,并将它们逐块附加到现有文件中。 - mgulde
“Save”方法在此处不适用,不过也许可以使用“SaveAdd”方法(http://msdn.microsoft.com/en-us/library/tathhx27.aspx, http://msdn.microsoft.com/en-us/library/dsyxs70s.aspx)。否则,您需要直接管理流并使用外部库,例如FreeImage(http://freeimage.sourceforge.net/index.html)。 - Cyan
SaveAdd仅适用于多帧图像,例如GIF。我会看看FreeImage,但实际上我希望保持独立于外部库。 - mgulde
1
我刚刚查看了FreeImage,但是没有找到任何关于如何逐块写入JPG的方法。 - mgulde

0

我怀疑在.NET中你做不到这一点,但你可以调用C库来完成重活。我自己没有使用过,但我建议你看看GraphicsMagick库。它是一个带有许可证的C库,允许开源和商业使用,看起来它可能会满足你的需求。


谢谢,我会看一下的。但实际上我更倾向于避免使用额外的库。 - mgulde
我查看了GraphicsMagick库,但没有找到有关如何以块的形式编写JPEG(或PNG)的任何信息。 - mgulde

0

(因为标题说“或其他压缩格式”)

TIFF格式有瓦片和条带的概念。两者都可用于避免一次在内存中拥有整个大图像。也许对您有用的是瓦片,因为您正在拼接小图像。

您可以尝试任务使用LibTiff.Net(免费、商业友好、开源、仅限C#编写)。

有一篇文章库功能基础介绍,其中包含有关面向条带和瓦片的图像IO的信息(在文章的第二部分中)。

声明:我在这家公司工作。


谢谢,我会尝试的。你知道是否有类似的东西适用于jpeg、png、aso等吗?通常,我总是选择jpeg。 - mgulde
据我所知,对于JPEG格式的图片没有类似的东西。不幸的是,我不知道是否有类似的东西适用于PNG格式的图片。 - Bobrovsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接