Python PIL 处理无损16位TIFF图像有困难

11 投票
2 回答
13887 浏览
提问于 2025-04-17 18:21

我的系统是 Mac OS X v10.8.2。我有几张 2560x500 的无压缩 16 位 TIFF 图片(灰度图,使用的是无符号 16 位整数)。我首先尝试用 PIL(通过 Homebrew 安装,版本 1.7.8)来加载它们:

from PIL import Image
import numpy as np

filename = 'Rocks_2ptCal_750KHz_20ms_1ma_120KV_2013-03-06_20-02-12.tif'
img = Image.open(filename)

# >>> img
# <PIL.TiffImagePlugin.TiffImageFile image mode=I;16B size=2560x500 at 0x10A383C68>

img.show() 

# almost all pixels displayed as white.  Not correct.  
# MatLab, EZ-draw, even Mac Preview show correct images in grayscale.

imgdata = list(img.getdata()) 

# most values negative:
# >>> imgdata[0:10]
# [-26588, -24079, -27822, -26045, -27245, -25368, -26139, -28454, -30675, -28455]

imgarray = np.asarray(imgdata, dtype=np.uint16) 

# values now correct
# >>> imgarray
# array([38948, 41457, 37714, ..., 61922, 59565, 60035], dtype=uint16)

负值偏差了 65,536……这可能不是巧合。

如果我假装修改像素,然后通过 PIL 把它们还原成 TIFF 图片(只需把数组放回去当作图片):

newimg = Image.fromarray(imgarray)

我遇到了错误:

File "/usr/local/lib/python2.7/site-packages/PIL/Image.py", line 1884, in fromarray
    raise TypeError("Cannot handle this data type")
TypeError: Cannot handle this data type

我在 PIL 的文档中找不到 Image.fromarray() 这个方法。我尝试用 Image.fromstring() 来加载,但我看不懂 PIL 的文档,而且示例也很少。

如上面的代码所示,PIL 似乎把数据“识别”为 I;16B。根据我从 PIL 文档中了解到的,模式 I 是:

*I* (32-bit signed integer pixels)

显然,这不对。

我在 Stack Overflow 上看到很多帖子说 PIL 不支持 16 位的图片。我也看到有人建议使用 pylibtiff,但我觉得那是 Windows 专用的?

我在寻找一种“轻量级”的方法来在 Python 中处理这些 TIFF 图片。我很惊讶这竟然这么困难,这让我觉得问题对其他人来说应该是显而易见的。

2 个回答

5

试试 Pillow,这是一个“友好的”PIL分支。最近他们对16位和32位图像的支持做得更好了,包括在numpy数组的接口中。这段代码可以在最新的Pillow上运行:

from PIL import Image
import numpy as np

img = Image.open('data.tif')
data = np.array(img)
8

结果发现,Matplotlib可以用两行代码来处理16位无压缩的TIFF图片:

import matplotlib.pyplot as plt
img = plt.imread(filename)

# >>> img
# array([[38948, 41457, 37714, ..., 61511, 61785, 61824],
#       [39704, 38083, 36690, ..., 61419, 60086, 61910],
#       [41449, 39169, 38178, ..., 60192, 60969, 63538],
#       ...,
#       [37963, 39531, 40339, ..., 62351, 62646, 61793],
#       [37462, 37409, 38370, ..., 61125, 62497, 59770],
#       [39753, 36905, 38778, ..., 61922, 59565, 60035]], dtype=uint16)

就这样。我想这并不符合我对“轻量级”的要求,因为对我来说,Matplotlib是一个比较大的模块,但把图片转成Numpy数组的过程非常简单。我希望这能帮助其他人快速找到解决方案,因为这对我来说并不是很明显。

撰写回答