彩色图像具有高度、宽度和颜色通道维度。
当表示为三维数组时,图像数据的通道维度默认是最后一个维度,但可以为了性能优化而将其移至第一个维度。
对初学者来说,使用这两种“通道排序格式”以及准备数据以满足特定偏好通道排序可能会令人困惑。
在本教程中,您将了解通道排序格式,如何准备和操作图像数据以适应这些格式,以及如何为不同的通道排序配置 Keras 深度学习库。
完成本教程后,您将了解:
- 图像的三维数组结构以及通道优先和通道在后数组格式。
- 如何添加通道维度以及如何在通道格式之间转换图像。
- Keras 深度学习库如何管理首选通道排序以及如何更改和查询此偏好设置。
用我的新书《计算机视觉深度学习》启动您的项目,包括分步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2019年4月更新:修正了代码注释中的一个错别字(感谢 Antonio)。
- 2019年9月更新:更新以适应 Keras 2.2.5 API 的微小更改。将 set_image_dim_ordering() 的用法改为 set_image_data_format()。
教程概述
本教程分为三个部分;它们是:
- 作为三维数组的图像
- 操纵图像通道
- Keras 通道排序
作为三维数组的图像
图像可以作为三维数组存储在内存中。
通常,图像格式有一个维度用于行(高度),一个用于列(宽度),一个用于通道。
如果图像是黑白的(灰度),通道维度可能不明确存在,例如,图像中每个(行,列)坐标有一个无符号整数像素值。
彩色图像通常有三个通道,分别用于红、绿、蓝分量的(行,列)坐标处的像素值。
深度学习神经网络要求图像数据以三维数组的形式提供。
即使您的图像是灰度图像,也适用此规则。在这种情况下,必须添加单个颜色通道的额外维度。
有两种方法可以将图像数据表示为三维数组。第一种是将通道作为数组的最后一个或第三个维度。这称为“通道在后”。第二种是将通道作为数组的第一个维度,称为“通道优先”。
- 通道在后。图像数据以三维数组表示,其中最后一个通道表示颜色通道,例如 [行][列][通道]。
- 通道优先。图像数据以三维数组表示,其中第一个通道表示颜色通道,例如 [通道][行][列]。
一些图像处理和深度学习库偏好通道优先排序,另一些则偏好通道在后排序。因此,熟悉这两种表示图像的方法很重要。
想通过深度学习实现计算机视觉成果吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
操纵图像通道
您可能需要更改或操纵图像通道或通道排序。
这可以使用 NumPy Python 库轻松实现。
让我们看一些例子。
在本教程中,我们将使用 Larry Koester 拍摄的一张照片,该照片拥有部分权利,内容是菲利普岛企鹅游行。

