基于yolov5的ignore classes训练

本文提到的忽略类别和检测中的忽略类别不一样,前者是在训练中加入忽略类,后者是在检测中仅检测想要的类。

ignore class的定义

我们在标注数据集的时候都是标注的正样本,训练过程中也是这样训练,让网络对正样本计算loss。但我们也遇到过这样的目标,这个目标即不属于正样本,也不属于负样本,比如正样本是person,那么人形雕塑或者人的影子,这类物体他并不是正样本,但如果直接归为负样本也是不严谨的,因此就可以将这类物体标注为“忽略类”,这类物体在标注的时候是有标签和box信息的,比如给这类物体的标签是"-2"。又或者说在训练中,希望对某些像素大小目标设置为ignore,比如训练中忽略20x20的目标

ignore训练

ignore训练并不是说把该类目标丢弃,如果你是把该类目标从label中删除,那么不就相当于把这个目标作为负样本进行训练了嘛,这是不对的。

但如果将ignore作为一个正常的类进行训练,参与loss,那么相当于将该类作为正样本进行训练了。因此这样也是不对的。我们需要时刻记得ignore class既不是正样本,也不是负样本,是一种夹在两者之间的样本

那么问题就是如何正确对待ignore class

对于ignore class的训练,其实是希望不对其进行反向传播,仅前向传播即可。这是因为不进行反向传播就不用当正样本进行学习,而且也不会因为直接丢弃而变成负样本。

想法很简单,但做起来还是有难度的。首先第一个问题就是如果做ignore class的样本匹配。我最初的想法是在做样本匹配的时候将忽略类样本与正样本分开,比如下面的代码,通过对class_id进行类别的筛选。【在yolov5的utils/loss.py中的build_targets函数中修改】

这里的t和t_ign shape含义是一样的,【image_id,class_id,x,y,w,h,anchor_id】

t = targets * gain  # 将Box缩放到对应的特征层上
t_ign = t[t[..., 1] < 0].view(t.shape[0], -1, t.shape[2])  # 忽略类
t = t[t[..., 1] >= 0].view(t.shape[0], -1, t.shape[2])  # 正样本
image_id = torch.as_tensor(t_ign[..., 0])  # [3, num_classes] 记录每个anchor对应的image id

然后按原yolov5的方法对这两个部分分别进行anchor的宽高匹配,然后仅把正样本的结果送入后面的三个loss计算。但这是有问题的,因为在yolov5中anchor是通过宽高匹配,虽然可以分别对两个部分进行匹配,但如何判断忽略类的匹配到的anchor也会被正样本匹配到呢?因为后面需要进行一个去重工作,就是将忽略类与正样本匹配到的相同anchor去除掉

第二个问题就是,针对ignore class的训练,loss部分怎么处理?我最初想这个问题的时候,就仅仅是觉得不需要对三个loss进行处理,这显然不对,如果不对loss进行处理,那前面只做样本匹配其实是没有意义的。

ignore class样本匹配

针对第一个问题

为了可以获得更多的anchor框与ignore class的匹配,以减小对于正样本的影响,我们需要做的是在当前特征图上生成网格,并将特征图所有网格中的anchor box与ignore class进行匹配,你可以用iou,但我这里用的是ioa匹配。

在特征层上生成网格并生成anchor box代码如下:

                    # 生成网格,anchor中心
                    grid_y, grid_x = torch.meshgrid([torch.arange(p[i].shape[3],device=targets.device), torch.arange(p[i].shape[2], device=targets.device)])
                    grid = torch.stack((grid_x, grid_y), 2).view((1, 1, p[i].shape[3], p[i].shape[2], 2)).float()
                    # plot_anchor(grid_x, grid_y, gain)
                    # 在网格上生成anchor
                    anchors_boxes = torch.zeros(3, p[i].shape[3], p[i].shape[2], 4)  # [3,80,80,4]
                    anchors_boxes[..., :2] = grid[..., :2]  # 给所有anchor分配中心点
                    anchors_boxes[0, :, :, 2:] = anchors[0, :]
                    anchors_boxes[1, :, :, 2:] = anchors[1, :]
                    anchors_boxes[2, :, :, 2:] = anchors[2, :]  # 将所有anchor的w,h传入
                    # 此刻的anchors_boxes为所有batch下三种anchor对应的x,y,w,h shape[batch_size,3,feat_w,feat_h,4]
                    # xywh->x1y1x2y2
                    an_box = torch.zeros_like(anchors_boxes)  # [3,80,80,4]
                    an_box[..., :2] = anchors_boxes[..., :2] - anchors_boxes[..., 2:] / 2
                    an_box[..., 2:] = anchors_boxes[..., :2] + anchors_boxes[..., 2:] / 2

