人工智能计算机视觉
1. 计算机视觉简介
近年来,计算机视觉
已成为创新的关键领域,越来越多的应用程序重塑了人们的生活方式,从美颜相机到以图搜图,计算机视觉的快速发展已经为人们带来了愈加便捷的工作与娱乐形式。
很难完整的定义计算机视觉这一概念,因为它涉及到多个学科,例如计算机科学、物理学、数学以及生物学等。但是,我们可以说计算机视觉的核心是从数字图像中自动提取信息
。
在人类的视觉系统中,眼睛可以不断捕捉到视觉刺激,立即将一个物体与另一个物体区分开来,并识别我们可能仅见过一次的物体。而对于计算机来说,图像只是像素点,是红-绿-蓝值的矩阵,没有进一步的实质性意义。
计算机视觉的目标是使计算机“学习”
以人类的方式理解这些图像中像素,甚至能够挖掘出其潜在的信息。近年来,随着深度学习的发展,计算机视觉已经取得了突破性进展,在某些任务中已经能够取得超越人类水平的表现,例如图片识别和人脸验证等。
随着算法的不断改进,以及不断增加的数据量和视觉传感器,越来越多的计算机视觉问题开始得到解决:基于视觉的自动驾驶导航、基于内容的图像和视频检索、以及目标检测和超分辨率等。
由于本文的主要目的是介绍人工智能背景下的计算机视觉技术,因此并不会介绍类似图像读取、图像运算等知识,当然这些知识对于本文而言并非必不可少,对相关知识感兴趣的话,可以参考专栏《OpenCV从入门到项目实战》了解相应基础知识。
2. 计算机视觉应用
本节中,将介绍计算机视觉在实际场景中的主流应用。
2.1 内容识别
计算机视觉的一个核心目标是理解图像,即从像素中提取有意义的语义信息,例如图像中存在的对象、对象的位置等,根据不同任务,内容识别又可以分为以下类别。
2.1.1 图像分类
图像分类,也称对象分类,是为数据集中的图像分配相应标签的任务,早在 2012 年,图像分类就成为了将深度卷积神经网络应用于计算机视觉的第一个成功案例,从那时起,图像分类得到了飞速的发展,以至于在各种用例中都实现了超越人类水平的性能表现。常见的图像分类应用是包括图像数据库自动注释等。
2.1.2 实例分类
对象分类方法可以为图片从预定义集合中选取标签进行分类,而对象识别(或实例分类)方法学习识别类的特定实例。例如,可以将对象分类识别图像中是否包含人脸,而对象识别则专注于提取人脸特征以识别所有图像中的每个人脸。因此,对象识别可以看作是对数据集进行聚类的过程,虽然都属于人脸图片,但可能这些面部属于不同人物,对象识别的任务就是将属于不同人物的面部图片聚类,以区分不同人脸。
2.1.3 目标检测和定位
目标检测和定位是检测图像中的特定对象,并在对象周围绘制一个边界框定位其在图片中的位置。它通常用于相机应用程序的人脸检测、医学中癌细胞的检测、工业工厂中受损零件的检测等。
检测和定位通常是进行下一步计算之前的步骤,根据得到的边界框,可以将图片分为较小图像块,以进行进一步处理,例如用于增强现实应用,或者识别边界框内的面部表情等,如下图所示:
2.1.4 对象分割和实例分割
分割可以看作是一种更高级的识别类型,分割不是简单地为识别的对象提供边界框,而是返回标记属于特定类或特定类实例的所有像素的掩码。目前,这一任务仍待深入探索,实际上它是计算机视觉中为数不多的深度神经网络仍远未达到人类性能的任务之一,人类的大脑非常善于绘制视觉元素的精确轮廓方面。
对象分割和实例分割具有一定的区别,例如,对象分割算法为属于汽车类的所有像素返回同一掩码,但实例分割算法为它识别的每个汽车实例返回不同的掩码。这些分割算法也可以用于医学图像,在医学扫描中精确分割不同的组织可以实现更快的诊断。
2.1.5 姿势估计
根据目标任务,姿势估计可以具有不同的含义。对于刚性物体,通常是指估计物体在 3D
空间中相对于相机的位置和方向。这对机器人特别有用,以便它们可以与环境交互(拾取对象,避免碰撞等)。它也经常在增强现实中用于将 3D
信息叠加在对象之上。
对于非刚性元素,姿态估计也可以表示其子部分相对于彼此的位置的估计。更具体地,当将人类视为非刚性目标时,典型的应用是识别人类的姿势(站立,行走,奔跑等)或理解手语。
2.2 视频分析
计算机视觉不仅适用于单个图像,还适用于视频。某些任务逐帧分析视频流,而某些任务需要整体考虑一个图像序列,以便考虑时间一致性.
2.2.1 实例追踪
一些与视频流有关的任务可以通过单独研究每个帧来简单地完成,但是更有效的方法是考虑图像之间的差异以将过程引导到新帧,或将完整的图像序列作为其预测的输入。跟踪,即定位视频流中的特定元素。
通过对每个帧应用检测和识别方法,可以逐帧进行跟踪。但是,使用先前的结果对实例的运动进行建模,以便部分预测它们在未来帧中的位置,具有更高的效率。因此,运动连续性 (Motion Continuity
) 虽然并不总是成立(例如,对于快速移动的对象),但预测仍具有重要作用。
2.2.2 动作识别
动作识别属于只能通过一系列图像运行的任务。类似于当单词无序排列时无法理解一个句子,不研究连续的图像序列就无法识别动作。
识别动作意味着识别预定义集合中的特定动作,例如,对于跳舞,游泳等。动作识别从监控(例如检测异常或可疑行为)到人机交互(例如使用手势控制设备)都有着广泛的应用。由于对象识别可以分为对象分类,检测,分割等,因此动作识别也可以分为动作分类,检测等。
2.2.3 运动估计
某些方法的目的并不是尝试识别运动类别,而是着重于估计视频中捕获的实际运动速度/轨迹。通常还需要评估摄像机本身相对于所表示场景的运动。这在娱乐行业有着广泛的应用前景,例如,捕获运动以应用视觉效果或将 3D
信息叠加在视频流(例如体育广播)中。
2.3 图像编辑
除了分析其内容外,计算机视觉方法还可用于改善图像本身。利用图像内容的先验知识来改善其视觉质量。此概念适用于任何类型的图像恢复,无论是去噪,去模糊还是分辨率增强。
内容感知算法还可以用于某些摄影或艺术应用程序中,例如旨在增强某些视觉效果的智能人像或美颜模式,或用于消除不必要元素并以连贯的背景进行替换的智能删除工具。
2.4 场景重建
场景重建是在给定一个或多个图像的情况下恢复场景的 3D
几何的任务。基于人类视觉的一个简单示例是立体匹配。这是从不同的视角查找场景的两个图像之间的对应关系以便得出每个可视化元素的距离的过程。更高级的方法拍摄几张图像并将其内容匹配在一起,以获得目标场景的 3D
模型。这可以应用于对象,人物,建筑物等的 3D
扫描。
3. 构建卷积神经网络实现图片分类
本节中,我们将构建卷积神经网络 (Convolutional Neural Network
, CNN
) 模型用来识别 MNIST
手写数字。我们采用的以下策略构建 CNN
模型:
- 输入形状为
28 x 28 x 1
,使用的卷积核尺寸为3 x 3 x 1
- 需要注意的是,卷积核的大小可以更改,但是通道数不能更改,需要与输入通道数相同
- 使用
10
个卷积核
- 输入图像经过卷积层后,使用池化层:
- 输出的图像尺寸减半
- 展平池化后获得的输出
- 展平层连接到一个具有
1000
个单位的隐藏层 - 最后,将隐藏层连接到输出层,输出层中有
10
类(包括数字0-9
)
接下来,使用 Keras
实现上述定义的 CNN
架构,以了解如何在 MNIST
数据上使用 CNN
模型。
加载并预处理数据,预处理方法与上一节所用方法完全相同:
from keras.layers import Dense,Conv2D,MaxPool2D,Flatten
from keras.models import Sequential
from keras.datasets import mnist
from keras.utils import np_utils
import numpy as np
(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(x_train.shape[0], x_train.shape[1], x_train.shape[1], 1)
x_test = x_test.reshape(x_test.shape[0], x_test.shape[1], x_test.shape[1], 1)
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]
建立并编译模型:
model = Sequential()
model.add(Conv2D(10, (3, 3), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.summary()
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])
可以获取我们在前面的代码中初始化的模型的简要架构信息:
model.summary()
输出该模型的简要架构信息如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 10) 100
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 10) 0
_________________________________________________________________
flatten (Flatten) (None, 1690) 0
_________________________________________________________________
dense (Dense) (None, 512) 865792
_________________________________________________________________
dense_1 (Dense) (None, 10) 5130
=================================================================
Total params: 871,022
Trainable params: 871,022
Non-trainable params: 0
_________________________________________________________________
卷积层中总共有 100
个参数,因为卷积层中有 10
个 3 x 3 x 1
的卷积核,因此总共有 90
个权重参数和 10
个偏置项(每个卷积核中 1
个),共 100
个参数。最大池化层没有任何参数,因为它只需要计算每个大小为 2 x 2
的池化核中的最大值。可以看到使用 CNN
模型可以大幅降低网络参数量。
最后,拟合模型:
model.fit(x_train, y_train,
validation_data=(x_test, y_test),
epochs=10,
batch_size=1024,
verbose=1)
以上模型在 10
个 epoch
训练后,可以达到 99%
的准确率:
4. 计算机视觉常用现代网络架构
本节中,将介绍介个经典的常用于计算机视觉的神经网络架构:VGG
,Inception
和 ResNet
,除了这些我们介绍的这些模型架构外,还有许多更新的模型,包括 DenseNet
、Xception
等。
4.1 VGG
VGG
模型对后续的很多网络架构都具有重要的启发意义, VGG
架构以更少的参数实现更高的准确度。AlexNet
模型是第一个成功训练用于复杂的识别任务的 CNN
,并做出了许多卓有成效的贡献,例如:
- 使用
ReLU
作为激活函数,防止梯度消失问题,从而改善了训练 - 在
CNN
中应用Dropout
- 开创了典型的
CNN
架构——结合卷积和池化层的网络块,最后使用全连接层进行预测 - 应用随机变换扩充数据集,来增加训练图像的数量
尽管如此,这种架构很明显仍有改进的空间,许多研究人员的主要方向是尝试更增加网络深度,即构建由大量堆叠层组成的网络。但更多的层通常意味着要训练更多的参数,从而使学习过程更加复杂。而 VGG
小组成功解决了这一问题,提出了比之前的大多数网络更深的网络。他们引入了六种不同的 CNN
架构,深度从 11
到 25
层不等。每个网络由五个连续卷积块组成,后跟一个最大池化层和三个全连接层。所有的卷积的步幅为 s = 1
,并且使用 ReLU
函数进行激活。一个典型的 VGG
网络如下图所示:
VGG-16
和 VGG-19
中的数字代表这些 CNN
架构的深度,即可训练的网络层数。例如,上图中,VGG-16
包含 13
个卷积层和 3
个全连接层,因此深度为 16
,不包括不可训练的网络层。VGG
的主要贡献还包括:
- 使用更小尺寸的卷积核,同时增加卷积核的数量,在减小参数量的同时保证了感受野不会减小
- 增加特征图的深度
- 用卷积替换全连接层,虽然经典的
VGG
架构包含若干个全连接层,但作者提出了一个可以用卷积层代替全连接层的方案
4.2 Inception
为了更好理解 Inception
模型的核心思想,我们首先考虑在数据集中,有一些图像中的对象占据了图像的大部分,但在另一些图像中,对象可能仅仅占整个图像的一小部分。如果在两种情况下我们都使用相同大小的卷积核,则将使模型难以同时学习到识别图像中较小的对象和图像中较大的对象。
为了解决这个问题,我们可以在同一层中使用的多种不同尺寸的卷积核。在这种情况下,网络本质上是变宽了,而不是变深了:
在上图中,我们在给定层中使用多种不同尺寸的卷积核进行卷积。Inception v1
模块具有九个线性堆叠的 Inception
模块,如下所示:
此体系结构既深又宽,这很可能会导致梯度消失。为了解决梯度消失的问题,Inception v1
有两个辅助分类器,它们源于 Inception
模块,试图将基于 Inception
网络的总损失降到最低,如下所示:
total_loss = real_loss + 0.3 * aux_loss_1 + 0.3 * aux_loss_2
需要注意的是,辅助损失仅在训练期间使用,而在模型测试期间中将被忽略。Inception v2
和 Inception v3
是对 Inception v1
体系结构的改进,其中在 Inception v2
中,Inception
作者在卷积运算的基础上进行了优化,以更快地处理图像;在 Inception v3
中,Inception
作者在原有卷积核的基础上添加了 7 x 7
的卷积核,并将它们串联在一起。总而言之,Inception
的贡献如下:
- 使用
Inception
模块捕获图像的多尺度细节 - 使用
1 x 1
卷积作为瓶颈层 - 使用平均池化层代替全连接层,降低模型参数量
- 使用辅助分支来避免梯度消失
4.3 ResNet
深层网络在学习任务中取得了超越人眼的准确率,但是,经过实验表明,模型的性能和模型的深度并非成正比,是由于模型的表达能力过强,反而在测试数据集中性能下降。ResNet
的核心是,为了防止梯度弥散或爆炸,让信息流经快捷连接到达浅层。
更正式的讲,输入 x x x 通过卷积层,得到特征变换后的输出 F ( x ) F(x) F(x),与输入 x x x 进行对应元素的相加运算,得到最终输出 H ( x ) H(x) H(x):
H ( x ) = x + F ( x ) H(x) = x + F(x) H(x)=x+F(x)
VGG
模块和残差模块对比如下:
为了能够满足输入 x x x 与卷积层的输出 F ( x ) F(x) F(x) 能够相加运算,需要输入 x x x 的 shape
与 F ( x ) F(x) F(x) 的 shape
完全一致。当出现 shape
不一致时,一般通过 Conv2D
进行变换,该 Conv2D
的核为 1×1
,步幅为 2
。典型的 ResNet
架构如下所示:
在上图中,可以看出,模型中具有跳跃连接。ResNet
的贡献主要有以下几点:
- 使用残差函数而非恒等映射
- 残差块不包含额外的参数,因此,可以有效地用作超深度网络的构建
5. 计算机视觉趣味实战
5.1 目标检测
除了图像识别外,计算机视觉还有许多强大的应用,下面以目标检测为例,目标检测的任务是检测图像或视频中预定义类(例如,猫、汽车和人类)的实例,并使用边界框标记实例位置。MobileNets
是用于移动视觉应用的高效卷积神经网络,MobileNet-SSD
在 COCO
数据集上进行了训练,达到了 72.27% mAP
,可以用于检测到 20 种对象类别(这里对训练后 MobileNet-SSD
模型架构和模型权重参数文件进行压缩供大家进行下载,也可以自己构建模型训练获得 MobileNet-SSD
模型参数):
Person(人): Person(人)
Animal(动物): Bird(鸟), cat(猫), cow(牛), dog(狗), horse(马), sheep(羊)
Vehicle(交通工具): Aeroplane(飞机), bicycle(自行车), boat(船), bus(公共汽车), car(小轿车), motorbike(摩托车), train(火车)
Indoor(室内): Bottle(水瓶), chair(椅子), dining table(餐桌), potted plant(盆栽), sofa(沙发), TV/monitor(电视/显示器)
通过使用 MobileNet-SSD
和 Caffe
预训练模型,使用 OpenCV DNN
模块执行对象检测:
import cv2
# 加载模型及参数
net = cv2.dnn.readNetFromCaffe('MobileNetSSD_deploy.prototxt', 'MobileNetSSD_deploy.caffemodel')
# 图片读取
image = cv2.imread('test_img.jpg')
# 定义类别名
class_names = {0: 'background', 1: 'aeroplane', 2: 'bicycle', 3: 'bird', 4: 'boat', 5: 'bottle', 6: 'bus', 7: 'car',8: 'cat', 9: 'chair', 10: 'cow', 11: 'diningtable', 12: 'dog', 13: 'horse', 14: 'motorbike', 15: 'person', 16: 'pottedplant', 17: 'sheep', 18: 'sofa', 19: 'train', 20: 'tvmonitor'}
# 预处理
blob = cv2.dnn.blobFromImage(image, 0.007843, (300, 300), (127.5, 127.5, 127.5))
print(blob.shape)
# 前向计算
net.setInput(blob)
detections = net.forward()
t, _ = net.getPerfProfile()
print('Inference time: %.2f ms' % (t * 1000.0 / cv2.getTickFrequency()))
# 输入图像尺寸
dim = 300
# 处理检测结果
for i in range(detections.shape[2]):
# 获得预测的置信度
confidence = detections[0, 0, i, 2]
# 去除置信度较低的预测
if confidence > 0.2:
# 获取类别标签
class_id = int(detections[0, 0, i, 1])
# 获取检测到目标对象框的坐标
xLeftBottom = int(detections[0, 0, i, 3] * dim)
yLeftBottom = int(detections[0, 0, i, 4] * dim)
xRightTop = int(detections[0, 0, i, 5] * dim)
yRightTop = int(detections[0, 0, i, 6] * dim)
# 缩放比例系数
heightFactor = image.shape[0] / dim
widthFactor = image.shape[1] / dim
# 根据缩放比例系数计算检测结果最终坐标
xLeftBottom = int(widthFactor * xLeftBottom)
yLeftBottom = int(heightFactor * yLeftBottom)
xRightTop = int(widthFactor * xRightTop)
yRightTop = int(heightFactor * yRightTop)
# 绘制矩形框
cv2.rectangle(image, (xLeftBottom, yLeftBottom), (xRightTop, yRightTop), (0, 255, 0), 2)
# 绘制置信度和类别
if class_id in class_names:
label = class_names[class_id] + ": " + str(confidence)
labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 1, 2)
yLeftBottom = max(yLeftBottom, labelSize[1])
cv2.rectangle(image, (xLeftBottom, yLeftBottom - labelSize[1]),
(xLeftBottom + labelSize[0], yLeftBottom + 0), (0, 255, 0), cv2.FILLED)
cv2.putText(image, label, (xLeftBottom, yLeftBottom), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)
程序输出如下图所示,几乎所有对象都被正确且高精度地检测到:
通过这一示例可以看到计算机视觉模型的强大性能,可以参考《计算机视觉与深度学习》查看更多计算机视觉有趣的应用。
5.2 神经风格迁移
5.2.1 神经风格迁移原理
我们已经介绍了如何预测图像的类别并检测对象在图像中的位置。接下来,我们将介绍另一类计算机视觉中非常有趣的应用——图像生成。
在神经风格迁移中,我们需要一个内容图像和一个风格图像,我们的目标是保持内容图像的同时融和风格图像中的风格样式,以组合这两个图像。
首先,了解神经风格迁移的相关原理。神经风格迁移需要将损失值分为内容损失
和风格损失
。内容损失用于衡量生成的图像与内容图像有多大差异。风格损失用于衡量风格图像与生成的图像之间的关联程度。
尽管上述损失是根据图像的差异计算的,但实际上,我们使用图像的在网络层中激活而不是原始图像来计算损失值。例如,第 2
层的内容损失是内容图像和生成的图像通过第 2
网络层得到的激活之间的平方差。
计算内容损失较为简单,接下来我们继续学习如何计算生成图像和风格图像之间的相似度。我们将使用一种称为 gram
矩阵的技术,gram
矩阵用于计算生成图像和风格图像之间的相似度,相似度计算方法如下:
L G M ( S , G , l ) = 1 4 N l 2 M l 2 ∑ i j ( G M [ l ] ( S ) i j − G M [ l ] ( G ) i j ) 2 L_{GM}(S,G,l)=\frac 1 {4N_l^2M_l^2}\sum_{ij}(GM[l](S)_{ij}-GM[l](G)_{ij})^2 LGM(S,G,l)=4Nl2Ml21ij∑(GM[l](S)ij−GM[l](G)ij)2
其中 G M ( l ) GM(l) GM(l) 是风格图像 S S S 和生成图像 G G G 在第 l l l 层的 gram
矩阵值。gram
矩阵是通过将矩阵与其自身的转置矩阵相乘得到的。计算得到风格损失和内容损失后,最终生成的修改图像是使总损失最小的图像,即令风格和内容损失的加权平均值最小。
5.2.2 神经风格迁移实现
因此,通过以下步骤实现神经风格迁移:
- 通过预训练的模型传递图像
- 在预定义的网络层上提取图像特征
- 将内容图像输入到预训练模型中,并在预定义的内容网络层上提取图像特征
- 计算内容损失
- 将风格图像输入到预训练模型中,并在预定义的风格网络层上提取图像特征,然后计算风格图像的
gram
矩阵值 - 将生成图像传递给风格图像所经过的同样的网络层,并计算对应的
gram
矩阵值 - 提取两个图像的
gram
矩阵值的平方差,得到的结果即为风格损失 - 总损失将为风格损失和内容损失的加权平均值
- 根据损失修改输入图像,得到令总损失最小的图像即为最终的图像
接下来,我们实现利用 keras
实现神经风格迁移。
导入所需要的库,以及用于神经风格迁移的内容图像和风格图像,如下所示:
from keras.applications import vgg19
from keras import backend as K
import numpy as np
import cv2
from PIL import Image
from matplotlib import pyplot as plt
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
style_img = cv2.imread('Vincent_van_Gogh_779.jpg')
style_img = cv2.cvtColor(style_img, cv2.COLOR_BGR2RGB)
style_shape = style_img.shape
content_img = cv2.imread('3.jpeg')
content_img = cv2.cvtColor(content_img, cv2.COLOR_BGR2RGB)
content_shape = content_img.shape
风格和内容图像如下所示:
plt.subplot(211)
# 为了进行对比,将风格图像进行缩放,以与内容图像具有相同的尺寸
plt.imshow(cv2.resize(style_img, (content_shape[1], content_shape[0])))
plt.title('Style image')
plt.axis('off')
plt.subplot(212)
plt.imshow(content_img)
plt.title('Content image')
plt.axis('off')
plt.show()
初始化 VGG19
模型,以便获取输入图像在网络层中的特征输出:
from keras.applications import vgg19
model = vgg19.VGG19(include_top=False, weights='imagenet')
定义图像预处理和逆处理函数:
def preprocess_image(img):
img = np.array(img).astype(float)
img = np.expand_dims(img, axis=0)
# 数据预处理
img[:, :, :, 0] -= 103.939
img[:, :, :, 1] -= 116.779
img[:, :, :, 2] -= 123.68
img = img[:, :, :, ::-1] / 255
return img
def deprocess_image(x):
x = x[:, :, :, ::-1] * 225
x[:, :, :, 0] += 103.939
x[:, :, :, 1] += 116.779
x[:, :, :, 2] += 123.68
x = np.clip(x, 0, 255).astype('uint8')[0]
x = Image.fromarray(x).resize((content_shape[1], content_shape[0]))
return x
对内容图像进行预处理,并将其输入到模型中提取 VGG19
模型 block3_conv4
层的特征值:
content_img = preprocess_image(content_img)
get_3rd_layer_output = K.function([model.layers[0].input],[model.get_layer('block3_conv4').output])
layer_output_base = get_3rd_layer_output([content_img])[0]
在以上代码中,我们定义了一个函数用于获取输入图像并提取预定义层的输出特征。
接下来,定义需要提取用于计算内容和风格损失的神经网络图层,并为每一网络层分配相应权重:
layer_contributions_content = {'block3_conv4': 0.1}
layer_contributions_style = {'block1_conv1':5,
'block2_conv1':10,
'block3_conv1':10,
'block4_conv1':15,
'block5_conv1':15}
在以上代码中,我们定义了用于计算内容和风格损失的网络层,并为这些网络分配了不同权重,用于计算总体损失。
定义 gram
矩阵和风格损失函数。我们首先定义函数 gram_matrix
,用于计算输入的 gram
矩阵:
def gram_matrix(x):
# 对图像进行展平
features = K.batch_flatten(K.permute_dimensions(x, (2, 0 ,1)))
# 计算特征矩阵和特征转置矩阵的点积
gram = K.dot(features, K.transpose(features))
return gram
在以下代码中,我们指定的样式损失方程式定义的方式来计算样式损失:
def style_loss(style, combination):
S = gram_matrix(style)
C = gram_matrix(combination)
channels = 3
size = content_img.shape[0] * content_img.shape[1]
return K.sum(K.square(S - C)) / (4. * (pow(channels, 2)) * (pow(size, 2)))
初始化损失值函数,并计算内容损失:
layer_dict = dict([(layer.name, layer) for layer in model.layers])
loss = K.variable(0.)
for layer_name in layer_contributions_content:
coeff = layer_contributions_content[layer_name]
activation = layer_dict[layer_name].output
scaling = K.prod(K.cast(K.shape(activation), 'float32'))
loss =
loss + coeff * K.sum(K.square(activation - layer_output_base)) / scaling
在以上代码中,根据内容神经网络层 layer_contributions_content
的损失来更新损失值,layer_output_base
是将原始图像通过内容神经网络层时的输出。激活 activations
(即基于修改后的图像提取到的特征)和 layer_output_base
之间的差异越大,则图像内容损失越大。
计算风格损失:
for layer_name in layer_contributions_style:
coeff = layer_contributions_style[layer_name]
activation = layer_dict[layer_name].output
scaling = K.prod(K.cast(K.shape(activation), 'float32'))
style_layer_output = K.function([model.layers[0].input], [model.get_layer(layer_name).output])
layer_output_style = style_layer_output([preprocess_image(style_img)])[0][0]
loss = loss + style_loss(layer_output_style, activation[0])
在以上代码中,我们以与计算内容损失相同的方式计算风格损失,不同的是,风格损失的计算使用不同的神经网络层。
构建函数将输入图像映射到损失值和相应的梯度变化值:
input_image = model.input
grads = K.gradients(loss, input_image)[0]
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
outputs = [loss, grads]
fetch_loss_and_grads = K.function([input_image], outputs)
def eval_loss_and_grads(img):
outs = fetch_loss_and_grads([img])
loss_value = outs[0]
grad_values = outs[1]
return loss_value, grad_values
使用以上代码可以获取损失和梯度变化值以生成风格迁移图像。
最后在多个 epochs
内运行模型:
img = content_img.copy()
for i in range(2000):
step=0.001
loss_value, grad_values = eval_loss_and_grads(img)
print('...Loss value at', i, ':', loss_value)
img = img - step * grad_values
if((i+1)%100 == 0):
img2 = img.copy()
img2 = deprocess_image(img2)
plt.imshow(img2)
plt.axis('off')
plt.show()
使用以上代码生成的图像,得到内容图像和风格图像的融合后的风格迁移图片:
可以通过选择不同的神经网络层来计算内容和风格损失,并为不同网络层分配不同的权重系数,观察生成图像的差别。
总结
本节中,我们学习了计算机视觉的相关概念,并了解了计算机视觉中主流的任务包括:对象识别、实例识别、动作识别、场景重建等。 在人工智能的背景下,我们介绍了现代常用与计算机视觉中的网络架构,并实现了一个基础的卷积神经网络模型。在最后,我们使用两个有趣的计算机视觉项目来作为我们开启计算机视觉之旅的入场券。
系列链接
使用Scikit-learn开启机器学习之旅
一文开启深度学习之旅
一文开启自然语言处理之旅
一文开启监督学习之旅
一文开启无监督学习之旅
- 点赞
- 收藏
- 关注作者
评论(0)