Zodiac Wang
  • Home
  • Categories
  • Tags
  • Archives

各种图像处理库中imread函数的区别

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

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

Table of Contents

  • 1  常见的图片 io 方式
  • 2  分类
  • 3  代码实现
  • 4  总结

常见的图片 io 方式¶

  • PIL.Image.open
  • scipy.misc.imread
  • scipy.ndimage.imread
  • cv2.imread
  • matplotlib.image.imread
  • skimge.io.imread
  • imageio.imread

分类¶

  • PIL 类

    • 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

  • cv 类
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']
}

使用 matplotlib.pyplot.imshow 进行可视化

如果使用 cv2.imshow,那么由于它是针对 cv2 中 imread 返回的结果设计的,所以它内部会做通道顺序上的调整,传入为 BGR 则转换为 RGB,所以如果传入 RGB 显示的就是 BGR 了。

更新:

scipy 的读取方式被官方弃用,改用 imageio 库的 imageio.imread

matplotlib.image.imread 被弃用,改用 matplotlib.pyplot.imread

代码实现¶

In [1]:
#encoding=utf8
from PIL import Image
from scipy import misc, ndimage
import imageio
import numpy as np
import cv2
import matplotlib
import matplotlib.pyplot as plt 
import skimage
import sys
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( ) 
In [2]:
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
True
False
False
True
True
Out[2]:
<matplotlib.image.AxesImage at 0x7f4f6cc6bda0>

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

看一看为什么

In [3]:
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]]
(214639, 3)
[236 226 236 ... 225 225 225] [237 225 239 ... 227 227 227]
Out[3]:
Array([255,   1, 253, ..., 254, 254, 254], dtype=uint8)

原来imageio 和 cv2 读取到的图片数值会有略微的差异,比如 236 和 237 这样的差异

值得注意的是, im_imageio 和 im_cv2image 作差之后仍为 image 对象,由于 image 对象不允许存在负值,所以有些负数会被加上 256,比如 -1 会自动转换为 255

In [4]:
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
True
operands could not be broadcast together with shapes (1691,911) (1691,911,3) 
False
True
Out[4]:
<matplotlib.image.AxesImage at 0x7f4f6cdb2240>

图片为灰度图片时,除了 cv2 其他方式读取的图片都是单通道的,cv2 读取的图片依然是三通道的。

可以看出,cv读取的三通道图片无论交换通道顺序与否,显示效果都相同。

matplotlib 读取的结果也与其他两种不同,matplotlib 读取的结果为 0~1 之间的浮点数

In [5]:
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
True
True
In [6]:
np.allclose(im_imageio, im_cv2[:,:,0])
Out[6]:
True

cv2 对灰度图片的处理,是三通道完全一样,且任一通道都和其他读取方式(除了 matplotlib)读取的结果一样

和 imageio 读取结果略有不同的现象在但通道情况下似乎没有出现

In [7]:
im_imageio
Out[7]:
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)
In [8]:
im_matplotlib
Out[8]:
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)

总结¶

建议统一使用 imageio 进行图像 io,使用 skimage 进行图像处理。

References

  • Python 中各种imread函数的区别与联系
  • Python的各种imread函数在实现方式和读取速度上有何区别?

  • « PDB基本使用
  • [分享]Matplotlib-3D绘图 »

Published

11 22, 2018

Category

posts

Tags

  • Python 16
  • 图像处理 1

Contact

  • Zodiac Wang - A Fantastic Learner
  • Powered by Pelican. Theme: Elegant