详细理解(学习笔记) | DETR(整合了Transformer的目标检测框架) DETR入门解读以及Transformer的实操实现

文章目录

  • 一、概述
  • 二、Transformer
    • Transformer的pytorch实现
  • 三、DETR
    • Transformer.py
    • position_encoding.py
    • detr.py
      • class DETR
      • class SetCriterion
      • 分类 loss
      • box loss
  • DETR 在全景分割上的应用(浅看)
  • 最后(个人见解)

一、概述

DETR,全称 DEtection TRansformer,是Facebook提出的基于Transformer的端到端目标检测网络,发表于ECCV2020。

原文:
链接
源码:
链接

DETR 端到端目标检测网络模型,是第一个将Transformer成功整合为检测pipline中心构建块的目标检测框架模型。基于Transformers的端到端目标检测,没有NMS后处理步骤、真正的实现不利用anchor,且其与Faster RCNN相比,超越了后者。

在COCO数据集上,对比效果图如下:

图片来自原文
由上图可以看出,DETR的效果很不错。基于ResNet50的DETR取得了和经过各种精调的Faster-RCNN相当的效果。同时DETR大目标检测上性能是最好的,但是小目标上稍差,而且基于match的loss导致学习很难收敛(即难以学习到最优的情况)。Deformable DETR的出现对这两个问题进行了比较好的改进。

Detection TransformerDETR的新框架的主要组成部分是基于集合的全局损失函数,该损失函数通过二分匹配和transformer编码器-解码器体系结构强制进行唯一的预测。给定一个固定的学习对象查询的小集合,DETR会考虑目标对象与全局图像上下文之间的关系,并直接并行输出最终的预测集合。

与许多其他现代检测器不同,新模型在概念上很简单,并且不需要专门的库。DETR与具有挑战性的COCO对象检测数据集上公认的且高度优化的Faster R-CNN baseline具有同等的准确性和运行时性能。此外,可以很容易地将DETR迁移到其他任务例如全景分割。

Detection Transformer可以预测所有物体的剧烈运动,并通过设置损失函数进行端到端训练,该函数可以在预测的物体与地面真实物体之间进行二分匹配。DETR通过删除多个手工设计的后处理过程例如nms,对先验知识进行编码的组件来简化检测流程。与大多数现有的检测方法不同,DETR不需要任何自定义层,因此可以在包含标准CNN和转换器类的任何框架中轻松复制。

二、Transformer

Transformer,原文,自被提出以来,便迅速得到了广泛地应用.它的核心是注意力机制的叠加使用,使得AI模型有选择地聚焦于输入的某些部分,因此推理更加高效。不仅在NLP领域上有了显著的成果,现在更是被挪用到了CV领域。它本质上仍然是一个Encoder-Decoder的结构,encoder和decoder均是一种Self-Attention模块的多重叠加,通过编码-解码的形式,实现以多重多头注意力学习获取重要特征,然后两相结合实现整合得到图像的“上下文语序信息”,从而更好地进行目标检测,模块结构如下图所示:

在这里插入图片描述
在这里插入图片描述
Encoder模块:
在这里插入图片描述

和传统的序列模型如RNN相比,Transformer主要改进于:

  1. 将RNN变为多个自注意力Self-Attention结构的叠加
  2. 并行计算序列中任意元素相对于其他所有元素的相关性,高效地提取上下文中的相关性,并引入多头注意力Multi-head Attention机制,从多角度提取特征。
  3. 位置编码来描述序列的前后信息,取代了RNN串行的计算过程。

Transformer的pytorch实现

使用pytorch接口展示Transformer的实际用法如下这篇文章
Transformer实战

pytorch 对Transformer的封装是通过 torch.nnTransformer 来实现的,其主要包含以下几个参数:

torch.nn.Transformer(d_model: int = 512,nhead: int = 8,num_encoder_layers: int = 6,num_decoder_layers: int = 6,dim_feedforward: int = 2048,dropout: float = 0.1,activation: str = 'relu',custom_encoder: Optional[Any] = None,custom_decoder: Optional[Any] = None)

其中,
d_model 是 word embedding 的 channel 数,
n_head 是多头注意力的头的个数,
num_encoder_layers 和 num_decoder_layers 分别对应编码器和解码器的自注意模块的叠加的次数,
dim_feedforward对应编码器-解码器中的 Linear层的维度。

nn.Transformer 的 forward 函数实现了编码和解码的过程:

