文章目录
说明
本文翻译自作者** Adrian Rosebrock**的一篇文章。
原文链接:Deep learning: How OpenCV’s blobFromImage works
介绍
之前有PyImageSearch 的读者在前几期的深度学习课程评论里想了解Opencv的blobFromImage的工作机制,所以特意写了此篇博文。
如你所见,为了从深度神经网络获得(正确的)预测,首先需要预处理你的数据。
在深度学习和图像分类的背景下,这些预处理通常包括:
1.均值减法(Mean subtraction)
2.缩放(scaling)
(PS:专业名词劝退系列,不过后面有详细解释)
OpenCV的深度神经网络(dnn)模块包含了两个方法,可以用来预处理图像,也可以通过已训练的深度学习模型为物体分类做准备。
在今天的博文中,我们将剖析opencv的cv2.dnn.blobFromImage和cv2.dnn.blobFromImages预处理函数,并了解它们是如何工作的。
想要了解更多的opencv深度学习的图像预处理的内容,请继续阅读(链接)
blobFromImage 的工作流程
前面提到过,opencv提供了两种方法进行图像的预处理:
cv2.dnn.blobFromImage
cv2.dnn.blobFromImages
这两个方法的作用是:
1.均值减法(Mean subtraction)
2.缩放(scaling)
3.可选的频道交换
接下来的部分,我们将:
1.探索均值减法和缩放
2.了解每个深度学习预处理函数的参数
3.学习这些方法的细节
4.最后,举例。
让我们开始吧
深度学习和均值减法
图片描述:均值减法直观点,就是左边图像的RGB减去中间的值后得到后边的值
在解释opencv的预处理功能之前,我们首先需要了解均值减法。在预处理的图片中,均值减法用来适应光照的变换(combat illumination changes)。因此,我们可以将均值减法视为一种协助卷积神经网络的技术。
在开始训练我们自己的深度神经网络之前,需要计算我们要训练的图片中RGB三个通道的平均像素(average pixel)。我们会得到三个变量:
每个变量是一个通道的平均值。总共三个通道也就是三个变量。
比如,ImageNet的均值为R=103.93, G=116.77,和 B=123.68(如果你使用过ImageNet,那你可能清楚这些值)
不过,在某些情况下,红色、绿色和蓝色的均值可能是按通道计算的,而不是按像素计算的,这会产生一个MxN矩阵。在这种情况下,处于训练或测试阶段的图片就减去每个通道值的MxN矩阵。
这两种方法(就是基于通道计算和像素计算)都是均值减法的形式,不过我们更看重基于像素的方式,尤其是数据多的情况下。
当我们准备好通过网络传递图片时(无论是为了训练还是测试),我们要将每个图片的每个通道的值减掉平均值。
我们还有一个缩放(scaling )的因子sigma,用来规范化。
sigma的值也许是训练过程中设置的均方差(也叫标准差)。不过,也可以手动设置sigma值将图片缩放到特定比例。
(PS:下面公式来自百度百科,标准差能反映一个数据集的离散程度。
例如,两组数的集合{0,5,9,14}和{5,6,8,9}其平均值都是7,但第二个集合具有较小的标准差。(百度百科))
重点要注意,并非所有的深度学习框架都有均值减法和缩放。所以在你预处理图片前,要了解你使用的深度神经网络。正如你在深度学习杂志上发现的那样,有些框架只有均值减法(将sigma设为了1),有些框架都有,有些都没有。
blobFromImage 和 blobFromImages
让我们首先参考opencv官方文档关于cv2.dnn.blobFromImage
的说明。
(PS:可能需要的知识点blob概念)
【blobFromImage】为图片创建了一个4维数组的blob。可以设置图片大小,并从图片中心进行剪切,减去均值,进行缩放,交换B和R通道
cv2.dnn.blobFromImage和cv2.dnn.blobFromImages方法几乎相同。
让我们先看看cv2.dnn.blobFromImage方法的参数:
blob = cv2.dnn.blobFromImage(image, scalefactor=1.0, size, mean, swapRB=True)
每个参数的作用:
1.image:我们想要预处理的图片,之后会将图片放入深度神经网络来分类。
2.scalefactor:在均值减法处理后,我们可以用这个参数对图片进行缩放。这个参数默认为1。
3.size:设置卷积神经网络训练时输入的图片的大小。当前比较好的神经网络一般设置为224×224, 227×227, 299×299。
4.mean:这是均值减法中的均值,可以是3通道RGB的均值也可以是每个通道的均值,若是每个通道的均值需要用图片的每个通道减掉每个通道的均值。要确保通道顺序是RGB,尤其是swapRB=True时。
5.swapRB:opencv设定的图片是BGR通道顺序,然而,均值减法中要求我们使用RGB顺序。为了解决这个冲突,将该参数设为Ture,将R和B通道调换。该参数默认为Ture
cv2.dnn.blobFromImage方法在经过均值减法、标准化(PS:应该就是缩放)和通道交换后返回一个blob。
cv2.dnn.blobFromImages方法的参数是一样的。
blob = cv2.dnn.blobFromImages(images, scalefactor=1.0, size, mean, swapRB=True)
唯一的不同就是,我们可以传输多个图片,使我们能够批量处理图片。
如果你想预处理多个图片或帧,要使用cv2.dnn.blobFromImages,因为这会有更少的开销,批处理速度也更快。
使用blobFromImage 方法深度学习
现在我们已经学习了blobFromImage和blobFromImages方法,让我们将它们应用到一些图片中,然后通过卷积神经网络进行分类。
在这之前,你的Opencv版本最少为3.3.0。Numpy也需要安装(pip install numpy)。安装imutils( pip install imutils)
假设你的图片预处理环境都已准备好了,让我们新建一个名为blob_from_images.py的文件,然后插入如下代码:
(PS:源码和所需的文件在源站已放出,需要翻墙,我将代码放在了最后,百度网盘)
# import the necessary packages
from imutils import paths
import numpy as np
import cv2
# load the class labels from disk
rows = open("synset_words.txt").read().strip().split("n")
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
# load our serialized model from disk
net = cv2.dnn.readNetFromCaffe("bvlc_googlenet.prototxt",
"bvlc_googlenet.caffemodel")
# grab the paths to the input images
imagePaths = sorted(list(paths.list_images("images/")))
首先导入imutils , numpy , and cv2。(2-4行)
然后我们读取synset_words.txt (ImageNet 的物体分类标签文件),取出类和类标签(7-8行)
使用DNN方法cv2.dnn.readNetFromCaffe加载模块,
bvlc_googlenet.prototxt为模型参数,
bvlc_googlenet.caffemodel为模型框架(11-12行)
接下来我们要将加载图片和使用blobFromImage预处理。
# (1) load the first image from disk, (2) pre-process it by resizing
# it to 224x224 pixels, and (3) construct a blob that can be passed
# through the pre-trained network
image = cv2.imread(imagePaths[0])
resized = cv2.resize(image, (224, 224))
blob = cv2.dnn.blobFromImage(resized, 1, (224, 224), (104, 117, 123))
print("First Blob: {}".format(blob.shape))
在这一块中,我们首先加载图片(4行)并将它的大小设置为224x244(5行),这是GoogLeNet所需要的图片尺寸。
现在到了这篇博文的核心
在第6行中,我们使用了cv2.dnn.blobFromImage。如前一节所述,它会产生4维的数组blob用于我们的神经网络。
输出blob的尺寸,方便在终端对它进行分析。(7行)
接下来,我们将通过GoogLeNet输入blob:
# set the input to the pre-trained deep learning network and obtain
# the output predicted probabilities for each of the 1,000 ImageNet
# classes
net.setInput(blob)
preds = net.forward()
# sort the probabilities (in descending) order, grab the index of the
# top predicted label, and draw it on the input image
idx = np.argsort(preds[0])[::-1][0]
text = "Label: {}, {:.2f}%".format(classes[idx],
preds[0][idx] * 100)
cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 0, 255), 2)
# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)
如果你熟悉我近期的深度学习博客,上面的几行代码看起来会很熟悉。我们通过网络输入了blob,并获取到了相似程度(原文为predictions)。(4-5行)
提取preds中相似程度最高的那一个下标(9行),生成一个标签展示在图片上。这个标签由物体分类标签和相似度百分比构成。(10-11行)
至此,我们在图片的顶部写了一个标签(12-13行),并等待我们按键以继续运行程序。(16-17行)。
现在是时候使用blobFromImage的复数形式了
除了我们创建和输入批量图片外,我们会做(几乎)同样的步骤。
# initialize the list of images we'll be passing through the network
images = []
# loop over the input images (excluding the first one since we
# already classified it), pre-process each image, and update the
# `images` list
for p in imagePaths[1:]:
image = cv2.imread(p)
image = cv2.resize(image, (224, 224))
images.append(image)
# convert the images list into an OpenCV-compatible blob
blob = cv2.dnn.blobFromImages(images, 1, (224, 224), (104, 117, 123))
print("Second Blob: {}".format(blob.shape))
首先我们初始images列表类型(2行),然后读取imagePath里的图片,改变其大小,并将其添加进images中。(7-10行)
我们将images列表代入cv2.dnn.blobFromImages第一个参数中(13行)。其他参数和cv2.dnn.blobFromImage相同
接下来将通过GoogLeNet 传入blob,并在每个图片的顶部写入分类标签和相似度百分比。
# set the input to our pre-trained network and obtain the output
# class label predictions
net.setInput(blob)
preds = net.forward()
# loop over the input images
for (i, p) in enumerate(imagePaths[1:]):
# load the image from disk
image = cv2.imread(p)
# find the top class label from the `preds` list and draw it on
# the image
idx = np.argsort(preds[i])[::-1][0]
text = "Label: {}, {:.2f}%".format(classes[idx],
preds[i][idx] * 100)
cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 0, 255), 2)
# display the output image
cv2.imshow("Image", image)
cv2.waitKey(0)
以上代码和之前差不多,只不过现在通过for
循环进行了每个图片的处理。
就这样!让我们看看下一节的代码
结果展示
现在到了有趣的环节了。
首先打开终端运行程序
$ python blob_from_images.py
终端第一个输出结果是我们使用的cv2.dnn.blobFromImage方法
First Blob: (1, 3, 224, 224)
酒杯图片的结果展示如下:
图片描述:一杯诱人的啤酒被贴上了相应的标签,并被GoogLeNet识别为高识别度,由blobFromImage产生的blob尺寸输出在终端上
这啤酒我馋了,在我独自享用它之前,我来解释下为什么blob的shape为(1, 3, 224, 224)
这个元组的结构如下:
(num_images=1, num_channels=3, width=224, height=224)
因为我们只处理了一张图片,我们只有一个实体在blob中,所以第一个参数num_images=1。第二个是通道的数目,因为是BGR通道所以为3。最后两个是我们设置输入图片的长宽224x224。
接下来,让我们从其他图片生成一个blob。
第二个blob的shape为:
Second Blob: (4, 3, 224, 224)
因为这次blob包含了4个图片,故num_images=4,其他参数同上。
其他已识别的图片展示:
总结
在今天的课程中我们学习了opencv的blobFromImage和blobFromImages深度学习方法。
在已训练的深度学习模块中,这些方法经常被用来输入图片的分类。
blobFromImage和blobFromImages都包含均值减法和缩放。我们也可以根据需要交换R和B通道。几乎所有的主流深度学习模型都支持均值减法和缩放-----好处就是opencv使这些预处理变的非常简单。
(PS:后面就是作者推荐了自己写的书,不再翻译)
书的相关信息
源码和其他文件
1.在源站最下面有代码下载
2.百度网盘:链接 (提取码:3ful )