菲利普岛企鹅游行
照片由Larry Koester拍摄,部分权利保留。
下载图片并将其放置在您的当前工作目录中,文件名为“penguin_parade.jpg”。
本教程中的代码示例假设已安装 Pillow 库。
如何向灰度图像添加通道
灰度图像以二维数组形式加载。
在用于建模之前,您可能需要向图像添加一个显式通道维度。这不会添加新数据;相反,它会改变数组数据结构,使其具有一个额外的第三轴,该轴只有一个维度来保存灰度像素值。
例如,尺寸为 [行][列] 的灰度图像可以更改为 [行][列][通道] 或 [通道][行][列],其中新的 [通道] 轴只有一个维度。
这可以通过使用 expand_dims() NumPy 函数来实现。“axis”参数允许您指定新维度将添加到何处,例如,对于通道优先,添加到第一个;对于通道在后,添加到最后一个。
以下示例使用 Pillow 库将企鹅游行照片作为灰度图像加载,并演示如何添加通道维度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 扩展维度示例 from numpy import expand_dims from numpy import asarray from PIL import Image # 加载图像 img = Image.open('penguin_arade.jpg') # 将图像转换为灰度 img = img.convert(mode='L') # 转换为numpy数组 data = asarray(img) print(data.shape) # 添加通道优先 data_first = expand_dims(data, axis=0) print(data_first.shape) # 添加通道在后 data_last = expand_dims(data, axis=2) print(data_last.shape) |
运行示例首先使用 Pillow 库加载照片,然后将其转换为灰度图像。
图像对象被转换为 NumPy 数组,我们确认数组的形状是二维的,具体为 (424, 640)。
然后使用 expand_dims() 函数通过 axis=0 在数组前添加一个通道,并通过形状 (1, 424, 640) 确认更改。然后使用相同的函数通过 axis=2 在数组的末尾或第三维添加一个通道,并通过形状 (424, 640, 1) 确认更改。
1 2 3 |
(424, 640) (1, 424, 640) (424, 640, 1) |
另一种流行的扩展数组维度的方法是使用 reshape() NumPy 函数并指定一个包含新形状的元组;例如
1 |
data = data.reshape((424, 640, 1)) |
如何更改图像通道排序
彩色图像作为三维数组加载后,可以更改通道排序。
这可以通过使用 moveaxis() NumPy 函数来实现。它允许您指定源轴和目标轴的索引。
此函数可用于将通道在后格式(例如 [行][列][通道])的数组更改为通道优先格式(例如 [通道][行][列]),反之亦然。
以下示例以通道在后格式加载企鹅游行照片,并使用 moveaxis() 函数将其更改为通道优先格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 将图像从通道在后格式更改为通道优先格式 from numpy import moveaxis from numpy import asarray from PIL import Image # 加载彩色图像 img = Image.open('penguin_arade.jpg') # 转换为numpy数组 data = asarray(img) print(data.shape) # 将通道在后更改为通道优先格式 data = moveaxis(data, 2, 0) print(data.shape) # 将通道优先更改为通道在后格式 data = moveaxis(data, 0, 2) print(data.shape) |
运行示例首先使用 Pillow 库加载照片并将其转换为 NumPy 数组,确认图像以通道在后格式加载,形状为 (424, 640, 3)。
然后使用 moveaxis() 函数将通道轴从位置 2 移动到位置 0,并通过显示通道优先格式 (3, 424, 640) 确认结果。然后反转此操作,将位置 0 的通道再次移动到位置 2。
1 2 3 |
(424, 640, 3) (3, 424, 640) (424, 640, 3) |
Keras 通道排序
Keras 深度学习库对您希望以通道优先还是通道在后格式表示图像是不可知的,但使用该库时必须指定并遵循此偏好设置。
Keras 封装了许多数学库,每个库都有一个首选的通道排序。Keras 可能封装的三个主要库及其首选通道排序如下:
- TensorFlow:通道在后排序。
- Theano:通道优先排序。
- CNTK:通道在后排序。
默认情况下,Keras 配置为使用 TensorFlow,并且通道排序也默认为通道在后。您可以将任何库和 Keras 库与任何通道排序一起使用。
一些库声称,首选通道排序可能会导致性能出现巨大差异。例如,将 MXNet 数学库作为 Keras 后端使用时,建议使用通道优先排序以获得更好的性能。
我们强烈建议将 image_data_format 更改为 channels_first。MXNet 在 channels_first 数据上速度显著更快。
— 使用 MXNet 后端优化 Keras 性能,Apache MXNet
默认通道排序
库和首选通道排序列在 Keras 配置文件中,该文件存储在您的主目录下的 ~/.keras/keras.json 中。
首选通道排序存储在“image_data_format”配置设置中,可以设置为“channels_last”或“channels_first”。
例如,以下是 keras.json 配置文件的内容。在其中,您可以看到系统配置为使用 TensorFlow 和 channels_last 顺序。
1 2 3 4 5 6 |
{ "image_data_format": "channels_last", "backend": "tensorflow", "epsilon": 1e-07, "floatx": "float32" } |
根据您首选的通道排序,您将不得不准备您的图像数据以匹配首选排序。
具体而言,这将包括以下任务:
- 调整或扩展任何训练、验证和测试数据的维度以满足预期。
- 在定义模型时指定样本的预期输入形状(例如 input_shape=(28, 28, 1))。
模型特定通道排序
此外,那些设计用于处理图像的神经网络层,例如 Conv2D,也提供了一个名为“data_format”的参数,允许您指定通道排序。例如
1 2 |
... model.add(Conv2D(..., data_format='channels_first')) |
默认情况下,这将使用 Keras 配置文件中“image_data_format”值指定的首选排序。然而,您可以更改给定模型的通道顺序,反过来,数据集和输入形状也必须更改以使用模型的新通道排序。
当加载用于迁移学习的模型时,如果该模型的通道排序与您首选的通道排序不同,这会很有用。
查询通道排序
您可以通过打印 image_data_format() 函数的结果来确认您当前的首选通道排序。以下示例演示了这一点。
1 2 3 |
# 显示首选通道顺序 from keras import backend print(backend.image_data_format()) |
运行示例会打印您在 Keras 配置文件中配置的首选通道排序。在这种情况下,使用的是通道在后格式。
1 |
channels_last |
访问此属性有助于您根据系统首选的通道排序自动构建模型或以不同方式准备数据;例如
1 2 3 4 |
if backend.image_data_format() == 'channels_last': ... else: ... |
强制通道排序
最后,可以强制特定程序的通道排序。
这可以通过调用 Keras 后端的 set_image_data_format() 函数来实现,将其设置为“channels_first”(theano)用于通道优先排序,或“channels_last”(tensorflow)用于通道在后排序。
如果希望程序或模型无论 Keras 默认通道排序配置如何都能一致运行,这会很有用。
1 2 3 4 5 6 7 8 |
# 强制通道排序 from keras import backend # 强制通道优先排序 backend.set_image_data_format('channels_first') print(backend.image_data_format()) # 强制通道在后排序 backend.set_image_data_format('channels_last') print(backend.image_data_format()) |
运行示例首先强制通道优先排序,然后强制通道在后排序,并通过更改后打印通道排序模式来确认每个配置。
1 2 |
channels_first channels_last |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
总结
在本教程中,您了解了通道排序格式,如何准备和操作图像数据以适应这些格式,以及如何为不同的通道排序配置 Keras 深度学习库。
具体来说,你学到了:
- 图像的三维数组结构以及通道优先和通道在后数组格式。
- 如何添加通道维度以及如何在通道格式之间转换图像。
- Keras 深度学习库如何管理首选通道排序以及如何更改和查询此偏好设置。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
很棒的教程。我最近一直在研究深度学习。这个教程确实增加了一些现实知识,对我的学习很有帮助。感谢分享。
谢谢,我很高兴这能有所帮助。
TensorFlow 默认的 maxpoolingop 仅支持在 CPU 设备类型上的 nhwc
有人能帮我解决这个错误吗?
看起来是个警告。也许可以忽略。
您好,只是外观上的小问题。第一行代码15的注释与第12行的注释相同。它们应该不同。
谢谢 Antonio,已修复!
嗨,Jason,
我喜欢你的教程!我对 Keras 通道顺序有一个疑问。当我强制 Keras 为“通道优先”时,我需要对输入图像进行任何预处理吗?还是只保留像“train_datagen = ImageDataGenerator(rescale=1. / 255)”这样的设置?谢谢!
如果您强制 Keras 通道顺序,那么加载图像时就必须采用该顺序。
不确定 ImageDataGenerator 是否会为您处理,也许可以尝试一下?
感谢您的教程,我使用 tf.Keras 训练了一个通道在后的 ResNet 模型,我想将其部署到 TensorRT 中,因为 TensorRT 在通道优先时表现更好。
所以,有一个 uff 转换器应该接受通道在后格式,我尝试转换但没有成功,因为 fusedbatchv3 层不存在,所以我使用了 onnx 转换器,过程如下:
我的训练模型是通道在后,我在输入层之后添加了一个 permute,它没有增加任何权重。我用 keras2onnx 转换了我的模型,之后用 trtexec 得到了一个引擎,但它使用了通道优先。既然我添加了 permute,我试图欺骗 tensorrt 作为一个通道优先模型,只交换输入……这样做对吗?或者网络内部的卷积是否依赖于通道顺序,我不能这样欺骗 tensorrt?
将模型和数据分开。例如,确定模型是通道优先还是通道在后,然后修改数据以满足此预期。
关于模型转换,我不太熟悉,抱歉。
一个设置为通道在后、输入为 HCHW 的模型,与一个设置为通道优先的模型是否相同?如果我将输入层大小设置为接收通道优先,但没有将后端设置为通道优先,会发生什么?
您可以按模型、通过后端或两者兼顾来控制顺序。
非常有帮助。谢谢!
很高兴听到这个!
您的帖子总是清晰简洁地呈现观点!
这里我有一个关于不完整通道数据的最佳实践问题
我正在尝试在股票价格时间序列上测试深度学习模型。在我选择的范围内大约有2500只股票。股票价格数据的一个问题是,许多股票的首次公开募股(IPO)时间比其他许多股票晚得多,例如,Facebook(股票代码FB)于2012年上市,但IBM可能在1910年代就上市了。因此,如果我使用10年的历史数据,FB在IPO日期之前的数据是空的,例如np.nan。
我的问题是:为了使学习更稳健,我应该在数据准备中保留IPO日期之前的np.nan,还是将其填充为0?这两种方法的优缺点是什么?或者是否有其他更好的方法来处理这种情况?
谢谢!
谢谢!
也许可以探索针对您的数据和模型的许多不同数据转换,并使用受控实验来发现哪种效果最好。
非常感谢您发布这篇非常直观且有用的教程。
但我有一个关于灰度图像扩展一个维度的问题。当我们使用 numpy 添加一个额外的分量时,它会为该列添加所有的零吗?结果是,我们会丢失像素信息吗?例如,如果我们有一个 100 行乘 100 列的灰度图像,其灰度值为 0 到 10,当我们使用 expand_dim 时,我们如何确保其额外列包含必要的 0 到 10 的亮暗信息。抱歉,这可能是一个非常天真的问题,因为我是新手。也许我对行和列的理解不完全正确。
行 列 灰度
[0, 0, 5]
[0, 1, 3]
…
[100, 100, 3]
是的,新维度具有零值。
如果您将灰度图像扩展为彩色图像通道,您应该将灰度值复制到每个新的通道维度。
不客气!
谢谢,不知何故我再也看不到评论区我的问题了。真有趣。
我审核评论
https://machinelearning.org.cn/faq/single-faq/where-is-my-blog-comment
这篇文章对我很有帮助。谢谢!
不客气!
PyTorch 也是“通道优先”的:(N,C,H,W)
https://pytorch.ac.cn/docs/stable/generated/torch.nn.Conv2d.html
谢谢你!
嗨,Jason,
当我尝试在用 keras.Sequential 定义的模型上使用 model.fit(x_train,y_train, batch_size = 1000, epochs = 10) 时,该模型包含一个 conv2d(我确保在数据格式中指定了通道优先),然后是一些线性层,数据形状为:x_train – [6000,1,64,1000] 和 y_train – [6000,11],其中 6000 是我的训练集大小,我得到以下错误:
InvalidArgumentError: 图形执行错误
在 ‘gradient_tape/sequential_1/conv2d_3/Conv2D/Conv2DBackpropInput’ 节点处检测到(最近一次调用在)
但是,如果我使用通道优先数据,即 x_train 形状为 [6000,64,1000,1] 且 y_train – [6000,11],它就可以正常工作。我想知道这是否与 TensorFlow 偏爱通道优先有关,还是我哪里犯了错误?
嗨 Siddharth…您可能会发现以下讨论很有启发性
https://stackoverflow.com/questions/71153492/invalid-argument-error-graph-execution-error
先生,我有一个小疑问。
对于不同的图像格式(如 jpeg、png 等),它们的数组结构是否不同?当我尝试使用 NumPy 将图像转换为数组时,对于数据集(所有 jpeg 图像格式)它能正常工作,但当我尝试转换包含混合图像格式的数据集时,我遇到了错误。
嗨,Vijay……请您详细说明您遇到的错误,以便我们更好地帮助您。