forward(src: torch.Tensor,tgt: torch.Tensor,src_mask: Optional[torch.Tensor] = None,tgt_mask: Optional[torch.Tensor] = None,memory_mask: Optional[torch.Tensor] = None,src_key_padding_mask: Optional[torch.Tensor] = None,tgt_key_padding_mask: Optional[torch.Tensor] = None,memory_key_padding_mask: Optional[torch.Tensor] = None)→ torch.Tensor

其中,必须输入的两个参数是 src和tgt,分别对应于编码器的输入inputs和解码器的输入outputs。tgt的作用类似于一种条件约束,Decoder的第一层的tgt输入是一个词嵌入向量,从第二层开始是前一层的计算结果。

其他可选参数中,[src/tgt/memory]_mask是一个掩码数组,定义了计算Attention的策略,对应于原文的3.1节。一个通俗解释为:一个词序列中,每个词只能被它前面的词所影响,所以这个词后面的所有位置都需要被忽略,所以在计算Attention的时候,该词向量和它后面的词向量的相关性为0。(然而,实际上每个词特别是在中文中,每个词应该是和上下文语义、语序相关,才能更好的学习到该词的具体含义。

[src,tgt,memory]_key_padding_mask也是掩码数组,定义了src, tgt和memory中哪些位置需要保留,哪些需要忽略。

三、DETR

DETR的思路和传统的目标检测的本质思路有相似之处,但表现方式很不一样。传统的方法比如Anchor-based方法本质上是对预定义的密集anchors进行类别的分类和边框系数的回归。DETR则是将目标检测视为一个集合预测问题(集合和anchors的作用类似)。由于Transformer本质上是一个序列转换的作用,因此,可以将DETR视为一个从图像序列到一个集合序列的转换过程。该集合实际上就是一个可学习的位置编码(文章中也称为object queries或者output positional encoding,代码中叫作query_embed)。

DETR 的网络结构图(算法流程):
在这里插入图片描述
DETR使用的Transformer结构:
在这里插入图片描述
spatial positional encoding是作者自己提出的二维空间位置编码方法,该位置编码分别被加入到了encoderself attentiondecodercross attention,同时object queries也被加入到了decoder的两个attention中。而原版的Transformer将位置编码加到了input和output embedding中。值得一提的是,作者在消融实验中指出即使不给encoder添加任何位置编码,最终的AP也只比完整的DETR下降了1.3个点。

代码基于PyTorch重写了TransformerEncoderLayer, TransformerDecoderLayer类,用到的PyTorch接口只有nn.MultiheadAttention类。源码需要PyTorch 1.5.0以上版本。

代码核心位于models/transformer.pymodels/detr.py

Transformer.py

class Transformer(nn.Module):def __init__(self, d_model=512, nhead=8, num_encoder_layers=6,num_decoder_layers=6, dim_feedforward=2048, dropout=0.1,activation="relu", normalize_before=False,return_intermediate_dec=False):super().__init__()encoder_layer = TransformerEncoderLayer(d_model, nhead, dim_feedforward,dropout, activation, normalize_before)encoder_norm = nn.LayerNorm(d_model) if normalize_before else Noneself.encoder = TransformerEncoder(encoder_layer, num_encoder_layers, encoder_norm)decoder_layer = TransformerDecoderLayer(d_model, nhead, dim_feedforward,dropout, activation, normalize_before)decoder_norm = nn.LayerNorm(d_model)self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers, decoder_norm,return_intermediate=return_intermediate_dec)def forward(self, src, mask, query_embed, pos_embed):# flatten NxCxHxW to HWxNxCbs, c, h, w = src.shapesrc = src.flatten(2).permute(2, 0, 1)pos_embed = pos_embed.flatten(2).permute(2, 0, 1)query_embed = query_embed.unsqueeze(1).repeat(1, bs, 1)mask = mask.flatten(1)tgt = torch.zeros_like(query_embed)memory = self.encoder(src, src_key_padding_mask=mask, pos=pos_embed)hs = self.decoder(tgt, memory, memory_key_padding_mask=mask,pos=pos_embed, query_pos=query_embed)return hs.transpose(1, 2), memory.permute(1, 2, 0).view(bs, c, h, w)

Transformer类包含了一个Encoder和一个Decoder对象。相关类的实现均可在transformer.py中找到。重点看forward函数,有一个对输入tensor的变换操作:# flatten NxCxHxW to HWxNxC。

