接着caffe2 教程记录二,这个是第三篇
##3.图像加载与预处理
图像加载和预处理:
在本教程中,我们将研究如何从本地文件或URL加载图像,然后您可以在其他教程或示例中使用这些文件。此外,我们将深入研究使用caffe2 和图像时所需的预处理类型。
mac osx 先决条件
如果您尚未安装这些python 依赖,你现在需要这样做。( 执行下面的命令,进行安装)
sudo pip install scikit-image scipy matplotlib
fromfrom __future____futur import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
%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.")
测试图片:
在下面的代码块中,使用IMAGE_LOCATION加载您要测试的内容。 只需更改注释标记即可完成每轮教程。 通过这种方式,您将了解各种图像格式会发生什么,以及有关如何预处理它们的一些提示。 如果要尝试自己的图像,请将其放在images文件夹中或使用远程URL。 当您选择远程URL时,请轻松自己并尝试查找指向常见图像文件类型和扩展名的URL,而不是某些长标识符或查询字符串,这可能会破坏下一步。
颜色问题:
记住,当你从智能手机摄像头加载图像时,你可能会遇到颜色格式化问题。下面我们展示RGB和BGR之间如何翻转可以影响图像的例子。这显然会导致模型中的检测失效。确保你传递的图像数据是你所想的!
caffe2 使用RGB 订单:
由于OpenCV在Caffe中的传统支持,以及它如何处理蓝绿红(BGR)顺序的图像,而不是更常用的红绿蓝(RGB)顺序,Caffe2还期望BGR顺序。从长远来看,这个决定在很多方面都有帮助,因为您使用了不同的计算机视觉实用程序和库,但是它也可能是混淆的来源。
# You can load either local IMAGE_FILE or remote URL
# For Round 1 of this tutorial, try a local image.
IMAGE_LOCATION = 'images/cat.jpg'
# For Round 2 of this tutorial, try a URL image with a flower:
# IMAGE_LOCATION = "https://cdn.pixabay.com/photo/2015/02/10/21/28/flower-631765_1280.jpg"
# IMAGE_LOCATION = "images/flower.jpg"
# For Round 3 of this tutorial, try another URL image with lots of people:
# IMAGE_LOCATION = "https://upload.wikimedia.org/wikipedia/commons/1/18/NASA_Astronaut_Group_15.jpg"
# IMAGE_LOCATION = "images/astronauts.jpg"
# For Round 4 of this tutorial, try a URL image with a portrait!
# IMAGE_LOCATION = "https://upload.wikimedia.org/wikipedia/commons/9/9a/Ducreux1.jpg"
# IMAGE_LOCATION = "images/Ducreux.jpg"
img = skimage.img_as_float(skimage.io.imread(IMAGE_LOCATION)).astype(np.float32)
# test color reading
# show the original image
pyplot.figure()
pyplot.subplot(1,2,1)
pyplot.imshow(img)
pyplot.axis('on')
pyplot.title('Original image = RGB')
# show the image in BGR - just doing RGB->BGR temporarily for display
imgBGR = img[:, :, (2, 1, 0)]
#pyplot.figure()
pyplot.subplot(1,2,2)
pyplot.imshow(imgBGR)
pyplot.axis('on')
pyplot.title('OpenCV, Caffe2 = BGR')
正如您在上面的示例中所看到的,顺序的差异是很重要的。 在下面的代码块中,我们将拍摄图像并转换为BGR顺序,以便Caffe适当地处理它。
但是,等等,有更多的色彩乐趣…
Caffe更喜欢CHW订单:
现在你知道,什么是CHW和HWC吗,这两种格式经常都会出现在图像处理中。
- H: Height (高)
- W: Width (宽)
- C: Channel (渠道 颜色)
深入研究图像数据的存储方式是内存分配顺序。您可能已经注意到,当我们第一次加载图像时,我们强制它通过一些有趣的转换。这些是数据转换,让我们可以像使用立方体一样使用图像。我们看到的是在立方体的顶部,操纵下面的层可以改变我们所看到的。我们可以修补它的基本属性,如上所述,很容易交换颜色。
对于GPU处理,这是Caffe2擅长的,这个订单需要是CHW。对于CPU处理,此顺序通常为HWC。基本上,您将要使用CHW并确保步骤包含在图像管道中。将RGB调整为BGR,将其封装为此“C”有效负载,然后调整HWC,“C”是您刚刚切换的相同颜色。
你可能会问为什么!原因指向cuDNN,这有助于加速GPU的处理。它只使用CHW,我们总结说它更快。
鉴于这两种转变,您可能认为这已足够,但事实并非如此。我们仍然需要调整大小和/或裁剪,并可能会查看方向(旋转)和镜像等内容。
旋转和镜像:
这个话题通常保留在来自智能手机的图像上。一般来说,手机拍摄的照片很棒,但在拍摄照片的方式以及应该采用的方向方面做得非常的糟糕。然后是用户会用手机做所有的事情,让他们做设计师从未预料到的事情。相机-是的,因为通常有两个相机,这两个相机在像素数和纵横比上都采用不同大小的图片,不仅如此,它们有时会将它们镜像,有时也会以肖像和风景模式拍摄它们,有时它们不费力地告诉它们。他们在哪个模式。
在很多方面,这是您需要在预处理中评估的第一件事,然后查看大小(下面描述),然后计算颜色情况。如果你正在开发iOS,那么你很幸运,这将是比较容易的。如果你是一个超级黑客的向导开发人员,有lead-lined shorts,为Android开发,那么至少你有lead-lined shorts.
Android市场的变化是惊人的和可怕的。在理想的情况下,您可以依赖来自任何相机的图片中的EXIF数据,并使用它来确定方向和镜像,并且您有一个简单的case函数来处理转换。没有这样的运气,但你并不孤单。许多人来到你面前,为你受苦。
# Image came in sideways - it should be a portait image!
# How you detect this depends on the platform
# Could be a flag from the camera object
# Could be in the EXIF data
# ROTATED_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/8/87/Cell_Phone_Tower_in_Ladakh_India_with_Buddhist_Prayer_Flags.jpg"
ROTATED_IMAGE = "images/cell-tower.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')
# Image came in flipped or mirrored - text is backwards!
# Again detection depends on the platform
# This one is intended to be read by drivers in their rear-view mirror
# MIRROR_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/2/27/Mirror_image_sign_to_be_read_by_drivers_who_are_backing_up_-b.JPG"
MIRROR_IMAGE = "images/mirror-image.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')
所以你可以看到我们遇到了一些问题。 如果我们正在探测地点,地标或物体,那么侧身的细胞塔就不好了。 如果我们检测文本并进行自动语言翻译,那么镜像文本就不好了。 但是,嘿,也许你想制作一个可以同时检测英语的模型。 这将是非常棒的,但不适用于本教程!
让我们把这些babies into 转换成caffe2能识别的,我们周围的标准检测模型可以检测到的。此外,这个小技巧可能会拯救你,例如,例如,你真的必须检测细胞塔,但没有找到EXIF数据:那么你将循环每一次旋转,每次翻转,产生这张照片的许多衍生物 并运行它们。 当检测的置信度百分比足够高时,Bam !,你找到了你需要的方向和偷偷摸摸的细胞塔。
总之,对于示列代码:
# Run me to flip the image back and forth
imgMirror = np.fliplr(imgMirror)
pyplot.figure()
pyplot.imshow(imgMirror)
pyplot.axis('off')
pyplot.title('Mirror image')
# Run me to rotate the image 90 degrees
imgRotated = np.rot90(imgRotated, 3)
pyplot.figure()
pyplot.imshow(imgRotated)
pyplot.axis('off')
pyplot.title('Rotated image')
# Model is expecting 224 x 224, so resize/crop needed.# Model
# First, let's resize the image to 256*256
orig_h, orig_w, _ = img.shape
print("Original image's shape is {}x{}".format(orig_h, orig_w))
input_height, input_width = 224, 224
print("Model's input shape is {}x{}".format(input_height, input_width))
img256 = skimage.transform.resize(img, (256, 256))
# Plot original and resized images for comparison
f, axarr = pyplot.subplots(1,2)
axarr[0].imshow(img)
axarr[0].set_title("Original Image (" + str(orig_h) + "x" + str(orig_w) + ")")
axarr[0].axis('on')
axarr[1].imshow(img256)
axarr[1].axis('on')
axarr[1].set_title('Resized image to 256x256')
pyplot.tight_layout()
print("New image shape:" + str(img256.shape))
图片大小:
预处理的一部分是调整大小。 由于我们不会进入这里的原因,Caffe2预处理中的图像应该是方形的。 另外,为了提高性能,应将它们调整为标准高度和宽度,通常小于原始来源。 在下面的示例中,我们将调整为256 x 256像素,但您可能会注意到input_height和input_width设置为224 x 224,然后用于指定裁剪。 这是几个基于图像的模型所期望的。 他们接受了大小为224 x 224的图像训练,为了使模型能够正确识别您投射的可疑图像,这些图像也应该是224 x 224。
注意,调整大小会使图像失真一点点。在处理过程中认识到这种影响是很重要的,因为它可以对模型的结果产生影响。花和动物可以稍微伸展或挤压,但面部特征可能不会。
当原始图像的尺寸与所需的尺寸成比例地精确时,可能会发生这种情况。在这个特定的例子中,最好是调整大小到224x224,而不麻烦裁剪。让我们尝试重新缩放图像和保持纵横比的另一种策略。
重新缩放:
如果你设想肖像图像与风景图像,你就会知道有很多东西可以通过调整大小来处理,不然会搞砸。 重新缩放假设您正在锁定宽高比以防止图像失真。 在这种情况下,我们将图像缩小到与模型输入大小匹配的最短边。
在我们这里的示例中,模型大小为224 x 224.当您在1920x1080中查看显示器时,它的宽度比高度更长,如果将其缩小到224,则在用完之前就会超出高度。 宽度,所以......
风景:限制高度调整大小
纵向:限制按宽度调整大小
print("Original image shape:" + str(img.shape) + " and remember it should be in H, W, C!")
print("Model's input shape is {}x{}".format(input_height, input_width))
aspect = img.shape[1]/float(img.shape[0])
print("Orginal aspect ratio: " + str(aspect))
if(aspect>1):
# landscape orientation - wide image
res = int(aspect * input_height)
imgScaled = skimage.transform.resize(img, (input_height, res))
if(aspect<1):
# portrait orientation - tall image
res = int(input_width/aspect)
imgScaled = skimage.transform.resize(img, (res, input_width))
if(aspect == 1):
imgScaled = skimage.transform.resize(img, (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")
此时,只有一个维度设置为模型输入所需的维度。 我们仍然需要裁剪一边做一个正方形。
裁剪:
事实上,我们可以倒退并决定做一个中心作物。 所以我们不是缩小到最小的,我们可以至少在一边,我们从中间拿出一大块。 如果我们在没有缩放的情况下完成了这项操作,那么我们最终只能使用花朵踏板的一部分,因此我们仍需要对图像进行一些调整。
下面我们将尝试一些裁剪策略:
- 只要从中间抓取你需要的精确尺寸!
- 调整到一个非常接近的正方形然后从中间抓取。
- 使用重新缩放的图像并抓住中间。
# Compare the images and cropping strategies
# Try a center crop on the original for giggles
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)
starty = y//2-(cropy//2)
return img[starty:starty+cropy,startx:startx+cropx]
# yes, the function above should match resize and take a tuple...
pyplot.figure()
# Original image
imgCenter = crop_center(img,224,224)
pyplot.subplot(1,3,1)
pyplot.imshow(imgCenter)
pyplot.axis('on')
pyplot.title('Original')
# Now let's see what this does on the distorted image
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')
pyplot.tight_layout()
正如你所看到的那样,除了最后一个之外,效果还不太好。中间的一个也可能是好的,但你不会知道,直到你尝试模型和测试大量的候选图像。在这一点上,我们可以看到我们的差异,把它分成两半,从两边去除一些像素。然而,这确实有一个缺点,因为一个偏离中心的主题会被剪辑。
如果你现在已经运行了几次教程并且在第3轮,你会发现一个相当大的问题。你错过了astronauts!你还可以从第2轮看到花的问题。截切后缺少的东西,可能会给你带来麻烦。可以这样想想:如果不知道正在使用的模型是如何准备的,那么就不知道如何,所以要使图像符合要求,因此请注意测试结果!如果模型使用了许多不同的高宽比图像,并且只是将它们压缩成一个正方形,那么随着时间推移和大量样本,它“学习”了什么是压缩的并且可以匹配的。但是,如果您正在寻找像面部特征和地标之类的细节,或者任何图像中的细微差别元素,那么这可能是危险的,并且容易出错。
没有进一步策略?
另一种策略是使用真实数据重新调整到最佳尺寸,然后使用您可以在模型中安全忽略的信息填充图像的其余部分。 因为你在这里经历了足够的经验,我们将把它保存到另一个教程中!
Upscaling(倍增):
当你想要运行的图像是“微小”的时候,你会怎么做?在我们的例子中,我们已经准备了输入图像,规格为224x224。请考虑下面的128X128图像。
现在我们不是在谈论超分辨率或CSI效应,我们可以拍摄模糊的ATM照片,犯罪人员脖子上的纹身, 尽管如此,深度学习已经提供了一些进展 (some advances),如果您正在及时阅读本文(3/1/17之前),那么可以去看看(check this out)。我们想要做的很简单,但是,就像裁剪一样,它确实有各种你应该考虑的策略。
最基本的方法是从一个小方块到一个更大的方块,并使用defauls skimage为您提供的默认功能。 这个调整大小的方法默认插值顺序参数为1,如果你甚至关心,这个参数恰好是双线性的,但值得一提,因为这些可能是你以后需要修改问题的微调旋钮,比如奇怪的视觉伪像, 可以在图像 upscaling中引入。
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')
太棒了,效果不错 您可以在形状输出中看到(128,128,4)并且您已收到(224,224,4)。 等一下!4? 到目前为止,在每个例子中,形状的最后一个值是3! 当我们使用png文件时,我们进入了一个新的现实; 一个可以透明的地方。 第4个值描述不透明度或透明度,具体取决于您是否为玻璃半空类型。 无论如何,我们可以很好的处理,但要注意这个数字。
重要的是要知道,在我们对图像进行进一步操作之前,如果对图像的当前格式进行简单的重采样,那么它的数据顺序和整体有效负载可能会使数据和图像变得混乱。 请记住,它目前是一个数据立方体,现在还有更多的数据,而不仅仅是红色,绿色和蓝色(以及不透明度)。 根据您决定调整大小时,您必须考虑额外的数据。
我们打破一下吧! 将图像切换为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)
# swapping the axes to go from HWC to CHW
# uncomment the next line and run this block!
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)
# we know this is going to go wrong, so...
try:
# Plot original
pyplot.figure()
pyplot.subplot(1, 2, 1)
pyplot.imshow(imgTiny)
pyplot.axis('on')
pyplot.title('128x128')
except:
print("Here come bad things!")
# hands up if you want to see the error (uncomment next line)
#raise
失败了,对吧,如果你让上面的代码块交换轴,然后调整图像大小,你会看到这个输出:
Image shape after resize: (224, 224, 128)
现在你有128个,你应该还有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')
好吧,这对于升级 如何失败的一个例子来说有点延伸。 得到它? 伸展? 然而,这可能是一种生死攸关的失败。 如果正常细胞是圆形的并且患病的细胞被拉长并弯曲怎么办? 镰状细胞性贫血例如:
在这种情况下,你会怎么做? 这实际上取决于模型以及它是如何训练的。 在某些情况下,可以将图像的其余部分填充为白色,或者可能是黑色,或者可能是噪声,或者甚至可以使用png和透明度并为图像设置遮罩,以便模型忽略透明区域。 看看你能想出多少有趣的事情,你也可以取得医学上的突破!
让我们继续我们已经提到的最后一步,即将图像输入调整为BGR顺序。 Caffe2还有另一个特点,即批量术语。 我们已经谈过CHW了。 对于NCHW中的图像数量,这是N.
最终预处理和批处理术语:
在下面的最后一步中,我们将把图像的数据顺序切换到BGR,将其填入Color列,然后对列进行重新排序以进行GPU处理(HCW - > CHW),然后向图像添加第四维(N) 跟踪图像的数量。 从理论上讲,您可以继续为数据添加维度,但Caffe2需要这个维度,因为它会向Caffe传达此批次中预期的图像数量。 我们将其设置为一(1)表示此批次中只有一张图像进入Caffe。 请注意,在我们检查img.shape的最终输出中,顺序是完全不同的。 我们为图像数添加了N,并改变了顺序:N,C,H,W
# This next line helps with being able to rerun this section
# if you want to try the outputs of the different crop strategies above
# 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) Since Caffe expects CHW order and the current image is HWC,
# we will need to change the order.
imgCropped = imgCropped.swapaxes(1, 2).swapaxes(0, 1)
print("Image shape after HWC --> CHW conversion: ", imgCropped.shape)
pyplot.figure()
for i in range(3):
# For some reason, pyplot subplot follows Matlab's indexing
# convention (starting with 1). Well, we'll just follow it...
pyplot.subplot(1, 3, i+1)
pyplot.imshow(imgCropped[i], cmap=pyplot.cm.gray)
pyplot.axis('off')
pyplot.title('RGB channel %d' % (i+1))
# (2) Caffe uses a BGR order due to legacy OpenCV issues, so we
# will change RGB to BGR.
imgCropped = imgCropped[(2, 1, 0), :, :]
print("Image shape after BGR conversion: ", imgCropped.shape)
# for discussion later - not helpful at this point
# (3) (Optional) We will subtract the mean image. Note that skimage loads
# image in the [0, 1] range so we multiply the pixel values
# first to get them into [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):
# For some reason, pyplot subplot follows Matlab's indexing
# convention (starting with 1). Well, we'll just follow it...
pyplot.subplot(1, 3, i+1)
pyplot.imshow(imgCropped[i], cmap=pyplot.cm.gray)
pyplot.axis('off')
pyplot.title('BGR channel %d' % (i+1))
# (4) Finally, since caffe2 expect the input to have a batch term
# so we can feed in multiple images, we will simply prepend a
# batch dimension of size 1. Also, we will make sure image is
# of type np.float32.
imgCropped = imgCropped[np.newaxis, :, :, :].astype(np.float32)
print('Final input shape is:', imgCropped.shape)
在上面的输出中,您应该注意这些变化:
HWC到CHW之前和之后的变化。 3,这是移动到开头的颜色通道的数量。
在上面的图片中,您可以看到颜色顺序也已切换。 RGB成为BGR。 蓝色和红色切换位置。
最终的输入形状,意味着对图像的最后一次更改是将批处理字段添加到开头,所以现在你有(1,3,224,224)用于:
- 1 image in the batch(一张图片在批处理中),
- 3 color channels (in BGR) 3种颜色通道(BGR),
- 224 height, 224高度。
- 224 width.224宽度。