可以用“普通”python将numpy数组保存为16位图像吗?
有没有办法用常见的Python库把一个numpy数组保存为16位的图像(比如tif或png格式)呢?我以前用过的这个方法,但需要安装FreeImage这个库,感觉有点麻烦。
这看起来是个很基础的任务,所以我觉得应该有scipy来处理这个问题,但scipy.misc.imsave只支持8位的图像。
有没有什么好主意呢?
5 个回答
我写了一个自定义脚本,只用到了numpy和OpenCV来完成这个任务:
(不过感觉这方法有点过于复杂了…)
import numpy as np
import cv2
def save_gray_deep_bits(filepath, float_array, bitdepth=16):
assert bitdepth in [8,16,24]
arr = np.squeeze(float_array)
assert len(arr.shape) == 2
assert '.png' in filepath
bit_iterations = int(bitdepth/8)
img_bgr = np.zeros((*arr.shape, 3), np.uint8)
encoded = np.zeros(arr.shape, np.uint8)
for i in range(bit_iterations):
residual = float_array - encoded
plane_i = (residual*(256**i)).astype(np.uint8)
img_bgr[:,:,i] = plane_i
encoded += plane_i
cv2.imwrite(filepath, img_bgr)
return img_bgr
def bgr_to_gray_deep_bits(bgr_array, bitdepth=16):
gray = np.zeros((bgr_array.shape[0], bgr_array.shape[1]), dtype = np.float32)
for i in range(int(bitdepth/8)):
gray += bgr_array[:,:,i] / float(256**i)
return gray
def load_gray_deep_bits(filepath, bitdepth=16):
bgr_image = cv2.imread('test.png').astype(np.float64)
gray_reconstructed = bgr_to_gray_deep_bits(bgr_image, bitdepth = bd)
return gray_reconstructed
bd = 24
gray_image_full_precision = np.random.rand(1024, 1024)*255.
save_gray_deep_bits('test.png', gray_image_full_precision, bitdepth = bd)
# Read image and check if our array is restored without losing precision
bgr_image = cv2.imread('test.png').astype(np.float64)
gray_reconstructed = bgr_to_gray_deep_bits(bgr_image, bitdepth = bd)
avg_residual = np.mean(np.abs(gray_reconstructed - gray_image_full_precision))
print("avg pixel residual: %.3f" %avg_residual)
如前所述,PyPNG非常有用。对于Enthought的用户,可以通过以下方式安装:
conda install -c eaton-lab pypng
我会使用库里的from_array
方法:
import png
import numpy as np
bit_depth = 16
my_array = np.ones((800, 800, 3))
png.from_array(my_array*2**bit_depth-1, 'RGB;%s'%bit_depth).save('foo.png')
模式使用的是PIL风格的格式,比如'L'、'LA'、'RGB'或者'RGBA',后面可以加上';16'或';8'来设置位深。如果不指定位深,就会使用数组的dtype。
想了解更多,可以点击 这里。
你可以把你的16位数组转换成一个双通道的图像(或者把24位数组转换成一个三通道的图像)。像下面这样的代码就可以很好地实现这个功能,只需要用到numpy这个库:
import numpy as np
arr = np.random.randint(0, 2 ** 16, (128, 128), dtype=np.uint16) # 16-bit array
print(arr.min(), arr.max(), arr.dtype)
img_bgr = np.zeros((*arr.shape, 3), np.int)
img_bgr[:, :, 0] = arr // 256
img_bgr[:, :, 1] = arr % 256
cv2.imwrite('arr.png', img_bgr)
# Read image and check if our array is restored without losing precision
img_bgr_read = cv2.imread('arr.png')
B, G, R = np.split(img_bgr_read, [1, 2], 2)
arr_read = (B * 256 + G).astype(np.uint16).squeeze()
print(np.allclose(arr, arr_read), np.max(np.abs(arr_read - arr)))
结果:
0 65523 uint16
True 0
这段关于png和numpngw的解释非常有帮助!不过,我觉得有一个小“错误”需要提一下。在把数据转换成16位无符号整数时,y.max()应该用y.min()。对于随机颜色的图片,这个问题不大,但如果是处理真实的图片,我们就得认真对待。下面是修正后的代码行……
z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)
一个替代方案是使用 pypng。你还是需要安装另一个包,但它是纯Python写的,所以应该很简单。(其实在pypng的源代码里有一个Cython文件,但用不使用它是可选的。)
下面是一个使用pypng将numpy数组写入PNG文件的例子:
import png
import numpy as np
# The following import is just for creating an interesting array
# of data. It is not necessary for writing a PNG file with PyPNG.
from scipy.ndimage import gaussian_filter
# Make an image in a numpy array for this demonstration.
nrows = 240
ncols = 320
np.random.seed(12345)
x = np.random.randn(nrows, ncols, 3)
# y is our floating point demonstration data.
y = gaussian_filter(x, (16, 16, 0))
# Convert y to 16 bit unsigned integers.
z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)
# Use pypng to write z as a color PNG.
with open('foo_color.png', 'wb') as f:
writer = png.Writer(width=z.shape[1], height=z.shape[0], bitdepth=16,
greyscale=False)
# Convert z to the Python list of lists expected by
# the png writer.
z2list = z.reshape(-1, z.shape[1]*z.shape[2]).tolist()
writer.write(f, z2list)
# Here's a grayscale example.
zgray = z[:, :, 0]
# Use pypng to write zgray as a grayscale PNG.
with open('foo_gray.png', 'wb') as f:
writer = png.Writer(width=z.shape[1], height=z.shape[0], bitdepth=16,
greyscale=True)
zgray2list = zgray.tolist()
writer.write(f, zgray2list)
这是彩色输出:
这是灰度输出:
更新:我创建了一个叫 numpngw
的库(可以在 PyPI
和 github
找到),它提供了一个函数,可以将numpy数组写入PNG文件。这个库里有一个 setup.py
文件,可以用来安装它,但核心代码在一个单独的文件 numpngw.py
中,你可以把它复制到任何方便的地方。numpngw
只依赖于numpy。
下面是一个生成和上面相同的16位图像的脚本:
import numpy as np
import numpngw
# The following import is just for creating an interesting array
# of data. It is not necessary for writing a PNG file.
from scipy.ndimage import gaussian_filter
# Make an image in a numpy array for this demonstration.
nrows = 240
ncols = 320
np.random.seed(12345)
x = np.random.randn(nrows, ncols, 3)
# y is our floating point demonstration data.
y = gaussian_filter(x, (16, 16, 0))
# Convert y to 16 bit unsigned integers.
z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)
# Use numpngw to write z as a color PNG.
numpngw.write_png('foo_color.png', z)
# Here's a grayscale example.
zgray = z[:, :, 0]
# Use numpngw to write zgray as a grayscale PNG.
numpngw.write_png('foo_gray.png', zgray)