基于yoloV3的目标检测

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


CNN:RCNN、SPPNet、Fast RCNN、Faster RCNN、YOLO V1 V2 V3、SSD、FCN、SegNet、U-Net、DeepLab V1 V2 V3、Mask RCNN

自动驾驶:车道线检测、车速检测、实时通行跟踪、基于视频的车辆跟踪及流量统计

车流量检测实现:多目标追踪、卡尔曼滤波器、匈牙利算法、SORT/DeepSORT、yoloV3、虚拟线圈法、交并比IOU计算

多目标追踪:DBT、DFT、基于Kalman和KM算法的后端优化算法、SORT/DeepSORT、基于多线程的单目标跟踪的多目标跟踪算法KCF

计算交并比IOU、候选框不同表示方式之间的转换

卡尔曼滤波器

卡尔曼滤波器实践

目标估计模型-卡尔曼滤波

匈牙利算法

数据关联:利用匈牙利算法对目标框和检测框进行关联

SORT、DeepSORT

多目标追踪

yoloV3模型

基于yoloV3的目标检测

叉乘:基于虚拟线圈法的车流量统计

视频中的车流量统计


4.8. 基于yoloV3的目标检测

学习目标

  • 熟悉利用yolo模型进行目标检测的方法
  • 能够完成目标检测功能的实现

在这里我们进行的目标检测是基于OPenCV的利用yoloV3进行目标检测,不涉及yoloV3的模型结构、理论及训练过程,只是利用训练好的模型进行目标检测,整个流程如下:

基于OPenCV中的DNN模块

  • 加载已训练好的yolov3模型及其权重参数
  • 将要处理的图像转换成输入到模型中的blobs
  • 利用模型对目标进行检测
  • 遍历检测结果
  • 应用非极大值抑制
  • 绘制最终检测结果,并存入到ndarray中,供目标追踪使用。

代码如下:

1.加载yolov3模型及其权重参数

# 1.加载可以识别物体的名称,将其存放在LABELS中,一共有80种,在这我们只使用car
labelsPath = "./yolo-coco/coco.names"
LABELS = open(labelsPath).read().strip().split("\n")

# 设置随机数种子,生成多种不同的颜色,当一个画面中有多个目标时,使用不同颜色的框将其框起来
np.random.seed(42)
COLORS = np.random.randint(0, 255, size=(200, 3),dtype="uint8")

# 加载已训练好的yolov3网络的权重和相应的配置数据
weightsPath = "./yolo-coco/yolov3.weights"
configPath = "./yolo-coco/yolov3.cfg"

# 加载好数据之后,开始利用上述数据恢复yolo神经网络
net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
# 获取YOLO中每一网络层的名称:['conv_0', 'bn_0', 'relu_0', 'conv_1', 'bn_1', 'relu_1', 'conv_2', 'bn_2', 'relu_2'...]
ln = net.getLayerNames()
# 获取输出层在网络中的索引位置,并以列表的形式:['yolo_82', 'yolo_94', 'yolo_106']
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]

2.要处理的图像转换成输入到模型中的blobs

# 2. 读取图像
frame = cv2.imread("./images/car1.jpg")
# 视频的宽度和高度,即帧尺寸
(W, H) = (None, None)
if W is None or H is None:
    (H, W) = frame.shape[:2]

# 根据输入图像构造blob,利用OPenCV进行深度网路的计算时,一般将图像转换为blob形式,对图片进行预处理,包括缩放,减均值,通道交换等
# 还可以设置尺寸,一般设置为在进行网络训练时的图像的大小
blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)

3.利用模型对目标进行检测

# 3.将blob输入到前向网络中,并进行预测
net.setInput(blob)
start = time.time()
# yolo前馈计算,获取边界和相应的概率
# 输出layerOutsputs介绍:
# 是YOLO算法在图片中检测到的bbx的信息
# 由于YOLO v3有三个输出,也就是上面提到的['yolo_82', 'yolo_94', 'yolo_106']
# 因此layerOutsputs是一个长度为3的列表
# 其中,列表中每一个元素的维度是(num_detection, 85)
# num_detections表示该层输出检测到bbx的个数
# 85:因为该模型在COCO数据集上训练,[5:]表示类别概率;[0:4]表示bbx的位置信息;[4]表示置信度
layerOutputs = net.forward(ln)

4.遍历检测结果,获得检测框

# 下面对网络输出的bbx进行检查:
# 判定每一个bbx的置信度是否足够的高,以及执行NMS算法去除冗余的bbx

boxes = []  # 用于存放识别物体的框的信息,包括框的左上角横坐标x和纵坐标y以及框的高h和宽w
confidences = []  # 表示识别目标是某种物体的可信度
classIDs = []  # 表示识别的目标归属于哪一类,['person', 'bicycle', 'car', 'motorbike'....]

