Caffe2 图像预处理(Image Pre-Processing)[6]
学习如何使得图像符合预训练模型的需求,或者用其他数据集的图像来测试自己的模型。
- 调整大小
- 缩放
- HWC和CHW,数据通道交换
- RGB和BGR,颜色通道的交换
- Caffe2的图像预处理
Ipython Notebook的教程在这里获取
在这一节中,我们将会展示如何从本地文件或网络链接载入一个图像,并能用于其他的教程和例子。当然,我们将继续深入多种预处理,这些预处理都是使用Caffe2时非常有必要的的。
Mac OSx Prerequisites
首先,确保你有Python的这些模块。
sudo pip install scikit-image scipy matplotlib
然后,我们开始载入这些模块
%matplotlib inline
import skimage
import skimage.io as io
import skimage.transform
import sys
import numpy as np
import math
from matplotlib import pyplot
import matplotlib.image as mpimg
print("Required modules imported.")
Test an Image
在下面的代码块中,用 IMAGE_LOCATION
去载入你想要测试的图像。改变其内容,并重新看看整个教程,你会看到对于不同的图片格式会有不同的处理。如果你想尝试自己的图像,把它改为你的图像路径或者远程URL。当你使用远程URL时,必须确保这个URL指向一个普通的图像文件类型和后缀,一些长的表示符或者字符串可能会导致程序中断。
Color Issues
记住,如果你载入的图像来自智能手机,那么你可能会遇到图像颜色格式问题。在下面我们将会展示在RGB和BGR对一张图像的影响。确保图像数据和你想象中的一致。
Caffe Uses BGR Order
Caffe使用了OpenCV,而OpenCV处理图像是Blue-Green-Red (BGR) 形式的。而不是通用的RGB形式,所以在Caffe2中,图像的格式也是BGR。从长远来看,这种做法在很多方面是有益的,当你使用不同的计算机和库。但是这也是困惑的起源。
# 你可以载入本地图片或者远程连接
# 第一种方案,使用本地图像
#IMAGE_LOCATION = 'images/cat.jpg'
# 第二种线路使用网络图像,图像是一朵花
IMAGE_LOCATION = "https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg"
#第三种线路使用网络图像,网络图像有很多人
#IMAGE_LOCATION = "https://upload.wikimedia.org/wikipedia/commons/1/18/NASA_Astronaut_Group_15.jpg"
# 第四种使用一个网络图像,是一个竖图
#IMAGE_LOCATION = "https://upload.wikimedia.org/wikipedia/commons/9/9a/Ducreux1.jpg"
img = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)
# 显示原始图像
pyplot.figure()
pyplot.subplot(1,2,1)
pyplot.imshow(img)
pyplot.axis('on')
pyplot.title('Original image = RGB')
#交换颜色通道并显示交换后的的BGR图像
imgBGR = img[:, :, (2, 1, 0)]
pyplot.subplot(1,2,2)
pyplot.imshow(imgBGR)
pyplot.axis('on')
pyplot.title('OpenCV, Caffe2 = BGR')
由上面的例子中,你可以看到,不同的顺序是相当重要的。接下来的代码块中,我们将会图像转换为BGR顺序,这样Caffe2才能正确处理它。
不,稍等。关于颜色还有些有趣的东西。
Caffe Prefers CHW Order
什么是CHW?还有HWC。这两个都是来源于图像处理。
- H:Height
- W:Width
- C:Channel
深入了解图像在内存分配中的顺序。你可能注意到,当我们第一次载入图像时,我们进行了一些有趣的转换。这些数据转换就像把一幅图像当做一个魔方来玩。我们看到的是魔方的顶层,操作下面的层,可以改变看到的东西。
在GPU下,Caffe2需要的图像数据是CHW,在CPU下,一般需要的顺序是HWC。基本上,你需要CHW的顺序,并确保转换为CHW这步包含在你的图像预处理。把RGB转换为BGR,然后把HWC转换为CHW。这里的C就转换后的BGR。你可能会问,为什么呢?原因在于,在GPU上使用cuDNN库能获得非常大的加速,而cuDNN只使用CHW。总的来说,这样做能更快。
有了上面两步,你可能会觉得够了吧?不,你还是太年轻了。我们还需要resize(调整大小),crop(剪切),可能还需要些旋转和镜像。
Rotation and Mirroring
来自智能手机的相片普遍存在着旋转或者镜像,有时,我们可以通过照片中的EXIF信息进行修正。但是并不是都这么幸运。
Library for Handling Mobile Images
下面展示的是旋转图像和镜像图像
# 对于这样的图像如何知道它是竖屏模式?
ROTATED_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/8/87/Cell_Phone_Tower_in_Ladakh_India_with_Buddhist_Prayer_Flags.jpg"
imgRotated = skimage.img_as_float(skimage.io.imread(ROTATED_IMAGE)).astype(np.float32)
pyplot.figure()
pyplot.imshow(imgRotated)
pyplot.axis('on')
pyplot.title('Rotated image')
#这种图像是给司机用后视镜看的
MIRROR_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/2/27/Mirror_image_sign_to_be_read_by_drivers_who_are_backing_up_-b.JPG"
imgMirror = skimage.img_as_float(skimage.io.imread(MIRROR_IMAGE)).astype(np.float32)
pyplot.figure()
pyplot.imshow(imgMirror)
pyplot.axis('on')
pyplot.title('Mirror image')
然我们做一些变换。同时,这些技巧可能能够帮到你,例如,你无法获取图像的EXIF信息,那么你可以对图像进行旋转,翻转,从而产生很多副本,对于这些图像,用你的模型全部跑一遍。当检测的置信度足够高时,找到了你需要的方向。
代码如下:
#下面代码实现图像的左右翻转
imgMirror = np.fliplr(imgMirror)
pyplot.figure()
pyplot.imshow(imgMirror)
pyplot.axis('off')
pyplot.title('Mirror image')
#逆时针旋转90度
imgRotated = np.rot90(imgRotated)
pyplot.figure()
pyplot.imshow(imgRotated)
pyplot.axis('off')
pyplot.title('Rotated image')
Sizing
下面的例子先将图像resize到256x256大小,然后从中剪切出224x224大小,因为网络的输入大小是224x224。
#模型的输入是224x224大小,因此需要resize或者crop
# (1) Resize 图像 256*256, 然后剪切中心部分
input_height, input_width = 224, 224
print("Model's input shape is %dx%d") % (input_height, input_width)
img256 = skimage.transform.resize(img, (256, 256))
pyplot.figure()
pyplot.imshow(img256)
pyplot.axis('on')
pyplot.title('Resized image to 256x256')
print("New image shape:" + str(img256.shape))
输出
Model's input shape is 224x224
New image shape:(256, 256, 3)
注意resize有可能在一定程度上扭曲图像。你在测试时必须考虑这个问题,因为这会影响到你的模型输出的结果。花和动物被拉长或者压缩一点可能不会太大问题。但是面部特征就不一定行了。现在尝试另一种缩放图像的策略,并保持图像的比例不变。
Rescaling
保持图像的比例关系,并将最小的一边缩放到和网络的输入大小一致。在我们的例子中,网络的输入是224x224。
- 横向(Landscape):限制高度进行resize
- 纵向(Portrait):限制宽度进行resize
print("Original image shape:" + str(img.shape) + " and remember it should be in H, W, C!")
print("Model's input shape is %dx%d") % (input_height, input_width)
aspect = img.shape[1]/float(img.shape[0])#宽/高
print("Orginal aspect ratio: " + str(aspect))
if(aspect>1):
# 横向 - 宽图像
res = int(aspect * input_height)#译者认为这里应该为input_width
imgScaled = skimage.transform.resize(img, (input_width, res))#译者认为这里应该为input_height
if(aspect<1):
# 竖向 - 高图像
res = int(input_width/aspect)#译者认为这里应该为input_height
imgScaled = skimage.transform.resize(img, (res, input_height))#译者认为这里应该为input_width
if(aspect == 1):
imgScaled = skimage.transform.resize(img, (input_width, input_height))#译者认为这里应该为 input_height, input_width
pyplot.figure()
pyplot.imshow(imgScaled)
pyplot.axis('on')
pyplot.title('Rescaled image')
print("New image shape:" + str(imgScaled.shape) + " in HWC")
输出
Original image shape:(751, 1280, 3) and remember it should be in H, W, C!
Model's input shape is 224x224
Orginal aspect ratio: 1.70439414115
New image shape:(224, 381, 3) in HWC
Cropping
这里有很多策略可以使用。我们可以比例不变的将图像缩小到一边大小符合网络的输入,然后从图像中间剪切出一块。但是如果不进行缩放,可能只能剪切到图像中花的一部分。所以我们还是需要缩放。
下面我们提供三种剪切的策略:
- 直接从图像中间取出你需要的大小的patch
- resize到一个很接近网络输入大小的正方形,然后从中间抓取
- 保持图像比例不变的缩放,然后从中间截取一部分
# 傻瓜式的从中间剪切
print("Original image shape:" + str(img.shape) + " and remember it should be in H, W, C!")
def crop_center(img,cropx,cropy):
y,x,c = img.shape
startx = x//2-(cropx//2) #python中//表示取结果的整数
starty = y//2-(cropy//2)
return img[starty:starty+cropy,startx:startx+cropx]
pyplot.figure()
# Original image
imgCenter = crop_center(img,224,224)
pyplot.subplot(1,3,1)
pyplot.imshow(imgCenter)
pyplot.axis('on')
pyplot.title('Original')
# 从256x256的变形图像中剪切中间的224x224
img256Center = crop_center(img256,224,224)
pyplot.subplot(1,3,2)
pyplot.imshow(img256Center)
pyplot.axis('on')
pyplot.title('Squeezed')
# Scaled image
imgScaledCenter = crop_center(imgScaled,224,224)
pyplot.subplot(1,3,3)
pyplot.imshow(imgScaledCenter)
pyplot.axis('on')
pyplot.title('Scaled')
Original image shape:(751, 1280, 3) and remember it should be in H, W, C!
注意:内存上保存始终是H,W,C
图像输出:
看起来好像最后一个比较好。第二种方法也不差,不过,这和很难说,要在你的模型上进行大批量测试才知道。如果你的模型在训练时使用不同比例的图像,并且直接将他们压缩到一个正方形,那么久而久之,你的模型将从压缩图像上学到那些物体被压缩时的样子,所以也能做出判断。但是如果你的模型专注于细节,比如面部特征,特征点,或者一些非常细微的元素,那么图像信息的丢失和变形将会带来非常大的误差。
更好的策略
更好的方法是,把你的图像缩放到最接近真实数据,然后在图像边缘填补信息,填补的信息不能对你的模型产生影响,也就是你的模型会忽略掉这些信息。这个方法,我们会在另外一个教程中给出,因为,这个教程已经讲了不少了。
Upscaling
如果你想要跑的图像很小,怎么办?在我们的例子中,我们网络的输入是224x224,但是如果遇到下面的128x128的图像大小呢?
最常用的方法就是,用skimage的工具把一个小的正方形图像变到一个大的正方形图像。resize
的默认参数是1,对应着使用双线性插值。
imgTiny = "images/Cellsx128.png"
imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)
print "Original image shape: ", imgTiny.shape
imgTiny224 = skimage.transform.resize(imgTiny, (224, 224))
print "Upscaled image shape: ", imgTiny224.shape
# Plot original
pyplot.figure()
pyplot.subplot(1, 2, 1)
pyplot.imshow(imgTiny)
pyplot.axis('on')
pyplot.title('128x128')
# Plot upscaled
pyplot.subplot(1, 2, 2)
pyplot.imshow(imgTiny224)
pyplot.axis('on')
pyplot.title('224x224')
Original image shape: (128, 128, 4)
Upscaled image shape: (224, 224, 4)
看到没,输出是 (224, 224, 4)。等等,为什么是4?前面所有例子都是3。当我们使用一个png
文件时,它是由四个通道的。第四个通道代表的是‘模糊度’或者‘透明度’.无论怎么样,我们仍然能很好地处理它,不过,要留意这个通道数。现在让我们先转换成CHW,然后放大图像。
imgTiny = "images/Cellsx128.png"
imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)
print "Image shape before HWC --> CHW conversion: ", imgTiny.shape
#交换坐标系HWC to CHW
imgTiny = imgTiny.swapaxes(1, 2).swapaxes(0, 1)
print "Image shape after HWC --> CHW conversion: ", imgTiny.shape
imgTiny224 = skimage.transform.resize(imgTiny, (224, 224))
print "Image shape after resize: ", imgTiny224.shape
try:
pyplot.figure()
pyplot.subplot(1, 2, 1)
pyplot.imshow(imgTiny)#交换顺序后无法显示
pyplot.axis('on')
pyplot.title('128x128')
except:
print "Here come bad things!"
# 如果你想看到错误,反注释掉下面一行
#raise
什么都没显示,对吧,因为存储顺序调换了。但是通道数仍然是4.
现在让我们展示一个例子,一个比你网络输入小的图像,并且不是正方形的。来自于一个只能给出矩形图像的显微镜。
imgTiny = "images/Cellsx128.png"
imgTiny = skimage.img_as_float(skimage.io.imread(imgTiny)).astype(np.float32)
imgTinySlice = crop_center(imgTiny, 128, 56)
# Plot original
pyplot.figure()
pyplot.subplot(2, 1, 1)
pyplot.imshow(imgTiny)
pyplot.axis('on')
pyplot.title('Original')
# Plot slice
pyplot.figure()
pyplot.subplot(2, 2, 1)
pyplot.imshow(imgTinySlice)
pyplot.axis('on')
pyplot.title('128x56')
# Upscale?
print "Slice image shape: ", imgTinySlice.shape
imgTiny224 = skimage.transform.resize(imgTinySlice, (224, 224))
print "Upscaled slice image shape: ", imgTiny224.shape
# Plot upscaled
pyplot.subplot(2, 2, 2)
pyplot.imshow(imgTiny224)
pyplot.axis('on')
pyplot.title('224x224')
Slice image shape: (56, 128, 4)
Upscaled slice image shape: (224, 224, 4)
通道数没变。
这是一个非常严重的错误,例如正常的细胞都是接近圆形,而病变细胞则是镰刀形的。在这种情况下,你怎么办?这很依赖于你的模型和你的模型是如何训练出来的。在某些情况下,可以通过给图像填充白色或者黑色的,或者噪声的边缘解决这个问题。
下面我们继续讨论,我们已经说过BGR和CHW的问题了,但是在caffe2中还需要考虑一个就是batch term
,也就是N,图像的个数。
Final Preprocessing and the Batch Term
# 如果你想尝试不同策略的剪切
# swap out imgScaled with img (original) or img256 (squeezed)
imgCropped = crop_center(imgScaled,224,224)
print "Image shape before HWC --> CHW conversion: ", imgCropped.shape
# (1)HWC->CHW
imgCropped = imgCropped.swapaxes(1, 2).swapaxes(0, 1)
print "Image shape after HWC --> CHW conversion: ", imgCropped.shape
pyplot.figure()
for i in range(3):
# pyplot subplot 索引和MATLAB的一样,从1开始
pyplot.subplot(1, 3, i+1)
pyplot.imshow(imgCropped[i])
pyplot.axis('off')
pyplot.title('RGB channel %d' % (i+1))
# (2) RGB->BGR
imgCropped = imgCropped[(2, 1, 0), :, :]
print "Image shape after BGR conversion: ", imgCropped.shape
# 以下代码后面用到,现在没用
# (3) 减均值,由于skimage 读取的图像在[0,1]之间,所以我们需要乘以255,使像素范围回到[0,255]
#mean_file = os.path.join(CAFFE_ROOT, 'python/caffe/imagenet/ilsvrc_2012_mean.npy')
#mean = np.load(mean_file).mean(1).mean(1)
#img = img * 255 - mean[:, np.newaxis, np.newaxis]
pyplot.figure()
for i in range(3):
pyplot.subplot(1, 3, i+1)
pyplot.imshow(imgCropped[i])
pyplot.axis('off')
pyplot.title('BGR channel %d' % (i+1))
# (4)最后由于Caffe2要求输入要有一个batch ,所以我们可以一次传递多张图像,我们仅仅让batch size=1,还要保证数据类型是 np.float32
imgCropped = imgCropped[np.newaxis, :, :, :].astype(np.float32)
print 'Final input shape is:', imgCropped.shape
输出:
Image shape before HWC --> CHW conversion: (224, 224, 3)
Image shape after HWC --> CHW conversion: (3, 224, 224)
Image shape after BGR conversion: (3, 224, 224)
Final input shape is: (1, 3, 224, 224)
在上面的输出中,你应该注意到如下变化:
- HWC->CHW,图像的通道数3,由最后移到了前面
- RGB->BGR,蓝色和红色分量进行了交换
- 输入数据的最后形状,在前面添加了batch size。所以数据格式是(1, 3, 224, 224)
- 1是图像的个数
- 3是图像的通道数
- 224是高
- 224是宽
这一教程到此结束。转载请注明出处:http://www.jianshu.com/c/cf07b31bb5f2