经典目标检测YOLO系列(二)YOLOV2的复现(1)总体网络架构及前向推理过程

经典目标检测YOLO系列(二)YOLOV2的复现(1)总体网络架构及前向推理过程

和之前实现的YOLOv1一样,根据《YOLO目标检测》(ISBN:9787115627094)一书,在不脱离YOLOv2的大部分核心理念的前提下,重构一款较新的YOLOv2检测器,来对YOLOV2有更加深刻的认识。

书中源码连接: RT-ODLab: YOLO Tutorial

对比原始YOLOV2网络,主要改进点如下:

  • 添加了后续YOLO中使用的neck,即SPPF模块

  • 使用普遍用在RetinaNet、FCOS、YOLOX等通用目标检测网络中的解耦检测头(Decoupled head)

  • 修改损失函数,分类分支替换为BCE loss,回归分支替换为GIou loss。

  • 由基于边界框的正样本匹配策略,改为基于先验框的正样本匹配策略。

对比之前实现的YOLOV1网络,主要改进点:

  • 主干网络由ResNet18改为DarkNet19

  • 添加先验框机制

  • 正样本匹配策略改为:基于先验框的正样本匹配策略

  • YOLOv2代码和之前实现的YOLOv1相比,修改之处不多,建议先看之前实现的YOLOv1的相关文章。

1、YOLOv2网络架构

1.1 DarkNet19主干网络

  • 使用原版YOLOv2中提出的DarkNet19作为主干网络(backbone)。
  • 不同于分类网络,我们去掉网络中的平均池化层以及分类层。DarkNet19网络的下采样倍数为32,一张图片(416×416×3)经过主干网络,得到13×13×1024的特征图。
  • 根据官方的做法,DarkNet19需要现在ImageNet数据集上进行预训练。不过,作者提供了DarkNet19在ImageNet数据集上的预训练权重,因此,我们只需要直接加载即可。
  • 这里我们不去实现原版YOLOv2中的passthrough层,仅仅输出一个尺度,即c5层。

在这里插入图片描述

# RT-ODLab/models/detectors/yolov2/yolov2_backbone.pyimport torch
import torch.nn as nnmodel_urls = {"darknet19": "https://github.com/yjh0410/image_classification_pytorch/releases/download/weight/darknet19.pth",
}__all__ = ['DarkNet19']# --------------------- Basic Module -----------------------
class Conv_BN_LeakyReLU(nn.Module):def __init__(self, in_channels, out_channels, ksize, padding=0, stride=1, dilation=1):super(Conv_BN_LeakyReLU, self).__init__()self.convs = nn.Sequential(nn.Conv2d(in_channels, out_channels, ksize, padding=padding, stride=stride, dilation=dilation),nn.BatchNorm2d(out_channels),nn.LeakyReLU(0.1, inplace=True))def forward(self, x):return self.convs(x)# --------------------- DarkNet-19 -----------------------
class DarkNet19(nn.Module):def __init__(self):super(DarkNet19, self).__init__()# backbone network : DarkNet-19# output : stride = 2, c = 32self.conv_1 = nn.Sequential(Conv_BN_LeakyReLU(3, 32, 3, 1),nn.MaxPool2d((2,2), 2),)# output : stride = 4, c = 64self.conv_2 = nn.Sequential(Conv_BN_LeakyReLU(32, 64, 3, 1),nn.MaxPool2d((2,2), 2))# output : stride = 8, c = 128self.conv_3 = nn.Sequential(Conv_BN_LeakyReLU(64, 128, 3, 1),Conv_BN_LeakyReLU(128, 64, 1),Conv_BN_LeakyReLU(64, 128, 3, 1),nn.MaxPool2d((2,2), 2))# output : stride = 8, c = 256self.conv_4 = nn.Sequential(Conv_BN_LeakyReLU(128, 256, 3, 1),Conv_BN_LeakyReLU(256, 128, 1),Conv_BN_LeakyReLU(128, 256, 3, 1),)# output : stride = 16, c = 512self.maxpool_4 = nn.MaxPool2d((2, 2), 2)self.conv_5 = nn.Sequential(Conv_BN_LeakyReLU(256, 512, 3, 1),Conv_BN_LeakyReLU(512, 256, 1),Conv_BN_LeakyReLU(256, 512, 3, 1),Conv_BN_LeakyReLU(512, 256, 1),Conv_BN_LeakyReLU(256, 512, 3, 1),)# output : stride = 32, c = 1024self.maxpool_5 = nn.MaxPool2d((2, 2), 2)self.conv_6 = nn.Sequential(Conv_BN_LeakyReLU(512, 1024, 3, 1),Conv_BN_LeakyReLU(1024, 512, 1),Conv_BN_LeakyReLU(512, 1024, 3, 1),Conv_BN_LeakyReLU(1024, 512, 1),Conv_BN_LeakyReLU(512, 1024, 3, 1))def forward(self, x):c1 = self.conv_1(x)                    # c1c2 = self.conv_2(c1)                   # c2c3 = self.conv_3(c2)                   # c3c3 = self.conv_4(c3)                   # c3c4 = self.conv_5(self.maxpool_4(c3))   # c4c5 = self.conv_6(self.maxpool_5(c4))   # c5return c5# --------------------- Fsnctions -----------------------
def build_backbone(model_name='darknet19', pretrained=False):if model_name == 'darknet19':# modelmodel = DarkNet19()feat_dim = 1024# load weightif pretrained:print('Loading pretrained weight ...')url = model_urls['darknet19']# checkpoint state dictcheckpoint_state_dict = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)# model state dictmodel_state_dict = model.state_dict()# checkfor k in list(checkpoint_state_dict.keys()):if k in model_state_dict:shape_model = tuple(model_state_dict[k].shape)shape_checkpoint = tuple(checkpoint_state_dict[k].shape)if shape_model != shape_checkpoint:checkpoint_state_dict.pop(k)else:checkpoint_state_dict.pop(k)print(k)model.load_state_dict(checkpoint_state_dict)return model, feat_dimif __name__ == '__main__':import timemodel, feat_dim = build_backbone(pretrained=True)x = torch.randn(1, 3, 416, 416)t0 = time.time()for layer in model.children():x = layer(x)print(layer.__class__.__name__, 'output shape:', x.shape)# y = model(x)t1 = time.time()print('Time: ', t1 - t0)

