Yolo算法采用一个单独的CNN模型实现end-to-end的目标检测,首先将输入图片resize到448x448,然后送入CNN网络,最后处理网络预测结果得到检测的目标。其速度很快,而且Yolo的训练过程也是端到端的。与滑动窗口不同的是,yolo先将图片分成SxS个块,每个单元格会预测B个边(bounding box)以及边界框的置信度(confidence score)。所谓置信度其实包含两个方面,一是这个框中目标存在的可能性大小,二是这个边界框的位置准确度。前者把它记做Pr(obj),若框中没有目标物,则Pr(obj)=0,若含有目标物则Pr(obj)=1。那么边界框的位置的准确度怎么去判断呢?我们使用了一种叫做IOU(交并比)的方法,意思就是说预测的框与真实的框相交的面积,和预测的框与真实框合并的面积的比例,可以记做IOU(pred),那么置信度就可以定义为这两项相乘。现在有了另一个问题,每个格子预测的边界框应该怎么表示呢?边界框的大小和位置可以用四个值来表示,(x,y,w,h),这里面的x,y是指预测出的边界框的中心位置相对于这个格子的左上角位置的偏移量,而且这个偏移量不是以像素为单位,而是以这个格子的大小为一个单位。而这个w,h指的是这个框的大小,占整张图片大小的宽和高的相对比例,有了中心点的位置,有了框的大小,画出一个框是就很容易。(x,y,w,h,c)这五个值理论上都应该在[0,1]区间上。最后一个c是置信度的意思。一般一个网格会预测多个框,而置信度是用来评判哪一个框是最准确的。 框预测好了,接下来就是分类的问题,每个单元格预测出(x,y,w,h,c)的值后,还要给出对于C个类别的概率值。B是边界框的个数,C是我有多少个类别要分类,S是怎么划分单元格,那么每个单元格要预测B*5+C个值。如果将输入图片划分为S×S网格,那么最终预测值为S×S×(B∗5+C)大小的张量。对于卷积层和全连接层,采用Leaky ReLU激活函数。最后一层采用线性激活函数。对于每一个单元格,前20个元素是类别概率值,然后2个元素是边界框置信度,两者相乘可以得到类别置信度,最后8个元素是边界框的(x,y,w,h)。下面是训练损失函数的分析,Yolo算法将目标检测看成回归问题,采用的是均方差损失函数。但是对不同的部分造成的误差采用了不同的权重值。还有一点就是较小的边界框的坐标误差应该要比较大的边界框要更敏感。这其中还用到了另一种机制,叫做NMS(non maximum suppression)非极大值抑制算法。NMS算法主要解决的是一个目标被多次检测的问题。这个机制不是针对YOLO的,而是对于各种检测算法都有用到。就比如说YOLO,yolo-v1预测了两个边界框,但是检测的时候总不能把两个框都画上去,因此只取概率值最大的那个框,这就叫做非极大值抑制,除了极大概率的那个框,别的丢弃掉。
①搭配环境 参考上一篇博客 ②下载笑脸数据集和yolov4代码包,网上搜索就能直接下载 ③由于笑脸数据集没有对应表示笑脸区域的xml文件,所以需要编写python代码生成xml文件 代码如下:
import os import numpy as np import codecs import json from glob import glob import cv2 import shutil from sklearn.model_selection import train_test_split image_path = "D:\\xiaolian\\genki4k\\files"# 图片 saved_path = "D:\\xiaolian\\genki4k\\xml" # 保存路径 #创建要求文件夹 if not os.path.exists(saved_path): os.makedirs(saved_path) # 3.获取待处理文件 files = glob(labelme_path + "*.json") files = [i.split(os.sep)[-1].split(".json")[0] for i in files] # 4.读取标注信息并写入 xml for i in range(4000): image_name="non" j=i+1 if j<=9: image_name="file000"+str(j) elif j<=99: image_name="file00"+str(j) elif j<=999: image_name="file0"+str(j) else: image_name="file"+str(j) height, width, channels = cv2.imread(image_path + os.sep+image_name + ".jpg").shape with codecs.open(saved_path + os.sep + image_name+ ".xml", "w", "utf-8") as xml: xml.write('<annotation>\n') xml.write('\t<folder>' + 'UAV_data' + '</folder>\n') xml.write('\t<filename>' + image_name + ".jpg" + '</filename>\n') xml.write('\t<source>\n') xml.write('\t\t<database>The UAV autolanding</database>\n') xml.write('\t\t<annotation>UAV AutoLanding</annotation>\n') xml.write('\t\t<image>flickr</image>\n') xml.write('\t\t<flickrid>NULL</flickrid>\n') xml.write('\t</source>\n') xml.write('\t<owner>\n') xml.write('\t\t<flickrid>NULL</flickrid>\n') xml.write('\t\t<name>ChaojieZhu</name>\n') xml.write('\t</owner>\n') xml.write('\t<size>\n') xml.write('\t\t<width>' + str(width) + '</width>\n') xml.write('\t\t<height>' + str(height) + '</height>\n') xml.write('\t\t<depth>' + str(channels) + '</depth>\n') xml.write('\t</size>\n') xml.write('\t\t<segmented>0</segmented>\n') xml.write('\t<object>\n') if j<=2162: xml.write('\t\t<name>' + "smile" + '</name>\n') else: xml.write('\t\t<name>' + "no_smile" + '</name>\n') xml.write('\t\t<pose>Unspecified</pose>\n') xml.write('\t\t<truncated>1</truncated>\n') xml.write('\t\t<difficult>0</difficult>\n') xml.write('\t\t<bndbox>\n') xml.write('\t\t\t<xmin>' + str(0) + '</xmin>\n') xml.write('\t\t\t<ymin>' + str(0) + '</ymin>\n') xml.write('\t\t\t<xmax>' + str(width) + '</xmax>\n') xml.write('\t\t\t<ymax>' + str(height) + '</ymax>\n') xml.write('\t\t</bndbox>\n') xml.write('\t</object>\n') xml.write('</annotation>') print(j)生成的xml文件: ④运行以下代码完成训练集测试集划分到文本文件: 代码如下:
import os import numpy as np import codecs import json from glob import glob import cv2 import shutil from sklearn.model_selection import train_test_split labelme_path = "D:\\xiaolian\\genki4k" + os.sep # 原始labelme标注数据路径 saved_path = "D:\\xiaolian\\genki4k" + os.sep + "VOC2007" + os.sep # 保存路径 # 2.创建要求文件夹 if not os.path.exists(saved_path + "Annotations"): os.makedirs(saved_path + "Annotations") if not os.path.exists(saved_path + "JPEGImages" + os.sep): os.makedirs(saved_path + "JPEGImages" + os.sep) if not os.path.exists(saved_path + "ImageSets" + os.sep + "Main" + os.sep): os.makedirs(saved_path + "ImageSets" + os.sep + "Main" + os.sep) txtsavepath = saved_path + "ImageSets" + os.sep + "Main" + os.sep ftrainval = open(txtsavepath + os.sep + 'trainval.txt', 'w') ftest = open(txtsavepath + os.sep + 'test.txt', 'w') ftrain = open(txtsavepath + os.sep + 'train.txt', 'w') fval = open(txtsavepath + os.sep + 'val.txt', 'w') # 需要修改路径 total_files = glob("D:\\xiaolian\\genki4k\\xml" + os.sep + "*.xml") total_files = [i.split(os.sep)[-1].split(".xml")[0] for i in total_files] trainval_files = [] test_files = [] trainval_files, test_files = train_test_split(total_files, test_size=0.1, random_state=55) for file in trainval_files: ftrainval.write(file + "\n") train_files, val_files = train_test_split(trainval_files, test_size=0.1, random_state=55) # train for file in train_files: ftrain.write(file + "\n") # val for file in val_files: fval.write(file + "\n") for file in test_files: print(file) ftest.write(file + "\n") ftrainval.close() ftrain.close() fval.close() ftest.close()生成的txt文件: ⑤将xml文件和图片复制到VOC2007对应位置: ⑥将VOC2007文件夹复制yolo项目: ⑦将测试图片导入项目: ⑧修改分类: ⑨运行以下两个代码: ⑩运行train.py,我设置的训练轮数为5,这里需要等一段时间,如果需要更高的精度,可以适当增加训练轮数,但是过大可能会产生过拟合 ⑪训练完成,生成以下文件: ⑫加载权重 ⑬预测测试,运行predict.py
1、运行video.py 2、结果:
①输入以下内容: ②打开链接: ③结果:
可以明显地看到loss曲线和val_loss曲线都呈下降趋势,所以该训练网络正常,训练结果还算比较好,但是可能因为训练轮数过少或者训练数据太少,会导致漏掉目标的情况,后续我会适当改善这些不足,提高模型的精确度。
①https://blog.csdn.net/Dongjiuqing/article/details/84763430 ②https://blog.csdn.net/shuaigeek/article/details/105210500 ③https://zhuanlan.zhihu.com/p/143747206