从头实现YOLOv3:第4部分
原文为英文,进行了翻译和部分修改,原文地址
代码地址:github仓库、ACgit仓库
相关内容:
YOLOv3论文翻译
YOLOv3原理及流程简述
从头实现YOLOv3:第1部分
从头实现YOLOv3:第2部分
从头实现YOLOv3:第3部分
从头实现YOLOv3:第5部分
第4部分:目标得分阈值化和非极大值抑制
这是从头实现 YOLO v3
检测器教程的第 4 部分。在上一部分中,我们实现了网络的前向传递。在这部分中,我们通过目标置信度和非最大抑制来阈值化检测。
在前面的部分中,我们建立了一个模型,该模型在给定输入图像的情况下输出多个目标检测。准确地说,输出是一个形状为 B x 10647 x 85
的张量。B
是一批图像的数量,10647
是每个图像预测的边界框数量(3 x (13 x 13 + 26 x 26 + 52 x 52)
),85
是边界框属性的数量(4 + 1 + 80
)。
然而,如第 1 部分所述,我们必须将输出进行objectness score
阈值处理和非极大值抑制,以获得"真正检测"的内容。为此在文件 util.py
中创建一个名为 write_results
的函数
def write_result(prediction, confidence, num_classes, nms_conf=0.4):
该函数将prediction
、confidence
(objectness score
阈值)、num_classes
(在本例子中为 80)和 nms_conf
(NMS IoU
阈值)作为输入参数。
目标置信度的阈值化
我们的prediction
张量包含有关 B x 10647
个边界框的信息。对于objectness score
低于阈值的每个边界框,将其每个属性(代表边界框的一整行)的值设置为零。
conf_mask = (prediction[:, :, 4] > confidence).float().unsqueeze(2) # torch.Size([1, 10647, 1])prediction = prediction * conf_mask # 将 objectness score低于confidence 阈值的行清零,其余行不变
进行非极大抑制
现在拥有的边界框属性有中心坐标以及边界框的高度和宽度。但是,使用每个框的一对对角的坐标更容易计算两个框的 IoU
。因此,我们将框的 (center x, center y, height, width) 属性转换为 (左上角 x, 左上角 y, 右下角 x, 右下角 y)。
box_corner = prediction.new(prediction.shape)# 左上角box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2 # 中心x-宽/2box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2 # 中心y-高/2# 右下角box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2 # 中心x+宽/2box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2 # 中心y+高/2
每个图像中"真正检测"的数量可能不同。例如,一个大小为 3 的批次,其中图像 1、2 和 3 分别有 5、2、4 个真实检测。因此,必须一次对一张图像进行置信度阈值化和 NMS
。这意味着,我们不能矢量化所涉及的操作,并且必须循环prediction
的第 0 维(批次中的图像索引)。
batch_size = prediction.size(0)write = Falsefor ind in range(batch_size):image_pred = prediction[ind] # image Tensor
如前所述,write
标志用于指示尚未初始化输出,我们将使用一个张量来收集整个批次的"真正检测"。
一旦进入循环,请注意,每个边界框的那一行都有 85 个属性,其中 80 个是class scores
。在这一点上,我们只关心具有最大值的class scores
。因此,我们从每行中删除 80 个class scores
,然后添加具有最大值的类别的索引以及该类别的class scores
。
# confidence threshholding# NMSmax_conf, max_conf_score = torch.max(image_pred[:, 5:5 + num_classes], 1)max_conf = max_conf.float().unsqueeze(1) # 最大置信度 torch.Size([10647, 1])max_conf_score = max_conf_score.float().unsqueeze(1) # 最大置信度索引 torch.Size([10647, 1])seq = (image_pred[:, :5], max_conf, max_conf_score)image_pred = torch.cat(seq, 1)
还记得我们将目标置信度小于阈值的边界框的那一行设置为零吗?直接去掉它们。
# 将 objectness score 低于 confidence 阈值的行去掉non_zero_ind = (torch.nonzero(image_pred[:, 4]))try:image_pred_ = image_pred[non_zero_ind.squeeze(), :].view(-1, 7)except:continueif image_pred_.shape[0] == 0:continue
try-except
块用于处理没有检测到目标的情况。在这种情况下,使用 continue
跳过此图像的其余循环体。
现在,让我们在图像中检测类别
# Get the various classes detected in the imageimg_classes = unique(image_pred_[:, -1]) # -1 index holds the class index
由于同一类别可能被多次检测到,因此我们使用一个 unique
函数来获取任何给定图像中存在的类。
def unique(tensor):tensor_np = tensor.cpu().numpy()unique_np = np.unique(tensor_np)unique_tensor = torch.from_numpy(unique_np)tensor_res = tensor.new(unique_tensor.shape)tensor_res.copy_(unique_tensor)return tensor_res
然后按类别 NMS
。
for cls in img_classes:# perform NMS
进入循环后,要做的第一件事就是提取特定类(由变量 cls 表示)的检测结果。
# get the detections with one particular classcls_mask = image_pred_ * (image_pred_[:, -1] == cls).float().unsqueeze(1)# image_pred_class 只保留当前类的检测框,并且去除置信度为 0 的检测框class_mask_ind = torch.nonzero(cls_mask[:, -2]).squeeze()image_pred_class = image_pred_[class_mask_ind].view(-1, 7)# sort the detections such that the entry with the maximum objectness# confidence is at the topconf_sort_index = torch.sort(image_pred_class[:, 4], descending=True)[1] # 获得objectness score降序排列的索引image_pred_class = image_pred_class[conf_sort_index] # 对当前类别下的检测框按objectness score降序排列idx = image_pred_class.size(0) # Number of detections
现在进行NMS
for i in range(idx):# Get the IOUs of all boxes that come after the one we are looking at in the looptry:# 计算当前检测框和后面所有检测框的IOU,舍弃掉IOU大于阈值的检测框ious = bbox_iou(image_pred_class[i].unsqueeze(0), image_pred_class[i + 1:])except ValueError:breakexcept IndexError:break# Zero out all the detections that have IoU > treshholdiou_mask = (ious < nms_conf).float().unsqueeze(1)image_pred_class[i + 1:] *= iou_mask# Remove the zero entriesnon_zero_ind = torch.nonzero(image_pred_class[:, 4]).squeeze()image_pred_class = image_pred_class[non_zero_ind].view(-1, 7)
在这里,我们使用一个函数 bbox_iou
。第一个输入是由循环中的变量 i
索引的边界框。
bbox_iou
的第二个输入是后面多行边界框的张量。函数 bbox_iou
的输出是一个张量,其中包含第一个输入表示的边界框和第二个输入中每个边界框的IOU
。
如果有两个相同类别的边界框的 IoU
大于阈值,则消除类置信度较低的边界框。我们已经对边界框进行了排序,其中置信度较高的边界框位于顶部。
在循环体中,以下几行给出了框的 IoU
,由 i
索引,所有边界框的索引都高于 i
。
ious = bbox_iou(image_pred_class[i].unsqueeze(0), image_pred_class[i+1:])
每次迭代,如果任何索引大于 i
的边界框的 IoU
(框由 i
索引)大于阈值 nms_thres
h,则该特定框将被消除。
# Zero out all the detections that have IoU > treshholdiou_mask = (ious < nms_conf).float().unsqueeze(1)image_pred_class[i + 1:] *= iou_mask# Remove the zero entriesnon_zero_ind = torch.nonzero(image_pred_class[:, 4]).squeeze()image_pred_class = image_pred_class[non_zero_ind].view(-1, 7)
另请注意,我们已将计算 iou
的代码行放在 try-catch
块中。这是因为循环旨在运行 idx
迭代(image_pred_class
中的行数)。然而,当我们继续循环时,可能会从 image_pred_class
中删除一些边界框。因此,我们可能会索引一个越界的值(IndexError
),或者切片 image_pred_class[i+1:]
可能会返回一个空的张量,分配它会触发 ValueError
。如果出现这两种错误,可以确定 NMS
不能移除更多的边界框,就跳出循环。
计算IOU
def bbox_iou(box1, box2):"""Returns the IoU of two bounding boxes"""# Get the coordinates of bounding boxesb1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]# get the corrdinates of the intersection rectangle# 计算两个box左上角点坐标的最大值和右下角坐标的最小值inter_rect_x1 = torch.max(b1_x1, b2_x1)inter_rect_y1 = torch.max(b1_y1, b2_y1)inter_rect_x2 = torch.min(b1_x2, b2_x2)inter_rect_y2 = torch.min(b1_y2, b2_y2)# Intersection area 交集面积inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1 + 1, min=0) * torch.clamp(inter_rect_y2 - inter_rect_y1 + 1, min=0)# Union Area 并集面积b1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1)b2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1)iou = inter_area / (b1_area + b2_area - inter_area)return iou
预测
函数 write_results
输出一个形状为 D x 8
的张量。这里 D
是所有图像中的真实检测,每个图像由一行表示。每个检测有8
个属性,即检测所属批次中图像的索引、4个角坐标、objectness score
、置信度最大的类的score
和该类的索引。
和以前一样,除非我们有一个检测要分配,否则我们不会初始化我们的output
张量。一旦输出张量被初始化,就将后续的检测连接到它。使用write
标志来指示张量是否已初始化。在迭代类的循环结束时,将结果检测添加到张量output
中。
# 指定 batch 中的图像索引batch_ind = image_pred_class.new(image_pred_class.size(0), 1).fill_(ind)# Repeat the batch_ind for as many detections of the class cls in the imageseq = batch_ind, image_pred_class # shape (-1, 8)if not write:output = torch.cat(seq, 1)write = Trueelse:out = torch.cat(seq, 1)output = torch.cat((output, out)) # 按行连接
在函数的最后,检查输出是否已经初始化。如果没有,则意味着在该批次的任何图像中都没有进行过一次检测。在这种情况下返回 0
。
try:return output # (D, 8)except:return 0
在这篇文章的最后,最终得到了一个张量形式的预测,它列出了每个预测框的行。现在唯一剩下的就是创建一个输入管道来从磁盘读取图像、计算预测框、在图像上绘制边界框,然后显示/写入这些图像。这就是将在下一部分中做的事情。
从头实现YOLOv3:第4部分相关推荐
- 【YOLOv3从头训练 数据篇】
YOLOv3从头训练 数据篇 前言 数据下载 数据可视化 标签生成 生成训练路径文件 结语 前言 最近在忙着怎么从头实现YOLOv3,从网上找了很多教程,也在GitHub上面找到了挺多的代码的,有些能 ...
- yolov3从头实现(六)损失计算
损失计算 一.损失计算的输入 损失计算的预测值输入是yolov3从头实现(五)中的yolov3网络块的输出 损失计算的真实值输入是yolov3从头实现(三)中我们制作出的标签值 二.损失的结构 yol ...
- 从头开始实现YOLOV3
本讲义是关于从头开始构建YOLO v3检测器的简要说明,详细介绍了如何从配置文件创建网络架构,加载权重和设计输入/输出管道. 看懂后文说明的先决条件 对于后文的阅读,如果不熟悉一下概念的同学,请先复习 ...
- yolov3从头实现(五)-- yolov3网络块
yolov3网络块 一.上采样卷积块类 1.上采样卷积块结构 1*1的卷积作用是为了调整上采样输出的通道数 2.上采样卷积类的实现 # 上采样卷积类 class _Upsamling(tf.keras ...
- 从头开始训练一个检测QR二维码区域的YOLOv3模型
条形码和二维码在识别的时候主要包含定位和解码两个步骤.寻找码的位置,除了用传统的图像算法之外,也可以借助深度学习.那么深度学习的效率如何,我做了一个实验. 为QR二维码训练YOLOv3模型 编译Dar ...
- yolov3从头实现(一)-- xml标签制作与读取
标签制作与读取 这里说的标签制作并非yolov3所需要的标签,而是一般的没有处理的标签 一.标签的制作 1.制作工具及制作结果 制作工具使用的是:labelImg 用labelimg制作完成后的标签 ...
- yolov3从头实现(三)-- yolov3标签制作
yolov3标签制作 一 .自定义anchors yolov3中需要自己定义不同特征尺度下的anchor的大小 而anchor的大小时根据标注数据集聚类得出来的,可以理解成在标注数据集中的大多数标注框 ...
- 从头开始复现YOLOv3(三)训练模型
YOLOv3模型训练 1 迁移学习 (1)两种权重文件 (2)导入权重方法 (3)保存模型的方法 2 标签转化函数 3 模型训练 4.模型评价 (1)mAP的计算原理 (2)mAP的计算程序 (3)在 ...
- yolov3从头实现(四)-- darknet53网络tf.keras搭建
darknet53网络tf.keras搭建 一.定义darknet块类 1 .darknet块网络结构 2.darknet块实现 # 定义darknet块类 class _ResidualBlock( ...
最新文章
- oracle 条件反转,Oracle反转倒置函数
- 用串口模拟printf函数输出
- boost::locale::utf8_codecvt用法的测试程序
- 聊聊RocksDB Compact
- linux系统备份和恢复
- C++的六个默认函数
- ofo在北京上线有桩模式,违规最高罚20元
- 计算机导论的知识,计算机导论课的认识
- jQuery - 获取内容和属性
- 在Docker上运行微服务
- WINDOWS.H already included.MFC apps must not #include windows.h
- 炭足迹计算机的火车好处,碳足迹与碳足迹计算器.pdf
- 网约车定价策略:手机越贵打车越贵?
- 关于《2012年我的十大工程》双季进展情况报告总结
- 1.9无穷小新生五十年
- python循环中释放内存的方法_我怎样才能在Python中明确释放内存?
- 使用redis客户端可以连接集群,但使用JedisCluster连接redis集群一直报Could not get a resource from the poo
- 核心价值观与企业文化管理实践---基于华为的企业文化的管理实践-林 安老师
- python人民币和美元转换
- 【R语言爬虫】R语言提交post请求抓取盈盈理财数据
热门文章
- 103.网络安全渗透测试—[权限提升篇1]—[Linux内核漏洞提权]
- 彩色飘带字体怎么制作?AE教程教给你
- 移动端事件--touch事件的分类、touch事件的event对象、 其他触摸事件
- CLion的Toolchains are not configured和no CMAKE profiles问题
- 什么是 yum?更改yum源 yum的相关命令
- 计算机竖版桌面,电脑桌面竖屏了怎么办
- 项目目标的SMART原则
- vs2015 C# 控制台简单SpringDEMO
- Dynamics finance and operation官方虚拟机10.0.24使用私人账号访问
- windows录屏_录制视频的软件有哪些?三款软件轻松应对录屏_