1.2 添加neck

  • 和之前实现的YOLOv1一致,选择YOLOV5版本中所用的SPPF模块。
  • 代码在RT-ODLab/models/detectors/yolov2/yolov2_neck.py文件中,不在赘述。

在这里插入图片描述

1.3 Detection Head网络

  • 和之前实现的YOLOv1一致,即使用解耦检测头(Decoupled head)。
  • 代码在RT-ODLab/models/detectors/yolov2/yolov1_head.py文件中,不在赘述。

在这里插入图片描述

1.4 预测层

  • 如下图,由于预测层多了先验框,因此预测层的输出通道的数量略有变化。

在这里插入图片描述

        ## 预测层# 与YoloV1相比,YoloV2每个网格会预测5个框(VOC数据集),因此需×5self.obj_pred = nn.Conv2d(head_dim, 1 * self.num_anchors, kernel_size=1)self.cls_pred = nn.Conv2d(head_dim, num_classes * self.num_anchors, kernel_size=1)self.reg_pred = nn.Conv2d(head_dim, 4 * self.num_anchors, kernel_size=1)

1.5 改进YOLOv2的详细网络图

  • 与之前实现的YOLOv1相比,主干网络由ResNet18变为DarkNet19,每个网格预测5个anchor box,其他方面一致。
  • 与原版的YOLOv2相比,做了更加符合当下的设计理念的修改,包括添加Neck模块、修改检测头等,但是没有引入passthrough层。
  • 尽管和原版的YOLOv2有所差别,但内核思想是一致的,均是在YOLOv1的单级检测架构上引入了先验框。

在这里插入图片描述

