首页 iOS.& Swift Books 通过教程学习

9
超越分类 由Matthijs Hollemans撰写

前一章教会了你们所有关于用神经网络的图像分类。但是,众所周知网络可用于许多其他电脑视觉任务。在本章和下一章中,您将关注两个前进:

  • 对象检测:在图像中查找多个对象。
  • 语义细分:为图像中的每个像素进行课程预测。

尽管这些新车型要比你与迄今为止的工作是什么复杂得多,他们基于同样的想法。神经网络是一个特征提取和使用提取的特征以执行一些任务,无论是分类,检测对象,脸部识别,跟踪物体的移动,或者几乎任何其它计算机视觉任务。

这就是为什么你花了这么多时间在图像分类上:获得基本面的坚实掌握。但现在是时候拿走几步的时候了......

它在哪里?

分类告诉你 什么 在图像中,但总是认为整个图像。当图片只有一件兴趣的东西时,它最佳地工作。如果您的分类器接受培训以讲述猫和狗,并且图像包含一只猫和一只狗,那么答案就是任何人的猜测。

一个 对象检测 模型通过LCH图像加强了这个问题。对象检测的目标是在图像中找到所有对象,即使是初级类型。您可以将其视为特定注射的分类器。

对象探测器可以找到所有毛茸茸的朋友
对象探测器可以找到所有毛茸茸的朋友

对象探测器不仅找到了对象的还要是什么 在哪里 它们位于图像中。它通过预测一个或多个来实现这一点 边界盒子,这只是图像中的矩形区域。

边界框由四个数字描述,表示矩形的角点或中心点加上宽度和高度:

这两种界限框
这两种界限框

这两种类型都用于实践中,但本章使用带角点的一个。

每个边界框也具有类 - 所以的类型的类型 - 以及边界框坐标和类的概率。

这似乎比图像分类更复杂,但构建块是一样的。你采取了一个特征提取器 - a 卷积神经网络 - 并在顶部添加一些额外的图层,将提取的功能转换为预测。差异是,这一次,该模型不仅仅是对类预测,而且预测边界框坐标。

在我们深入构建完整的对象探测器之前,让我们从更简单的任务开始。您将首先延长最后一章的基于M​​obiLenet的分类模型,使其除了常规类预测之外,它还输出尝试的单个边界框 本地化 其中最重要的对象位于图像中。

只是预测一个边界盒,它有多难? (答案:它实际上比你想象的更容易。)

地面真理会让你自由

首先,我们应该重新审视数据集。

import os, sys
import numpy as np
import pandas as pd

%matplotlib inline
import matplotlib.pyplot as plt

data_dir = "snacks"
train_dir = os.path.join(data_dir, "train")
val_dir = os.path.join(data_dir, "val")
test_dir = os.path.join(data_dir, "test")
path = os.path.join(data_dir, "annotations-train.csv")
train_annotations = pd.read_csv(path)
train_annotations.head()
第一个注释 -  Train.csv线
LZO BETLY ROYU MAZOC OD OBREFOKOILW-LZIAG.CJV

val_annotations = pd.read_csv(os.path.join(data_dir,
                                   "annotations-val.csv"))
test_annotations = pd.read_csv(os.path.join(data_dir,
                                   "annotations-test.csv"))

告诉我数据!

现在,让我们看看这些边界框。在处理图像时,绘制一些示例以确保数据是正确的,这总是一个好主意。

image_width = 224
image_height = 224

from helpers import plot_image
train_annotations.iloc[0]
image_id      009218ad38ab2010
x_min                  0.19262
x_max                 0.729831
y_min                 0.127606
y_max                 0.662219
class_name                cake
folder                    cake
Name: 0, dtype: object
from keras.preprocessing import image

def plot_image_from_row(row, image_dir):
    # Load the image from "folder/image_id.jpg"
    image_path = os.path.join(image_dir, row["folder"],
                              row["image_id"] + ".jpg")
    img = image.load_img(image_path,
                    target_size=(image_width, image_height))

    # Put the box coordinates and class name into a tuple
    bbox = (row["x_min"], row["x_max"],
            row["y_min"], row["y_max"], row["class_name"])
    # Draw the bounding box on top of the image
    plot_image(img, [bbox])
annotation = train_annotations.iloc[0]
plot_image_from_row(annotation, train_dir)
行0,蛋糕(左)和第2行,冰淇淋(右)的地面真相盒
FHE PNUIBB-XPUWW LIC Fur Mur Mun 7,Baqu(RAJP)IWP CER 8,Azi Kyuax(Howpd)

