一看到题主的问题,其实就有个猜想,其实底层应该都是一样的,或者说没几个独立的实现吧?主要是关注开源软件一段时间之后,就会发现开源界其实挺缺人的,现有这些人也都挺懒的。挖坑的人多,填坑的人少。说是这活谁都可以参与,但其实真没多少人实际做。一个活有一个人干了,其他人一般就懒得再重复了。以下就是从源码来看这些开源库的具体实现。
1. PIL.Image.open
代码在这里:Pillow/Image.py at 3.1.x · python-pillow/Pillow · GitHub
open() 函数打开图像,但并不读入,直到有操作发生。
具体的读取操作是在 ImageFile.py 写的。大体流程是先检测文件类型,整块地读入文件内容,然后调用解码器解码,做了很多优化,效率应该还是很高的。
2. scipy.misc.imread
misc 的 __init__.py 在这里:scipy/__init__.py at v0.17.1 · scipy/scipy · GitHub
调用的还是 pilutil 中的 imread
相关代码如下:
try:
from .pilutil import *
from . import pilutil
__all__ += pilutil.__all__
del pilutil
except ImportError:
pass
也算是学了一招,从 pilutil 导入其所有函数添加到当前空间,然后又删除了 pilutil 消除影响。
3. scipy.ndimage.imread
代码在这里:scipy/io.py at v0.17.1 · scipy/scipy · GitHub
imread 调用 scipy.misc.pilutil.imread。从名字就能看出来其实调用的还是 Pillow。
根据 pilutil 代码:scipy/pilutil.py at v0.17.1 · scipy/scipy · GitHub
确实是调用 pil.image.open(),然后返回一个 fromimage()。
0410 更新:scipy.ndimage.imread,其实调用的就是 scipy.misc.imread。我之前没写清楚。代码在这里:/scipy/scipy/blob/80e76d43f0ada0a1964195cb6d13ae519feb07e8/scipy/ndimage/io.py#L8
_have_pil = True
try:
from scipy.misc.pilutil import imread as _imread
except ImportError:
_have_pil = False
挺过分的,本身已经用的是 pillow 的 imread 了。又在自己的包里面提供了两个不同的接口。不知道的,还以为做了多少工作呢。其实同一个代码文件里面也标记了,这个 imread 要废弃了,还请直接调用 matplotlib……啊???什么鬼!!!你自己包里面用了两处 pillow 的 imread,然后废弃一个,却推荐别人用 matplotlib 替代,口是心非傲娇代码包。戏精代码如下:
# Use the implementation of `imread` in `scipy.misc.pilutil.imread`.
# If it weren't for the different names of the first arguments of
# ndimage.io.imread and misc.pilutil.imread, we could simplify this file
# by writing
# from scipy.misc.pilutil import imread
# Unfortunately, because the argument names are different, that
# introduces a backwards incompatibility.
@np.deprecate(message="`imread` is deprecated in SciPy 1.0.0.\n"
"Use ``matplotlib.pyplot.imread`` instead.")
def imread(fname, flatten=False, mode=None):
if _have_pil:
return _imread(fname, flatten, mode)
raise ImportError("Could not import the Python Imaging Library (PIL)"
" required to load image files. Please refer to"
" /en/latest/installation.html"
" for installation instructions.")
4. skimage.io.imread
代码在这里:scikit-image/_io.py at master · scikit-image/scikit-image · GitHub
(0410 更新:该部分代码已修改,链接不可用了
是通过插件 plugin 来读入不同的文件,而且会试用几个不同的 plugins 来找到合适的。
使用 call_plugin 来调用,代码在这里:scikit-image/manage_plugins.py at master · scikit-image/scikit-image · GitHub
可以根据如下代码查看插件调用的优先级
# 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': ['pil', 'matplotlib', 'qt', 'freeimage'],
'imshow': ['matplotlib'],
'imshow_collection': ['matplotlib']
}
plugins 的源代码在这里:scikit-image/scikit-image
可以看到 pil 的 imread,是用 open 打开图像之后,再转换成 ndarray。
0410 更新:skimage 的插件管理,代码改在了这里:/scikit-image/scikit-image/blob/37ed59d2880fa96ff908608d7f723b98cd319c9b/skimage/io/manage_plugins.py#L46
最近一次更新去掉了 freeimage 的插件选项。图像读取的基本原理还是没变。还是优先调用 Pillow,就是插件里面的 pil。
preferred_plugins = {
# Default plugins for all types (overridden by specific types below).
'all': ['pil', 'matplotlib', 'qt'],
'imshow': ['matplotlib'],
'imshow_collection': ['matplotlib']
}
5. cv2.imread
这里是调用的 CV::imread(),代码在这里:opencv/loadsave.cpp at master · opencv/opencv · GitHub
。一般来说 C\C++ 的实现,应该比 python 速度快一点。
0410 更新:看了一下 opencv 的 python3 接口。imread 读进来的 Mat,在转换为 PyObject(Python 对象)的时候,会检查一下,是不是无符号的 UMat。如果是,直接拷贝一个指针给 PyObject;如果不是,会新开一个 Mat,读数据,再生成 PyObject。
而 CV::imread() 本身,调用的,是内部的 imread_() 函数:/opencv/opencv/blob/6d83a8038525cf64b0edd866ebbd94906eb5691d/modules/imgcodecs/src/loadsave.cpp#L633
cvLoadImage 到 IplImage 和 cvLoadImageM 到 CvMat,其实也都是调用的 imread_(),详情见代码:/opencv/opencv/blob/6d83a8038525cf64b0edd866ebbd94906eb5691d/modules/imgcodecs/src/loadsave.cpp#L966
就是这段:
CV_IMPL IplImage*
cvLoadImage( const char* filename, int iscolor )
{
return (IplImage*)cv::imread_(filename, iscolor, cv::LOAD_IMAGE );
}
CV_IMPL CvMat*
cvLoadImageM( const char* filename, int iscolor )
{
return (CvMat*)cv::imread_( filename, iscolor, cv::LOAD_CVMAT );
}
这里 imread_() 是图像读取的总接口,为表面的各种函数提供核心功能。图像读取的真正魔法,其实发生在 ImageDecoder 里面,代码则在 imgcodecs 目录:opencv/opencv
6. matplotlib.image.imread
matplotlib 的文档里面说,matplotlib 原生只可以读取 PNG 文件,有 PIL 的时候,可以读取其他类型的文件。如果使用 URL 打开在线图像文件,需要符合 PIL 的文档要求。
matplotlib.image.imread 的代码在这里:matplotlib/image.py at master · matplotlib/matplotlib · GitHub
matplotlib 的原生 PNG 读取和写入,是用 C 实现的,代码在这里:matplotlib/_png.cpp at master · matplotlib/matplotlib · GitHub
matplotlib 是先用 pil 的 open 打开图像,如果格式是 png,就用原生方法打开。相关代码如下:
handlers = {'png': _png.read_png, }
if format is None:
if cbook.is_string_like(fname):
parsed = urlparse(fname)
# If the string is a URL, assume png
if len(parsed.scheme) > 1:
ext = 'png'
else:
basename, ext = os.path.splitext(fname)
ext = ext.lower()[1:]
elif hasattr(fname, 'name'):
basename, ext = os.path.splitext(fname.name)
ext = ext.lower()[1:]
else:
ext = 'png'
else:
ext = format
if ext not in handlers:
im = pilread(fname)
if im is None:
raise ValueError('Only know how to handle extensions:%s; '
'with Pillow installed matplotlib can handle '
'more images' % list(six.iterkeys(handlers)))
return im
声明的处理器只有 png。如果是 png 文件,调用 _png.read_png。如果不是 png 直接使用 pilread(就是用 pil 的 Image.open 然后 pil_to_array)。
matplotlib 的源码确实比较复杂,一大部分主体是用 C 写的,改动很激进,功能更新猛烈。
0410 更新:matplotlib.image.imread() 是原生的。改名版有 matplotlib.pyplot.imread(),车祸现场见:/matplotlib/matplotlib/blob/dec87a31379c617704fce7225bfd1ad0dd799141/lib/matplotlib/pyplot.py#L2209
from matplotlib.image import imread as _imread
...
@docstring.copy_dedent(_imread)
def imread(fname, format=None):
return _imread(fname, format)
改名版还有 matplotlib.pylab.imread,代码见:/matplotlib/matplotlib/blob/438aa08df1037f059b4915b9a28871a90ae89976/lib/matplotlib/pylab.py#L246
from matplotlib.pyplot import *
小结
从源码看,绝大多数常用代码库其实都还是用的 PIL。原因容易理解,PIL 是纯 Python 实现的,而且经过了优化,性能应该还不错。除了 PIL 之外,常用的就是 OpenCV 和 Qt。OpenCV 和 Qt 是 C/C++ 实现的,可能速度会快一些,但具体要测试才知道。稍后会在 GitHub 上开一个对比测试的 Repo 再测试一下性能。
scikit-images 的插件机制很灵活有趣,尤其是它提供了其他几种图像读取的实现。
matplotlib 代码稍显乱一点,但更证明了其功能迭代速度快,开发激进。用 matplotlib 尽量多用PNG 以便利用原生的 PNG 支持
补充
补充一些读代码时遇到的一些其他的图像读取方法。
7. PyQt4.QtGui.QImage.load(filename)
8. freeimage
scikit-image 是使用 ctype 调用 freeimage 的动态链接库,来调用 Freeimage_Load()。代码在此:freeimage/FreeImage.h at master · imazen/freeimage · GitHub
9. imread
其在 PyPi 上的主页:Python Package Index
代码在此:imread/imread.py at master · luispedro/imread · GitHub