【目标检测】YOLOv9理论解读与代码分析

前言

YOLO这个系列的故事已经很完备了,比如一些Decoupled-Head或者Anchor-Free等大的策略改动已经在YOLOv8固定下来,后面已经估计只有拿一些即插即用的tricks进行小改。

mmdetection框架的作者深度眸也在知乎上对“是否会有YOLOv9”这一观点发表看法:
在这里插入图片描述
然而,yolov9却还是在今年2月出来了,一作是中国台湾Academia Sinica的Chien-Yao Wang,和YOLOv4,v7是同一作者。

YOLOv9论文:https://arxiv.org/abs/2402.13616
YOLOv9仓库:https://github.com/WongKinYiu/yolov9

v9的改动和v8差别比较大,并不是在v8的基础上进行改进的,而是在作者之前的工作v7的基础上进行进一步改进。因此,要理解v9的相关理论,需要对v4和v7做一些回顾。

研究背景

YOLOv9的核心问题聚焦于多数方法都忽略了输入数据在前馈过程中可能具有不可忽略的信息损失量。

作者用下面一组图来说明该问题,下面是不同模型在深度空间中高维特征的可视化,以往的算法会造成信息丢失,导致可视化出现失真。
在这里插入图片描述
该篇论文,作者主要提出两点贡献:

  • 1.可编程梯度信息(PGI):通过辅助可逆分支生成可靠梯度,以便深层特征仍然可以保持足够的信息量。
  • 2.基于ELAN设计了广义ELAN(GELAN):在计算块选择上更加自由。

PGI:Programmable Gradient Information

可编程梯度信息:Programmable Gradient Information(PGI)这个概念乍一看有点拗口,看了不少解读论文,对此概念也只停留在论文的翻译,这里谈谈我的理解。

首先来回溯一下YOLOv7的这篇论文[1],文中已经提及辅助训练的概念,如下图所示,图a是普通模型的一个正常检测输出流程,图b是在图a的基础上,在网络浅层特征直接引出Auxiliary head,损失只是根据浅层网络的特征来进行计算,这样有助于网络利用深层特征做损失的信息损失。
在这里插入图片描述
理解这一点后,再看YOLOv9里面的这张图:
在这里插入图片描述
这张图里放了四种架构:

  • 图a是一个普通的PAN,和v7中的Normal model一样
  • 图b是RevCol,其改动是在网络浅层旧加入一些本来只作用于深层的neck,用来储存浅层的特征信息,不过问题也很明显,内存开销太大(Heavy Cost)
  • 图c是深度监督(Deep Supervision),其思想是在网络浅层旧单独Copy一个检测头,这和v7中的图b思想一样。
  • 图d就是yolov9提出的pgi思想,想法挺简单,一方面是继续保留Deep Supervision的设计,在浅层就搞一个检测头,另一个方面是单开一路,将原图单独塞入一个辅助可逆训练分支(Auxiliary Reversible Branch),这其实类似于copy了一个主分支的backbone,蓝色的是原始的主分支,在主分支做neck部分的时候,一方面在浅层就直接做一个检测头,令一方面和原始一样,到深层再去检测。

因此,pgi并不是一种特定的网络结构,而是一种辅助训练的思想,它可以根据不同的网络特点进行结合,是自由的,并且参数可以随训练不断进行调整,按作者的话说就是可编程(这就是包装的艺术!)

由于是辅助训练,因此模型在训练阶段的参数量会变大很多,但对推理阶段并不会造成影响,推理仍使用主分支,因此对推理的速度影响不大。

GELAN: Generalized Efficient Layer Aggregation Network

GELAN这项工作并不是为了解决论文着眼的深度模型信息缺失的问题,而是对PGI的补充和优化。由于PGI的策略会导致网络过于庞大,计算成本过高,因此引入GELAN,用来缓解计算量。

对于GELAN的解读,还是需要重新追溯到YOLOv4,在YOLOv4中,作者采用了之前提到一种CSP(Cross Stage Partial)的架构思想,如下图所示。
在这里插入图片描述
图a是一个DenseNet架构,图b是CSPDenseNet的架构,从图中不难看出,CSP的思想就是将网络的特征图拆成两部分,一部分进入原始网络中做特征提取等操作,另一部分直接Concat到第一部分的输出。