结合PyTorch中对src和tgt的形状定义可以发现,**DETR的思路是将backbone输出特征图的像素展开成一维后当成了序列长度,而batch和channel的定义不变。**故而DETR可以计算特征图的每一个像素相对于其他所有像素的相关性,这一点在CNN中是依靠感受野来实现的,可以看出Transformer能够捕获到比CNN更大的感受范围。

DETR在计算attention的时候没有使用masked attention,因为将特征图展开成一维以后,所有像素都可能是互相关联的,因此没必要规定mask。而src_key_padding_mask是用来将zero_pad的部分给去掉。

forward函数中有两个关键变量pos_embedquery_embed。其中pos_embed是位置编码,位于models/position_encoding.py.

position_encoding.py

针对二位特征图的特点,DETR实现了自己的二维位置编码方式。实现代码如下:

class PositionEmbeddingSine(nn.Module):"""This is a more standard version of the position embedding, very similar to the oneused by the Attention is all you need paper, generalized to work on images."""def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None):super().__init__()self.num_pos_feats = num_pos_featsself.temperature = temperatureself.normalize = normalizeif scale is not None and normalize is False:raise ValueError("normalize should be True if scale is passed")if scale is None:scale = 2 * math.piself.scale = scaledef forward(self, tensor_list: NestedTensor):x = tensor_list.tensorsmask = tensor_list.maskassert mask is not Nonenot_mask = ~masky_embed = not_mask.cumsum(1, dtype=torch.float32)x_embed = not_mask.cumsum(2, dtype=torch.float32)if self.normalize:eps = 1e-6y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scalex_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scaledim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)pos_x = x_embed[:, :, :, None] / dim_tpos_y = y_embed[:, :, :, None] / dim_tpos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)return pos

而里面的,mask是一个位置掩码数组,对于一个没有经过zero_pad的图像,它的mask是一个全为0的数组。

对照代码,可以看出DETR是为二维特征图的 x和 y 方向各自计算了一个位置编码,每个维度的位置编码长度为num_pos_feats(该数值实际上为hidden_dim的一半),对x或 y,计算奇数位置的正弦,计算偶数位置的余弦,然后将pos_xpos_y拼接起来得到一个NHWD的数组,再经过permute(0,3,1,2),形状变为NDHW,其中D等于hidden_dim。这个hidden_dim是Transformer输入向量的维度,在实现上,要等于CNN backbone输出的特征图的维度。所以pos code和CNN输出特征的形状是完全一样的。

src = src.flatten(2).permute(2, 0, 1)         
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)

将CNN输出的featurespos code均进行flattenpermute操作,将形状变为SNE,符合PyTorch的输入形状定义。在TransformerEncoder中,会将src和pos_embed相加。可自行查看代码。

detr.py

class DETR

该类封装了整个DETR计算流程。首先来看论文中反复提到的object queries到底是什么。

答案就是query_embed

代码中,query_embed实际上就是一个embedding数组:

self.query_embed = nn.Embedding(num_queries, hidden_dim)

其中,num_queries是预定义的目标查询的个数,代码中默认为100。它的意义是:**根据Encoder编码的特征,Decoder将100个查询转化成100个目标。**通常100个查询已经足够了,很少有图像能包含超过100个目标(除非超密集的任务),相比之下,基于CNN的方法要预测的anchors数目动辄上万,计算代价实在是很大。

Transformerforward函数中定义了一个和query_embed形状相同的全为0的数组target,然后在TransformerDecoderLayerforward中把query_embedtarget相加(这里query_embed的作用表现的和位置编码类似),在self attention中作为querykey;在multi-head attention中作为query

class TransformerDecoderLayer(nn.Module):def forward_post(self, tgt, memory,tgt_mask: Optional[Tensor] = None,memory_mask: Optional[Tensor] = None,tgt_key_padding_mask: Optional[Tensor] = None,memory_key_padding_mask: Optional[Tensor] = None,pos: Optional[Tensor] = None,query_pos: Optional[Tensor] = None):q = k = self.with_pos_embed(tgt, query_pos)tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,key_padding_mask=tgt_key_padding_mask)[0]tgt = tgt + self.dropout1(tgt2)tgt = self.norm1(tgt)tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt, query_pos),key=self.with_pos_embed(memory, pos),value=memory, attn_mask=memory_mask,key_padding_mask=memory_key_padding_mask)[0]tgt = tgt + self.dropout2(tgt2)tgt = self.norm2(tgt)tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))tgt = tgt + self.dropout3(tgt2)tgt = self.norm3(tgt)return tgt