# 4. 遍历每一个输出层的输出
for output in layerOutputs:
    # 遍历某个输出层中的每一个目标
    for detection in output:
        scores = detection[5:]  # 当前目标属于某一类别的概率

        classID = np.argmax(scores)  # 目标的类别ID
        confidence = scores[classID]  # 得到目标属于该类别的置信度

        # 只保留置信度大于0.3的边界框,若图片质量较差,可以将置信度调低一点
        if confidence > 0.3:
            # 将边界框的坐标还原至与原图片匹配,YOLO返回的是边界框的中心坐标以及边界框的宽度和高度
            box = detection[0:4] * np.array([W, H, W, H])
            (centerX, centerY, width, height) = box.astype("int") # 使用 astype("int") 对上述 array 进行强制类型转换,centerX:框的中心点横坐标, centerY:框的中心点纵坐标,width:框的宽度,height:框的高度

            x = int(centerX - (width / 2))  # 计算边界框的左上角的横坐标
            y = int(centerY - (height / 2))  # 计算边界框的左上角的纵坐标

            # 更新检测到的目标框,置信度和类别ID
            boxes.append([x, y, int(width), int(height)])  # 将边框的信息添加到列表boxes
            confidences.append(float(confidence))  # 将识别出是某种物体的置信度添加到列表confidences
            classIDs.append(classID) # 将识别物体归属于哪一类的信息添加到列表classIDs

5.非极大值抑制

# 5. 非极大值抑制
idxs = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.3)

6.最终检测结果,绘制,并存入到ndarray中,供目标追踪使用

# 6. 获得最终的检测结果
dets = []  # 存放检测框的信息,包括左上角横坐标,纵坐标,右下角横坐标,纵坐标,以及检测到的物体的置信度,用于目标跟踪
if len(idxs) > 0:  # 存在检测框的话(即检测框个数大于0)
    for i in idxs.flatten():  #  循环检测出的每一个box
        # yolo模型可以识别很多目标,因为我们在这里只是识别车,所以只有目标是车的我们进行检测,其他的忽略
        if LABELS[classIDs[i]] == "car":
            (x, y) = (boxes[i][0], boxes[i][1])  # 得到检测框的左上角坐标
            (w, h) = (boxes[i][2], boxes[i][3])  # 得到检测框的宽和高
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0,255,0), 2)  # 将方框绘制在画面上
            dets.append([x, y, x + w, y + h, confidences[i]])  # 将检测框的信息的放入dets中
# 设置数据类型,将整型数据转换为浮点数类型,且保留小数点后三位
np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
# 将检测框数据转换为ndarray,其数据类型为浮点型
dets = np.asarray(dets)

plt.imshow(frame[:,:,::-1])

在视频中进行目标检测:

labelsPath = "./yolo-coco/coco.names"
LABELS = open(labelsPath).read().strip().split("\n")

# 设置随机数种子,生成多种不同的颜色,当一个画面中有多个目标时,使用不同颜色的框将其框起来
np.random.seed(42)
COLORS = np.random.randint(0, 255, size=(200, 3),dtype="uint8")

# 加载已训练好的yolov3网络的权重和相应的配置数据
weightsPath = "./yolo-coco/yolov3.weights"
configPath = "./yolo-coco/yolov3.cfg"

# 加载好数据之后,开始利用上述数据恢复yolo神经网络
net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
# 获取YOLO中每一网络层的名称:['conv_0', 'bn_0', 'relu_0', 'conv_1', 'bn_1', 'relu_1', 'conv_2', 'bn_2', 'relu_2'...]
ln = net.getLayerNames()
# 获取输出层在网络中的索引位置,并以列表的形式:['yolo_82', 'yolo_94', 'yolo_106']
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]

"""
视频处理类
"""

# 初始化vediocapture类,参数指定打开的视频文件,也可以是摄像头
vs = cv2.VideoCapture('./input/test_1.mp4')
# 视频的宽度和高度,即帧尺寸
(W, H) = (None, None)
# 视频文件写对象
writer = None

try:
    # 确定获取视频帧数的方式
    prop = cv2.cv.CV_CAP_PROP_FRAME_COUNT if imutils.is_cv2() \
        else cv2.CAP_PROP_FRAME_COUNT
    # 获取视频的总帧数
    total = int(vs.get(prop))
    # 打印视频的帧数
    print("[INFO] {} total frames in video".format(total))
except:
    print("[INFO] could not determine # of frames in video")
    print("[INFO] no approx. completion time can be provided")
    total = -1

