之前做的机器学习小项目中需要用到简单的图片处理,但是代码中使用了两个不同库里的图像处理函数,出了一个 Bug 找了很久才找到,今天就稍微总结下 Python 中几个不同图像库的 io 处理。

Python 中有很多用于图片处理的包,这些包实现了自己的输入输出函数,这些函数功能大体相同却有着一些细微的区别。

  • PIL.Image.open
  • scipy.misc.imread # 调用的还是 pilutil 中的 imread
  • scipy.ndimage.imread # 调用的就是 scipy.misc.imread
  • imageio.imread # dependency 有 pillow 应该也是调用 PIL 实现
  • 以上这些方法都调用 PIL.Image.open 读取图像的信息。

    PIL.Image.open 不直接返回 numpy.ndarray 对象而是 PIL image 对象,可以用 numpy 提供的函数进行转换

    imageio 返回 Image 对象,应该是 numpy.ndarray 的一种封装,通道顺序 RGB,通道值默认范围 0-255

    其他模块都直接返回 numpy.ndarray 对象,通道顺序为 RGB,通道值得默认范围为 0-255

  • matplotlib 类
  • matplotlib.image.imread # 独立实现 比较复杂
    

    直接返回 numpy.ndarray 对象,通道顺序为 RGB,读取彩色图像时通道值默认范围 0-255,读取单通道灰度图像时通道值默认范围 0.0-1.0

    cv2.imread # 独立实现
    

    使用 opencv 读取图像,直接返回 numpy.ndarray 对象,通道顺序为 BGR ,注意是 BGR,通道值默认范围 0-255

  • skimage 类
  • skimage.io.imread
    

    直接返回 numpy.ndarray 对象,通道顺序为 RGB,通道值默认范围 0-255

    通过插件选择不同的读取方式

    # For each plugin type, default to the first available plugin as defined by
    # the following preferences.
    preferred_plugins = {
        # Default plugins for all types (overridden by specific types below).
        'all': ['imageio', 'pil', 'matplotlib', 'qt'],
        'imshow': ['matplotlib'],
        'imshow_collection': ['matplotlib']
    from skimage import io
    %matplotlib inline
    %config InlineBackend.figure_format = 'retina'
    def display(imagepath):
        # PIL
        im_PIL = Image.open(imagepath)
        print("PIL type1", type(im_PIL))
        im_PIL = np.array(im_PIL)# PIL 方法读取后需要通过转换获得numpy对象,RGB
        print("PIL type2", type(im_PIL))
        print("PIL shape", im_PIL.shape)
        print("PIL min {} max {}".format(im_PIL.min(), im_PIL.max()))
        # imageio
        im_imageio = imageio.imread(imagepath)
        print("imageio type", type(im_imageio))
        print("imageio shape", im_imageio.shape)
        print("imageio min {} max {}".format(im_imageio.min(), im_imageio.max()))
        # # scipy.ndimage
        # im_scipy_ndimage = imageio.imread(imagepath)
        # print(type(im_scipy_ndimage))
        # print(im_scipy_ndimage.shape)
        # matplotlib
        im_matplotlib = plt.imread(imagepath)
        print("matplotlib type", type(im_matplotlib))
        print("matplotlib shape", im_matplotlib.shape)
        print("matplotlib min {} max {}".format(im_matplotlib.min(), im_matplotlib.max()))
        # cv2
        im_cv2=cv2.imread(imagepath)
        print("cv2 type", type(im_cv2))
        print("cv2 shape", im_cv2.shape)
        print("cv2 min {} max {}".format(im_cv2.min(), im_cv2.max()))
        # skimge
        im_skimge = io.imread(imagepath)
        print("skimge type", type(im_skimge))
        print("skimge shape", im_skimge.shape)
        print("skimge min {} max {}".format(im_skimge.min(), im_skimge.max()))
        # cv2.imshow('test',im4)
        # cv2.waitKey()
        #统一使用plt进行显示,不管是plt还是cv2.imshow,在python中只认numpy.array,但是由于cv2.imread 的图片是BGR,cv2.imshow 时相应的换通道显示
        plt.figure(figsize=(6,9))
        plt.subplot(321)
        plt.title('PIL read')
        plt.imshow(im_PIL)
        plt.subplot(322)
        plt.title('imageio read')
        plt.imshow(im_imageio)
        plt.subplot(324)
        plt.title('matplotlib read')
        plt.imshow(im_matplotlib)
        plt.subplot(325)
        plt.title('cv2 read')
        plt.imshow(im_cv2)
        plt.subplot(326)
        plt.title('skimge read')
        plt.imshow(im_skimge)
        plt.tight_layout()
        plt.show()
        print(np.allclose(im_imageio, im_PIL))
        try:
            print(np.allclose(im_imageio, im_cv2))
            print(np.allclose(im_imageio, im_cv2[:,:,::-1]))
        except ValueError as e:
            print(e)
        print(np.allclose(im_imageio, im_matplotlib))
        print(np.allclose(im_imageio, im_skimge))
        return im_PIL, im_imageio, im_cv2, im_matplotlib, im_skimge
    #     try:
    #         print(np.array_equal(im_PIL, im_imageio, im_cv2, im_matplotlib, im_skimge))
    #     except ValueError as e:
    #         print(e)
    #         print( ) 
    
    imagepath='img/doreamon.jpg'
    im_PIL, im_imageio, im_cv2, im_matplotlib, im_skimge = display(imagepath)
    plt.imshow(im_cv2[:,:,::-1]) #交换 BGR 中的 BR
    
    PIL type1 <class 'PIL.JpegImagePlugin.JpegImageFile'>
    PIL type2 <class 'numpy.ndarray'>
    PIL shape (1242, 1239, 3)
    PIL min 0 max 255
    imageio type <class 'imageio.core.util.Array'>
    imageio shape (1242, 1239, 3)
    imageio min 0 max 255
    matplotlib type <class 'numpy.ndarray'>
    matplotlib shape (1242, 1239, 3)
    matplotlib min 0 max 255
    cv2 type <class 'numpy.ndarray'>
    cv2 shape (1242, 1239, 3)
    cv2 min 0 max 255
    skimge type <class 'numpy.ndarray'>
    skimge shape (1242, 1239, 3)
    skimge min 0 max 255
    

    图片为三通道时,五种读取方式读取的结果几乎都一样,只是 cv2 读取的结果通道顺序为 BGR, 所以显示上出现了一点不同,通过转换将 BR 通道交换之后显示效果正常,但是 np allclose 函数显示两张图片还是有一些不同

    看一看为什么

    index = np.argwhere(im_imageio-im_cv2[:,:,::-1]!=0) # 不相等的 index
    print(index.shape)
    print(im_imageio[index[:,0], index[:,1],index[:,2]], im_cv2[:,:,::-1][index[:,0], index[:,1],index[:,2]]) # 对比差异
    # 作差比较
    s = im_imageio - im_cv2[:,:,::-1]
    s[index[:,0], index[:,1],index[:,2]]
    
    imagepath='img/Great-Britain.png'
    im_PIL, im_imageio, im_cv2, im_matplotlib, im_skimge = display(imagepath)
    plt.imshow(im_cv2[:,:,::-1]) #交换 BGR 中的 BR
    plt.show()
    plt.imshow(im_cv2) #交换 BGR 中的 BR
    
    PIL type1 <class 'PIL.PngImagePlugin.PngImageFile'>
    PIL type2 <class 'numpy.ndarray'>
    PIL shape (1691, 911)
    PIL min 127 max 255
    imageio type <class 'imageio.core.util.Array'>
    imageio shape (1691, 911)
    imageio min 127 max 255
    matplotlib type <class 'numpy.ndarray'>
    matplotlib shape (1691, 911)
    matplotlib min 0.49803921580314636 max 1.0
    cv2 type <class 'numpy.ndarray'>
    cv2 shape (1691, 911, 3)
    cv2 min 127 max 255
    skimge type <class 'numpy.ndarray'>
    skimge shape (1691, 911)
    skimge min 127 max 255
    
    cmp1 = im_cv2[:,:,0]==im_cv2[:,:,1]
    cmp2 = im_cv2[:,:,0]==im_cv2[:,:,2]
    print(np.all(cmp1)) # all True
    print(np.all(cmp2)) # all True
    
    Array([[255, 255, 255, ..., 255, 255, 255],
           [255, 255, 255, ..., 255, 255, 255],
           [255, 255, 255, ..., 255, 255, 255],
           [255, 255, 255, ..., 255, 255, 255],
           [255, 255, 255, ..., 255, 255, 255],
           [255, 255, 255, ..., 255, 255, 255]], dtype=uint8)
    array([[1., 1., 1., ..., 1., 1., 1.],
           [1., 1., 1., ..., 1., 1., 1.],
           [1., 1., 1., ..., 1., 1., 1.],
           [1., 1., 1., ..., 1., 1., 1.],
           [1., 1., 1., ..., 1., 1., 1.],
           [1., 1., 1., ..., 1., 1., 1.]], dtype=float32)