object queries在经过decoder的计算以后,会输出一个形状为TNE的数组,其中T是object queries的序列长度,即100,N是batch size,E是特征channel。

最后通过一个Linear层输出class预测,通过一个多层感知机结构输出box预测

self.class_embed = nn.Linear(hidden_dim, num_classes + 1)
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3)#forward
hs = self.transformer(self.input_proj(src), mask, self.query_embed.weight, pos[-1])[0]
outputs_class = self.class_embed(hs)
outputs_coord = self.bbox_embed(hs).sigmoid()
out = {'pred_logits': outputs_class[-1], 'pred_boxes': outputs_coord[-1]}

分类输出的通道为num_classes+1,类别从0开始,背景类别为num_classes。

class SetCriterion

该类负责 loss 的计算。

基于CNN的方法会计算每个anchor的预测结果,然后利用预测结果和ground truth box之间计算iou,挑选iou大于一定阈值的那些anchors作为正样本,来回归它们的classbox deltas。类似的,DETR也会计算每个object query的prediction,但DETR会直接计算box的四个角的归一化值,而不再基于box deltas:

然后将这些object predictionsground truth box之间进行二分匹配。DETR使用匈牙利算法来完成这一匹配过程。

完整流程图:
在这里插入图片描述
假如有N个目标,那么100个object predictions中就会有N个能够匹配到这N个ground truth,其他的都会和“no object”匹配成功,这些predictions的类别label就会被分配为num_classes,即表示该prediction是背景。

这样的设计非常不错,是DETR中的一大亮点,也是其特点之一,使其在理论上每个object query都有唯一匹配的目标,不会存在重叠,所以DETR不需要nms进行后处理。

其根据匹配结果进行损失函数 loss 的计算。这里不再列出损失函数的计算公式。

class SetCriterion(nn.Module):def forward(self, outputs, targets):""" This performs the loss computation.Parameters:outputs: dict of tensors, see the output specification of the model for the formattargets: list of dicts, such that len(targets) == batch_size.The expected keys in each dict depends on the losses applied, see each loss' doc"""outputs_without_aux = {k: v for k, v in outputs.items() if k != 'aux_outputs'}# Retrieve the matching between the outputs of the last layer and the targetsindices = self.matcher(outputs_without_aux, targets)# Compute the average number of target boxes accross all nodes, for normalization purposesnum_boxes = sum(len(t["labels"]) for t in targets)num_boxes = torch.as_tensor([num_boxes], dtype=torch.float, device=next(iter(outputs.values())).device)if is_dist_avail_and_initialized():torch.distributed.all_reduce(num_boxes)num_boxes = torch.clamp(num_boxes / get_world_size(), min=1).item()# Compute all the requested losseslosses = {}for loss in self.losses:losses.update(self.get_loss(loss, outputs, targets, indices, num_boxes))

通过self.matcheroutputs_without_auxtargets进行匹配。匈牙利算法会返回一个indices tuple,该tuple包含了srctarget的index。具体匹配过程请参考models/matcher.py。

分类 loss

分类loss采用的是交叉熵损失,针对所有predictions

def loss_labels(self, outputs, targets, indices, num_boxes, log=True):src_logits = outputs['pred_logits']idx = self._get_src_permutation_idx(indices)target_classes_o = torch.cat([t["labels"][J] for t, (_, J) in zip(targets, indices)])target_classes = torch.full(src_logits.shape[:2], self.num_classes,dtype=torch.int64, device=src_logits.device)target_classes[idx] = target_classes_oloss_ce = F.cross_entropy(src_logits.transpose(1, 2), target_classes, self.empty_weight)losses = {'loss_ce': loss_ce}return losses

target_classes_o是按target index获取的所有匹配成功的真值类别,并按src index将其放入target_classes的对应位置。匹配失败的predictionstarget_classes中用self.num_classes来填充。函数_get_src_permutation_idx的作用是从indices tuple中取得srcbatch index和对应的match index

box loss

box loss采用了l1 lossgiou loss,针对匹配成功的predictions

