低内存图像调整大小

7

我想请教如何构建一个非常低内存的图像调整程序,该程序将作为我的nodejs应用程序在linux中的子进程运行。

我需要的解决方案是一个Linux可执行文件,它将使用stdin接收客户端上传的base64字符串图像,将照片调整为指定的大小,然后通过stdout输出结果图像数据。

我已经研究了ImageMagick,可能会使用它,但我想问问是否有其他建议。

如果您能提供C/C++预编译可执行文件的库或示例,那将不胜感激。此外,有用的答案将包括低内存图像调整的一般策略。

谢谢


“尽可能低的内存”是什么意思?你可以让它使用多少内存?100KB?1MB?更多还是更少?它是否总是被允许使用与图像本身相同的内存量?在某些时候,降低内存使用会影响性能或图像质量。那么这种权衡何时是可接受的? - jalf
3
我认为问题在于某人可能会上传一个尺寸为40000x30000像素但只有几KB大小的JPEG文件,通过让服务器分配1.2GB内存来进行拒绝服务攻击。 - R.. GitHub STOP HELPING ICE
6个回答

9
根据您想要支持的图像格式,几乎肯定可以通过每次仅解码少量行并在写入输出后丢弃数据来执行增量解码和缩放。但是,这可能需要编写自己的代码或调整现有的解码器库以支持此类操作。
值得注意的是,通过简单地跳过高频系数并使用较小的IDCT,可以有效地缩小巨大的JPEG。例如,要将宽度和高度减半解码,请丢弃系数的除左上角象限以外的所有内容(水平和垂直频率<4),并对它们使用4x4 IDCT而不是通常的8x8。无论是libjpeg解码器还是libavcodec解码器都支持2次幂缩放(1/2、1/4或1/8)的操作。这种方法可能使增量解码/缩放变得不必要。
您可以使用"djpeg -scale 1/4 < src.jpg | cjpeg > dest.jpg"进行尝试。如果您想要固定的输出大小,则可能首先按最接近所需大小而不至于太低的1/2、1/4或1/8进行缩放,然后执行插值以完成最终步骤,例如"djpeg -scale 1/4 < src.jpg | convert pnm:- -scale 640x480 dest.jpg"。

djpeg是否支持标准输入/输出? - Skawful
是的,在我的答案示例中,它使用标准输入和标准输出。 :-) - R.. GitHub STOP HELPING ICE
抱歉我的幼稚。我现在明白了。您认为从一个进程(nodejs)向另一个进程(可能是djpeg、cjpeg)传输位是否会比在同一进程中进行处理更占用内存?如果这将使系统的内存成本增加2倍,我不想这样做。 - Skawful
2
它可能会使用更多(如果两个进程同时完全复制图像,则为2倍),或者更少(如果通过管道传输数据可以使整个图像从未在内存中保留),但可能大致相同(如果djpeg在解码时将图像写出而不在内存中保留它,而convert在加载整个图像以进行缩放时,则是我认为它们的工作方式...)。 - R.. GitHub STOP HELPING ICE

6

当处理像0.25 GPix及更大的非常大图像时,即使使用djpeg先解码JPEG图像,ImageMagick也会使用约2 GB的RAM。

以下命令链将仅使用约3 MB RAM调整大小为几乎任何大小的JPEG图像:

djpeg my-large.jpg | pnmscale -xysize 16000 16000 | cjpeg > scaled-large.jpg

这正是我所做的。好主意,它很容易实现,并且表现非常出色。不过我认为 R 也有同样的想法。 - Skawful

1

GraphicsMagick通常是ImageMagick的更好版本,我建议你看一下它。如果你真的需要快速处理,你可能想要使用类似libjpeg这样的库——虽然你说你想要非阻塞IO的东西,但你想要做的操作相对来说更加CPU密集(即解码图像,然后尝试调整大小)。


是的,这都是CPU/内存。很有道理。 - Skawful

1

在性能方面,没有什么能够击败英特尔集成性能原语。如果你有能力的话,我强烈建议使用它。

否则,只需实现自己的调整大小例程。Lanczos可以得到相当不错的结果,尽管速度不会非常快。

编辑:我强烈建议您不要使用Image Magick或Graphics Magick。它们都是很棒的库,但是设计用于完全不同的目的——处理许多文件格式、深度、像素格式等。它们为我提到的这些事情牺牲了性能和内存效率。


谢谢你的回答。我是否正确地认为,IIPP通过支持多线程获得了比其他解决方案更高的性能? - Skawful
它还是矢量化的,这也是它如此快速的主要原因。是的,它还支持多线程,进一步提高了性能。 - Aidynskas

1

如果有什么的话,这只是一个按照他所描述的样本:

import sys
from PIL import Image
import binascii
import cStringIO
x,y = sys.stdin.readline().strip().split(' ')
x,y = int(x), int(y)
img = Image.open(cStringIO.StringIO(binascii.b2a_base64(sys.stdin.read())).resize(x,y)
img.save(sys.stdout, format="png")

由于需要读取输入,解码、调整大小并重新编码和写出,因此无法将所使用的内存大小缩小到小于输入图像的大小。


请记住,将有两个进程共享这些数据。我认为有一种方法,就像R所提到的那样,通过标准IO传输小块而不是整个文件,就像你的例子一样。这样可以减小内存占用。你有什么想法? - Skawful
是的,可能可以,但我不认为Node.js能够使用这样的图像格式。通常情况下,即使是一个10kx10k 8位每组分量RGB图像也只有286.2MB,通常有足够的内存,如果没有,你就不处理这么大的图像。如果你有一个像JPEG这样的格式,你可以解码一个缩小版本,那么你应该使用它,但是如果解码后的缩减版本太大,你可能需要再次进行缩放,这样你就不能流式传输调整大小。 - Dan D.

0

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