一、结构
上图就是复现vgg16的全部文件,data文件夹是测试图像,这次复现只是调用别人训练好的模型来识别图片。vgg16.py复现了vgg16的网络结构,并导入了别人训练好的模型参数,utils.py为输入图片预处理的程序,Nclasses.py则是我们给定的每个图像的标签,以及对应的索引值,app.py是我们的调用文件,进行图像识别。
二、代码详解
1、vgg16.py
1 import tensorflow as tf
2 import numpy as np
3 import os
4 import time
5 import matplotlib.pyplot as plt
6 from Nclasses import labels
7
8 VGG_MEAN = [103.939,116.779,123.68] #样本RGB的平均值
9
10 class Vgg16:
11 def __init__(self,vgg16_npy_path=None):
12 if vgg16_npy_path is None:
13 vgg16_npy_path = os.path.join(os.getcwd(),'vgg16.npy') # os.getcwd() 方法用于返回当前工作目录。
14 print(vgg16_npy_path)
15 # 遍历键值对,导入模型参数
16 self.data_dict = np.load(vgg16_npy_path,encoding='latin1').item() # 这里加载vgg16.npy的参数
17 # 遍历data_dict中的每个键
18 for i in self.data_dict:
19 print(i)
20 #前向传播模型
21 def inference(self,images):
22 start_time = time.time() # 获取前向传播的开始时间
23 print('build model started')
24 ## 逐像素乘以 255.0(根据原论文所述的初始化步骤)
25 images_scaled = images * 255
26 # 从 GRB 转换色彩通道到 BGR,也可使用 cv 中的 GRBtoBGR
27 #tf.split()将图像里的rgb通道分离
28 red,green,blue = tf.split(images_scaled,num_or_size_splits=3,axis=3)
29 # 逐样本减去每个通道的像素平均值,这种操作可以移除图像的平均亮度值,该方法常用在灰度图像上
30 bgr = tf.concat(values=[
31 blue-VGG_MEAN[0],
32 green-VGG_MEAN[1],
33 red-VGG_MEAN[2]
34 ],axis=3)
35
36
37 #构建vgg网络结构
38 ## 接下来构建 VGG 的 16 层网络(包含 5 段卷积, 3 层全连接),并逐层根据命名空间读取网络参数
39 # 第一段卷积,含有两个卷积层,后面接最大池化层,用来缩小图片尺寸
40 # 传入命名空间的 name,来获取该层的卷积核和偏置,并做卷积运算,最后返回经过经过激活函数后的值
41 conv1_1 = self.conv_layer(bgr,'conv1_1')
42 conv1_2 = self.conv_layer(conv1_1,'conv1_2')
43 # 根据传入的 pooling 名字对该层做相应的池化操作
44 pool1 = self.max_pool(conv1_2,'pool1')
45
46 #第二层卷积层
47 # 下面的前向传播过程与第一段同理
48 # 第二段卷积,同样包含两个卷积层,一个最大池化层
49 conv2_1 = self.conv_layer(pool1,'conv2_1')
50 conv2_2 = self.conv_layer(conv2_1,'conv2_2')
51 pool2 = self.max_pool(conv2_2,'pool2')
52 # 第三段卷积,包含三个卷积层,一个最大池化层
53 conv3_1 = self.conv_layer(pool2,'conv3_1')
54 conv3_2 = self.conv_layer(conv3_1,'conv3_2')
55 conv3_3 = self.conv_layer(conv3_2,'conv3_3')
56 pool3 = self.max_pool(conv3_3,'pool3')
57 # 第四段卷积,包含三个卷积层,一个最大池化层
58 conv4_1 = self.conv_layer(pool3,'conv4_1')
59 conv4_2 = self.conv_layer(conv4_1,'conv4_2')
60 conv4_3 = self.conv_layer(conv4_2,'conv4_3')
61 pool4 = self.max_pool(conv4_3,'pool4')
62 # 第五段卷积,包含三个卷积层,一个最大池化层
63 conv5_1 = self.conv_layer(pool4, 'conv5_1')
64 conv5_2 = self.conv_layer(conv5_1, 'conv5_2')
65 conv5_3 = self.conv_layer(conv5_2, 'conv5_3')
66 pool5 = self.max_pool(conv5_3, 'pool5')
67 # 第六层全连接
68 #全连接层
69 fc6 = self.fc_layer(pool5,'fc6') # 根据命名空间 name 做加权求和运算
70 fc6_relu = tf.nn.relu(fc6)
71 fc7 = self.fc_layer(fc6_relu,'fc7')
72 fc7_relu = tf.nn.relu(fc7)
73 fc8 = self.fc_layer(fc7_relu,'fc8')
74 # 经过最后一层的全连接后,再做 softmax 分类,得到属于各类别的概率
75 self.out = tf.nn.softmax(fc8,name='prediction')
76
77 # 清空本次得到的模型参数字典,结束前向传播的时间
78 self.data_dict = None
79 print(("build model finished: %ds" % (time.time() - start_time)))
80
81 #创建卷积层函数,卷积核、偏置都从vgg16.npy中获取
82 def conv_layer(self,conv_in,name):
83 with tf.variable_scope(name): # 根据命名空间找到对应卷积层的网络参数
84 conv_W = tf.constant(self.data_dict[name][0],name= name + '_filter') # 读到该层的卷积核
85 conv_biase = tf.constant(self.data_dict[name][1],name= name + '_biases') # 读到偏置项
86 conv = tf.nn.conv2d(conv_in,conv_W,strides=[1,1,1,1],padding='SAME') # 卷积计算
87 return tf.nn.relu(tf.nn.bias_add(conv,conv_biase)) # 加上偏置,并做激活计算
88
89 #最大池化层
90 def max_pool(self,input_data,name):
91 return tf.nn.max_pool(input_data,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME',name=name)
92
93 #平均池化层
94 def avg_pool(self, input_data, name):
95 return tf.nn.avg_pool(input_data, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', name=name)
96
97 # 定义全连接层的前向传播计算
98 def fc_layer(self,input_data,name):
99 fc_W = tf.constant(self.data_dict[name][0],name=name + '_Weight')
100 fc_b = tf.constant(self.data_dict[name][1],name=name + '_b')
101
102 shape = input_data.get_shape().as_list() #获得输入的维度信息列表
103 dim =1
104 for i in shape[1:]:
105 dim *= i # 将每层的维度相乘,即,长*宽*通道数
106 # 改变特征图的形状,也就是将得到的多维特征做拉伸操作,只在进入第六层全连接层做该操作
107 input_data = tf.reshape(input_data,[-1,dim])
108
109 return tf.nn.bias_add(tf.matmul(input_data,fc_W),fc_b) # 对该层输入做加权求和,再加上偏置
110
111 # 定义百分比转换函数
112 def percent(self,value):
113 return '%.2f%%' % (value * 100)
114 #判断图片类别函数
115 def test(self,file_path,prob):
116 #定义一个figure画图窗口,并指定窗口的名称,也可以设置窗口的大小
117 fig = plt.figure(u"Top-5 预测结果")
118 synset = [l.strip() for l in open(file_path).readlines()]
119
120 # argsort函数返回的是数组值从小到大的索引值
121 # np.argsort 函数返回预测值(probability 的数据结构[[各预测类别的概率值]])由小到大的索引值,
122 # 并取出预测概率最大的五个索引值
123 top5 = np.argsort(prob)[-1:-6:-1]
124 #print(pred)
125
126 # 定义两个 list---对应的概率值和实际标签(zebra)
127 values = []
128 bar_label = []
129 # 输出概率组大的5种可能性,并且和标签一一列举
130 for n, i in enumerate(top5):
131 print("n:", n)
132 print("i:", i)
133 values.append(prob[i])# 将索引值对应的预测概率值取出并放入 values
134 bar_label.append(labels[i]) # 根据索引值取出对应的实际标签并放入 bar_label
135 # 属于这个类别的概率
136 print(i, ":", labels[i], "----", self.percent(prob[i])) # 打印属于某个类别的概率
137 # 柱状图处理
138 ax = fig.add_subplot(111) # 将画布划分为一行一列,并把下图放入其中
139 # bar()函数绘制柱状图,参数 range(len(values)是柱子下标, values 表示柱高的列表(也就是五个预测概率值,
140 # tick_label 是每个柱子上显示的标签(实际对应的标签), width 是柱子的宽度, fc 是柱子的颜色)
141 ax.bar(range(len(values)), values, tick_label=bar_label, width=0.5, fc='g')
142 ax.set_ylabel(u'probabilityit') # 设置横轴标签
143 ax.set_title(u'Top-5') # 添加标题
144 for a, b in zip(range(len(values)), values):
145 # 在每个柱子的顶端添加对应的预测概率值, a, b 表示坐标, b+0.0005 表示要把文本信息放置在高于每个柱子顶端
146 # 0.0005 的位置,
147 # center 是表示文本位于柱子顶端水平方向上的的中间位置, bottom 是将文本水平放置在柱子顶端垂直方向上的底端
148 # 位置, fontsize 是字号
149 ax.text(a, b + 0.0005, self.percent(b), ha='center', va='bottom', fontsize=7)
150 plt.savefig('.result.jpg') # 保存图片
151 plt.show()# 弹窗展示图像
这一部分我们创建了VGG16网络结构的前向传播,包括卷积核、偏置、池化层以及全连接层,这里需要说一下的是全连接层的建立,这里我们创建全连接层首先需要读取到该层的维度信息列表,然后我们要改变特征图的形状,在第六层将得到的多维特征进行拉伸操作,使其符合全连接层的输入即可,这里的shape中有元素[-1],表示将该维度打平到一维,实现降维的目的。
2、utils.py
from skimage import io, transform
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from pylab import mpl
import os
mpl.rcParams['font.sans-serif']=['SimHei'] # 正常显示中文标签
mpl.rcParams['axes.unicode_minus']=False # 正常显示正负号
def load_image(path):
fig = plt.figure('Center and Resize')
img = io.imread(path) # 根据传入的路径读入图片
img = img / 255.0 # 将像素归一化到[0,1]
ax0 = fig.add_subplot(131)
ax0.set_xlabel(u'原始图片') # 添加子标签
ax0.imshow(img)
## 图像处理部分
#把图片的宽和高减去最短的边,并且求均值,取出切分出的中心图像
short_edge = min(img.shape[:2]) # 找到该图像的最短边
y = int((img.shape[0] - short_edge) // 2)
x = int((img.shape[1] - short_edge)// 2)
crop_img = img[y:y + short_edge,x:x + short_edge] #取出切分出的中心图像
ax1 = fig.add_subplot(132)
ax1.set_xlabel(u"中心图片")
ax1.imshow(crop_img)
#中心图像resize为224, 224
resized_img = transform.resize(crop_img,(224,224))
ax2 = fig.add_subplot(133)
ax2.set_xlabel('切分图片') # 添加子标签
ax2.imshow(resized_img)
#plt.show()
img_read = resized_img.reshape((1,224,224,3)) # shape [1, 224, 224, 3]
return img_read
def load_data():
imgs = {'tiger':[],'kittycat':[]}
for k in imgs.keys():
dir = './data/' + k
for file in os.listdir(dir):
if not file.lower().endswith('.jpg'):
continue
try:
resized_img = load_image(os.path.join(dir,file))
except OSError:
continue
imgs[k].append(resized_img) # [1, height, width, depth] * n
if len(imgs[k]) == 2: # only use 400 imgs to reduce my memory load
break
return imgs['tiger'],imgs['kittycat']
这部分对输入图片进行处理,裁剪,缩放等,以满足网络输入的需要,主要的思路是将图像归一化后进行处理,实现结果如下图所示:
3、app.py
1 import numpy as np
2 import tensorflow as tf
3 import matplotlib.pyplot as plt
4 import vgg16
5 import utils
6 #待检测图像输入,并进行预处理
7 batch = utils.load_image(r'E:\vgg16\for_transfer_learning\data\kittycat/000129037.jpg')
8
9 #定义一个figure画图窗口,并指定窗口的名称,也可以设置窗口的大小
10 fig = plt.figure(u"Top-5 预测结果")
11 print('Net built')
12 with tf.Session() as sess:
13 # 定义一个维度为[?,224,224,3],类型为 float32 的 tensor 占位符
14 images = tf.placeholder(tf.float32, [None, 224, 224, 3])
15 feed_dict = {images: batch}
16 #类Vgg16实例化出vgg
17 vgg = vgg16.Vgg16()
18 # 调用类的成员方法 inference(),并传入待测试图像,这也就是网络前向传播的过程
19 vgg.inference(images)
20 ## 将一个 batch 的数据喂入网络,得到网络的预测输出
21 probability = sess.run(vgg.out, feed_dict=feed_dict)
22
23 vgg.test('./synset.txt',probability[0])
这部分对上述程序进行调用,对图像进行识别,我们要做的是调用VGG16的网络结构,然后计算概率,输出概率最大的五种可能性,并且和标签一一对应,最后用柱状图画下来,表达出结果.
三、测试
运行app.py,把batch里的图片目录换成你想识别的图片目录即可
第一组
第二组
来源:oschina
链接:https://my.oschina.net/u/4381686/blog/4312733