YOLOv3是YOLO作者优化的YOLO算法,与之前的相比,网络结构多了残差块来连接网络,采用了金字塔结构,网络的深度大大加深,达到了53层。所以作者把网络命名为DarkNet-53。
由于作者的编码能力实在是太强了,连所用的框架好像都是自己写的,所以目前并没有其他的框架的版本的发布。我根据作者写的关于YOLOv3的论文和之前的关于YOLO的所有论文,按我对论文的理解来实现YOLOv3。(若我对论文的理解有误, 欢迎指出。不胜感激)
还是老样子,各种设置可以到config
文件夹下修改对应的设置项
项目代码
https://github.com/IronMastiff/YOLOv3_tensorflow
简要介绍YOLO
YOLO( you only look once)的缩写。顾名思义,就是只看一次,整个预测过程只看图片一次。这有别与之前的目标定位识别的项目,YOLO之前的项目一般是定位目标看一次图像,物体分类再看一次图像。所以简单直观地看,YOLO似乎效率比他们都高,毕竟少看了一次,省下了时间。事实上YOLO也是快的惊人,作者在Titan GPU上实现了实时目标定位与识别,识别视频在官网首页就有,若没有可能是冲浪方法不够科学。
项目结构
本次的项目主要有3个文件,2个文件夹。utils
文件夹中有6个文件。config
文件夹中config.yml
配置文件。
reader.py
:
- 存放了用来读取数据集,数据集标签存放地址与文件的方法。并在读取名字的过程中实现的
mini_batch
的操作
- 存放了用来读取数据集,数据集标签存放地址与文件的方法。并在读取名字的过程中实现的
train.py
:
- 就是组合各种工具,用来训练网络的代码
eval.ph
:
- 用来跑训练好的YOLOv3
utils
文件夹:
extract_labels.py
:
- 里面的
labels_normaliszer()
根据传入的标签的存放地址来读取标签,并且把标签转化成我们网络需要的格式
- 里面的
get_loss.py
:
- 整合了计算YOLOv3作者之前提到的3个
loss
的计算方法,并计算batch
的loss
- 整合了计算YOLOv3作者之前提到的3个
IOU.py
:
- 里面的
IOU_calculator()
方法根据传入的预测值和目标的标签的值来计算IOU
- 里面的
net.ph
:
- 里面实现了YOLOv3的核心算法。DarkNet-53
read_config.py
:
- 读取
config
配置文件中的配置参数
- 读取
- ·select_things.py`:
- 顾名思义,里面的方法实现了选择功能。例如选择YOLOv3中scale的大小,选择scale对应的check_point文件
Utils 工具
extract_labels
对于标注的数据的处理我的思路是生成一个shape与神经网络输出的数组shape相同的数组,并把目标的标签赋给对应下标的数组单元
labels_normalizer()
方法中,我建立了一个map,用来把所有的种类(class)转化为数组中对应的位置的下标。这里我用的是VOC2007数据集,可以从官网下载,官网上说明了数据集中的种类(class)的个数。更换数据集记得过来修改map的内容。
由于VOC数据集的标签格式是xml类型的,所以我调用了xml.dom.minidom
这个库来解析xml文件,
并且组合成元组,然后连接元组组成链表。然后返回链表
从中提取了object_name
, bdbox
, xmin
,ymin
,xmax
,ymax
import xml.dom.minidom
def xml_extractor( dir ):
DOMTree = parse( dir )
collection = DOMTree.documentElement # 得到xml文件的根节点
file_name_xml = collection.getElementsByTagName( 'filename' )[0]
objects_xml = collection.getElementsByTagName( 'object' )
size_xml = collection.getElementsByTagName( 'size' )
file_name = file_name_xml.childNodes[0].data
for size in size_xml:
width = size.getElementsByTagName( 'width' )[0]
height = size.getElementsByTagName( 'height' )[0]
width = width.childNodes[0].data
height = height.childNodes[0].data
objects = []
for object_xml in objects_xml:
object_name = object_xml.getElementsByTagName( 'name' )[0]
bdbox = object_xml.getElementsByTagName( 'bndbox' )[0]
xmin = bdbox.getElementsByTagName( 'xmin' )[0]
ymin = bdbox.getElementsByTagName( 'ymin' )[0]
xmax = bdbox.getElementsByTagName( 'xmax' )[0]
ymax = bdbox.getElementsByTagName( 'ymax' )[0]
object = ( object_name.childNodes[0].data,
xmin.childNodes[0].data,
ymin.childNodes[0].data,
xmax.childNodes[0].data,
ymax.childNodes[0].data )
objects.append( object )
return file_name, width, height, objects
在labels_normalizer()
方法中我才正式的把得到的labels转化为数组。有一点值得注意,在生成新的数组的时候务必要加1e-8(一个近似0的数),为的是防止之后在计算IOU时出现分母为0从而输出为nan的情况。因为VOC数据集标记的是目标的对角线的坐标,而我们需要的是目标中点的坐标与之对应的boundding box的长宽。所以需要一点小计算。
而且由于YOLOv3的检测机制是中点所在的对应的box对目标物体进行预测,所以还需要得出物体所在的box,并对对应下标的数组赋值。为了防止下标越界我把最右与最下边界上的点归为前一个box管理,他们本来归下一个box管理,但是会发生越界错误
def labels_normalizer( batches_filenames, target_width, target_height, layerout_width, layerout_height ):
class_map = {
'person' : 5,
'bird' : 6,
'cat' : 7,
'cow' : 8,
'dog' : 9,
'horse' : 10,
'sheep' : 11,
'aeroplane' : 12,
'bicycle' : 13,
'boat' : 14,
'bus' : 15,
'car' : 16,
'motorbike' : 17,
'train' : 18,
'bottle' : 19,
'chair' : 20,
'diningtable' : 21,
'pottedplant': 22,
'sofa' : 23,
'tvmonitor' : 24
}
height_width = []
batches_labels = []
for batch_filenames in batches_filenames:
batch_labels = []
for filename in batch_filenames:
_, width, height, objects = xml_extractor( filename )
width_preprotion = target_width / int( width )
height_preprotion = target_height / int( height )
label = np.add( np.zeros( [int( layerout_height ), int( layerout_width ), 255] ), 1e-8 ) # 这里加1e-8的原因是防止之后在用该数据在计算IOU时出现分母为0从而导致输出为nan的情况
for object in objects:
class_label = class_map[object[0]]
xmi