本文转载自:https://zhuanlan.zhihu.com/p/183261974
- 输入图片:一般用一个矩阵表示;
- 输出结果:一般用one-hot vector表示**,哪一维是1,就代表图片属于哪一类;
因此,整体模型结构是这样的:
img cbrp16 cbrp32 cbrp64 cbrp128 … fc256-fc10
cbrp指的是conv,bn,relu,pooling的串联
由于输入要是one-hot形式,所以最后我们设计了2个fc层,我们称之为“分类头”或者“决策层”
YOLO v0
- 输入与输出的不同:首先他们的输入都是image,但是分类器的输出是一个one-hot vector,而检测器的输出是一个框(Bounding Box),如下图所示:
表示方法有如下几种:
- x,y,w,h(如上图)
- p1,p2,p3,p4(4个点坐标)
- cx,cy,w,h(cx,cy为中心点坐标)
- x,y,w,h,angle(还有的目标是有角度的,这时叫做Rotated Bounding Box)
- …
但无论用什么形式去表达这个框,模型的输出结果一定是个向量,它和分类器的输出是一样的(分类模型输出是one-hot向量,检测模型输出是我们标注的结果)
如何用分类模型做检测——把检测的任务当做是遍历性的分类任务——如何遍历?
我们的目标是一个个框,那就用这个框去遍历所有的位置,所有的大小。比如下面这张图片,我需要你检测葫芦娃的脸,如图1所示:
对边框的区域/位置进行遍历:属不属于头;
先预设一个框的大小,然后在图上遍历这个框,比如:第一行全都不是头。第4个框只有一部分目标在,也不算。第5号框算是一个头,我们记住它的位置。这样不断地滑动,就是遍历性地分类。
然后就是遍历框的大小,如下图所示:
然后就是遍历所有位置,如下图所示:
其实就是滑动窗口分类方法——其准确度和遍历得彻不彻底有关:遍历得越精确,检测器的精度就越高——>问题:检测的耗时非常大
- 训练该方法有着以下问题:
- 框有不同的大小,对于不同大小的框,输入到相同的二分类器中吗——是的。要先把不同大小的input归一化到统一的大小;
- 背景图片很多,前景图片很少(二分类样本不均衡)
对此,分类器输出一个one-hot vector,把它换成(x,y,w,h,c),c表示confidence置信度,把问题转化成一个回归问题,直接回归出Bounding Box的位置。把上边的结构的最后一个fc10改成fc5就行,并且其输出就是:(c,x,y,w,h),这样一来就变成了一个检测器
其实本质上都是矩阵映射到矩阵,只是代表的意义不一样而已
如何训练?把label设置为 ( 1 , x ∗ , y ∗ , w ∗ , h ∗ ) (1,x^{*},y^{*},w^{*},h^{*}) (1,x∗,y∗,w∗,h∗),其中 x ∗ x^{*} x∗代表了真值。而这种方法比上边的拿个滑动窗口分类方法简单太多。
YOLO v1
v0只能输出一个目标,但是许多应用场景中都不止一个目标。为了解决这种问题,最优的方法就是用一个(c,x,y,w,h)去负责image某个区域的目标,如下图所示:
这样一来,一次就可以输出多个框,每个框是1个(c,x,y,w,h),如下图所示:
最优的应对方法就是用一个(c,x,y,w,h)去负责image某个区域的目标,如下图所示:
图片设置为16个区域,每个区域用1个(c,x,y,w,h)去负责。这一一来就可以一次输出16个框,每个框是1个(c,x,y,w,h)
如此一来,张量的维度就变成了:(5,4,4)
- 如何设置c的值?——看他的脸的中心落在了哪个grid里面。根据这一原则,c的真值为下图所示:
不难看出:7个葫芦娃却只有6个1,这是因为某一个grid里面有2个目标。这种情况还没法解决
其对应的损失函数如下所示:
loss = 0
for img in img_all:
for i in range(4):
for j in range(4):
loss_ij = lamda_1*(c_pred-c_label)**2 + c_label*(x_pred-x_label)**2 +\
c_label*(y_pred-y_label)**2 + c_label*(w_pred-w_label)**2 + \
c_label*(h_pred-h_label)**2
loss += loss_ij
loss.backward()
其实就是:遍历所有图片,遍历所有位置,计算loss。
两个问题:
- 模型现在能力就这么大,只能在一个区域中检测出一个目标,如何改进?
进一步细分区域,就可以缓解多目标问题,但是无法从根本上去解决;
- 按上面的设计你检测得到了16个框,可是图片上只有7个葫芦娃的脸,怎么从16个结果中筛选出7个我们要的呢?
有两种方法:聚类,聚成7类,在这7个类中,选择confidence最大的框(但是容易出现2个目标本身比较近聚成了1个类怎么办?如果不知道到底有几个目标呢?为何聚成7类?不是3类?);NMS(非极大值抑制):2个框重合度很高,大概率是一个目标,那就只取一个框(重合度的计算方法:交并比IoU=两个框的交集面积/两个框的并集面积)
- 新问题:多类目标怎么办?就比如既要你检测葫芦娃的脸,又要你检测葫芦娃的葫芦:
img → cbrp16 → cbrp32 → cbrp64 → cbrp128 → … → fc256-fc[5+2]*N → [c,x,y,w,h,one-hot]*N
在这里是两个类,那么one-hot就是[0,1],[1,0],也就是下图中红色的那两个:
- 新新问题:小目标检测怎么办?专门设计神经元去拟合小目标:
对于每个区域,我们用2个五元组(c,x,y,w,h),一个负责回归大目标(上半部分),一个负责回归小目标(下半部分),同样添加one-hot vector,one-hot就是[0,1],[1,0]这样子,来表示属于哪一类(葫芦娃的头or葫芦娃的葫芦)。
对应的伪代码就是:
loss = 0
for img in img_all:
for i in range(3):
for j in range(4):
c_loss = lamda_1*(c_pred-c_label)**2
geo_loss = c_label_big*(x_big_pred-x_big_label)**2 +\
c_label_big*(y_big_pred-y_big_label)**2 + c_label_big*(w_big_pred-w_big_label)**2 + \
c_label_big*(h_big_pred-h_big_label)**2 +\
c_label_small*(x_small_pred-x_small_label)**2 +\
c_label_small*(y_small_pred-y_small_label)**2 + c_label_small*(w_small_pred-w_small_label)**2 + \
c_label_small*(h_small_pred-h_small_label)**2
class_loss = 1/m * mse_loss(p_pred, p_label)
loss_ij =c_loss + geo_loss + class_loss
loss += loss_ij
loss.backward()
看看正经论文里的图:
这里的2*5代表2层结构,分别用于检测大/小目标,其中每一层有5个块(c,x,y,w,h)再加上20个类,就是30了
这边就是区域范围是 7 × 7 7\times7 7×7,并且它所预测是20类
对应的损失函数:
前2行计算前景的geo_loss。
第3行计算前景的confidence_loss。
第4行计算背景的confidence_loss。
第5行计算分类损失class_loss。
这样一来,这个模型就是这样的
img → \to →cbrp192 → \to →cbrp256 → \to →cbrp512 → \to →cbrp1024 → \to →… → \to →fc4096-fc[5+2]*N → 7 × 7 × 30 \to7\times7\times30 →7×7×30
检测层的设计:回归坐标值+one-hot分类
但是,它并没有计算背景的geo_loss
来看看v1的代码:
1. 模型定义:
- 定义特征提取层:
class VGG(nn.Module):
# init方法中初始化各个层:
# features层包含一系列卷积层和最大池化层;
# classifier层包含由于分类的全连接/密集层;
# avgpool层执行全剧平均池化(这是一种在将特征传递到全连接层之前汇总最后一个卷积层特征的技术)
def __init__(self):
super(VGG,self).__init__()
# the vgg's layers
#self.features = features
cfg = [64,64,'M',128,128,'M',256,256,256,'M',512,512,512,'M',512,512,512,'M']
layers= []
batch_norm = False
in_channels = 3
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2d(kernel_size=2,stride = 2)]
else:
conv2d = nn.Conv2d(in_channels,v,kernel_size=3,padding = 1)
if batch_norm:
layers += [conv2d,nn.Batchnorm2d(v),nn.ReLU(inplace=True)]
else:
layers += [conv2d,nn.ReLU(inplace=True)]
in_channels = v
# use the vgg layers to get the feature
self.features = nn.Sequential(*layers)
# 全局池化
self.avgpool = nn.AdaptiveAvgPool2d((7,7))
# 决策层:分类层
self.classifier = nn.Sequential(
nn.Linear(512*7*7,4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096,4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096,1000),
)
for m in self.modules():
if isinstance(m,nn.Conv2d):
nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias,0)
elif isinstance(m,nn.BatchNorm2d):
nn.init.constant_(m.weight,1)
nn.init.constant_(m.bias,1)
elif isinstance(m,nn.Linear):
nn.init.normal_(m.weight,0,0.01)
nn.init.constant_(m.bias,0)
# forward方法是VGG模型的前向传递,它接受一个输入图像,并通过features、avgpool和classifier层。
# 它返回classifier层的输出,以及最后一个卷积层的特征映射(x_fea)和avgpool层的输出(x_avg)
def forward(self,x):
x = self.features(x)
x_fea = x
x = self.avgpool(x)
x_avg = x
x = x.view(x.size(0),-1)
x = self.classifier(x)
return x,x_fea,x_avg
# extractor方法用于仅使用features层从输入图像中提取特征。这对于特征可视化或迁移学习等任务非常有用
def extractor(self,x):
x = self.features(x)
return x
- 定义检测头:
self.detector = nn.Sequential(
nn.Linear(512*7*7,4096),
nn.ReLU(True),
nn.Dropout(),
nn.Linear(4096,1470),
)
- 整体模型:
class YOLOV1(nn.Module):
def __init__(self):
super(YOLOV1,self).__init__()
vgg = VGG()
self.extractor = vgg.extractor
self.avgpool = nn.AdaptiveAvgPool2d((7,7))
# 决策层:检测层
self.detector = nn.Sequential(
nn.Linear(512*7*7,4096),
nn.ReLU(True),
nn.Dropout(),
#nn.Linear(4096,1470),
nn.Linear(4096,245),
#nn.Linear(4096,5),
)
for m in self.modules():
if isinstance(m,nn.Conv2d):
nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias,0)
elif isinstance(m,nn.BatchNorm2d):
nn.init.constant_(m.weight,1)
nn.init.constant_(m.bias,1)
elif isinstance(m,nn.Linear):
nn.init.normal_(m.weight,0,0.01)
nn.init.constant_(m.bias,0)
def forward(self,x):
x = self.extractor(x)
#import pdb
#pdb.set_trace()
x = self.avgpool(x)
x = x.view(x.size(0),-1)
x = self.detector(x)
b,_ = x.shape
#x = x.view(b,7,7,30)
x = x.view(b,7,7,5)
#x = x.view(b,1,1,5)
return x
这个模型由VGG网络的卷积部分和一个检测层组成,用于目标检测任务;
用一个图片张量来作为输入,哦那个CGG的卷积来提取特征,然后通过一个自适应的平均池化层将特征图转为7*7的,接着将特征图展开成一维向量,并通过一个检测层进行检测;
(检测层包含一个全连接层,其输入是特征向量,输出是一个长度为245的向量。该向量可以被解析成每个物体的信息,包括物体的位置、大小、置信度等。最终输出的张量可以被解析成每个检测框的信息,并用于目标检测任务中);
forward方法中,输入的图片张量经过VGG网络的卷积部分和自适应的平均池化层,然后被展开成一维向量,并输入到检测层中进行检测;
检测层的输出是一个大小为(batch_size, 7, 7, 5)的张量,其中每个元素代表一个检测框的信息,包括物体的位置、大小、置信度等。最后将检测层的输出张量返回
- 主函数:
if __name__ == '__main__':
vgg = VGG()
x = torch.randn(1,3,512,512)
feature,x_fea,x_avg = vgg(x)
print(feature.shape)
print(x_fea.shape)
print(x_avg.shape)
yolov1 = YOLOV1()
feature = yolov1(x)
# feature_size b*7*7*30
print(feature.shape)
这段代码分别用VGG和YOLOV1模型对一个随机输入的图片进行了前向传播,并打印了输出张量的大小。流程如下:
首先创建了一个VGG模型对象,然后随机生成一个大小为1x3x512x512的图片张量进行前向传播,得到VGG网络的输出特征图、最后一层卷积层的输出和自适应平均池化层的输出,并打印了它们的大小;
接着,创建了一个YOLO V1模型对象,将同样的图片张量输入到该模型中进行前向传播,得到一个大小为1x7x7x5的输出张量,并打印了它的大小;
可以看出:
- 在VGG网络中,输入图片经过卷积部分后,输出特征图的大小为1x512x16x16,最后一层卷积层的输出大小为1x512x16x16,自适应平均池化层的输出大小为1x512x7x7;
- 在YOLOV1模型中,输入图片经过卷积部分和自适应平均池化层后,通过检测层输出的张量大小为1x7x7x5,其中每个元素代表一个检测框的信息,包括物体的位置、大小、置信度等;
2. 模型训练:
- train函数定义如下:
def train():
for epoch in range(epochs):
ts = time.time()
for iter, batch in enumerate(train_loader):
optimizer.zero_grad()
# 取图片
inputs = input_process(batch)
# 取标注
labels = target_process(batch)
# 获取得到输出
outputs = yolov1_model(inputs)
#import pdb
#pdb.set_trace()
#loss = criterion(outputs, labels)
loss,lm,glm,clm = lossfunc_details(outputs,labels)
loss.backward()
optimizer.step()
#print(torch.cat([outputs.detach().view(1,5),labels.view(1,5)],0).view(2,5))
if iter % 10 == 0:
# print(torch.cat([outputs.detach().view(1,5),labels.view(1,5)],0).view(2,5))
print("epoch{}, iter{}, loss: {}, lr: {}".format(epoch, iter, loss.data.item(),optimizer.state_dict()['param_groups'][0]['lr']))
#print("Finish epoch {}, time elapsed {}".format(epoch, time.time() - ts))
#print("*"*30)
#val(epoch)
scheduler.step()
训练过程中,对于每个epoch,遍历训练数据集中的每个batch,将输入数据和标注数据分别通过input_process()和target_process()函数进行处理,然后将处理后的输入数据输入到YOLOV1模型中进行前向传播,得到输出结果;
接着,将输出结果和标注数据传入lossfunc_details()函数计算损失函数,然后进行反向传播和优化器更新。在每个epoch中,记录损失函数并按照一定的频率打印出来
最后,使用学习率调度器scheduler更新学习率;
具体来说,训练过程中使用了一个优化器optimizer,它可以通过调用zero_grad()清空之前的梯度,然后使用backward()方法计算当前的梯度,并使用step()方法更新模型参数。每个epoch中,使用一个学习率调度器scheduler来调整学习率。在打印损失函数时,还打印了当前epoch、当前batch、损失函数值和当前学习率。在训练结束后,可以调用val()函数对模型进行验证。
- 2个训练集的数据处理函数:
def input_process(batch):
#import pdb
#pdb.set_trace()
batch_size=len(batch[0])
input_batch= torch.zeros(batch_size,3,448,448)
for i in range(batch_size):
inputs_tmp = Variable(batch[0][i])
inputs_tmp1=cv2.resize(inputs_tmp.permute([1,2,0]).numpy(),(448,448))
inputs_tmp2=torch.tensor(inputs_tmp1).permute([2,0,1])
input_batch[i:i+1,:,:,:]= torch.unsqueeze(inputs_tmp2,0)
return input_batch
batch[0]就是batch中包含的图像的数量;
然后创建一个形状为 (batch_size, 3, 448, 448) 的 tensor,用于存储处理后的输入数据。其中,3 表示图像的通道数,448 表示图像的宽和高都为 448 像素;
在处理每个样本数据时,首先将其封装成Variable对象,然后使用OpenCV库中的resize()函数将其缩放到448x448的大小;
接着,将其转换为张量格式,并将通道维度由最后一维移动到第一维;
最后,将处理后的张量添加到一个大小为(batch_size, 3, 448, 448)的零张量中,并返回该张量作为输出;
batch[0]为image,batch[1]为label,batch_size为1个batch的图片数量。
batch[0][i]为这个batch的第i张图片,inputs_tmp2为尺寸变成了3,448,448之后的图片,再经过unsqueeze操作拓展1维,size=[1,3,448,448],存储在input_batch中。
permute()函数是PyTorch中的一个函数,用于对张量进行维度重新排列。它可以用来实现维度的转置、交换和删除等操作。
具体来说,permute()函数接收一个整数序列作为参数,用于指定新的维度顺序。例如,对于一个大小为(3, 4, 5)的张量,可以使用permute([2, 0, 1])将其转换为一个大小为(5, 3, 4)的张量,其中第1维变成了第3维,第2维变成了第1维,第3维变成了第2维。
需要注意的是,permute()函数不会改变原始张量的数据,而是返回一个新的张量,因此需要将其赋值给一个新的变量或者使用inplace=True参数将其应用于原始张量。
def target_process(batch,grid_number=7):
# batch[1]表示label
# batch[0]表示image
batch_size=len(batch[0])
target_batch= torch.zeros(batch_size,grid_number,grid_number,30)
#import pdb
#pdb.set_trace()
for i in range(batch_size):
labels = batch[1]
batch_labels = labels[i]
#import pdb
#pdb.set_trace()
number_box = len(batch_labels['boxes'])
for wi in range(grid_number):
for hi in range(grid_number):
# 遍历每个标注的框
for bi in range(number_box):
bbox=batch_labels['boxes'][bi]
_,himg,wimg = batch[0][i].numpy().shape
bbox = bbox/ torch.tensor([wimg,himg,wimg,himg])
#import pdb
#pdb.set_trace()
center_x= (bbox[0]+bbox[2])*0.5
center_y= (bbox[1]+bbox[3])*0.5
#print("[%s,%s,%s],[%s,%s,%s]"%(wi/grid_number,center_x,(wi+1)/grid_number,hi/grid_number,center_y,(hi+1)/grid_number))
if center_x<=(wi+1)/grid_number and center_x>=wi/grid_number and center_y<=(hi+1)/grid_number and center_y>= hi/grid_number:
#pdb.set_trace()
cbbox = torch.cat([torch.ones(1),bbox])
# 中心点落在grid内,
target_batch[i:i+1,wi:wi+1,hi:hi+1,:] = torch.unsqueeze(cbbox,0)
#else:
#cbbox = torch.cat([torch.zeros(1),bbox])
#import pdb
#pdb.set_trace()
#rint(target_batch[i:i+1,wi:wi+1,hi:hi+1,:])
#target_batch[i:i+1,wi:wi+1,hi:hi+1,:] = torch.unsqueeze(cbbox,0)
return target_batch
该函数接收一个batch的图像数据和目标数据作为输入,并返回一个大小为(batch_size, grid_number, grid_number, 30)的张量作为输出
在处理每个样本数据时,首先获取该样本对应的标注数据batch_labels,然后遍历每个标注框并计算其中心点在哪个网格单元格内。对于中心点在某个网格单元格内的标注框,将其转换为一个大小为(1, 5)的张量,并将其添加到目标张量target_batch的对应网格单元格中
- 具体计算过程如下:
对于一个大小为(w, h)的图像,将其划分为grid_number x grid_number个网格单元格。对于每个标注框,将其左上角和右下角坐标归一化为相对于图像宽度和高度的比例,并计算其中心点的坐标。然后对于每个网格单元格,判断其中心点是否在该网格单元格内,如果是则将标注框转换为一个大小为(1, 5)的张量,并将其添加到目标张量target_batch的对应网格单元格中。张量的前5个元素分别是一个标志位和标注框的4个位置坐标- 作用:
将输入的目标数据进行预处理,将其转换为一个大小为(batch_size, grid_number, grid_number, 30)的张量,用于在YOLOV1模型中进行处理和训练
要从batch里面获得label,首先要想清楚label(就是bounding box)应该是什么size,输出的结果应该是 7 × 7 × 30 7\times7\times 30 7×7×30的,所以label的size应该是:[batch_size,7,7,30]。在这个程序里我们实现的是输出 7 × 7 × 5 7\times7\times 5 7×7×5。这个 5 5 5就是x,y,w,h,所以label的size应该是:[batch_size,7,7,5]
batch_labels表示这个batch的第i个图片的label,number_box表示这个图有几个真值框。
接下来3重循环遍历每个grid的每个框,bbox表示正在遍历的这个框。
bbox = bbox/ torch.tensor([wimg,himg,wimg,himg])表示对x,y,w,h进行归一化。
接下来if语句得到confidence的真值,存储在target_batch中返回。
- loss函数:
def lossfunc_details(outputs,labels):
# 判断维度
assert ( outputs.shape == labels.shape),"outputs shape[%s] not equal labels shape[%s]"%(outputs.shape,labels.shape)
#import pdb
#pdb.set_trace()
b,w,h,c = outputs.shape
loss = 0
#import pdb
#pdb.set_trace()
conf_loss_matrix = torch.zeros(b,w,h)
geo_loss_matrix = torch.zeros(b,w,h)
loss_matrix = torch.zeros(b,w,h)
for bi in range(b):
for wi in range(w):
for hi in range(h):
#import pdb
#pdb.set_trace()
# detect_vector=[confidence,x,y,w,h]
detect_vector = outputs[bi,wi,hi]
gt_dv = labels[bi,wi,hi]
conf_pred = detect_vector[0]
conf_gt = gt_dv[0]
x_pred = detect_vector[1]
x_gt = gt_dv[1]
y_pred = detect_vector[2]
y_gt = gt_dv[2]
w_pred = detect_vector[3]
w_gt = gt_dv[3]
h_pred = detect_vector[4]
h_gt = gt_dv[4]
loss_confidence = (conf_pred-conf_gt)**2
#loss_geo = (x_pred-x_gt)**2 + (y_pred-y_gt)**2 + (w_pred**0.5-w_gt**0.5)**2 + (h_pred**0.5-h_gt**0.5)**2
loss_geo = (x_pred-x_gt)**2 + (y_pred-y_gt)**2 + (w_pred-w_gt)**2 + (h_pred-h_gt)**2
loss_geo = conf_gt*loss_geo
loss_tmp = loss_confidence + 0.3*loss_geo
#print("loss[%s,%s] = %s,%s"%(wi,hi,loss_confidence.item(),loss_geo.item()))
loss += loss_tmp
conf_loss_matrix[bi,wi,hi]=loss_confidence
geo_loss_matrix[bi,wi,hi]=loss_geo
loss_matrix[bi,wi,hi]=loss_tmp
#打印出batch中每张片的位置loss,和置信度输出
print(geo_loss_matrix)
print(outputs[0,:,:,0]>0.5)
return loss,loss_matrix,geo_loss_matrix,conf_loss_matrix
首先需要注意:label和output的size应该是:[batch_size,7,7,5]。
outputs[bi,wi,hi]就是一个5位向量: ( c p r e d , x p r e d , y p r e d , w p r e d , h p r e d ) (c^{pred},x^{pred},y^{pred},w^{pred},h^{pred}) (cpred,xpred,ypred,wpred,hpred)。
我们分别计算了loss_confidence和loss_geo,因为我们实现的这个模型只检测1个类,所以没有class_loss。
该函数接收模型输出的张量outputs和标签数据的张量labels作为输入,并返回损失值、损失矩阵、几何损失矩阵和置信度损失矩阵;
- 在计算损失值时,先检查模型输出张量outputs和标签数据张量labels的维度是否相同,如果不同则抛出异常。然后遍历每个batch中的每个网格单元格,在每个网格单元格中计算置信度损失和几何损失,并将它们加权相加得到总的损失;
- 具体计算过程如下:
对于每个网格单元格,从模型输出张量outputs中获取当前检测向量detect_vector,从标签数据张量labels中获取当前目标检测向量gt_dv。然后从检测向量和目标检测向量中分别获取置信度、中心点坐标、宽度和高度等信息。接着计算置信度损失和几何损失,其中几何损失包括中心点坐标误差、宽度误差和高度误差,这些误差都是平方误差。最后将置信度损失和几何损失相加得到当前网格单元格的总损失。在计算几何损失时,将其乘以目标检测向量的置信度,以保证只有真正存在目标的网格单元格才计算几何损失;- 作用:
该函数还返回三个损失矩阵,分别是总损失矩阵、置信度损失矩阵和几何损失矩阵。
其中总损失矩阵记录了每个网格单元格的总损失值。对于每个位置,从 outputs 和 labels 中获取相应的预测值和标签值,并根据预测值和标签值计算置信度损失和位置和大小损失。最后将两种损失相加,并将计算出的损失值加到总损失中;
置信度损失矩阵记录了每个网格单元格的置信度损失值;
几何损失矩阵记录了每个网格单元格的几何损失值;这些矩阵可以用于调试和分析模型的性能;
最后,该函数还打印出每个网格单元格的几何损失矩阵和置信度输出,以便用户查看和分析模型的输出结果
- 各种模型的总结:
YOLO v1:直接回归出位置。
YOLO v2:全流程多尺度方法。
YOLO v3:多尺度检测头,resblock darknet53
YOLO v4:cspdarknet53,spp,panet,tricks