针对YOLOv1的paddle代码解读(一)

读完YOLOv1的论文,结合paddle的YOLOv1的代码分析细节

第一、查看数据的格式

我们对数据集pascalvoc进行解压得到如下的数据结构:

image.png

这次我们主要使用到的文件夹主要是以下三个:ImageSets、Annotations、JPEGImages

详细看下需要使用的文件的格式

  • JPEGImages
    • image.png
  • Annotations
    • image.png
    • image.png
  • ImageSets
    • image.png
    • 我们主要是从Main中得到文件
      • image.png
      • image.png
      • test、val和train中数据一样,不做展示

第二、准备工作

  • 导包
# 解析xml使用
import xml.etree.ElementTree as ET
import os
import cv2
import matplotlib.pyplot as plt
from visualdl import LogWriter
import random
import time
import numpy as np
复制代码

网络搭建

论文中的搭建

对网络进行梳理即可得到如下的网络结构

我们直接按照上面的卷积层进行搭建即可

import paddle
import paddle.nn as nn
import numpy as np

class YOLOv1(nn.Layer):
    def __init__(self):
        super(YOLOv1, self).__init__()
        net = []

        net.append(nn.Conv2D(3, 64, (7, 7),stride=2, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.MaxPool2D(kernel_size=2,stride=2))
        net.append(nn.Conv2D(64, 192, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.MaxPool2D(kernel_size=2,stride=2))

        net.append(nn.Conv2D(192, 128, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(128, 256, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 256, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 512, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.MaxPool2D(kernel_size=2,stride=2))

        net.append(nn.Conv2D(512, 256, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 512, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 256, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 512, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 256, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 512, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 256, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(256, 512, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 512, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.MaxPool2D(kernel_size=2,stride=2))

        net.append(nn.Conv2D(1024, 512, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(1024, 512, (1, 1),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(512, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(1024, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(1024, 1024, (3, 3),stride=2, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))

        net.append(nn.Conv2D(1024, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))
        net.append(nn.Conv2D(1024, 1024, (3, 3),stride=1, padding="SAME"))
        net.append(nn.LeakyReLU(negative_slope=0.1))

        self.conv_layers = nn.Sequential(*net)

        self.fc_layers = nn.Sequential(nn.Linear(50176,4096),
                                            nn.LeakyReLU(negative_slope=0.1),
                                            nn.Dropout(p=0.5),  # 加入dropout
                                            nn.Linear(4096,1470))    # 7*7*1024 = 50176  7*7*30=1470 
    
    def forward(self, x):
        features = self.conv_layers(x)
        features = paddle.flatten(features,start_axis=1)
        outputs = self.fc_layers(features)
        outputs = paddle.reshape(outputs,shape=(outputs.shape[0], 30,7,7))
        outputs = paddle.nn.functional.sigmoid(outputs)
        return outputs
复制代码

在这个地方值得我们学习和借鉴的就是列表配合Sequential进行使用,将卷积层打包成一个层

然后我们来查看下网络

# 查看网络
net = YOLOv1()

params_info = paddle.summary(net, (1, 3, 448, 448))
print(params_info)
复制代码

得到如下结果

---------------------------------------------------------------------------
 Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================
   Conv2D-1      [[1, 3, 448, 448]]   [1, 64, 224, 224]        9,472     
  LeakyReLU-1   [[1, 64, 224, 224]]   [1, 64, 224, 224]          0       
  MaxPool2D-1   [[1, 64, 224, 224]]   [1, 64, 112, 112]          0       
   Conv2D-2     [[1, 64, 112, 112]]   [1, 192, 112, 112]      110,784    
  LeakyReLU-2   [[1, 192, 112, 112]]  [1, 192, 112, 112]         0       
  MaxPool2D-2   [[1, 192, 112, 112]]   [1, 192, 56, 56]          0       
   Conv2D-3      [[1, 192, 56, 56]]    [1, 128, 56, 56]       24,704     
  LeakyReLU-3    [[1, 128, 56, 56]]    [1, 128, 56, 56]          0       
   Conv2D-4      [[1, 128, 56, 56]]    [1, 256, 56, 56]       295,168    
  LeakyReLU-4    [[1, 256, 56, 56]]    [1, 256, 56, 56]          0       
   Conv2D-5      [[1, 256, 56, 56]]    [1, 256, 56, 56]       65,792     
  LeakyReLU-5    [[1, 256, 56, 56]]    [1, 256, 56, 56]          0       
   Conv2D-6      [[1, 256, 56, 56]]    [1, 512, 56, 56]      1,180,160   
  LeakyReLU-6    [[1, 512, 56, 56]]    [1, 512, 56, 56]          0       
  MaxPool2D-3    [[1, 512, 56, 56]]    [1, 512, 28, 28]          0       
   Conv2D-7      [[1, 512, 28, 28]]    [1, 256, 28, 28]       131,328    
  LeakyReLU-7    [[1, 256, 28, 28]]    [1, 256, 28, 28]          0       
   Conv2D-8      [[1, 256, 28, 28]]    [1, 512, 28, 28]      1,180,160   
  LeakyReLU-8    [[1, 512, 28, 28]]    [1, 512, 28, 28]          0       
   Conv2D-9      [[1, 512, 28, 28]]    [1, 256, 28, 28]       131,328    
  LeakyReLU-9    [[1, 256, 28, 28]]    [1, 256, 28, 28]          0       
   Conv2D-10     [[1, 256, 28, 28]]    [1, 512, 28, 28]      1,180,160   
 LeakyReLU-10    [[1, 512, 28, 28]]    [1, 512, 28, 28]          0       
   Conv2D-11     [[1, 512, 28, 28]]    [1, 256, 28, 28]       131,328    
 LeakyReLU-11    [[1, 256, 28, 28]]    [1, 256, 28, 28]          0       
   Conv2D-12     [[1, 256, 28, 28]]    [1, 512, 28, 28]      1,180,160   
 LeakyReLU-12    [[1, 512, 28, 28]]    [1, 512, 28, 28]          0       
   Conv2D-13     [[1, 512, 28, 28]]    [1, 256, 28, 28]       131,328    
 LeakyReLU-13    [[1, 256, 28, 28]]    [1, 256, 28, 28]          0       
   Conv2D-14     [[1, 256, 28, 28]]    [1, 512, 28, 28]      1,180,160   
 LeakyReLU-14    [[1, 512, 28, 28]]    [1, 512, 28, 28]          0       
   Conv2D-15     [[1, 512, 28, 28]]    [1, 512, 28, 28]       262,656    
 LeakyReLU-15    [[1, 512, 28, 28]]    [1, 512, 28, 28]          0       
   Conv2D-16     [[1, 512, 28, 28]]   [1, 1024, 28, 28]      4,719,616   
 LeakyReLU-16   [[1, 1024, 28, 28]]   [1, 1024, 28, 28]          0       
  MaxPool2D-4   [[1, 1024, 28, 28]]   [1, 1024, 14, 14]          0       
   Conv2D-17    [[1, 1024, 14, 14]]    [1, 512, 14, 14]       524,800    
 LeakyReLU-17    [[1, 512, 14, 14]]    [1, 512, 14, 14]          0       
   Conv2D-18     [[1, 512, 14, 14]]   [1, 1024, 14, 14]      4,719,616   
 LeakyReLU-18   [[1, 1024, 14, 14]]   [1, 1024, 14, 14]          0       
   Conv2D-19    [[1, 1024, 14, 14]]    [1, 512, 14, 14]       524,800    
 LeakyReLU-19    [[1, 512, 14, 14]]    [1, 512, 14, 14]          0       
   Conv2D-20     [[1, 512, 14, 14]]   [1, 1024, 14, 14]      4,719,616   
 LeakyReLU-20   [[1, 1024, 14, 14]]   [1, 1024, 14, 14]          0       
   Conv2D-21    [[1, 1024, 14, 14]]   [1, 1024, 14, 14]      9,438,208   
 LeakyReLU-21   [[1, 1024, 14, 14]]   [1, 1024, 14, 14]          0       
   Conv2D-22    [[1, 1024, 14, 14]]    [1, 1024, 7, 7]       9,438,208   
 LeakyReLU-22    [[1, 1024, 7, 7]]     [1, 1024, 7, 7]           0       
   Conv2D-23     [[1, 1024, 7, 7]]     [1, 1024, 7, 7]       9,438,208   
 LeakyReLU-23    [[1, 1024, 7, 7]]     [1, 1024, 7, 7]           0       
   Conv2D-24     [[1, 1024, 7, 7]]     [1, 1024, 7, 7]       9,438,208   
 LeakyReLU-24    [[1, 1024, 7, 7]]     [1, 1024, 7, 7]           0       
   Linear-1         [[1, 50176]]          [1, 4096]         205,524,992  
 LeakyReLU-25       [[1, 4096]]           [1, 4096]              0       
   Dropout-1        [[1, 4096]]           [1, 4096]              0       
   Linear-2         [[1, 4096]]           [1, 1470]          6,022,590   
===========================================================================
Total params: 271,703,550
Trainable params: 271,703,550
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 2.30
Forward/backward pass size (MB): 225.96
Params size (MB): 1036.47
Estimated Total Size (MB): 1264.73
---------------------------------------------------------------------------

{'total_params': 271703550, 'trainable_params': 271703550}
复制代码

测试一张图片送入YOLOv1网络最终的输出

x = paddle.randn((1,3,448,448))
net = YOLOv1()
print(net)
y = net(x)
print(y.shape)
复制代码

得到的结果如下

YOLOv1(
  (conv_layers): Sequential(
    (0): Conv2D(3, 64, kernel_size=[7, 7], stride=[2, 2], padding=SAME, data_format=NCHW)
    (1): LeakyReLU(negative_slope=0.1)
    (2): MaxPool2D(kernel_size=2, stride=2, padding=0)
    (3): Conv2D(64, 192, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (4): LeakyReLU(negative_slope=0.1)
    (5): MaxPool2D(kernel_size=2, stride=2, padding=0)
    (6): Conv2D(192, 128, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (7): LeakyReLU(negative_slope=0.1)
    (8): Conv2D(128, 256, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (9): LeakyReLU(negative_slope=0.1)
    (10): Conv2D(256, 256, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (11): LeakyReLU(negative_slope=0.1)
    (12): Conv2D(256, 512, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (13): LeakyReLU(negative_slope=0.1)
    (14): MaxPool2D(kernel_size=2, stride=2, padding=0)
    (15): Conv2D(512, 256, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (16): LeakyReLU(negative_slope=0.1)
    (17): Conv2D(256, 512, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (18): LeakyReLU(negative_slope=0.1)
    (19): Conv2D(512, 256, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (20): LeakyReLU(negative_slope=0.1)
    (21): Conv2D(256, 512, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (22): LeakyReLU(negative_slope=0.1)
    (23): Conv2D(512, 256, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (24): LeakyReLU(negative_slope=0.1)
    (25): Conv2D(256, 512, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (26): LeakyReLU(negative_slope=0.1)
    (27): Conv2D(512, 256, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (28): LeakyReLU(negative_slope=0.1)
    (29): Conv2D(256, 512, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (30): LeakyReLU(negative_slope=0.1)
    (31): Conv2D(512, 512, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (32): LeakyReLU(negative_slope=0.1)
    (33): Conv2D(512, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (34): LeakyReLU(negative_slope=0.1)
    (35): MaxPool2D(kernel_size=2, stride=2, padding=0)
    (36): Conv2D(1024, 512, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (37): LeakyReLU(negative_slope=0.1)
    (38): Conv2D(512, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (39): LeakyReLU(negative_slope=0.1)
    (40): Conv2D(1024, 512, kernel_size=[1, 1], padding=SAME, data_format=NCHW)
    (41): LeakyReLU(negative_slope=0.1)
    (42): Conv2D(512, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (43): LeakyReLU(negative_slope=0.1)
    (44): Conv2D(1024, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (45): LeakyReLU(negative_slope=0.1)
    (46): Conv2D(1024, 1024, kernel_size=[3, 3], stride=[2, 2], padding=SAME, data_format=NCHW)
    (47): LeakyReLU(negative_slope=0.1)
    (48): Conv2D(1024, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (49): LeakyReLU(negative_slope=0.1)
    (50): Conv2D(1024, 1024, kernel_size=[3, 3], padding=SAME, data_format=NCHW)
    (51): LeakyReLU(negative_slope=0.1)
  )
  (fc_layers): Sequential(
    (0): Linear(in_features=50176, out_features=4096, dtype=float32)
    (1): LeakyReLU(negative_slope=0.1)
    (2): Dropout(p=0.5, axis=None, mode=upscale_in_train)
    (3): Linear(in_features=4096, out_features=1470, dtype=float32)
  )
)
[1, 30, 7, 7]
复制代码

在网络搭建的的最后一步,由一个reshape操作outputs = paddle.reshape(outputs,shape=(outputs.shape[0], 30,7,7)),使输出变成一个通道数为30,长宽为7x7的特征图大小。

论文中对30,7,7做出了解释:

也就是说假如448x448大小的图片,先分出7x7的网格。图片经过网络的卷积处理后,最终会得到一个30x7x7的特征图,特征图有30个通道,7x7个特征点,可以理解为此时每个特征点会对应到图片中7x7网格里的一个网格,30个通道就相当于每个网格经过网络卷积处理后,最终预测得到的东西。

30=2*5+20,其中2表示2个(confidence + bounding boxes),confidence+bounding boxes为 表示有无物体的置信度+中心点x,y的预测值(后面还会解释),20表示类别数

作者在slides中解释得更加清晰

数据读取

先查看一张图片

# 将数据集解压后,查看一张图片
import PIL.Image as Image

img1 = Image.open('pascalvoc/VOCdevkit/VOC2007/JPEGImages/000002.jpg')
print(img1.size) 
plt.imshow(img1)          #根据数组绘制图像
plt.show()
复制代码

image.png

标签信息转换(第一个重点)

数据集包含了20个类别。Annotations中xml文件包含了目标检测训练时需要的真实框信息,真实框的信息是按照左上角点坐标和右下角点坐标的格式存储的。

但是YOLO网络预测的是(x,y,w,h),所以需要把真实框信息转换一下。

理解
在这个地方进行转换主要是因为我们需要在计算损失函数的时候使用的都是相对坐标和相对宽高,而在我们的xml中提供的数据都是真实的左上角和右下角点的坐标,所以需要进行转化。

# 类别
CLASSES = ['person', 'bird', 'cat', 'cow', 'dog', 'horse', 'sheep',
           'aeroplane', 'bicycle', 'boat', 'bus', 'car', 'motorbike', 'train',
           'bottle', 'chair', 'diningtable', 'pottedplant', 'sofa', 'tvmonitor']

f_Dir = 'pascalvoc/VOCdevkit/VOC2007'
复制代码

上面的代码主要是交代类别,方便我们在后面的时候进行索引,交代我们文件的路径

def convert(size, box):  # 转换标签信息
    """
    将bbox的左上角点、右下角点坐标的格式,转换为中心点x,y + 宽高w,h的格式
    size:图片宽w,高h
    box:真实框信息,包含左上角点坐标与右下角点坐标
    返回归一化后的中心点坐标与宽高
    """
    dw = 1. / size[0]
    dh = 1. / size[1]
    x = (box[0] + box[1]) / 2.0 # 计算中心点x坐标
    y = (box[2] + box[3]) / 2.0 # 计算中心点y坐标
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw  # 归一化
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)  # 全部都已经归一化
复制代码

上面的代码主要是将真实的xy点坐标变成中心点坐标,并且完成中心点坐标和宽高的归一化

我们举个例子:
我们的图片的大小是100 * 100 ,对应到上面size = (100,100),然后我们的框是(xmin:25,xmax:75,ymin:50,ymax:80),那么我们计算出来的中心点坐标是x = (75 + 25)/2 = 50,y = (50 + 80)/2 = 65,我们的w = xmax - xmin = 75 - 25 = 50,h = ymax - ymin = 80 - 50 = 30 , 所以最终我们归一化 x * 1/100 = 0.5 , y * 1/100 = 0.65, w * 1/100 = 0.5, h * 1/100 = 0.3, 我们输入的是(xmin_25,xmax_75,ymin_50,ymax_80),经过转化后得到的(x_0.5,y_0.65,w_0.5,h_0.3)

def convert_annotation(image_id,f_Dir,Annotations_Dir='Annotations',labels_Dir='labels'): # 转换坐标信息后并写入txt文档中
    """
    把图像image_id的xml文件转换为目标检测需要的label文件(txt)

    文件中记录的信息包含  物体的类别,归一化后的bbox的中心点点坐标  以及bbox的宽、高
    """
    in_file = open(f_Dir + '/'+Annotations_Dir+'/%s' % (image_id))
    image_id = image_id.split('.')[0]
    out_file = open(f_Dir+'/'+labels_Dir+'/%s.txt' % (image_id), 'w')  #  labels文件夹需要提前创建好
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in CLASSES or int(difficult) == 1:
            continue
        cls_id = CLASSES.index(cls)
        xmlbox = obj.find('bndbox')
        points = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        bb = convert((w, h), points)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')  # 写入信息
复制代码

上面的代码主要是通过xml提取信息,从xml中获取的信息包括,width、height、xmin、xmax、ymin、ymax、name,其中width、height、xmin、xmax、ymin、ymax放到convert函数中完成归一化的工作,最后将转化好的数据和类别id拼接到一起,放到labels中对应的image_id.txt中,作为标签

小插曲(一):
if cls not in CLASSES or int(difficult) == 1: 直接跳过,就是不在我们的种类列表中或者困难值较高,不做记录
image.png

一共5个object,其中一个object的difficult是1,所以没有出现在标签文件中
image.png

小插曲(二):
points = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),float(xmlbox.find('ymax').text))
注意这个地方我们输入的顺序xmin,xmax,ymin,ymax

def make_label_txt(f_Dir, Annotations_Dir='Annotations'):  #  labels文件夹需要提前创建好
    """在labels文件夹下创建image_id.txt文件,对应每个image_id.xml提取出的bbox信息"""
    filenames = os.listdir(f_Dir + '/' + Annotations_Dir)
    for filename in filenames:
        convert_annotation(filename,f_Dir)
复制代码

上面的代码只要是循环读取Annotations文件夹下xml文件的信息,然后将文件中的object中的类别信息,和xmin,xmax,ymin,ymax的真是坐标消息,通过convert_annotation函数转化到相对坐标存储到对应的labels下的txt文件中,作为后续训练的标签文件

我们看到运行上面的函数,我们可以看到生成了一个labels标签文件夹,里面对应存放的都是图片的转化后的标签

image.png

绘制显示框

# 显示真实框函数
def show_labels_img(img_id, img_Dir='pascalvoc/VOCdevkit/VOC2007/JPEGImages'): 
    """imgname是输入图像的名称"""
    img = cv2.imread(img_Dir+ '/' + img_id + ".jpg") # 读取图像
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转换图像格式
    h, w = img.shape[:2] # 获取图像高宽
    print(w,h)
    label = []
    with open(f_Dir+"/labels/"+img_id+".txt",'r') as flabel: # 读取labels文件中的真实框信息
        for label in flabel:
            label = label.split(' ')
            label = [float(x.strip()) for x in label] # 读取后的信息为[11,0.332,0.588,0.12,0.09866666666666667] 都是归一化后的数值
            print(CLASSES[int(label[0])]) # 获取类别信息
            pt1 = (int(label[1] * w - label[3] * w / 2), int(label[2] * h - label[4] * h / 2)) # 根据x,y,w,h,转换得到真实框的左上角点
            pt2 = (int(label[1] * w + label[3] * w / 2), int(label[2] * h + label[4] * h / 2)) # 根据x,y,w,h,转换得到真实框的右下角点
            cv2.putText(img,CLASSES[int(label[0])],pt1,cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255))
            # print(type(pt1))
            cv2.rectangle(img,pt1,pt2,(0,0,255),2)  # 画框
    plt.imshow(img)
复制代码

在这个方法中我们最主要的工作就是将label中的归一化的x,y,w,h进行坐标还原,还原后我们可以得到左上角和右下角的点的坐标,调用rectangle方法绘制矩形框

实现方式:我们首先通过读取图片信息可以知道图片的真实的truth_w,truth_h,和上面保持一致,我们都使用100 * 100 来表示,然后我们从图片对应的labels下的txt文件中读取x,y,w,h,分别是(0.5,0.65,0.5,0.3),还原左上角坐标pt1 = (int(label[1] * w - label[3] * w / 2), int(label[2] * h - label[4] * h / 2)) ,int(label[1] * w - label[3] * w / 2) = 0.5 * 100 - 0.5 * 100 / 2 = 50 - 25 = 25 , int(label[2] * h - label[4] * h / 2) = 0.65 * 100 - 0.3 * 100 /2 = 65 - 15 = 50 , 所以左上角的点的坐标是(25,50),右下角同理(75,80),我们可以看到,和我们上面的转化前的坐标一致。

效果如下

image.png

剩下的部分见 : 针对YOLOv1的paddle代码解读(二)

猜你喜欢

转载自juejin.im/post/7075966072318328869
今日推荐