# 循环读取视频中的每一帧画面
while True:
    # 读取帧:grabbed是bool,表示是否成功捕获帧,frame是捕获的帧
    (grabbed, frame) = vs.read()
    # 若未捕获帧,则退出循环
    if not grabbed:
        break
    # 若W和H为空,则将第一帧画面的大小赋值给他
    if W is None or H is None:
        (H, W) = frame.shape[:2]

    # 根据输入图像构造blob,利用OPenCV进行深度网路的计算时,一般将图像转换为blob形式,对图片进行预处理,包括缩放,减均值,通道交换等
    # 还可以设置尺寸,一般设置为在进行网络训练时的图像的大小
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    # 将blob输入到前向网络中
    net.setInput(blob)
    start = time.time()
    # yolo前馈计算,获取边界和相应的概率
    layerOutputs = net.forward(ln)
    """
    输出layerOutsputs介绍:
    是YOLO算法在图片中检测到的bbx的信息
    由于YOLO v3有三个输出,也就是上面提到的['yolo_82', 'yolo_94', 'yolo_106']
    因此layerOutsputs是一个长度为3的列表
    其中,列表中每一个元素的维度是(num_detection, 85)
    num_detections表示该层输出检测到bbx的个数
    85:因为该模型在COCO数据集上训练,[5:]表示类别概率;[0:4]表示bbx的位置信息;[4]表示置信度
    """
    end = time.time()

    """
    下面对网络输出的bbx进行检查:
    判定每一个bbx的置信度是否足够的高,以及执行NMS算法去除冗余的bbx
    """
    boxes = []  # 用于存放识别物体的框的信息,包括框的左上角横坐标x和纵坐标y以及框的高h和宽w
    confidences = []  # 表示识别目标是某种物体的可信度
    classIDs = []  # 表示识别的目标归属于哪一类,['person', 'bicycle', 'car', 'motorbike'....]

    # 遍历每一个输出层的输出
    for output in layerOutputs:
        # 遍历某个输出层中的每一个目标
        for detection in output:
            scores = detection[5:]  # 当前目标属于某一类别的概率
            """
            # scores = detection[5:] ---> [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
            #                                 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
            #                               0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
            #                                 0. 0. 0. 0. 0. 0. 0. 0.]
            # scores的大小应该是1*80,因为在训练yolo模型时是80类目标
            """
            classID = np.argmax(scores)  # 目标的类别ID
            confidence = scores[classID]  # 得到目标属于该类别的置信度

            # 只保留置信度大于0.3的边界框,若图片质量较差,可以将置信度调低一点
            if confidence > 0.3:
                # 将边界框的坐标还原至与原图片匹配,YOLO返回的是边界框的中心坐标以及边界框的宽度和高度
                box = detection[0:4] * np.array([W, H, W, H])
                (centerX, centerY, width, height) = box.astype("int") # 使用 astype("int") 对上述 array 进行强制类型转换,centerX:框的中心点横坐标, centerY:框的中心点纵坐标,width:框的宽度,height:框的高度

                x = int(centerX - (width / 2))  # 计算边界框的左上角的横坐标
                y = int(centerY - (height / 2))  # 计算边界框的左上角的纵坐标

                # 更新检测到的目标框,置信度和类别ID
                boxes.append([x, y, int(width), int(height)])  # 将边框的信息添加到列表boxes
                confidences.append(float(confidence))  # 将识别出是某种物体的置信度添加到列表confidences
                classIDs.append(classID) # 将识别物体归属于哪一类的信息添加到列表classIDs

    # 上一步中已经得到yolo的检测框,但其中会存在冗余的bbox,即一个目标对应多个检测框,所以使用NMS去除重复的检测框
    # 利用OpenCV内置的NMS DNN模块实现即可实现非最大值抑制 ,所需要的参数是边界 框、 置信度、以及置信度阈值和NMS阈值
    # 第一个参数是存放边界框的列表,第二个参数是存放置信度的列表,第三个参数是自己设置的置信度,第四个参数是关于threshold(阈值
    # 返回的idxs是一个一维数组,数组中的元素是保留下来的检测框boxes的索引位置
    idxs = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.3)

    dets = []  # 存放检测框的信息,包括左上角横坐标,纵坐标,右下角横坐标,纵坐标,以及检测到的物体的置信度,用于目标跟踪
    if len(idxs) > 0:  # 存在检测框的话(即检测框个数大于0)
        for i in idxs.flatten():  #  循环检测出的每一个box
            # yolo模型可以识别很多目标,因为我们在这里只是识别车,所以只有目标是车的我们进行检测,其他的忽略
            if LABELS[classIDs[i]] == "car":
                (x, y) = (boxes[i][0], boxes[i][1])  # 得到检测框的左上角坐标
                (w, h) = (boxes[i][2], boxes[i][3])  # 得到检测框的宽和高
                dets.append([x, y, x + w, y + h, confidences[i]])  # 将检测框的信息的放入dets中
    # 设置数据类型,将整型数据转换为浮点数类型,且保留小数点后三位
    np.set_printoptions(formatter={'float': lambda x: "{0:0.3f}".format(x)})
    # 将检测框数据转换为ndarray,其数据类型为浮点型
    dets = np.asarray(dets)

总结

基于OPenCV的DNN模块利用yoloV3模型进行目标检测:

  • 加载已训练好的yolov3模型及其权重参数
  • 将要处理的图像转换成输入到模型中的blobs
  • 利用模型对目标进行检测
  • 遍历检测结果,应用非极大值抑制
  • 绘制最终检测结果,并存入到ndarray中,供目标追踪使用。

yolo、FPN特征金字塔

