yolov1论文解读
前面已经对yolov1的原理做了一个了解,下面就来看一下yolov1的代码实现过程
yolov1的代码倒是比Faster-Rcnn简单多了,但是一些逻辑顺序和Faster-Rcnn差不多
● pascal_voc.py:对图片数据和XML数据进行解析和预处理;
● yolo_net.py:搭建yolo v1网络,设置yolo v1的损失函数;
● train.py 和test.py :一个用来训练模型,一个用来测试模型。
1、pascal_voc.py
这部分主要是解析xml文件,读取图片数据和对数据进行预处理,主函数
def load_pascal_annotation(self, index):
"""
Load image and bounding boxes info from XML file in the PASCAL VOC
format.
"""
imname = os.path.join(self.data_path, 'JPEGImages', index + '.jpg')
im = cv2.imread(imname)
h_ratio = 1.0 * self.image_size / im.shape[0] # 448所占图片高度的比例
w_ratio = 1.0 * self.image_size / im.shape[1] # 448所占图片宽度的比例
# im = cv2.resize(im, [self.image_size, self.image_size])
label = np.zeros((self.cell_size, self.cell_size, 5+len(self.classes))) # label的数据维度(7, 7, 25),一个ceil只负责预测一个类别
filename = os.path.join(self.data_path, 'Annotations', index + '.xml')
tree = ET.parse(filename)
objs = tree.findall('object')
for obj in objs:
bbox = obj.find('bndbox')
# Make pixel indexes 0-based
x1 = max(min((float(bbox.find('xmin').text) - 1) * w_ratio, self.image_size - 1), 0)
y1 = max(min((float(bbox.find('ymin').text) - 1) * h_ratio, self.image_size - 1), 0)
x2 = max(min((float(bbox.find('xmax').text) - 1) * w_ratio, self.image_size - 1), 0)
y2 = max(min((float(bbox.find('ymax').text) - 1) * h_ratio, self.image_size - 1), 0)
cls_ind = self.class_to_ind[obj.find('name').text.lower().strip()]
# 将(x1, y1, x2, y2)转化为(x_center, y_center, width, height)
boxes = [(x2 + x1) / 2.0, (y2 + y1) / 2.0, x2 - x1, y2 - y1]
# 查看object的中心点落在那个cell里,cell的索引从0开始
x_ind = int(boxes[0] * self.cell_size / self.image_size)
y_ind = int(boxes[1] * self.cell_size / self.image_size)
# 设置标记,看这个ceil是否被访问过,1表示这个cell有object
if label[y_ind, x_ind, 0] == 1:
continue
label[y_ind, x_ind, 0] = 1
label[y_ind, x_ind, 1:5] = boxes
label[y_ind, x_ind, 5 + cls_ind] = 1
return label, len(objs)
输出的label是一个维度为[7, 7, 25]的数组,这是真实数据的标签值,需要注意的是yolov1的网络输出是[7, 7, 30],所以在计算loss时会对label进行维度扩展(后面计算loss的代码中详解)。
label [7, 7, 25]的含义为:
# 查看object的中心点落在那个cell里,cell的索引从0开始
x_ind = int(boxes[0] * self.cell_size / self.image_size)
y_ind = int(boxes[1] * self.cell_size / self.image_size)
# 设置标记,看这个ceil是否被访问过,1表示这个cell有object
if label[y_ind, x_ind, 0] == 1:
continue
label[y_ind, x_ind, 0] = 1
label[y_ind, x_ind, 1:5] = boxes
label[y_ind, x_ind, 5 + cls_ind] = 1
可看出:
label 维度[7, 7, 25]
[..., 0]值为0或1,1表示object的中心点落在这个cell里,0表示没有
[..., 1:5] ground_truth 标记框,格式为(x_center, y_center, width, height)
[..., 5:] 标记的类别,object的属于哪个类别,哪个类别对应位置为1,其他为0
举个栗子:
图中有三个物体,三个物体的中心分别落在7*7的格子中的(1,4),(2,3),(5,1),索引从0开始。
这张图片通过pascal_voc.py生成label是(7,7,25)的数组,其中label[1,4,0],label[2,3,0],label[5,1,0]处的值为1其余为0.(7,7,25)数组中深度(aixs=2)为(1,5)的部分存储着gt_boxes的坐标,所以label[1,4,1:5]、label[2,3,1:5]、label[5,1,1:5]存放着坐标,其余的label[:,:,1:5]全部为0。而(7,7,25)数组中深度(axis=2)为(5,25)的部分存储着gt_boxes的20分类的类别信息。
Q: 那如何判断object落在哪个cell里呢?
A:根据如下代码
x_ind = int(boxes[0] * self.cell_size / self.image_size)
y_ind = int(boxes[1] * self.cell_size / self.image_size)
假设 per = self.image_size / self.cell_size ,per是每个cell所占图片大小的比例,所以 boxes[0]/per 即 boxes[0] * self.cell_size / self.image_size 表示box的中心点落在第几个cell中。
2、yolo_net.py
yolo_net.py文件中主要包含三个部分
● 构建网络结构
● 计算iou
● 构建损失函数
构建网络结构
def build_network(self,
images,
num_outputs,
alpha,
keep_prob=0.5,
is_training=True,
scope='yolo'):
with tf.variable_scope(scope):
with slim.arg_scope(
[slim.conv2d, slim.fully_connected], # 卷积层+全连接层
activation_fn=leaky_relu(alpha), # 激活函数用leaky_relu
weights_regularizer=slim.l2_regularizer(0.0005), # L2正则项,防止过拟合
weights_initializer=tf.truncated_normal_initializer(0.0, 0.01) # 权值初始化
):
net = tf.pad(
images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]),
name='pad_1') # 进行填充,上、下、左、右
net = slim.conv2d(
net, 64, 7, 2, padding='VALID', scope='conv_2')
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')
net = slim.conv2d(net, 192, 3, scope='conv_4')
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')
net = slim.conv2d(net, 128, 1, scope='conv_6')
net = slim.conv2d(net, 256, 3, scope='conv_7')
net = slim.conv2d(net, 256, 1, scope='conv_8')
net = slim.conv2d(net, 512, 3, scope='conv_9')
net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10')
net = slim.conv2d(net, 256, 1, scope