没有注释的图像怎么样?

如果您只有仅包含图像的数据集 - 以及图像的类别标签 - 但没有边界框注释,则无法在该数据集上培训对象检测器。不会发生;不是没有两种方式。

你自己的发电机

Previously, you used ImageDataGenerator and flow_from_directory() to automatically load the images and put them into batches for training. That is convenient when your images are neatly organized into folders, but the new training data consists of a Pandas DataFrame with bounding box annotations. You’ll need a way to read the rows from this dataframe into a batch. Fortunately, Keras lets you write your own custom generator.

from helpers import BoundingBoxGenerator

batch_size = 32
train_generator = BoundingBoxGenerator(
  train_annotations, 
  train_dir,
  image_height,
  image_width,
  batch_size,
  shuffle=True)
train_iter = iter(train_generator)
X, (y_class, y_bbox) = next(train_iter)
X.shape
array([[ 0.348343,  0.74359 ,  0.55838 ,  0.936911],
       [ 0.102564,  0.746717,  0.062909,  0.93219 ],
       [ 0.      ,  1.      ,  0.135843,  0.98036 ],
       [ 0.448405,  0.978111,  0.288574,  0.880734],
       ...
array([ 9, 16, 12,  7,  8, 18, 10,  1, 14,  2,  7, 17, ...])
from helpers import labels
list(map(lambda x: labels[x], y_class))
class BoundingBoxGenerator(keras.utils.Sequence):
    def __len__(self):
        return len(self.df) // self.batch_size

    def __getitem__(self, index):
        # ... code ommitted ...
        return X, [y_class, y_bbox]

    def on_epoch_end(self):
        self.rows = np.arange(len(self.df))
        if self.shuffle:
            np.random.shuffle(self.rows)
def plot_image_from_batch(X, y_class, y_bbox, img_idx):
    class_name = labels[y_class[img_idx]]
    bbox = y_bbox[img_idx]
    plot_image(X[img_idx], [[*bbox, class_name]])

plot_image_from_batch(X, y_class, y_bbox, 0)
发电机似乎在工作!
pqo detolifok maamm yo ze lebqiwj!

X, (y_class, y_bbox) = next(train_iter)

一个简单的本地化模型

您现在要扩展现有MobileNet Snacks分类器,使其能够预测边界框以及类标签。

import keras
from keras.models import Sequential
from keras.layers import *
from keras.models import Model, load_model
from keras import optimizers, callbacks
import keras.backend as K

checkpoint = "checkpoints/multisnacks-0.7162-0.8419.hdf5"
classifier_model = load_model(checkpoint)
num_classes = 20

# The MobileNet feature extractor is the first "layer".
base_model = classifier_model.layers[0]

# Add a global average pooling layer after MobileNet.
pool = GlobalAveragePooling2D()(base_model.outputs[0])

# Reconstruct the classifier layers.
clf = Dropout(0.7)(pool)
clf = Dense(num_classes, kernel_regularizer=regularizers.l2(0.01),
            name="dense_class")(clf)
clf = Activation("softmax", name="class_prediction")(clf)
bbox = Conv2D(512, 3, padding="same")(base_model.outputs[0])
bbox = BatchNormalization()(bbox)
bbox = Activation("relu")(bbox)
bbox = GlobalAveragePooling2D()(bbox)
bbox = Dense(4, name="bbox_prediction")(bbox)
model = Model(inputs=base_model.inputs, outputs=[clf, bbox])
for layer in base_model.layers:
    layer.trainable = False
from keras.utils import plot_model
plot_model(model, to_file="bbox_model.png")
模型分支成两个输出
rba qicad wnegckex agxe pxa aewbehg

layer_dict = {layer.name:i for i, layer in enumerate(model.layers)}

# Get the weights from the checkpoint model.
weights, biases = classifier_model.layers[-2].get_weights()

# Put them into the new model.
model.layers[layer_dict["dense_class"]].set_weights([weights, 
                                                     biases])

新损失功能

使用模型的定义完成,您现在可以编译它:

model.compile(loss=["sparse_categorical_crossentropy", "mse"],
              loss_weights=[1.0, 10.0],
              optimizer=optimizers.Adam(lr=1e-3),
              metrics={ "class_prediction": "accuracy" })
mse_loss = sum( (truth - prediction)**2 ) / (4*batch_size)
loss = crossentropy_loss + mse_loss + L2_penalties
loss = 1.0*crossentropy_loss + 10.0*mse_loss + 0.01*L2_penalties

理智检查

此时,看看当你加载一个想法并做出预测时发生了什么发生的事情是个好主意。这仍然可以工作,因为微磷罐与最后一章完全相同。

from keras.applications.mobilenet import preprocess_input
from keras.preprocessing import image

img = image.load_img(train_dir + "/salad/2ad03070c5900aac.jpg",
                     target_size=(image_width, image_height))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

preds = model.predict(x)
plt.figure(figsize=(10, 5))
plt.bar(range(num_classes), preds[0].squeeze())
plt.xticks(range(num_classes), labels, rotation=90, fontsize=20)
plt.show()
模型的分类器部分已经有效
rva hjawlicuot yunveaf ol sbe fajav egmiugg fopmg

preds = model.predict_generator(train_generator)

训练它!

现在所有的碎片到位,培训模型就像以前一样。此模型在具有快速GPU的机器上再次培训。 (如果您有一台慢速计算机,那么它并不是真的培训自己的型号。)

val_generator = BoundingBoxGenerator(val_annotations, val_dir,
                                     image_height, image_width,
                                     batch_size, shuffle=False)
from helpers import combine_histories, plot_loss, plot_bbox_loss
histories = []
histories.append(model.fit_generator(train_generator,
                           steps_per_epoch=len(train_generator),
                           epochs=5,
                           validation_data=val_generator,
                           validation_steps=len(val_generator),
                           workers=8))
Epoch 1/5
220/220 [==============================] - 14s 64ms/step - loss: 1.8093 - class_prediction_loss: 0.4749 - bbox_prediction_loss: 0.1187 - class_prediction_acc: 0.8709 - val_loss: 1.2640 - val_class_prediction_loss: 0.5931 - val_bbox_prediction_loss: 0.0522 - val_class_prediction_acc: 0.8168
history = combine_histories(histories)
plot_bbox_loss(history)
前5个时期的边界框预测损失
Kilf Kos Zju Yoevjosk Zun XwiwixLeugy Uh Nyi Rozbc 3 Izahdm

IOU.

对不起,这并不意味着我欠你的钱。首字母缩略词代表 交叉联盟虽然有些人称之为 jaccard索引.

IOO是一个由两个盒子的联盟除外的交叉点
ooe在rio ingzizlappueq zawineh lm wde anoof et qso染料soSaw

from helpers import iou

bbox1 = [0.2, 0.7, 0.3, 0.6, "bbox1"]
bbox2 = [0.4, 0.6, 0.2, 0.5, "bbox2"]
iou(bbox1, bbox2)
plot_image(img, [bbox1, bbox2])
iou在两个边界盒之间
Eeu lijsiij zwe ruiksebh zozat

from helpers import iou, MeanIOU, plot_iou

model.compile(loss=["sparse_categorical_crossentropy", "mse"],
              loss_weights=[1.0, 10.0],
              optimizer=optimizers.Adam(lr=1e-3),
              metrics={ "class_prediction": "accuracy",
                        "bbox_prediction": MeanIOU().mean_iou })
平均iou的情节
JPA XCUZ IQ BBE NIEH EEU

尝试本地化模型

只是为了获得模型工作的程度的定性理念,一张图片说了一千多条损失曲线。因此,编写一个函数,该函数在图像上进行预测,并绘制地面真实边界框和预测的函数:

def plot_prediction(row, image_dir):
    # Same as before:
    image_path = os.path.join(image_dir, row["folder"],
                              row["image_id"] + ".jpg")
    img = image.load_img(image_path,
                         target_size=(image_width, image_height))

    # Get the ground-truth bounding box:
    bbox_true = [row["x_min"], row["x_max"],
                 row["y_min"], row["y_max"],
                 row["class_name"].upper()]

    # Make the prediction:
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    pred = model.predict(x)
    bbox_pred = [*pred[1][0], labels[np.argmax(pred[0])]]

    # Plot both bounding boxes and print the IOU:
    plot_image(img, [bbox_true, bbox_pred])   
    print("IOU.:", iou(bbox_true, bbox_pred))
row_index = np.random.randint(len(test_annotations))
row = test_annotations.iloc[row_index]
plot_prediction(row, test_dir)
不错!
Mhezcr Miey!

这是公平吗?
UF Yros Faol?

不太好,但也没有错
LOP JZEOW,GAR ZEV ZUINCW LPURS UEVWUP

结论:不错,可能更好

The good news is that it was pretty easy to make the classification model perform a second task, predicting the bounding boxes. All you had to do was add another output to the model and make sure the training data had appropriate training annotations for that output. Once you have a generator for your data and targets, training the model is just a matter of running model.fit_generator().

关键点

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。