# RT-ODLab/models/detectors/yolov2/yolov2.pyimport torch
import torch.nn as nn
import numpy as npfrom utils.misc import multiclass_nmsfrom .yolov2_backbone import build_backbone
from .yolov2_neck import build_neck
from .yolov2_head import build_head# YOLOv2
class YOLOv2(nn.Module):def __init__(self,cfg,device,num_classes=20,conf_thresh=0.01,nms_thresh=0.5,topk=100,trainable=False,deploy=False,nms_class_agnostic=False):super(YOLOv2, self).__init__()# ------------------- Basic parameters -------------------self.cfg = cfg                                 # 模型配置文件self.device = device                           # cuda或者是cpuself.num_classes = num_classes                 # 类别的数量self.trainable = trainable                     # 训练的标记self.conf_thresh = conf_thresh                 # 得分阈值self.nms_thresh = nms_thresh                   # NMS阈值self.topk = topk                               # topkself.stride = 32                               # 网络的最大步长self.deploy = deployself.nms_class_agnostic = nms_class_agnostic# ------------------- Anchor box -------------------self.anchor_size = torch.as_tensor(cfg['anchor_size']).float().view(-1, 2) # [A, 2]self.num_anchors = self.anchor_size.shape[0]# ------------------- Network Structure -------------------## 主干网络self.backbone, feat_dim = build_backbone(cfg['backbone'], trainable&cfg['pretrained'])## 颈部网络self.neck = build_neck(cfg, feat_dim, out_dim=512)head_dim = self.neck.out_dim## 检测头self.head = build_head(cfg, head_dim, head_dim, num_classes)## 预测层# 与YoloV1相比,YoloV2每个网格会预测5个框(VOC数据集),因此需×5self.obj_pred = nn.Conv2d(head_dim, 1 * self.num_anchors, kernel_size=1)self.cls_pred = nn.Conv2d(head_dim, num_classes * self.num_anchors, kernel_size=1)self.reg_pred = nn.Conv2d(head_dim, 4 * self.num_anchors, kernel_size=1)if self.trainable:self.init_bias()def init_bias(self):# init biasinit_prob = 0.01bias_value = -torch.log(torch.tensor((1. - init_prob) / init_prob))nn.init.constant_(self.obj_pred.bias, bias_value)nn.init.constant_(self.cls_pred.bias, bias_value)def generate_anchors(self, fmp_size):passdef decode_boxes(self, anchors, reg_pred):passdef postprocess(self, obj_pred, cls_pred, reg_pred, anchors):"""后处理代码,包括topk操作、阈值筛选和非极大值抑制"""pass@torch.no_grad()def inference(self, x):bs = x.shape[0]# 主干网络feat = self.backbone(x)# 颈部网络feat = self.neck(feat)# 检测头cls_feat, reg_feat = self.head(feat)# 预测层obj_pred = self.obj_pred(reg_feat)cls_pred = self.cls_pred(cls_feat)reg_pred = self.reg_pred(reg_feat)fmp_size = obj_pred.shape[-2:]# anchors: [M, 2]anchors = self.generate_anchors(fmp_size)# 对 pred 的size做一些view调整,便于后续的处理# [B, A*C, H, W] -> [B, H, W, A*C] -> [B, H*W*A, C]obj_pred = obj_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, 1)                 # [1, 845=13×13×5, 1]cls_pred = cls_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, self.num_classes)reg_pred = reg_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, 4)# 测试时,默认batch是1,# 因此,我们不需要用batch这个维度,用[0]将其取走。obj_pred = obj_pred[0]       # [H*W*A, 1]cls_pred = cls_pred[0]       # [H*W*A, NC]reg_pred = reg_pred[0]       # [H*W*A, 4]if self.deploy:scores = torch.sqrt(obj_pred.sigmoid() * cls_pred.sigmoid())bboxes = self.decode_boxes(anchors, reg_pred)# [n_anchors_all, 4 + C]outputs = torch.cat([bboxes, scores], dim=-1)return outputselse:# post processbboxes, scores, labels = self.postprocess(obj_pred, cls_pred, reg_pred, anchors)return bboxes, scores, labelsdef forward(self, x):if not self.trainable:return self.inference(x)else:bs = x.shape[0]# 主干网络feat = self.backbone(x)# 颈部网络feat = self.neck(feat)# 检测头cls_feat, reg_feat = self.head(feat)# 预测层obj_pred = self.obj_pred(reg_feat)cls_pred = self.cls_pred(cls_feat)reg_pred = self.reg_pred(reg_feat)fmp_size = obj_pred.shape[-2:]# A就是Anchor的数量,VOC数据集上设置为5# anchors: [M, 2], M = H*W*Aanchors = self.generate_anchors(fmp_size)# 对 pred 的size做一些view调整,便于后续的处理# [B, A*C, H, W] -> [B, H, W, A*C] -> [B, H*W*A, C]obj_pred = obj_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, 1)cls_pred = cls_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, self.num_classes)reg_pred = reg_pred.permute(0, 2, 3, 1).contiguous().view(bs, -1, 4)# decode bboxbox_pred = self.decode_boxes(anchors, reg_pred)# 网络输出outputs = {"pred_obj": obj_pred,                   # (Tensor) [B, M, 1]"pred_cls": cls_pred,                   # (Tensor) [B, M, C]"pred_box": box_pred,                   # (Tensor) [B, M, 4]"stride": self.stride,                  # (Int)"fmp_size": fmp_size                    # (List) [fmp_h, fmp_w]}           return outputs