获得t_ign的所有box,用于后面的anchor匹配,获得box代码:

                    # 获得t_ign的box框
                    center_xy = t_ign[..., 2:4]  # 取gt box的中心点
                    gt_wh = t_ign[..., 4:6]  # 取gt的w和h
                    t_ign_box = torch.zeros(3, t_ign.shape[1], 4)  # 用于存储框shape[3,num_obj,4]
                    t_ign_box[..., :2] = center_xy - gt_wh / 2  # 左上角
                    t_ign_box[..., 2:] = center_xy + gt_wh / 2  # 右下角

 然后是遍历当前head中所有cell中的anchor box与忽略类的box进行ioa匹配。image_id是之前定义的用于记录anchor对应的目标image id[或者说是batch id]。因为在前面我们已经给t_ign中的每个目标均分配了三种anchor,而且给每个目标记录了所在的image id【t_ign[...,0]就是image id,最后一个维度是anchor id了】。anchor_id用于记录匹配到的anchor id【每个head上设置了三种anchor】,ign_gi和ign_gj是记录满足ioa阈值的anchor的中心点坐标。

                    
                    anchor_id = []
                    ign_gi = []
                    ign_gj = []
                    image_id_list = []

                    #plot_ign_cls_box(t_ign_box, gain)
                    for an_idx in range(an_box.shape[0]):  # 遍历3种anchor
                        for i in range(an_box.shape[1]):  # 遍历每个网格获得每个anchor的box 行遍历
                            for j in range(an_box.shape[2]):  # 列遍历
                                # 将所有目标box与grid中的所有anchor计算ioa,输入shape[num_obj],得到每个目标ioa
                                ioa = compute_ioa(box1=t_ign_box[an_idx], box2=an_box[an_idx, i, j])
                                mask = ioa > ioa_thre
                                if mask.shape[0] and torch.max(mask):  # 所有batch中表示匹配到
                                    
                                    image_id_list.append(image_id[an_idx][mask])
                                    anchor_id.append(an_idx)
                                    ign_gi.append(j)  # x坐标
                                    ign_gj.append(i)  # y坐标

最后将上面几个列表存储在ign_match_anchor列表中。shape为【batch,anchor,x,y】。

ign_match_anchor.append((
                                         torch.IntTensor([val[0] for val in image_id_list]),
                                         torch.IntTensor(anchor_id),
                                         torch.IntTensor(ign_gj),
                                         torch.IntTensor(ign_gi)))

通过设置的ioa阈值的不同,匹配到的anchor数量也不同。下图分别是阈值为0.5和0.2时在80x80特征图上匹配示意图,绿色框为ignore class的gt,红色框为anchor box。阈值为>0.2的有更多的ign_class被anchor匹配到。

 

ioa阈值>0.5
ioa阈值>0.2

 

loss设计

代码中的indices记录了正样本的[image_id,anchor_id,x,y],ign_anchors则记录了与忽略类匹配到的【image_id,anchor_id,anchor_x,anchor_y】.

        if self.ignore_class:
            tcls, tbox, indices, anchors, ign_anchors = self.build_targets(p, targets)  # targets
        for i, pi in enumerate(p):  # layer index, layer predictions  pi shape [batch_size,3,feat_w,feat_h,5+classes]
            b, a, gj, gi = indices[i]  # image, anchor, gridy, gridx
            if self.ignore_class:
                b_ign, a_ign, gj_ign, gi_ign = ign_anchors[i]
                b_ign = b_ign.type_as(b)
                a_ign = a_ign.type_as(a)
                gj_ign = gj_ign.type_as(gj)
                gi_ign = gi_ign.type_as(gi)

                n_ign = b_ign.shape[0]
                tobj_ign = torch.zeros_like(pi[..., 0], device=device)
            tobj = torch.zeros_like(pi[..., 0], device=device)  # target obj

 在yolo中的三个损失函数中,box_loss,cls_loss其实对于忽略类可以不用,而obj_loss怎么算呢?因为通过前面的操作其实可以知道和忽略类匹配到的anchor框,然后我们需要在正样本以及预测pred中去重,也就是看正样本obj和pred哪些地方和忽略类anchor是重叠的,去除点,然后将这个像素点作为负样本【也就是将重复的坐标置0即可,这样该点就不反向传播了】。

                if self.ignore_class:
                    tobj_ign[b_ign, a_ign, gj_ign, gi_ign] = 1

                    # 去重
                    mask = (tobj != 0) & (tobj_ign != 0)  # 相同位置不为0的地方
                    tobj[mask] = 0  # 正样本与ign相同位置不为0的地方置0【正样本去重成功】
                    pi[..., 4][mask] = 0  # 预测中正样本与ign相同位置不为0的地方置0【预测类正样本去重成功】
                obji = self.BCEobj(pi[..., 4], tobj)
                lobj += obji * self.balance[i]  # obj loss    

通过以上的样本匹配和loss设计就完成了忽略训练的设计。 

