vgg16复现进行图片识别

左心房为你撑大大i 提交于 2020-08-15 07:25:41

一、结构

上图就是复现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里的图片目录换成你想识别的图片目录即可

 

 

 

 

第一组

第二组

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!