2、YOLOV2的前向推理

在1.5代码中,还遗留几个问题:

  1. 如何从边界框偏移量reg_pred解耦出边界框坐标box_pred?
  2. 如何实现后处理操作?
  3. 如何计算训练阶段的损失?

2.1 解耦边界框坐标

2.1.1 先验框矩阵的生成

YOLOv2网络配置参数如下,我们从中能看到anchor_size变量。这是基于kmeans聚类,在COCO数据集上聚类出的先验框,由于COCO数据集更大、图片更加丰富,因此我们将这几个先验框用在VOC数据集上。

# RT-ODLab/config/model_config/yolov2_config.py
# YOLOv2 Configyolov2_cfg = {# input'trans_type': 'ssd','multi_scale': [0.5, 1.5],# model'backbone': 'darknet19','pretrained': True,'stride': 32,  # P5'max_stride': 32,# neck'neck': 'sppf','expand_ratio': 0.5,'pooling_size': 5,'neck_act': 'lrelu','neck_norm': 'BN','neck_depthwise': False,# head'head': 'decoupled_head','head_act': 'lrelu','head_norm': 'BN','num_cls_head': 2,'num_reg_head': 2,'head_depthwise': False,'anchor_size': [[17,  25],[55,  75],[92,  206],[202, 21],[289, 311]],  # 416# matcher'iou_thresh': 0.5,# loss weight'loss_obj_weight': 1.0,'loss_cls_weight': 1.0,'loss_box_weight': 5.0,# training configuration'trainer_type': 'yolov8',
}
  • 回想一下,在之前实现的YOLOv1中,我们通过构造矩阵G,得到了每一个网格(grid_x,grid_y)的坐标。

  • 由于我们在YOLOv2中引入了先验框,因此,我们不仅需要每一个网格(grid_x,grid_y)的坐标,还要包含先验框(5个)的尺寸信息。

  • 先验框矩阵生成代码如下

    # RT-ODLab/models/detectors/yolov2/yolov2.pydef generate_anchors(self, fmp_size):"""fmp_size: (List) [H, W]默认缩放后的图像为416×416,那么经过32倍下采样后,fmp_size为13×13"""# 1、特征图的宽和高fmp_w, fmp_h  = fmp_size# 2、生成网格的x坐标和y坐标anchor_y, anchor_x = torch.meshgrid([torch.arange(fmp_h), torch.arange(fmp_w)])# 3、将xy两部分的坐标拼接起来,shape为[H, W, 2]#    再转换下, shape变为[HW, 2]anchor_xy = torch.stack([anchor_x, anchor_y], dim=-1).float().view(-1, 2)# 4、引入了anchor box机制,每个网格包含A个anchor,因此每个(grid_x, grid_y)的坐标需要复制A(Anchor nums)份# 相当于  每个网格左上角的坐标点复制5份  作为5个不同宽高anchor box的中心点# [HW, 2] -> [HW, A, 2] -> [M, 2]anchor_xy = anchor_xy.unsqueeze(1).repeat(1, self.num_anchors, 1)anchor_xy = anchor_xy.view(-1, 2).to(self.device)# 5、将kmeans聚类得出的5组anchor box的宽高复制13×13份# [A, 2] -> [1, A, 2] -> [HW, A, 2] -> [M, 2]anchor_wh = self.anchor_size.unsqueeze(0).repeat(fmp_h*fmp_w, 1, 1)anchor_wh = anchor_wh.view(-1, 2).to(self.device)# 6、将中心点和宽高cat起来,得到的shape为[M, 4]# 其中M=13×13×5 表示feature map为13×13,每个网格有5组anchor box# 4代表anchor box的位置(x_center, y_center, w, h)# 需要注意:#     x_center, y_center是feature map上的坐标位置,需要×stride  才能得到缩放后原始图像上的中心点#     w, h是针对缩放后原始图像anchors = torch.cat([anchor_xy, anchor_wh], dim=-1)return anchors