1.yolo网络结构:
	yolo网络结构分为两部分,分类模型和检测模型。
	1.yolo v1:
		检测模型部分为448*448的输入,前面有24个卷积层,最后面是2个全连接层。
		只使用1×1降维层,后面是3×3卷积层,全连接层输出7×7×30的特征图。
 	2.快速版的YOLO:
		较少卷积层(9层而不是24层)的神经网络,在这些层中使用较少的滤波器。
	  	yolo v1和快速版YOLO网络的最终输出都是7×7×30的预测张量。
	3.yolo v2:
		1.缩减了网络,所有卷积层上添加批量标准化,检测模型部分为416x416的输入,移除了全连接层,最终输出13x13的特征图。
		  由于仅使用卷积层和池化层,移除了全连接层,因此能够在不同尺寸的图像上运行,可以实时调整大小将多尺度训练应到模型中。
		2.引入Anchor机制使用锚框来预测边界框:
			检测模型输出13x13的特征图,映射到原图则为被切分成13x13的网络单元。
			13x13特征图中每个像素对应的每个单元格预测5个锚框anchor box,每个锚框都预测一个bbox边界框的位置信息、置信度和分类概率值。
			也即每个单元格上通过5个anchor锚框预测5个边界框bbox,YOLOv2可以预测13x13x5=845个边界框。
			以anchor锚框为基准,计算Ground Truth(GT)真实框对其anchor锚框的平移缩放变化参数,
			然后通过anchor锚框预测边界框(bounding boxes)bbox通过回归调整接近Ground Truth(GT)真实框以此构建回归的目标函数求最小值。
			在计算回归损失的时候,因为是以anchor窗口为基准来预测边界框bbox,所以要使预测边界框bbox和GT真实框足够接近,
			以此来构建目标函数求最小值,达到回归的目的。
		3.高分辨率分类器:
			1.分类模型:先用224x224的输入在ImageNet数据集训练分类网络,然后将输入调整到448x448继续在ImageNet数据集训练分类网络。
			2.检测模型:利用预训练得到的模型在检测数据集上fine-tuning微调。
		4.采用标准的k-means聚类实现边界框boxes尺寸维度聚类
			Faster-RCNN中的锚框anchor boxes的个数和宽高维度往往是手动精选的先验框,YOLOv2使用k-means聚类算法对训练集中的边界框做了聚类分析,
			尝试找到合适尺寸的Anchor,一开始就选择了更好的、更有代表性的先验boxes维度,那么网络就应该更容易学到准确的预测位置。
			由于我们限制bbox边界框的位置信息为单元格内的偏移量预测,因此参数化更容易学习,从而使网络更加稳定,使用维度聚类并直接预测边界框中心位置。
			希望的是误差和边界框boxes尺寸大小没有太大关系,所以通过IOU定义了如下的距离函数,使得误差和边界框boxes尺寸大小无关:

		5.细粒度功能
			不单只在13×13特征图上预测检测结果,这对于大型物体的检测是足够的,还增加从较早的层中提取26×26的分辨率特征,
			这可以增加更细粒度特征对定位较小物体有好处。实现:添加一个直通层将高分辨率特征与低分辨率特征连接起来,
			将相邻特征叠加到不同的通道中,而不是空间位置上,类似于ResNet中的恒等映射,
			即把在较早的层中所提取出来的26×26分辨率特征从26×26×512=346112的特征图变为13×13×2048=346112的特征图。
		6.多尺度训练			
			由于仅使用卷积层和池化层,移除了全连接层,因此yolo v2能够在不同尺寸的图像上进行读取训练,
			可以实时调整大小将多尺度图像训练应用到模型中。每隔几个批次迭代就改变一次网络,比如每10个批次网络会随机选择一个新的图像尺寸大小。
			由于模型缩减了32倍,所以从32的倍数中抽取:{320,352,…,608},因此,最小的选项是320×320,最大的是608×608。
	4.yolo v3:
		1.特征金字塔(FPN网络)
			1.yolo V3使用了特征金字塔(FPN网络),在13x13、26x26、52x52 一共3个不同大小的特征图上做bbox预测。
			2.3个不同大小的特征图上的每个像素点映射到原图中的每个单元格cell 均使用3种(默认)不同尺寸的锚框来预测bbox(边界框bounding boxes)。
			  每种不同尺寸的锚框Anchor boxes所预测的bbox(边界框bounding boxes)包含:
			  4个预测位置(x、y、w、h)、1个bbox置信度分数confidence scores、M个分类类别的概率值。
			3.那么一个NxN的特征图映射原图就有NxN个网格单元cell,那么这个NxN的特征图(NxN个网格单元)预测的数据量为NxNx(3x(4+1+M个分类类别的概率值))。
			  yolo V3分别在13x13、26x26、52x52 一共3个特征图做bbox预测,设置13x13为NxN的话,那么26x26为2x(NxN),52x52为4x(NxN),
			  那么3个不同尺度特征图一共预测的数据量为(NxN + 2x(NxN) + 4x(NxN)) x (3x(4+1+N个分类类别的概率值))
		2.使用二分类的逻辑回归代替了softmax来作为分类器,并使用二分类交叉熵作为损失。
		3.引入了残差模块,并进一步加深了网络,改进后的网络有53个卷积层,命名为Darknet-53。

	5.YOLO V3 Tensorflow2.0源码分析
		YOLOv3 的网络结构由基础特征提取网络、多尺度特征融合层和输出层组成。
		1.YOLOv3引入了残差模块,并进一步加深了网络,改进后的网络有53个卷积层,命名为Darknet-53。
		  特征提取网络:
			YOLOv3使用DarkNet-53作为特征提取网络:DarkNet-53 基本采用了全卷积网络,用步长为2的卷积操作替代了池化层,
			同时添加了 Residual残差单元,避免在网络层数过深时发生梯度弥散。

		2.YOLOv3借鉴了FPN的思想,从不同尺度提取特征。
		  特征融合层:
			为了解决之前YOLO版本对小目标不敏感的问题,YOLOv3采用了3个不同尺度的特征图来进行目标检测,
			分别为13x13,26x26,52x52,用来检测大、中、小三种目标。特征融合层选取Darknet-53产出的三种尺度特征图作为输入,
			借鉴了FPN(feature pyramid networks)的思想,通过一系列的卷积层和上采样对各尺度的特征图进行融合。
		3.输出层:
			同样使用了全卷积结构。3x(20+4+1)=75表示一个grid cell单元格预测的3个bounding box,4表示框的4个坐标信息,
			1表示Confidence Score,20表示VOC数据集中20个类别的概率。如果换用别的数据集,20可以更改为实际类别数量。
		