<think>嗯,用户让我介绍一下基于YOLOv8和双目相机的车牌识别与测距的代码。首先,我需要理清楚这个任务的主要组成部分。用户可能对计算机视觉有一定的了解,但可能不太清楚如何将目标检测和立体视觉结合起来。我需要分步骤解释,确保他们能跟上思路。 首先,YOLOv8用于车牌检测,这部分比较常见,但用户可能想知道具体的实现步骤,比如数据准备、模型训练。然后是双目相机的测距,这部分涉及立体匹配和深度计算,可能需要解释一些概念,比如视差图、基线距离等。用户可能对如何从两个相机图像计算出距离不太清楚,需要详细说明。 接下来,代码结构方面,用户可能需要一个框架性的指导,比如模块划分、流程顺序。可能需要提到检测模块、立体匹配模块、测距模块,以及主程序如何串联这些部分。同时,用户可能会关心实际的代码示例,比如如何使用YOLOv8进行检测,如何计算深度。 另外,需要注意技术细节,比如相机标定和立体校正的重要性,这可能影响到测距的准确性。用户可能没有意识到这部分的前期工作,需要提醒他们进行相机参数的校准。此外,测距的公式和实现步骤也需要数学推导,比如视差与深度的关系,可能需要用LaTeX公式展示,但根据系统指令,行内公式要用$...$,独立公式用$$...$$,并且正确使用语法。 用户可能还关心实际应用中的挑战,比如光照变化、遮挡、车牌倾斜等,这些因素会影响识别和测距的效果,需要给出一些优化建议,比如多模态数据融合或后处理算法。这部分可以增加回答的实用性,帮助用户预见并解决问题。 最后,代码示例部分需要确保语法正确,使用正确的中文注释,可能还要提到依赖库,比如Ultralytics的YOLOv8,OpenCV用于图像处理,numpy用于数值计算。用户可能需要具体的代码片段来参考,所以提供检测、视差计算和测距的关键代码是有帮助的。 总结一下,回答需要结构清晰,分步骤讲解,涵盖技术原理、代码结构、关键实现和优化建议,同时注意格式要求和用户的实际需求,确保他们能够理解并实现这个系统。</think>好的,我将分步骤为您解析基于YOLOv8双目相机的车牌识别与测距系统实现原理及关键代码逻辑: 一、技术原理 1. 车牌检测 - 使用YOLOv8定位车牌位置,输出边界框坐标 - 关键优势:轻量化模型结构,$AP_{50}$可达97%以上 2. 双目测距 - 视差原理:$$Z = \frac{f \cdot B}{d}$$ ($Z$为深度,$f$焦距,$B$基线距,$d$视差) 二、代码结构 ```python # 伪代码框架 class LicensePlateSystem: def __init__(self): self.detector = YOLOv8(weights='license_plate.pt') self.stereo = StereoMatcher(calib_file='camera_params.yaml') def process(self, left_img, right_img): # 车牌检测 bboxes = self.detector(left_img) # 立体匹配 disparity_map = self.stereo.compute(left_img, right_img) # 测距计算 depths = [] for (x1,y1,x2,y2) in bboxes: center = ((x1+x2)//2, (y1+y2)//2) depth = self.calculate_depth(disparity_map[center]) depths.append(depth) return bboxes, depths ``` 三、关键实现代码段 1. YOLOv8检测核心代码 ```python from ultralytics import YOLO model = YOLO('yolov8n.pt') results = model.predict(source=left_img, classes=0) # 假设0为车牌类别 boxes = results[0].boxes.xyxy.cpu().numpy() ``` 2. 视差计算(OpenCV实现) ```python stereo = cv2.StereoSGBM_create( minDisparity=0, numDisparities=64, blockSize=11 ) disparity = stereo.compute(left_gray, right_gray).astype(np.float32)/16.0 ``` 3. 深度计算函数 ```python def calc_depth(disparity, focal_length, baseline): with np.errstate(divide='ignore'): # 避免除零警告 depth = (focal_length * baseline) / (disparity + 1e-6) return np.where(disparity<=0, 0, depth) ``` 四、优化建议 1. 相机标定必须完成以下参数计算: - 相机矩阵:$$K = \begin{bmatrix}f_x & 0 & c_x\\ 0 & f_y & c_y\\ 0 & 0 & 1\end{bmatrix}$$ - 畸变系数 - 双目标定外参 2. 实际应用挑战: - 光照变化:建议使用CLAHE直方图均衡 - 遮挡处理:加入跟踪算法(如DeepSORT) - 倾斜车牌:集成透视变换矫正模块 五、依赖库 ```text ultralytics>=8.0.0 opencv-contrib-python>=4.5.0 numpy ``` 注意:完整实现需要约2000+行代码,建议分模块开发。建议测试距离误差控制在5%以内时,双目基线距应大于15cm,分辨率不低于1280×720。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱吃肉的鹏

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

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

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

打赏作者

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

抵扣说明:

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

余额充值