2.1.2 解耦边界框

  • 生成先验框矩阵后,我们就能通过边界框偏移量reg_pred解耦出边界框坐标box_pred》。
  • 计算预测边界框的中心点坐标与之前计算YOLOv1是一致的,但是计算宽高发生了变化。这是因为YOLOv2中,我们引入了先验框,而且我们先验框的尺寸设定是相对于resize后图像大小,因此不需要乘stride。
    def decode_boxes(self, anchors, reg_pred):"""1、依据预测值reg_pred(t_x,t_y,t_w,t_h)结算出边界框中心点坐标c_x, c_y和宽高b_w, b_hc_x = ( grid_x + sigmoid(t_x) ) × stridec_y = ( grid_y + sigmoid(t_y) ) × strideb_w = p_w × exp(t_w)b_h = p_h × exp(t_h)其中   grid_x,grid_y,p_w,p_h为先验框的结果,即anchors结果2、转换为常用的x1y1x2y2形式。注意:预测的宽高不是相对于feature map的,而是相对于resize后图像大小,因此不需要×stride"""# 1、计算预测边界框的中心点坐标和宽高pred_ctr = (anchors[..., :2] + torch.sigmoid(reg_pred[..., :2])) * self.stridepred_wh = anchors[..., 2:] * torch.exp(reg_pred[..., 2:]) # 不需要×stride# 2、将所有bbox的中心点坐标和宽高换算成x1y1x2y2形式pred_x1y1 = pred_ctr - pred_wh * 0.5pred_x2y2 = pred_ctr + pred_wh * 0.5pred_box = torch.cat([pred_x1y1, pred_x2y2], dim=-1)return pred_box

2.2 后处理操作

  • 之前YOLOv1的后处理操作,仅仅包含了阈值筛选和非极大值抑制NMS,这里由于引入了先验框,因此我们后处理的框的数量由之前的13×13变成了13×13×5(845)个。
  • 这845个框不都是高质量的,因此我们先做一个topk,依据得分从高到低取前k个。对于COCO数据集来说,一张图片的目标数量不超过100,因此一般只需要设定topk=100。这里,作者为了提高测试的mAP,默认设置topk=1000。
  • topk操作后,继续进行阈值筛选和非极大值抑制。
    # RT-ODLab/models/detectors/yolov2/yolov2.pydef postprocess(self, obj_pred, cls_pred, reg_pred, anchors):"""后处理代码,包括topk操作、阈值筛选和非极大值抑制1、topk操作:在coco数据集中,检测对象的数量一半不会超过100,因此先选择得分最高的k个边界框,这里为了取得更高的mAP,取k=1000在实际的场景中,不需要把k值取这么大2、滤掉低得分(边界框的score低于给定的阈值)的预测边界框;3、滤掉那些针对同一目标的冗余检测。Input:obj_pred: (Tensor) [H*W*A, 1]cls_pred: (Tensor) [H*W*A, C]reg_pred: (Tensor) [H*W*A, 4]anchors:  (Tensor) [H*W*A, 4]其中,H*W*A = 13×13×5 = 845"""# (H x W x A x C,)# 13×13×5×20 = 16900scores = torch.sqrt(obj_pred.sigmoid() * cls_pred.sigmoid()).flatten()# 1、topk操作# Keep top k top scoring indices only.num_topk = min(self.topk, reg_pred.size(0))# torch.sort is actually faster than .topk (at least on GPUs)predicted_prob, topk_idxs = scores.sort(descending=True)topk_scores = predicted_prob[:num_topk]topk_idxs = topk_idxs[:num_topk]# 2、滤掉低得分(边界框的score低于给定的阈值)的预测边界框# filter out the proposals with low confidence scorekeep_idxs = topk_scores > self.conf_threshscores = topk_scores[keep_idxs]topk_idxs = topk_idxs[keep_idxs]# 获取flatten之前topk_scores所在的idx以及相应的labelanchor_idxs = torch.div(topk_idxs, self.num_classes, rounding_mode='floor')  # 获取labels = topk_idxs % self.num_classesreg_pred = reg_pred[anchor_idxs]anchors = anchors[anchor_idxs]# 解算边界框, 并归一化边界框: [H*W*A, 4]bboxes = self.decode_boxes(anchors, reg_pred)# to cpu & numpyscores = scores.cpu().numpy()labels = labels.cpu().numpy()bboxes = bboxes.cpu().numpy()#  3、滤掉那些针对同一目标的冗余检测。# nmsscores, labels, bboxes = multiclass_nms(scores, labels, bboxes, self.nms_thresh, self.num_classes, self.nms_class_agnostic)return bboxes, scores, labels

