【问题标题】:Can I save a numpy array as a 16-bit image using "normal" (Enthought) python?我可以使用“普通”(Enthought)python 将 numpy 数组保存为 16 位图像吗?
【发布时间】:2014-10-31 00:59:04
【问题描述】:

有没有办法使用任何常用的 python 包将 numpy 数组保存为 16 位图像(tif、png)? This是过去我唯一可以上班的方式,但我需要安装FreeImage包,这有点烦人。

这似乎是一个非常基本的任务,所以我希望它应该被 scipy 覆盖,但 scipy.misc.imsave 只做 8 位。

有什么想法吗?

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    另一种方法是使用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)
        # 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 的库(在 PyPIgithub 上可用),它提供了一个将 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)
    

    【讨论】:

    • 这很好用!!作为额外的奖励,pyng 是“Canopy 软件包”之一,虽然默认情况下没有安装,但可以通过 Canopy 程序一键轻松安装。
    • 感谢您的回答!不幸的是,当我运行第一个示例时,我收到以下错误:ProtocolError: ProtocolError: Expected 320 values but got 960 value, in row 0 写入文件时(程序的最后一行)。我正在使用0.0.20。会不会是包的api发生了一些变化?
    • @fabian,我不知道。您可以尝试在github.com/drj11/pypng 创建一个 github 问题
    • ProtocolError: ProtocolError: Expected 320 values but got 960 value, in row 0 is due to a change in pngWriter now default is grayscale=True. writer = png.Writer(z.shape[1], z.shape[0], bitdepth=16,greyscale=False) 解决问题。
    【解决方案2】:

    这个对png和numpngw的解释很有帮助!但是,我想我应该提到一个小“错误”。在转换为 16 位无符号整数时,y.max() 应该是 y.min()。对于随机颜色的图片,这并不重要,但对于真实的图片,我们需要做对。这是更正后的代码行...

    z = (65535*((y - y.min())/y.ptp())).astype(np.uint16)
    

    【讨论】:

    【解决方案3】:

    您可以将 16 位阵列转换为两通道图像(甚至将 24 位阵列转换为 3 通道图像)。像这样的东西很好用,只需要 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
    

    【讨论】:

      【解决方案4】:

      如前所述,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')
      

      Mode 使用 PIL 样式格式,例如'L'、'LA'、'RGB' 或 'RGBA',后跟 ';16' 或 ';8' 也设置了位深度。如果省略位深度,则使用数组的 dtype。

      阅读更多here

      【讨论】:

        【解决方案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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-08-06
          • 2010-10-28
          • 2014-08-18
          • 2015-02-20
          • 1970-01-01
          • 2011-10-18
          • 1970-01-01
          相关资源
          最近更新 更多