Caffe实战,八:pycaffe和matcaffe接口读取图像的区别

Caffe实战(八):pycaffe和matcaffe接口读取图像的区别(特别注意通道顺序和维度顺序差别)

在进行caffe训练网络时,输入图像数据(特指三通道彩色图像)的通道顺序为BGR,blob维度顺序为[ num channel height width ],动态范围[0, 255]。因此,在进行网络模型测试时,输入数据一定要符合网络模型所需的数据格式,如果不符合,则要进行相应的转换。

这是由于在caffe中,加载图像调用的是cv::imread()函数,而cv::imread默认加载的通道顺序是BGR,范围[0, 255]。caffe首先通过convert_imagenet_data函数转换为lmdb格式数据,其中调用的是cv::imread,默认读取的是BGR顺序,所以通过这种方式训练的网络是以BGR通道顺序的数据训练的,则在测试时一定要输入对应的BGR通道顺序数据。

然后在采用不同语言环境(C++,python,matlab),调用不同的读取图像的函数(cv2.imread,imread)获取的图像格式有所差别,即便调用caffe自带的加载图像的函数caffe.io.load_image(),在pycaffe和matcaffe中获得的数据格式也不相同。这些很容易混淆,造成输入数据格式不满足网络模型需要的数据形式,影响测试的效果,而且很不容易察觉。本篇文章对不同情况下加载图像数据进行分析和对比,并进行总结,避免今后在读取数据上面犯错误。

matcaffe接口加载图像数据

因为Matlab的标号从1开始,且以列(column)为主,则blob通常的4个维度在Matlab中用 [width, height, channels, num]表示,width是第一维。另外,图像通道形式为BGR。Caffe使用单精度float型数据。如果你的数据不是单精度的,set_data 会自动将数据转换为单精度。

在使用matlab时,网络模型需要的数据形式:BGR,[width, height, channels, num](特别,特别,特别注意),[0, 255](单精度)

【caffe.io.load_image()加载图像】

caffe.io 类提供了基础的输入函数 load_image() 和read_mean()。