接下来,就到了正样本的匹配和损失函数计算了。

  • 原版YOLOv2会先解耦出边界框,计算边界框和目标框的IoU,只有IoU最大的才被标记为正样本,用来计算置信度损失、类别损失以及边界框位置损失,其他预测的边界框均为负样本,仅仅计算置信度损失。
  • 这样,先验框没有为正样本匹配带来直接影响,仅仅被用于解算边界框的坐标。
  • 既然先验框有边界框的先验尺寸信息,那么它可以直接参与正样本的匹配,因此我们接下来采用当下更加常用的策略来发挥先验框在标签匹配中的作用,即基于先验框的正样本匹配策略

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s资源介绍

Kubernetes架构图 Kubernetes系统用于管理分布式节点集群中的微服务或容器化应用程序,并且其提供了零停机时间部署、自动回滚、缩放和容器的自愈(其中包括自动配置、自动重启、自动复制的高弹性基础设施,以及容器的自动缩放等)等…

SDL2 连续帧图像显示

QT使用SDL多窗口显示视频(linux,ubuntu)_linux qt sdl-CSDN博客 QT使用SDL播放YUV视频 - C - QT SDL调用OPENGL渲染图像 - C - 心得 C 使用SDL显示RGB图像数据_c sdl-CSDN博客 SDL库入门:掌握跨平台游戏开发和多媒体编程_sdl开…

【Python学习】Python学习21- 正则表达式(1)

目录 【Python学习】Python学习21- 正则表达式(1) 前言re.match函数实例 re.search方法re.match与re.search的区别参考 文章所属专区 Python学习 前言 本章节主要说明Python的正则表达式。 正则表达式是一个特殊的字符序列,它能帮助你方便的…

文心一言使用分享

