从图像文件中提取感兴趣的区域而无需读取整个图像

3
我正在寻找一种库(任何语言都可以),它能够在不必读取整个图像文件的情况下,读取图像文件的一个区域。我已经发现了一些选项,例如vips,它确实不会将整个图像保存在内存中,但似乎仍然需要最初完全读取它。我知道对于压缩格式如jpg,这可能不可行,但理论上来说,bmp或tiff应该允许进行这种类型的读取。

你需要更加具体。大多数图像格式都是压缩的,否则图像会很大,存储它们会浪费空间。 你可以始终编写自定义文件映射读取到特定位置,但这可能不比至少一次读取大部分图像更快或更节省内存。 - Adi Shavit
如果您已经知道坐标,那么您可能也知道需要的文件名,因此您可以提前将它们缓存到本地机器上,以便带宽不成问题。 - Mark Setchell
你放弃这个问题了吗?你似乎没有对任何人的答案进行评论,或者感谢他们或澄清很多事情。 - Mark Setchell
libvips可以满足你的需求,我认为。我已经添加了一些时间的答案。 - jcupitt
实际上,您无法读取常规TIFF图像的任意部分,即使它们在内部被组织为一组条带。两个主要问题是:1)条带可以是任何大小,甚至比图像本身还大,因此缓存非常困难;2)通过条带进行随机访问将对类似90度旋转的操作产生灾难性的性能影响——为了写入一个输出条带,您需要读取每个输入条带!要旋转整个文件,您需要多次读取它。 - jcupitt
显示剩余7条评论
5个回答

7

libvips会尽可能地只读取需要的部分。例如,如果你从一个大的PNG文件中裁剪出左上角的100x100像素,它会很快:

$ time vips crop wtc.png x.jpg 0 0 100 100
real    0m0.063s
user    0m0.041s
sys 0m0.023s

(这四个数字分别代表从wtc.png中裁剪出的区域的左侧、顶部、宽度和高度,并将其写入x.jpg)
但是,从底部附近裁剪出一个100x100像素的区域会比较慢,因为它必须先读取和解压缩像素,才能到达文件中所需获取的像素点。
$ time vips crop wtc.png x.jpg 0 9000 100 100
real    0m3.063s
user    0m2.884s
sys 0m0.181s

JPG和条带式TIFF的工作方式相同,尽管它们是更快的格式,但这不太明显。
一些格式支持真正的随机访问读取。例如,平铺式TIFF在所有地方都很快,因为libvips可以使用libtiff仅读取所需的瓦片。
$ vips copy wtc.png wtc.tif[tile]
$ time vips crop wtc.tif x.jpg 0 0 100 100
real    0m0.033s
user    0m0.013s
sys 0m0.021s
$ time vips crop wtc.tif x.jpg 0 9000 100 100
real    0m0.037s
user    0m0.021s
sys 0m0.017s

OpenSlide、vips、平铺式的OpenEXR、FITS、二进制PPM/PGM/PBM、HDR、RAW、Analyze、Matlab,以及可能还有其他一些工具都支持这种真正的随机访问方式。
如果您对此更加感兴趣,API文档中有一章节描述了libvips如何打开一个文件。

http://libvips.github.io/libvips/API/current/How-it-opens-files.md.html

这是使用pyvips在Python中进行裁剪和保存:

import pyvips

image = pyvips.Image.new_from_file(input_filename, access='sequential')
tile = image.crop(left, top, width, height)
tile.write_to_file(output_filename)
access=是一个标志,提示libvips可以流式传输该图像,以防底层文件格式不支持随机访问。对于支持随机访问的格式(如平铺式TIFF),您不需要这个标志。
您不需要写入文件。例如,以下代码将创建一个包含以JPG编码的文件的缓冲区对象:
buffer = tile.write_to_buffer('.jpg', Q=85)

或者这将直接写入 stdout

target = pyvips.Target.new_from_descriptor(0)
tile.write_to_target('.jpg', Q=85)

Q=85 是设置JPG Q因子的可选参数。你可以设置任何文件保存选项


这似乎是迄今为止最好的选择。Cris Luengo 给出的从 tiff 中提取特定图块而无需读取整个文件的答案非常有用,然而这解决了提取真正自定义 ROI 的问题,并且在添加最小的编程开销时处理了它! - andrei
我会使用Python接口 https://pypi.python.org/pypi/pyvips ,这样可以获得良好的性能并且编码也非常简单。如果您有任何问题,请在pyvips问题跟踪器上提问 https://github.com/jcupitt/pyvips/issues。 - jcupitt
有没有办法让 vips 写入 stdout 而不是像上面的 x.jpg 那样写入磁盘文件? - Mark Setchell
1
嘿,马克,有一个操作符可以将MIME类型写入stdoutvips jpegsave_mime x.tif,但你不能在命令行上与裁剪结合使用。你需要运行两个命令(将裁剪保存到文件,然后将文件作为mime写入),或者使用类似Python的东西。 - jcupitt
我添加了一个Python示例。感谢您的建议! - jcupitt
是的,只需使用格式扩展名,例如 vips crop x.png .jpg 0 0 10 10 | cat > x.jpg 将裁剪并将其作为 JPG 写入标准输出。 - jcupitt

1

ITK可以使用一些格式进行操作。有一种方法CanStreamRead,对于支持流式传输的格式,如MetaImageIO,它返回true。一个例子可以在这里找到。您可以在ITK的论坛上提出更详细的问题。


1
如果您对文件格式有控制权,我建议使用平铺的TIFF文件。这些文件通常用于数字病理学全幻灯片图像,大小平均为100kx30k像素左右。 LibTiff使得读取与所选ROI对应的平铺变得容易。平铺可以进行压缩而不会降低读取小区域的效率(无需解码整个扫描线)。

使用libtiff提取瓦片已经进行了测试,似乎按预期工作(即在低带宽连接上从大型tiff中提取瓦片具有良好的性能)。我仍然在研究其他解决方案,但这绝对解决了问题 :) - andrei
@andrei,确实,这正是平铺的TIFF格式的设计目的! - Cris Luengo

0

BMP格式(未压缩)足够简单,您可以自己编写函数。

TIFF格式稍微有点难度,因为有很多子格式。但是TIFF库(TIFFlib)支持“瓦片导向”I/O模式。http://www.libtiff.org/libtiff.html#Tiles


那是一个过时的LibTiff网站。请使用http://www.simplesystems.org/libtiff/代替。 - Cris Luengo

0

我不知道有这样的库解决方案。
低级别的文件读取访问是格式特定的,尤其是文件映射是操作系统特定的。

如果您可以访问原始字节,则假设您知道宽度、高度、深度和通道数等,则计算文件偏移量很容易,因此只需自己编写代码即可。

如果您正在通过网络传输提取的数据,则在将其发送到网络之前,如果提取的ROI相对较大,则可以考虑在内存中压缩它。


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