2.yolo v1输出7x7的特征图映射到原图中的7x7=49个网格单元cell中,yolo v2输出13x13的特征图映射到原图中的13x13=169个网格单元cell中
  那么这些yolo网络中的每个网格单元预测目标的流程:
	1.原始图片resize到448x448,经过yolo v1的24个卷积层/快速版的YOLO的9个卷积层之后,将图片输出成了一个7*7*30的特征图。
	  yolo v1网络输出为7x7的特征图,那么即是把输入原图切分成7x7=49个网格单元cell。
	2.yolo v1的输入原图中的7x7=49个网格单元cell
		7x7=49个像素值,理解成49个单元格,每个单元格可以代表原图的一个方块。
		每个网格单元cell都会预测N个边界框bounding boxes、每个bbox框对应的1个置信度分数confidence scores、M个类别的概率值。
	3.每个bbox框对应的1个置信度分数confidence scores
		1.如果目标的中心点落到49个网格单元中的某个网格单元cell中,那么该网格就负责检测该目标。
		  判断一个网格单元中是否包含目标,首先看一个图片中真实目标的ground truth(GT真实框)的中心点坐标是在哪一个grid cell中。
		  如果某个真实目标的ground truth(GT真实框)的中心点坐标在这一个网格单元中的话,
		  那么也就是说这个真实目标的预测就由这一个网格单元所预测bbox(边界框bounding boxes)来负责。
		  注意:如果多个目标物体的ground truth(GT真实框)的中心点坐标都出现在同一个单元格cell中的话,那么并不建议使用yolo V1,
 		        因为yolo V1中每个网格所预测的2个bbox中最终只会有1个bbox用于预测目标物体,yolo V1的每个单元格都只能预测一个物体。
		2.一个网格单元会预测N个bbox(边界框bounding boxes),如果决定了由这一个单元格来负责预测真实目标的话,
		  那么会通过N个bbox对应的各自的置信度分数confidence scores来进行比较由哪个bbox来负责预测。
		3.置信度分数confidence scores评估标准:
			这些置信度分数反映了该模型对那个框内是否包含目标的信心,以及它对自己的预测的准确度的估量。
			1.比如yolo V1中的每个单元格会预测2个bounding box,那么会由其中一个bbox来预测目标,前提是这个单元格包含物体。
			  包含目标的某一个单元格所预测的2个bounding box分别与ground truth(GT真实框)的IOU哪个更大,
			  那么IOU最大的这一个bbox更接近目标的GT真实框,那么就由这一个bounding box来负责预测该对象是否存在。
			2.计算confidence score公式:
				1.如果单元格中不包含目标,那么这一个单元格所预测的N个bbox对应的confidence scores均为0。
				2.如果单元格中包含目标,那么某个bbox的confidence score 等于 预测的bbox和ground truth(GT真实框)的IOU乘积。
				  那么负责预测该目标的bbox与ground truth(GT真实框)的IOU的最大值为1。
				3.计算置信度分数confidence scores公式:

	4.M个类别的概率值
		1.所预测的类别概率值实际为类别的条件概率值。
		  类别的条件概率值中的条件指的是这个单元格包含物体的前提下,那么预测这个物体分别是M个类别中每一个类别的概率。
		2.联合概率、条件概率与相互独立
			1.联合概率:包含多个条件,且所有条件同时成立的概率
			  记作:P(A,B)
			2.条件概率:就是事件A在另外一个事件B已经发生条件下的发生概率
			  记作:P(A|B)
			  例子:P(程序员|喜欢):在女神喜欢的条件下,职业是程序员的概率?
			3.相互独立:如果P(A, B) = P(A)P(B),则称事件A与事件B相互独立。
		3.比如yolo v1中每个单元格所预测的20个类别概率值实际预测属于这个单元格的类别概率值,并不是预测属于这个bbox的类别概率值。
		4.并不会直接使用单元格所预测的类别的条件概率值,而是通过计算类别的条件概率值和每个bbox框预测的置信度分数相乘得到每个bbox框的特定类别的置信度分数。
		  这些bbox框的特定类别的置信度分数体现了某类别出现在某bbox框中的概率以及预测框拟合目标的程度。
		  计算每个bbox框的特定类别的置信度分数公式:

	5.非最大抑制(NMS)
		得到每个bbox框的特定类别的置信度分数之后,首先设置阈值过滤掉分数低的bbox预测框,然后对过滤后剩余的bbox预测框进行非最大抑制(NMS)处理。
		最终便得到预测为某类别的bbox预测框。