YOLOv4就是将CSP思想应用到v3提出的Darknet中,变成CSPDarknet。下面是YOLOv5中对CSP部分的实现,其中不难看出,在实际使用中,通过两个卷积实现对特征图通道的对半拆分。

import torch
import torch.nn as nn


def autopad(k, p=None):  # kernel, padding
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p


class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))


class Bottleneck(nn.Module):
    # Standard bottleneck
    def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion
        super(Bottleneck, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))


class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super(BottleneckCSP, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        self.act = nn.LeakyReLU(0.1, inplace=True)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))
        
bcsp = BottleneckCSP(1,2)
print(bcsp)

在CSP之后,ELAN这项工作在此基础将该架构进一步发展。在YOLOv7[3]中,也曾对ELAN这个架构进行拓展,提出了一个拓展ELAN(E-ELAN),如下图所示:

在这里插入图片描述

如图c所示,ELAN的特点是跨层聚合,即同时聚合浅层特征和深层特征,主要是为了解决模型深度加深时梯度消失的问题,同时也提升特征的利用效率。

在这里插入图片描述
YOLOv9提出的GELAN并没有对ELAN做特别大的改动,只是将原本固定的一系列卷积层(conv)换成任意的Block(any block),作者说这样可以降低模型复杂度,提升准确率和推理速度,个人觉得这一点其实挺牵强的…。

代码解读

YOLOv9这篇论文实际上可以拆分成两项工作,PGI和GELAN。因此,在代码方面,作者也进行了一系列拆分,整体框架仍然是采用v5那套,因此训练的方式和数据组织结构和v5通用。

这个仓库里面包含了两套模型,gelan和yolov9,yolov9等价于gelan+pgi,从作者给出的测试效果图来看,yolov9的数值明显要比gelan高出一点,因此,在使用yolov9代码时,完全可以忽略gelan。

在这里插入图片描述
作者在这里给了三个train和三个val,对应功能如下:

  • train.py :训练GELAN模型
  • train_dual.py:训练带有一个辅助训练分支的GELAN模型
  • train_triple.py:训练带有2个辅助训练分支的GELAN模型
  • val、val_dual、val_triple三者功能对应上述三个train

根据YOLOv9的论文所述,YOLOv9模型是GELAN+1个辅助训练分支,因此训练和验证v9模型就使用train_dual.pyval_dual.py

按照论文所述,YOLOv9共分四个版本,从小到大依次为小型(yolov9-s)、中型(yolov9-m)、紧凑型(yolov9-c)、扩展型(yolov9-e),截至目前,该仓库只开源了后两者型号。

至于train_triple,估计是作者实验性的代码,论文里也未提到两个辅助检测分支的实验效果,效果估计不会有太大的提升。

另外,仓库里还有一些实验性的文件和yolov9无关,是作者令一项最新工作:YOLOR-Based Multi-Task Learning,这篇工作是想通过多个不同的任务,比如目标检测、实例分割、语义分割和图像描述来相互促进,这部分内容对应panoptic,不过目前这部分内容并不成熟,作者也没给出对应的数据集组织形式,使用时略过即可。

下面看一些除网络结构外的代码细节,比如,yolov9在辅助训练部分,加了一组检测头,相当于共有6个检测头,此代码对应DualDDetect