def loss_boxes(self, outputs, targets, indices, num_boxes):"""Compute the losses related to the bounding boxes, the L1 regression loss and the GIoU losstargets dicts must contain the key "boxes" containing a tensor of dim [nb_target_boxes, 4]The target boxes are expected in format (center_x, center_y, w, h), normalized by the image size."""assert 'pred_boxes' in outputsidx = self._get_src_permutation_idx(indices)src_boxes = outputs['pred_boxes'][idx]target_boxes = torch.cat([t['boxes'][i] for t, (_, i) in zip(targets, indices)], dim=0)loss_bbox = F.l1_loss(src_boxes, target_boxes, reduction='none')losses = {}losses['loss_bbox'] = loss_bbox.sum() / num_boxesloss_giou = 1 - torch.diag(box_ops.generalized_box_iou(box_ops.box_cxcywh_to_xyxy(src_boxes),box_ops.box_cxcywh_to_xyxy(target_boxes)))losses['loss_giou'] = loss_giou.sum() / num_boxesreturn losses

target_boxes 是按target index获取的所有匹配成功的真值boxsrc_boxes是按src index获取的匹配成功的predictions,计算它们之间的l1_loss和giou loss

DETR 在全景分割上的应用(浅看)

Decoder的每个object embedding加上一个mask head就可以实现像素级分割的功能
mask head可以和box embed联合训练,也可以训练完了box embed以后再单独训练mask head

DETR的做法和Mask-RCNN比较像,都是在给定的box prediction的基础上预测该box对应instancesegmentation。这里DETRmask head输出的attention map进行上采样以后,和backbone的一些分支进行相加,实现一个FPN的功能,然后将所有的box对应的mask map加粗样式进行bitwise argmax操作,得到最终的分割图。

最后(个人见解)

Transformer结构在CV领域的应用,表明了CV和NLP两大计算机AI领域的密切关联,计算机领域间各大细分领域间的互相促进关系。不过,Transformer结构在语义语序上的巨大效果始终还是在自然语言处理上更为显著,而将其应用大CV领域,个人认为是因为其对时间序列这类特征数据的特殊作用,使得其在目标检测、图像理解等时序相关的任务上有了很不错成效。但是,或许后续的改进会使得两者相通以得到更好的结果。

本文仅作学习分享,笔记记录,侵权请联系删除

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

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

相关文章

详细的SAP的付款条件测试

详细的SAP的付款条件测试 正好需要看一个清账日期的问题,然后重新理了一下付款条件,特此记录,省的后面每次都要捋一遍。其实真正能做到按照条件付款的良心企业很少。 付款条件修改事务码:OBB8 1、凭证日期作为基准,…

股票贷款行业,给你一个低成本有效的获客渠道

我们比市场上的同行公司具有更多的质量优势。我们的推广部门不断使用大数据引导和定位技术以及促销策略的迭代升级,具有足够强大的硬实力,可以引导客户提供更优质的投资者。为金融公司带来更好的资源。现在的股票加粉主要是:微信加粉/Q组/留电…

气象科普丨气象站的分类与应用

气象站是一种用于收集、分析和处理气象数据的设备。根据不同的应用场景和监测需求,气象站可以分为以下几类: 一、农业气象站 农业气象站是专门为农业生产服务的气象站,主要监测土壤温度、土壤湿度等参数,为农业生产提供科学依据…

无涯教程-Android - RadioButton函数

RadioButton有两种状态:选中或未选中,这允许用户从一组中选择一个选项。 Radio Button 示例 本示例将带您完成一些简单的步骤,以展示如何使用Linear Layout和RadioButton创建自己的Android应用程序。 以下是修改后的主要Activity文件 src/MainActivity.java 的内容。 packa…

解读《生成式人工智能服务管理暂行办法》

《生成式人工智能服务管理暂行办法》 第一章 总 则第二章 技术发展与治理第三章 服务规范第四章 监督检查和法律责任第五章 附 则 以ChatGPT为代表的现象级互联网应用的出现,掀起了人工智能领域新一轮技术浪潮。作为新一代信息技术,生成式人工智能通过…

前端如何走通后端接口

0 写在前面 现在基本都是前后端分离的项目了,那么前端小伙伴如何获取后端小伙伴接口呢? 1 条件 同一WiFi下,让后端小伙伴分享出自己的ip地址: 步骤1:winr调出运行界面 步骤2:cmd调出命令行窗口 步骤3:…

Linux编程--进程--fork使用,创建父子进程

1.使用fork函数创建一个进程 #include <unistd.h>pid_t fork(void); 返回值为0&#xff0c;代表当前进程是子进程 返回值为非负数&#xff0c;代表当前进程为父进程 调用失败&#xff0c;返回-1 代码&#xff1a; #include <stdio.h> #include <sys/types.h&g…