3.yolo V1、yolo V2、yolo V3 的bbox(边界框bounding boxes)数目变化
	1.yolo V1:
		1.每个网格单元cell预测2个(默认)bbox(边界框bounding boxes):
			yolo网络输出的7x7的特征图把输入原图切分成7x7=49个网格单元,那么输入图像一共有 7x7x2=98个bbox(边界框bounding boxes)。
		2.每个网格单元就要预测2个(默认)bbox(边界框bounding boxes),那么一个网格单元要预测的数据量就是 2x(4+1)+20=30。
		  7x7=49个网格单元一共预测的数据量:7x7x(2x(4+1)+20)=1470。
			2:每个网格单元预测的2个(默认)bbox(边界框bounding boxes)。
			4+1:每个bbox(边界框bounding boxes)包含 4个预测位置(x、y、w、h)和1个bbox置信度分数confidence scores。
			20:所预测的20个类别概率值实际预测属于这个单元格的类别概率值,并不是预测属于这个bbox的类别概率值。
			    所预测的类别概率值实际为类别的条件概率值。类别的条件概率值中的条件指的是这个单元格包含物体的前提下,
			    那么预测这个物体分别是M个类别中每一个类别的概率。并不会直接使用单元格所预测的类别的条件概率值,
			    而是通过计算类别的条件概率值和每个bbox框预测的置信度分数相乘得到每个bbox框的特定类别的置信度分数。
		             这些bbox框的特定类别的置信度分数体现了某类别出现在某bbox框中的概率以及预测框拟合目标的程度。
		3.yolo V1的缺点:
			正因为yolo V1中每个网格所预测的2个bbox中最终只会有1个bbox用于预测目标物体,即每个单元格cell只会预测1个目标物体,
			如果多个目标物体的ground truth(GT真实框)的中心点坐标都出现在同一个单元格cell中的话,那么并不建议使用yolo V1,
			因为yolo V1的每个单元格都只能预测一个物体。

	2.yolo V2:
		1.每个网格单元cell都使用5种(默认)不同尺寸的锚框Anchor boxes来预测bbox(边界框bounding boxes),
		  一个网格单元cell中每种不同尺寸的锚框Anchor boxes各预测一个bbox(边界框bounding boxes),一共预测5个(默认)bbox(边界框bounding boxes)。
		  输入图像一共预测有 13x13x5=845个bbox(边界框bounding boxes)。
		  输入图像一共预测的数据量(假如预测20个类别和在13x13特征图上做预测):13x13x(5x(4+1+20))=169*125=21125
		  注意:5个(默认)的锚框Anchor boxes的尺寸大小都是不一样的。
		2.每种不同尺寸的锚框Anchor boxes所预测的bbox(边界框bounding boxes)包含:4个预测位置(x、y、w、h),1个bbox置信度分数confidence scores,
		  N个分类类别的预测概率值。
		3.一个网格单元cell中5种(默认)不同尺寸的锚框Anchor boxes所预测的5个(默认)bbox(边界框bounding boxes)一共预测的数据量(假如预测20个类别):
			5x(4+1+20)=125
				1.5代表5个(默认)bbox(边界框bounding boxes)。
				2.每个bbox(边界框bounding boxes)都分别有4个预测位置(x、y、w、h),1个bbox置信度分数confidence scores,20个类别的预测概率值。
				3.所预测的20个类别概率值实际预测属于这个单元格的类别概率值,并不是预测属于这个bbox的类别概率值。
			    	  所预测的类别概率值实际为类别的条件概率值。类别的条件概率值中的条件指的是这个单元格包含物体的前提下,
			    	  那么预测这个物体分别是M个类别中每一个类别的概率。并不会直接使用单元格所预测的类别的条件概率值,
			    	  而是通过计算类别的条件概率值和每个bbox框预测的置信度分数相乘得到每个bbox框的特定类别的置信度分数。
		             	  这些bbox框的特定类别的置信度分数体现了某类别出现在某bbox框中的概率以及预测框拟合目标的程度。
		4.YOLO V2基于卷积的Anchor机制(Convolutional With Anchor Boxes):
			移除了全连接层,并使用5个(默认)不同尺寸的锚框Anchor boxes来预测bbox(边界框bounding boxes)。
		  	YOLO V2通过缩减网络,使用416x416的输入,模型下采样的总步长为32,最后得到13x13的特征图,
		  	13x13的特征图对应在输入原图分割13x13个单元格cell。
		  	每个单元格cell预测5个不同尺寸锚框anchor boxes对应的bbox(边界框bounding boxes),
			每个锚框anchor box所预测的bbox(边界框bounding boxes) 包含4个位置信息、1个置信度、N个分类类别的概率值。
		  	YOLO V2采用的5种不同尺寸锚框Anchor boxes可以预测13x13x5=845个bbox(边界框bounding boxes)。
		  	YOLO V2引⼊faster rcnn中anchor机制,anchor尺度就是用来预测网络预测值和目标GT做尺度变换的。

	3.yolo V3:
		1.特征金字塔(FPN网络)
			1.yolo V3使用了特征金字塔(FPN网络),在13x13、26x26、52x52 一共3个不同大小的特征图上做bbox预测。
			2.3个不同大小的特征图上的每个像素点映射到原图中的每个单元格cell 均使用3种(默认)不同尺寸的锚框来预测bbox(边界框bounding boxes)。
			  每种不同尺寸的锚框Anchor boxes所预测的bbox(边界框bounding boxes)包含:
			  4个预测位置(x、y、w、h)、1个bbox置信度分数confidence scores、M个分类类别的概率值。
			3.那么一个NxN的特征图映射原图就有NxN个网格单元cell,那么这个NxN的特征图(NxN个网格单元)预测的数据量为NxNx(3x(4+1+M个分类类别的概率值))。
			  yolo V3分别在13x13、26x26、52x52 一共3个特征图做bbox预测,设置13x13为NxN的话,那么26x26为2x(NxN),52x52为4x(NxN),
			  那么3个不同尺度特征图一共预测的数据量为(NxN + 2x(NxN) + 4x(NxN)) x (3x(4+1+N个分类类别的概率值))

		2.每种不同尺度特征图上所设置的先验框(bbox边界框bounding boxes)大小,
		  会从下面的array数组yolo_anchors中选出对应合适的组合作为先验框(bbox边界框bounding boxes)的大小。
			yolo_anchors = np.array([(10, 13), (16, 30), (33, 23), (30, 61), (62, 45), (59, 119), (116, 90), (156, 198), (373, 326)], 
					  np.float32) / 416
						  