mean_data = caffe.io.read_mean(\'./data/ilsvrc12/imagenet_mean.binaryproto\');
 
通过caffe.io.load_image函数加载的图像已经是BGR,[width,height,channel]形式,可以直接使用。如果训练/测试要求的输入图像为其它尺寸,只需要改变大小就行,不用调整width,height的维度顺序。
im_data = caffe.io.load_image(\'./examples/images/cat.jpg\');    % 格式,维度顺序符合要求
im_data = imresize(im_data, [width, height]);                  % 只需要改变训练/测试要求的尺寸即可,resize using Matlab\'s imresize

这是因为在matcaffe提供的caffe.io.load_image() 中是通过imread()函数加载图像的,然后进行通道顺序的变换RGB-->BGR,维度顺序的变换H x W x C --> W x H x C。具体可以参考源码matcaffe\+caffe\io.m文件

classdef io
  % a class for input and output functions
  
  methods (Static)
    function im_data = load_image(im_file)
      % im_data = load_image(im_file)
      %   load an image from disk into Caffe-supported data format
      %   switch channels from RGB to BGR, make width the fastest dimension
      %   and convert to single
      %   returns im_data in W x H x C. For colored images, C = 3 in BGR
      %   channels, and for grayscale images, C = 1
      CHECK(ischar(im_file), \'im_file must be a string\');
      CHECK_FILE_EXIST(im_file);
      im_data = imread(im_file);
      % permute channels from RGB to BGR for colored images
      if size(im_data, 3) == 3
        im_data = im_data(:, :, [3, 2, 1]);
      end
      % flip width and height to make width the fastest dimension
      im_data = permute(im_data, [2, 1, 3]);
      % convert from uint8 to single
      im_data = single(im_data);
    end

【imread()加载图像】

如何知道caffe.io.load_image()函数怎么加载图像并进行转换的,那么自己很容易实现相应的转换。

记住width 是第一维度,通道为BGR,这与Matlab通常存储图片的方式不同。

im_data = imread(\'./examples/images/cat.jpg\'); % read image
im_data = im_data(:, :, [3, 2, 1]); % 从RGB转换为BGR
im_data = permute(im_data, [2, 1, 3]); % 改变width与height位置;其实相当于对im_data(:,:)转置,即把width按照列排,因为matalb是以列为主的。
im_data = single(im_data); % 转换为单精度

这里一定要注意加载图片的通道顺序和维度顺序:

  • matlab中imread()函数得到的通道顺序是RGB,而网络训练时使用的是BGR;因此需要对通道顺序改变。但是,在matcaffe中,caffe.io.load_image()函数加载的通道顺序是BGR,通道顺序与训练网络模型使用的通道顺序一致。(这是由于在matcaffe接口中,caffe.io.load_image()内部已经将输入的图像转换为网络模型需要的数据形式,具体可查看源码)
  • Matlab中用 [width, height, channels, num]表示,width是第一维,因此一定要改变图片宽高的顺序。(注意matlab矩阵中是按照列排的,因此第一维的数据width是按照列存的)

因此,在matcaffe中通过caffe.io.load_image()自带的函数加载的图像是符合训练/测试要求的,只需要改变图像的尺寸即可;而利用matlab自带的imread()加载的图像数据,则需要改变通道顺序(RGB-->BGR),宽高维度顺序([height,width]-->[width,height]),数据类型的改变uint8-->single。

pycaffe接口加载图像数据

在使用python调用pycaffe接口时,网络模型需要的数据形式为:BGR,[ num channel height width ],[0, 255](uint8)

【caffe.io.load_image()加载图像】

在python中,调用caffe.io.load_image()函数,获得的数据形式是RGB,[H W 3],[0, 1]。因此,需要对加载的数据进行以下操作:

  • 通道顺序的改变:RGB-->BGR,transformer.set_set_channel_swap(\'data\', (2,1,0))
  • 数据维度的改变:H x W x C --> C x H xW,transformer.set_transpose(\'data\', (2,0,1))
  • 取值范围的改变:[0,1]--> [0, 255], transformer.set_raw_scale(\'data\',255)
#transformer      
    transformer = caffe.io.Transformer({\'data\': net.blobs[\'data\'].data.shape})  
    transformer.set_transpose(\'data\', (2,0,1))      # 图片的默认格式是(height,width,channel),修改为(channel,height,width)
    transformer.set_channel_swap(\'data\', (2,1,0))   # from RGB to BGR
    transformer.set_raw_scale(\'data\', 255)          # from [0,1] to [0,255]
    transformer.set_mean(\'data\', np.load(self.mean_file).mean(1).mean(1)) # mean ixel
 
image = caffe.io.load_image(img_path)
transformed_image = transformer.preprocess(\'data\',image)

caffe.io.Transformer输入的是一个字典类型的数据,key为\'data\',value为net.blobs[\'data\']的形状。即表达的意思是针对key为\'data\',对应的shape设置的一系列的变换操作,也可以起其它名字的key。在transformer.preprocess使用时,对图像进行key为\'data\'对应的变换操作。

caffe.io.load_image()函数的源码为pycaffe\caffe\io.py

(如果不设定,返回值的图像也是RGB三个通道的图像,可以在参数中加一个False这个参数,返回就是灰度图像来。)

def load_image(filename, color=True):
    """
    Load an image converting from grayscale or alpha as needed.
 
    Parameters
    ----------
    filename : string
    color : boolean
        flag for color format. True (default) loads as RGB while False
        loads as intensity (if image is already grayscale).
 
    Returns
    -------
    image : an image with type np.float32 in range [0, 1]
        of size (H x W x 3) in RGB or
        of size (H x W x 1) in grayscale.
    """
    img = skimage.img_as_float(skimage.io.imread(filename, as_grey=not color)).astype(np.float32)
    if img.ndim == 2:
        img = img[:, :, np.newaxis]
        if color:
            img = np.tile(img, (1, 1, 3))
    elif img.shape[2] == 4:
        img = img[:, :, :3]
    return img

从io.py文件中可以看出,caffe.io.load_image()是通过skimage库来加载图像,skimage.io.imread()获得的图像数据范围[0, 255](uint8),通过skimage.img_as_float()转为为[0,1](float64).

【cv2.imread()加载图像】

通过OpenCV库中的cv2.imread()加载图像的数据形式为:BGR,[H W 3],[0, 255]。因此,只需要对维度数据进行改变即可:

  • 数据维度的改变:H x W x C --> C x H xW,transformer.set_transpose(\'data\', (2,0,1))
#transformer      
    transformer = caffe.io.Transformer({\'data\': net.blobs[\'data\'].data.shape})
    transformer.set_transpose(\'data\', (2,0,1))      # 图片的默认格式是(height,width,channel),修改为(channel,height,width)
    #transformer.set_channel_swap(\'data\', (2,1,0))   # from RGB to BGR
    #transformer.set_raw_scale(\'data\', 255)          # from [0,1] to [0,255]
    transformer.set_mean(\'data\', np.load(self.mean_file).mean(1).mean(1)) # mean ixel
 
image = cv2.imread(img_path)
transformed_image = transformer.preprocess(\'data\',image)

skimage.io.imread()加载图像可能存在的问题

skimage.io.imread()得到的是uint8的数据,而caffe.io.load_image()得到的是0-1之间的小数。

img=skimage.io.imread(img_path), uint8,0-255
img=caffe.io.load_image(img_path), float,0-1

在caffe中使用的时候,需要将caffe.io.load_image()获得的结果转为[0,255]之间,即:

img=skimage.io.imread(img_path),uint8,0-255
img=caffe.io.load_image(img_path)*255,float,0-255

然后在训练数据时,需要对输入数据减去均值,这里特别容易出错:

img=skimage.io.imread(img_path)-mean,uint8,0-255
img=caffe.io.load_image(img_path)*255-mean,float,0-255

由于skimage.io.imread()获得数据类型是uint8,减掉均值(uint8)后,很多地方变成0。这个在对整图操作时,可能影响还不大,但是如果考虑局部的信息,很可能获得局部大部分为0情况。

而第二种情况,因为是浮点数,减均值后还是有值的,在0附近的小数,于是这个还是比较正常的输入值,对DL来说,当你定位到局部信息时,还是比较真实的。

改进:

img=(skimage.io.imread(img_path))*1.0  float-255
img=caffe.io.load_image(img_path)*255  float,0-255

另外,如何设置的mean为浮点数,则不会出现上述情况。因为python同样遵循数据类型向优先级高的方向转换。

总结

  1. caffe中网络模型需要的输入数据形式为:BGR,[ num channel height width ],[0, 255];
  2. matcaffe接口需要的网络模型输入数据形式为:BGR,[width, height, channels](特别,特别,特别注意),[0, 255](单精度)
    • 通过caffe.io.load_image()获得的数据形式为:BGR,[width, height, channels],[0, 255](单精度),因此不需要进行任何数据形式的变化;
    • 通过imread()获得的数据形式为:RGB,[height, width, channels], [0, 255](uint8),因此需要进行通道顺序的变换RGB-->BGR,维度顺序的变换[height, width, channels]-->[width, height, channels],以及数据类型的变换uint6-->single;
  3. pycaffe接口需要的网络模型数据输入形式为:BGR,[ num channel height width ],[0, 255];
    • 通过caffe.io.load_image()获得的数据形式为:RGB,[height,width, channels],[0, 1],因此需要进行通道顺序的变换RGB-->BGR,维度顺序的变换[height, width, channels]-->[channels, height,width],以及数据范围的变换[0, 1]-->[0, 255];
    • 通过cv2.imread()获得的数据形式为:BGR,[height, width, channels], [0, 255](uint8),因此仅需要进行维度顺序的变换[height, width, channels]-->[width, height, channels];
    • 通过skimage.io.imread()获得的数据形式为:RGB,[height,width, channels],[0, 255](uint8),因此需要进行通道顺序的变换RGB-->BGR,维度顺序的变换[height, width, channels]-->[channels, height,width];

特别注意!特别注意!特别注意!

在matcaffe中,caffe.io.load_image()获得的数据形式是BGR,W x H x C(matlab中blob维度顺序是W x H x C),[0,255](single),满足网络模型需要的数据格式,因此直接使用,不需要进行改变(除了改变大小);

但在pycaffe中,caffe.io.load_image()获得的数据形式是RGB, H x W x C,[0, 1],需要进行通道顺序改变RGB-->BGR,数据维度顺序改变H x W x C-->C x H x W ,以及动态范围的改变[0,1]-->[0,255]

参考:

  1. Caffe实战(六):Caffe相关工具介绍
  2. caffe.io.load_image()和cv2.imread()读图像的差别