记录--前端使用a链接下载内容增加loading效果

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 问题描述&#xff1a;最近工作中出现一个需求&#xff0c;纯前端下载 Excel 数据&#xff0c;并且有的下载内容很多&#xff0c;这时需要给下载增加一个 loading 效果。 代码如下&#xff1a; // util…

【服务器使用基础】---华为云云耀云服务器实例使用实践

&#x1f996;我是Sam9029&#xff0c;一个前端 Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-JS学习,CSS学习,Vue-2领域博主 **&#x1f431;‍&#x1f409;&#x1f431;‍&#x1f409;恭喜你&#xff0c;若此文你认为写的不错&#xff0c;不要吝啬你的赞扬&#xff0c;求…

怎样免费在公司访问家中的树莓派

最近拿起了大学时买的树莓派&#xff0c;刚好看到了一篇文章写到无公网IP&#xff0c;从公网SSH远程访问家中的树莓派 便来试试&#xff1a; 我的树莓派之前装过ssh&#xff0c;所以插上电就能用了。其实过程很简单&#xff0c;只需要在树莓派中下载一个cpolar即可。 curl -…

【力扣每日一题】2023.9.1 买钢笔和铅笔的方案数

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们三个数&#xff0c;一个是我们拥有的钱&#xff0c;一个是钢笔的价格&#xff0c;另一个是铅笔的价格。 问我们一共有几种买笔…

【LeetCode】双指针妙解有效三角形的个数

Problem: 611. 有效三角形的个数 文章目录 题目分析讲解算法原理复杂度Code 题目分析 首先我们来分析一下本题的思路 看到题目中给出的示例 题目的意思很简单&#xff0c;就是将给到的数字去做一个组合&#xff0c;然后看看这三条边是否可以构成三角形。那判断的方法不用我说&a…

eureka迁移到nacos--双服务中心注册

服务注册中心的迁移有多种方式&#xff0c;官网使用nacos sync&#xff0c;还有民间开发的双注册中心组件eureka-nacos-proxy&#xff0c;但是我用了不太顺利&#xff0c;所以用的是阿里巴巴的双注册中心组件edas-sc-migration-starter spring boot&#xff1a;2.5.3 引入依赖 …

VFPBS 猫框直接将SQL image字段变成图片输出

代码很简单 itm_image为image字段 itm_fname为图片文件名 Define Class ctl_image As SESSIONProcedure getfilecfileHttpQueryParams2("file")CURSORSETPROP("MapBinary",.t.,0)TEXT TO lcSQLCmd NOSHOW TEXTMERGE SELECT itm_image FROM temp_itm_mstr_…

【8 排序】简单选择排序。

顺序表&#xff1a; void Swap(int &a,int &b){int temp;tempa;ab;btemp; } void SelectSort(int A[],int n){int min,i,j;for(i0;i<n-1;i){mini;for(ji1;j<n;j)if(A[j]<A[min])minj;if(min!i)Swap(A[i],A[min]);} } 单链表&#xff1a; void SelectSort…

简单聊聊Https的来龙去脉

简单聊聊Https的来龙去脉 Http 通信具有哪些风险Https Http SSL/TLS对称加密 和 非对称加密数字证书数字证书的申请数字证书怎么起作用 Https工作流程一定需要Https吗&#xff1f; Http 通信具有哪些风险 使用明文通信&#xff0c;通信内容可能会被监听不验证通信双方身份&a…

安防视频监控/视频集中存储/云存储平台EasyCVR平台无法播放HLS协议该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

超越编辑器的边界:掌握 Vs Code + Vim 最强操作技巧

看完这篇文章&#xff0c;从此刻开始你将成为一名真正的 “键盘侠” 作为程序员我们知道&#xff0c;当我们编写代码的时候频繁的操作鼠标是一件非常费劲的一件事&#xff0c;我们的很多时间都会浪费到去使用鼠标定位光标选中文本等等&#xff0c;要知道使用快捷键肯定是比我们…

学生信息管理系统MIS(前端)

改造HTML文件 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>学生信息管理系统MIS</title><!-- link在HTML文件中,引入外部的css文件 rel的值是固定写法,stylesheet样式表href用来指定样式表的位置--><lin…

volatile 关键字 与 CPU cache line 的效率问题

分析&回答 Cache Line可以简单的理解为CPU Cache中的最小缓存单位。目前主流的CPU Cache的Cache Line大小都是64Bytes。假设我们有一个512字节的一级缓存&#xff0c;那么按照64B的缓存单位大小来算&#xff0c;这个一级缓存所能存放的缓存个数就是512/64 8个。具体参见下…