4.预测bbox位置:通过回归offset 代替 直接回归坐标	  					  
	1.每个bbox(边界框bounding boxes)的预测数据量:4个预测位置(x、y、w、h)、1个bbox置信度分数confidence scores、N个类别的预测概率值。
	  预测的bbox的4个预测位置(x、y、w、h)都是相对于正在处理的网格单元进行计算而言的。
 
	2.(x, y) 
		1.预测的bbox的(x, y) 
			(x, y)表示bbox的中心点相对于单元格(grid cell)原点的偏移值,单元格(grid cell)的原点即为该单元格的左上角顶点坐标(top-left)。
	 	  	yolo将单元格的左上角的top-left顶点(原点)设置为(0, 0),右下角的bottom-right顶点设置为(1, 1),所以x和y的取值范围都分别在0到1之间。
	  	  	x和y将始终介于0到1之间,因为bbox的中心点始终位于该单元格(grid cell)之内。
	  	  	之所以把(x, y)预测为bbox中心点相对于单元格原点的位置坐标,是因为可以使得计算界限在0到1的值之间,也使得更加容易学习,从而使网络更加稳定。
		2.yolo输出层输出的(bx, by) 
			把预测的bbox的(tx, ty)转换为yolo输出层输出的(bx, by)。
			σ读作sigma。Cx和Cy分别为当前单元格(grid cell)距离输入原图的左上角原点的边距离。W和H为输入原图像的宽和高。分别除以W和H,目的是归一化。
			σ(tx) + Cx:边界框的中心点在输入原图像中的x坐标,也即边界框的中心点离输入原图像原点的x方向长度
		  	σ(ty) + Cy:边界框的中心点在输入原图像中的y坐标,也即边界框的中心点离输入原图像原点的y方向长度
			tx->bx:bx = (σ(tx) + Cx) / W
			ty->by:by = (σ(ty) + Cy) / H
			 (bx, by) 相对于整张图片的宽和高, 通过图像宽度和高度来规范边界框的中心点坐标,即使用图片的宽和高标准化自己, 使之取值范围也在(0, 1)之间。
	3.(w, h) 
		1.预测的bbox的(w, h)
			(w, h)分别为bbox边界框的高度与相应单元网格的高度之比、bbox边界框的宽度与相应单元网格的宽度之比。
		  	根据所预测的bbox(边界框bounding boxes)是大于还是小于单元格(grid cell)的尺寸来决定(w, h)的取值范围是大于1还是在0到1之间。
	  	  	如果边界框bbox的尺寸小于单元格(grid cell)的尺寸的话,w和h的取值范围都分别是在0到1之间。
	  	  	如果边界框bbox的尺寸大于单元格(grid cell)的尺寸的话,w和h的取值范围都可以大于1。
 		2.yolo输出层输出的(bw, bh)
			把预测的bbox的(tw, th)转换为yolo输出层输出的(bw, bh)。
			pw和ph分别为手动设定的锚框Anchor boxes宽和高。
		  	pw * e^tw:边界框在输入原图像中的宽度
		  	ph * e^th:边界框在输入原图像中的高度
			tw->bw:bw = (pw * e^tw) / W
			th->bh:bh = (ph * e^th) / H
			 (bw, bh) 相对于整张图片的宽和高, 通过图像宽度和高度来规范边界框的宽度和高度,即使用图片的宽和高标准化自己, 使之取值范围也在(0, 1)之间。
 
	4.yolo V2、yolo V3都基于卷积的Anchor机制(Convolutional With Anchor Boxes)
		yolo V2使用5种不同尺寸的锚框Anchor boxes预测一共5个边界框的4个位置信息、1个置信度、N个分类类别的概率值。
		yolo V3使用3种不同尺寸的锚框Anchor boxes预测一共3个边界框的4个位置信息、1个置信度、N个分类类别的概率值。

	5.anchor尺寸就是用来预测网络预测值和目标GT之间做尺度变换的。
	  比如下面的蓝色框是锚框Anchor boxes预测的bbox(边界框bounding boxes),黑色点的矩形框是锚框Anchor boxes。
	  每一个锚框Anchor boxes预测的bbox(边界框bounding boxes)都包含 tx、ty、tw、th、to(置信度)。
	  如果这个单元格(grid cell)距离输入原图的左上角原点的边距离为(cx,cy),该单元格(grid cell)对应的边界框bbox维度(边界框优先bounding box prior)的
	  长和宽分别为(pw,ph),pw和ph实际即为手动设定的锚框Anchor boxes宽和高,那么对应的边界框bbox计算结果实际为:
		1.yolo V2/yolo V3中不同尺寸的锚框Anchor boxes所预测的bbox(边界框bounding boxes)的4个位置信息为(tx, ty, tw, th),
		  那么tx和ty分别为相对于单元格(grid cell)原点的0到1之间取值的值,tw和th则根据所预测的bbox(边界框bounding boxes)是大于还是小于
		  单元格(grid cell)的尺寸来决定tw和th的取值范围是在0到1之间还是在大于1。
		2.pw和ph分别为手动设定的锚框Anchor boxes宽和高,而yolo网络最终计算的预测结果为(bx, by, bw, bh),
		  因此需要把预测的bbox的位置信息(tx, ty, tw, th)转换为yolo输出层的最终输出的位置信息(bx, by, bw, bh)。
		3.把(tx, ty, tw, th)转换为(bx, by, bw, bh)作为yolo输出层的最终输出:
			σ读作sigma。Cx和Cy分别为当前单元格(grid cell)距离输入原图的左上角原点的边距离。W和H为输入原图像的宽和高。分别除以W和H,目的是归一化。
			tx->bx:bx = (σ(tx) + Cx) / W
			ty->by:by = (σ(ty) + Cy) / H
			tw->bw:bw = (pw * e^tw) / W
			th->bh:bh = (ph * e^th) / H
		4.σ(tx) + Cx:边界框的中心点在输入原图像中的x坐标,也即边界框的中心点离输入原图像原点的x方向长度
		  σ(ty) + Cy:边界框的中心点在输入原图像中的y坐标,也即边界框的中心点离输入原图像原点的y方向长度
		  pw * e^tw:边界框在输入原图像中的宽度
		  ph * e^th:边界框在输入原图像中的高度

    """
    输出layerOutsputs介绍:
        是YOLO算法在图片中检测到的bbx的信息
        由于YOLO v3有三个输出,也就是上面提到的['yolo_82', 'yolo_94', 'yolo_106']
        因此layerOutsputs是一个长度为3的列表
        其中,列表中每一个元素的维度是(num_detection, 85)
        num_detections表示该层输出检测到bbx的个数
        85:因为该模型在COCO数据集上训练,[5:]表示类别概率;[0:4]表示bbx的位置信息;[5]表示置信度
    下面对网络输出的bbx进行检查:
        判定每一个bbx的置信度是否足够的高,以及执行NMS算法去除冗余的bbx
    """
    # 遍历每个输出层[yolo-82, yolo-94, yolo-106]
    for output in layerOutputs:
        # 遍历某个输出层的检测框结果
        for detection in output:
            # detction检测框:1*85维度的向量。其中[5:]表示类别,[0:4]bbox的位置信息 [4]置信度
            scores = detection[5:] #80个类别的概率值。scores的大小应该是1*80,因为在训练yolo模型时是80类目标
            classID = np.argmax(scores) #获取最大概率值的类别索引值
            confidence = scores[classID] #根据最大概率值的类别索引值 获取出对应的类别
            #如果该最大概率的类别的预测概率值 大于 0.3
            if confidence > 0.3:
                """
                1.pw和ph分别为手动设定的锚框Anchor boxes宽和高,而网络最终计算的预测结果为(bx, by, bw, bh),
                  因此需要把(tx, ty, tw, th)转换为(bx, by, bw, bh)。
                2.把(tx, ty, tw, th)转换为(bx, by, bw, bh)作为yolo输出层的最终输出:
                    σ读作sigma。Cx和Cy分别为当前单元格(grid cell)距离输入原图的左上角原点的边距离。
                    W和H为输入原图像的宽和高。分别除以W和H,目的是归一化。
                    
                        tx->bx:bx = (σ(tx) + Cx) / W
                        ty->by:by = (σ(ty) + Cy) / H
                        tw->bw:bw = (pw * e^tw) / W
                        th->bh:bh = (ph * e^th) / H
                        
                        σ(tx) + Cx:边界框的中心点在输入原图像中的x坐标,也即边界框的中心点离输入原图像原点的x方向长度
                        σ(ty) + Cy:边界框的中心点在输入原图像中的y坐标,也即边界框的中心点离输入原图像原点的y方向长度
                        pw * e^tw:边界框在输入原图像中的宽度
                        ph * e^th:边界框在输入原图像中的高度
                """
                # 将检测结果边界框的坐标还原至与原图片适配,YOLO返回的是边界框的中心坐标以及边界框的宽度和高度
                box = detection[0:4] * np.array([W, H, W, H])
                # 使用 astype("int") 对上述 array 进行强制类型转换
                # centerX:检测框的中心点横坐标, centerY:检测框的中心点纵坐标,width:检测框的宽度,height:检测框的高度
                (centerX, centerY, width, height) = box.astype("int")
                # 计算边界框的左上角的横坐标:检测框的中心点横坐标 - 检测框的宽度/2
                x = int(centerX - width / 2)
                # 计算边界框的左上角的纵坐标:检测框的中心点纵坐标 - 检测框的高度/2
                y = int(centerY - height / 2)
                # 更新检测到的目标框,置信度和类别ID
                # boxes:[边界框的左上角的横坐标, 边界框的左上角的纵坐标, 检测框的宽度, 检测框的高度]
                boxes.append([x, y, int(width), int(height)]) # 将边框的信息添加到列表boxes
                confidences.append(float(confidence)) # 将识别出是某种物体的置信度添加到列表confidences
                classIDs.append(classID) # 将识别物体归属于哪一类的信息添加到列表classIDs

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值