class DualDDetect(nn.Module):
    # YOLO Detect head for detection models
    dynamic = False  # force grid reconstruction
    export = False  # export mode
    shape = None
    anchors = torch.empty(0)  # init
    strides = torch.empty(0)  # init

    def __init__(self, nc=80, ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch) // 2  # number of detection layers
        self.reg_max = 16
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)
        self.stride = torch.zeros(self.nl)  # strides computed during build

        c2, c3 = make_divisible(max((ch[0] // 4, self.reg_max * 4, 16)), 4), max((ch[0], min((self.nc * 2, 128))))  # channels
        c4, c5 = make_divisible(max((ch[self.nl] // 4, self.reg_max * 4, 16)), 4), max((ch[self.nl], min((self.nc * 2, 128))))  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3, g=4), nn.Conv2d(c2, 4 * self.reg_max, 1, groups=4)) for x in ch[:self.nl])
        self.cv3 = nn.ModuleList(
            nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch[:self.nl])
        self.cv4 = nn.ModuleList(
            nn.Sequential(Conv(x, c4, 3), Conv(c4, c4, 3, g=4), nn.Conv2d(c4, 4 * self.reg_max, 1, groups=4)) for x in ch[self.nl:])
        self.cv5 = nn.ModuleList(
            nn.Sequential(Conv(x, c5, 3), Conv(c5, c5, 3), nn.Conv2d(c5, self.nc, 1)) for x in ch[self.nl:])
        self.dfl = DFL(self.reg_max)
        self.dfl2 = DFL(self.reg_max)

    def forward(self, x):
        shape = x[0].shape  # BCHW
        d1 = []
        d2 = []
        for i in range(self.nl):
            d1.append(torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1))
            d2.append(torch.cat((self.cv4[i](x[self.nl+i]), self.cv5[i](x[self.nl+i])), 1))
        if self.training:
            return [d1, d2]
        elif self.dynamic or self.shape != shape:
            self.anchors, self.strides = (d1.transpose(0, 1) for d1 in make_anchors(d1, self.stride, 0.5))
            self.shape = shape

        box, cls = torch.cat([di.view(shape[0], self.no, -1) for di in d1], 2).split((self.reg_max * 4, self.nc), 1)
        dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
        box2, cls2 = torch.cat([di.view(shape[0], self.no, -1) for di in d2], 2).split((self.reg_max * 4, self.nc), 1)
        dbox2 = dist2bbox(self.dfl2(box2), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
        y = [torch.cat((dbox, cls.sigmoid()), 1), torch.cat((dbox2, cls2.sigmoid()), 1)]
        return y if self.export else (y, [d1, d2])

相比于修改之前的Detect,该新类最终输出两个检测头的结果,即d1和d2,之后做损失时,对应做两个检测头的损失:

class ComputeLoss:
    # Compute losses
    def __init__(self, model, use_dfl=True):
        device = next(model.parameters()).device  # get model device
        h = model.hyp  # hyperparameters

        # Define criteria
        BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h["cls_pw"]], device=device), reduction='none')

        # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
        self.cp, self.cn = smooth_BCE(eps=h.get("label_smoothing", 0.0))  # positive, negative BCE targets

        # Focal loss
        g = h["fl_gamma"]  # focal loss gamma
        if g > 0:
            BCEcls = FocalLoss(BCEcls, g)

        m = de_parallel(model).model[-1]  # Detect() module
        self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02])  # P3-P7
        self.BCEcls = BCEcls
        self.hyp = h
        self.stride = m.stride  # model strides
        self.nc = m.nc  # number of classes
        self.nl = m.nl  # number of layers
        self.no = m.no
        self.reg_max = m.reg_max
        self.device = device

        self.assigner = TaskAlignedAssigner(topk=int(os.getenv('YOLOM', 10)),
                                            num_classes=self.nc,
                                            alpha=float(os.getenv('YOLOA', 0.5)),
                                            beta=float(os.getenv('YOLOB', 6.0)))
        self.assigner2 = TaskAlignedAssigner(topk=int(os.getenv('YOLOM', 10)),
                                            num_classes=self.nc,
                                            alpha=float(os.getenv('YOLOA', 0.5)),
                                            beta=float(os.getenv('YOLOB', 6.0)))
        self.bbox_loss = BboxLoss(m.reg_max - 1, use_dfl=use_dfl).to(device)
        self.bbox_loss2 = BboxLoss(m.reg_max - 1, use_dfl=use_dfl).to(device)
        self.proj = torch.arange(m.reg_max).float().to(device)  # / 120.0
        self.use_dfl = use_dfl

    def preprocess(self, targets, batch_size, scale_tensor):
        if targets.shape[0] == 0:
            out = torch.zeros(batch_size, 0, 5, device=self.device)
        else:
            i = targets[:, 0]  # image index
            _, counts = i.unique(return_counts=True)
            out = torch.zeros(batch_size, counts.max(), 5, device=self.device)
            for j in range(batch_size):
                matches = i == j
                n = matches.sum()
                if n:
                    out[j, :n] = targets[matches, 1:]
            out[..., 1:5] = xywh2xyxy(out[..., 1:5].mul_(scale_tensor))
        return out

    def bbox_decode(self, anchor_points, pred_dist):
        if self.use_dfl:
            b, a, c = pred_dist.shape  # batch, anchors, channels
            pred_dist = pred_dist.view(b, a, 4, c // 4).softmax(3).matmul(self.proj.type(pred_dist.dtype))
            # pred_dist = pred_dist.view(b, a, c // 4, 4).transpose(2,3).softmax(3).matmul(self.proj.type(pred_dist.dtype))
            # pred_dist = (pred_dist.view(b, a, c // 4, 4).softmax(2) * self.proj.type(pred_dist.dtype).view(1, 1, -1, 1)).sum(2)
        return dist2bbox(pred_dist, anchor_points, xywh=False)

    def __call__(self, p, targets, img=None, epoch=0):
        loss = torch.zeros(3, device=self.device)  # box, cls, dfl
        feats = p[1][0] if isinstance(p, tuple) else p[0]
        feats2 = p[1][1] if isinstance(p, tuple) else p[1]
        
        pred_distri, pred_scores = torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split(
            (self.reg_max * 4, self.nc), 1)
        pred_scores = pred_scores.permute(0, 2, 1).contiguous()
        pred_distri = pred_distri.permute(0, 2, 1).contiguous()
        
        pred_distri2, pred_scores2 = torch.cat([xi.view(feats2[0].shape[0], self.no, -1) for xi in feats2], 2).split(
            (self.reg_max * 4, self.nc), 1)
        pred_scores2 = pred_scores2.permute(0, 2, 1).contiguous()
        pred_distri2 = pred_distri2.permute(0, 2, 1).contiguous()

        dtype = pred_scores.dtype
        batch_size, grid_size = pred_scores.shape[:2]
        imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0]  # image size (h,w)
        anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)

        # targets
        targets = self.preprocess(targets, batch_size, scale_tensor=imgsz[[1, 0, 1, 0]])
        gt_labels, gt_bboxes = targets.split((1, 4), 2)  # cls, xyxy
        mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0)

        # pboxes
        pred_bboxes = self.bbox_decode(anchor_points, pred_distri)  # xyxy, (b, h*w, 4)
        pred_bboxes2 = self.bbox_decode(anchor_points, pred_distri2)  # xyxy, (b, h*w, 4)

        target_labels, target_bboxes, target_scores, fg_mask = self.assigner(
            pred_scores.detach().sigmoid(),
            (pred_bboxes.detach() * stride_tensor).type(gt_bboxes.dtype),
            anchor_points * stride_tensor,
            gt_labels,
            gt_bboxes,
            mask_gt)
        target_labels2, target_bboxes2, target_scores2, fg_mask2 = self.assigner2(
            pred_scores2.detach().sigmoid(),
            (pred_bboxes2.detach() * stride_tensor).type(gt_bboxes.dtype),
            anchor_points * stride_tensor,
            gt_labels,
            gt_bboxes,
            mask_gt)

        target_bboxes /= stride_tensor
        target_scores_sum = max(target_scores.sum(), 1)
        target_bboxes2 /= stride_tensor
        target_scores_sum2 = max(target_scores2.sum(), 1)

        # cls loss
        # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum  # VFL way
        loss[1] = self.BCEcls(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
        loss[1] *= 0.25
        loss[1] += self.BCEcls(pred_scores2, target_scores2.to(dtype)).sum() / target_scores_sum2 # BCE

        # bbox loss
        if fg_mask.sum():
            loss[0], loss[2], iou = self.bbox_loss(pred_distri,
                                                   pred_bboxes,
                                                   anchor_points,
                                                   target_bboxes,
                                                   target_scores,
                                                   target_scores_sum,
                                                   fg_mask)
            loss[0] *= 0.25
            loss[2] *= 0.25
        if fg_mask2.sum():
            loss0_, loss2_, iou2 = self.bbox_loss2(pred_distri2,
                                                   pred_bboxes2,
                                                   anchor_points,
                                                   target_bboxes2,
                                                   target_scores2,
                                                   target_scores_sum2,
                                                   fg_mask2)
            loss[0] += loss0_
            loss[2] += loss2_

        loss[0] *= 7.5  # box gain
        loss[1] *= 0.5  # cls gain
        loss[2] *= 1.5  # dfl gain
        return loss.sum() * batch_size, loss.detach()  # loss(box, cls, dfl)

从这段代码不难看出,这里损失是将三部分损失boxclsdfl损失对应相加,并且对不同类别的损失进行赋权,回归损失会占最主要的部分。

目前我跑了一下yolov9-c这个网络,模型大小约为98M,比起YOLOv5还是比较大的,加入辅助推理分支之后,模型大小比较大可以理解,不过对于部署推理时,应该可以做一些优化,比如把辅助推理的相关权重分支裁剪掉,这部分目前尚未实现。

总结

YOLOv9进一步延续了YOLOv7的工作,对于v7参数量太大的问题做了一些缓解。不过该网络还是比较偏学术性,目前模型的转换部署等均不够成熟,该论文的切入点比较小众,有点另辟蹊径的感觉,作者讲故事的思路和能力确实值得借鉴学习。

参考文献

[1] WANG C Y, BOCHKOVSKIY A, LIAO H Y. YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors[J].
[2] WANG C Y, MARK LIAO H Y, WU Y H, et al. CSPNet: A New Backbone that can Enhance Learning Capability of CNN[C/OL]//2020 IEEE/CVF Conference on Computer Vision and Pattern Recognition Workshops (CVPRW), Seattle, WA, USA. 2020. http://dx.doi.org/10.1109/cvprw50498.2020.00203. DOI:10.1109/cvprw50498.2020.00203.
[3] WANG C Y, BOCHKOVSKIY A, LIAO H Y. YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors[J].

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/480610.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【深入理解 Spring 事务】实现CURD

个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻‍❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…

Flutter动画(一)Ticker、Animate 原理

在任何系统的UI框架中,动画原理都是类似的,即:在一段时间内,快速地多次改变UI外观;由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。 Flutter中对动画进行了抽象,主要涉及 Anim…

在Windows中安装wsl2和ubuntu22.04

目录 一、概述二、安装wsl22.1 虚拟化设置2.2 虚拟化设置2.3 切换和更新wsl2 三、安装ubuntu3.1 下载Ubuntu22.043.2 配置Ubuntu22.04 一、概述 wsl2是一种面向Windows操作系统的虚拟化技术,可以让我们在Windows操作系统中“丝滑”的运行Linux系统。wsl2由微软团队…

时序预测 | Matlab实现BiTCN-GRU双向时间卷积神经网络结合门控循环单元时间序列预测

时序预测 | Matlab实现BiTCN-GRU双向时间卷积神经网络结合门控循环单元时间序列预测 目录 时序预测 | Matlab实现BiTCN-GRU双向时间卷积神经网络结合门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现BiTCN-GRU双向时间卷积神经网络结…

Vue模块化开发步骤—遇到的问题—解决办法

目录 1.npm install webpack -g 2.npm install -g vue/cli-init 3.初始化vue项目 4.启动vue项目 Vscode初建Vue时几个需要注意的问题-CSDN博客 1.npm install webpack -g 全局安装webpack 直接命令提示符运行改指令会报错,operation not permitted 注意&#…

【JavaScript 漫游】【041】File 对象、FileList 对象、FileReader 对象

文章简介 本篇文章为【JavaScript 漫游】专栏的第 041 篇文章,主要对浏览器模型中 File 对象、FileList 对象和 FileReader 对象的知识点进行了简记。 File 对象 File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种…

Microsoft Edge 中的 Internet Explorer 模式解决ie禁止跳转到edge问题

作为网工,网络中存在很老的设备只能用ie浏览器访问打开,但是win10后打开Internet Explorer 会强制跳转到Edge 浏览器,且有人反馈不会关,为此找到了微软官方的Microsoft Edge 中的 Internet Explorer 模式,可以直接在Mi…

在服务器上训练faster-rcnn模型(pycharm和Termius)

本文主要介绍使用服务器训练模型的两种方式:pycharm(可视化界面友好)and终端,本人用的是macos系统,可能pycharm某些入口的位置不一样,本教程代码以faster-rcnn为例 准备工作: 1.知道自己所用服…

【每日一问】IOS手机上Charles证书过期怎么办?

1、如何查看证书是否过期? 设置>通用>VPN与设备管理 2、在Charles中重置证书 步骤1:重置证书 Help>SSL Proxying>Reset Charles Root Certificate… 步骤2:在浏览器中,下载证书 首先,手机连上代理,然…

使用阿里CICD流水线打包Java项目到阿里的docker镜像私仓,并自动部署到服务器启动服务

文章目录 使用阿里CICD流水线打包Java项目到阿里的docker镜像私仓,并自动部署到服务器启动服务1、功能原理实现2、将自己的Java项目通过Git上传到阿里的代码仓库中,也可以通过绑定Gitee或者GitHub账号进行导入3、创建自己的阿里云镜像私仓3、进入阿里的C…

基于Python3的数据结构与算法 - 19 二叉搜索树

目录 一、二叉搜索树 1、二叉搜索树的插入 2、二叉搜索树的查找 3. 二叉搜素树的删除 一、二叉搜索树 二叉搜索树是一颗二叉树且满足性质&#xff1a;设x是二叉树的一个节点。如果y是x左子树的一个节点&#xff0c;那么y.key < x.key ; 如果y是x右子树的一个节点&#…

Rust之构建命令行程序(五):环境变量

开发环境 Windows 11Rust 1.77.0 VS Code 1.87.2 项目工程 这次创建了新的工程minigrep. 使用环境变量 我们将通过添加一个额外的功能来改进minigrep:一个不区分大小写的搜索选项&#xff0c;用户可以通过环境变量打开该选项。我们可以将此功能设置为命令行选项&#xff0c;…

Python模块与包管理使用pip与virtualenv【第151篇—模块与包管理】

Python模块与包管理&#xff1a;使用pip与virtualenv 在Python开发中&#xff0c;模块和包管理是至关重要的&#xff0c;它们使得代码的组织、重用和共享变得更加简单和高效。本文将介绍两个Python生态系统中最常用的工具&#xff1a;pip和virtualenv。通过这些工具&#xff0…

UE5 C++ 3D血条 响应人物受伤 案例

一.3Dwidget 1.创建C Userwidget的 MyHealthWidget&#xff0c;声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…

java中异常类

异常 异常体系继承结构 Throwable类是 Java 语言中所有错误或异常的超类&#xff0c;只有当对象是此类&#xff08;或其子类之一&#xff09;的实例时&#xff0c;才能通过 Java 虚拟机或者 Java throw 语句抛出。     异常是对象&#xff0c;而对象都采用类来定义。异常的…

C语言之strcspn用法实例(八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

使用jupyter-Python进行模拟股票分析

tushare财经数据接口包 pip install tushare作用&#xff1a;提供相关指定的财经数据 需求&#xff1a;股票分析 使用tushare包获取某股票的历史行情数据 输出该股票所有收盘比开盘上涨3%以上的日期 输出该股票所有开盘比前日收盘跌幅超过2%的日期 假如我从2015年1月1日开…

Ubuntu 22.04安装Python3.10.13

Ubuntu最好设置为英文&#xff0c;我之前用中文在make的test的时候&#xff0c;总是会有fail。 查了下有人怀疑是language的问题&#xff0c;保险起见都用英文&#xff0c;个人实践也证明改为英文就不报错了。 issue 44031: test_embed and test_tabnanny fails if the curre…

【算法刷题 | 二叉树 02】3.21 二叉树的层序遍历01(5题:二叉树的层序遍历、层序遍历||、右视图、层平均值,以及N叉树的层序遍历)

文章目录 5.二叉树的层序遍历5.1 102_二叉树的层序遍历5.1.1问题5.1.2解法&#xff1a;队列 5.2 107_二叉树的层序遍历||5.2.1问题5.2.2解法&#xff1a;队列 5.3 199_二叉树的右视图5.3.1问题5.3.2解决&#xff1a;队列 5.4 637_二叉树的层平均值5.4.1问题5.4.2解决&#xff1…

Dell戴尔XPS 12 9250二合一笔记本电脑原装出厂Windows10系统包下载

链接&#xff1a;https://pan.baidu.com/s/1rqUEM_q5DznF0om6eevcwg?pwdvij0 提取码&#xff1a;vij0 戴尔原厂WIN10系统自带所有驱动、出厂主题壁纸、系统属性专属联机支持标志、系统属性专属LOGO标志、Office办公软件、MyDell等预装程序 文件格式&#xff1a;esd/wim/sw…