ChatGPT 和文心一言哪个更好用? 一个直接可以用,一个还需要借助一些工具,还有可能账号会消失…… 没有可比性。 通用大模型用于特定功能的时候需要一些引导技巧。 import math import time def calculate_coordinate(c, d, e, f, g, h,…

Debian 10.13.0 安装图解

引导和开始安装 这里直接回车确认即可,选择图形化安装方式。 选择语言 这里要区分一下,当前选中的语言作为安装过程中安装器所使用的语言,这里我们选择中文简体。不过细心的同学可能发现,当你选择安装器语言之后,后续安…

WebDriverWait太强大

selenium webdriver及wait 1 implicitly包打天下2 Linkedin无法登录返回值很乱,怎么破? 1 implicitly包打天下 有了implicitly之后,基本上不再关注网速之类的影响。 self.driver.implicitly_wait(511)2 Linkedin无法登录返回值很乱&#xf…

【C语言编程之旅 6】刷题篇-for循环

第1题 解析 思路&#xff1a; 两个循环进行控制 外层循环控制打印多少行 内部循环控制每行打印多少个表达式以及表达式内容&#xff0c; 比较简单&#xff0c;具体参考代码 #include <stdio.h> int main() {int i 0;//控制行数for(i1; i<9; i){//打印每一行内容&am…

《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置(20)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置&#xff08;19&#xff09; 2.4 PCI总线的配置 PCI总线定义了两类配置请求&#xff0c;一个是Type 00h配置请求&#xff0c;另一个是Type 01h配置请求。PCI总线使用这些配置请求…

k8s 使用cert-manager证书管理自签

个人建议使用安装更快&#xff0c;比helm快&#xff0c;还要等待安装crd kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml#官网 https://cert-manager.io/docs/installation/kubectl/#创建自签的ClusterIssuer c…

Linux设备管理模型-02:sysfs

文章目录 sysfs1 使用sysfs控制GPIO2 sysfs编程2.1 完善sysfs属性文件的读写操作 上一篇文: 设备管理模型中的基础数据结构 sysfs sysfs是用于导出内核对象的文件系统&#xff0c;它是一个基于ram的文件系统&#xff0c;最初基于ramfs。 sysfs通常挂载在/sys目录下。它提供了一…

【 Qt 快速上手】-②- Qt 环境搭建

文章目录 1. Qt 开发工具概述1.1 Qt Creator 介绍1.2 Visual Studio 介绍1.3 Eclipse 介绍 2. Qt SDK 的下载与安装2.1 Qt SDK 的下载2.2 Qt SDK 的安装2.3 验证 Qt SDK 安装是否成功2.4 Qt 环境变量配置 1. Qt 开发工具概述 Qt 开发环境需要安装三个部分&#xff1a; C编译器…

分布式系统——树状算法

1 树状算法的基本理论 树算法常用于在分布式系统中实现广播操作&#xff0c;这里您提供了一些基本定义和一个引理关于广播操作的消息复杂度和时间复杂度的下界。 1.1 广播定义 广播操作是由单个处理器&#xff08;源节点&#xff09;发起的&#xff0c;其目的是向系统中的所有…

systemverilog/verilog文件操作

1、Verilog文件操作 Verilog具有系统任务和功能,可以打开文件、将值输出到文件、从文件中读取值并加载到其他变量和关闭文件。 1.1 、Verilog文件操作 1.1.1、打开和关闭文件 module tb; // 声明一个变量存储 file handler integer fd; initial begin // 以写权限打开一个文…

互联网加竞赛 基于机器视觉的手势检测和识别算法

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的手势检测与识别算法 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng…

【react】创建react项目+项目结构

使用create-react-app快速搭建开发环境 create-react-app是一个快速创建React开发环境的工具&#xff0c;底层由Webpack构建&#xff0c;封装了配置细节 npx create-react-app react_hm执行命令后开始创建 创建好执行cd react_hm npm start 当看到webpack compiled successfu…

VMware workstation安装FreeBSD14.0虚拟机并配置网络

VMware workstation安装FreeBSD14.0虚拟机并配置网络 FreeBSD是类UNIX操作系统&#xff0c;FreeBSD带有多个软件包&#xff0c;并覆盖了广阔的应用领域&#xff0c;且都是免费和易于安装的。该文档适用于在VMware workstation平台安装FreeBSD14.0虚拟机。 1.安装准备 1.1安装…

【c++笔记】用c++解决一系列质数问题!

质数是c语言和c中比较常见的数学问题&#xff0c;本篇文章将带你走进有关质数的一系列基础问题&#xff0c;其中包含常见的思路总结&#xff0c;本篇文章过后&#xff0c;将会持续更新c算法系列&#xff0c;感兴趣的话麻烦点个关注吧&#xff01; 希望能给您带来帮助&#xff…

openjdk源码了解

openjdk给出debug配置选项&#xff0c;common/autoconf/jdk-options.m4 AC_DEFUN_ONCE([JDKOPT_SETUP_DEBUG_LEVEL], [################################################################################# Set the debug level# release: no debug information, all opti…

Python项目——计算器(PySide6+Pyinstaller)

1、介绍 使用python编写一个计算器&#xff0c;可以实现基本的运算。【注】该项目最终还有一些细小的bug没有完善&#xff0c;例如符号可以一直输入。 2、实现 使用pyCharm创建一个新的项目。 2.1、设计UI 使用Qt designer设计一个UI界面&#xff0c;保存ui文件&#xff0…

从新手到高手:AI绘画实战中的Midjourney

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 随着人工智能&#xff08;AI&#xff09;技术的…