当前位置: 首页 > news >正文

【多模态融合】TransFusion学习笔记(2)

接上篇【多模态融合】TransFusion学习笔记(1)。

从TransFusion-L到TransFusion

    ok,终于可以给出论文中那个完整的框架图了,我第一眼看到这个图有几个疑问:

Q:Image Guidance这条虚线引出的Query Initialization是什么意思?

Q:图像分支中的Image Features as K,V是将整张图像的特征图都作为K,V么?

Q:有了第2阶段之后Initial Prediction还需要么?

Q:如果第一阶段的Q来自纯lidar bev feature map,用它来聚合Image Features靠普么,毕竟是两种模态的特征?

Q:第2阶段的Transformer Decoder Layer with SMCA,这个SMCA是什么意思?

Q:如果仅仅是纯Lidar分支产生的object query去聚合image featuers产生最终的预测肯定是不够的,你可能得到一个修正之后更准的边界框或者分类,但是lidar漏掉的框是没办法恢复的,所以应该还有补漏的环节?

带着诸的疑问结合论文及代码继续分析,仍然假定batch为2,数据集为nuScenes。说到nuScenes需要大该了解以下他lidar和camera配置。他在车顶端配备了一个32线Lidar,然后按321队形配置了6个Camera。所以,代码中推理的时候每一个batch同时包含了6张图像。

 #源文件mmdet3d/models/dense_heads/transfusion_head.py

    def forward(self, feats, img_feats, img_metas):"""Forward pass.Args:feats (list[torch.Tensor]): Multi-level features, e.g.,features produced by FPN.Returns:tuple(list[dict]): Output results. first index by level, second index by layer"""if img_feats is None:img_feats = [None]res = multi_apply(self.forward_single, feats, img_feats, [img_metas])assert len(res) == 1, "only support one level features."return res

现在再来看Tranfusion检测头推理入口forward函数的时候,img_feats和img_metas就包含了满满的图像及其特征信息了,其中img_feats的shape为(12,256,112,200),12为batch(2)*6(cameras的数量),它将batch和n_views整合在了一起,明白这一点很重要。                                                    

 def forward_single(self, inputs, img_inputs, img_metas):"""Forward function for CenterPoint.Args:inputs (torch.Tensor): Input feature map with the shape of[B, 512, 128(H), 128(W)]. (consistent with L748)Returns:list[dict]: Output results for tasks."""batch_size = inputs.shape[0]lidar_feat = self.shared_conv(inputs)  ##=>[2, 128, 128, 128]lidar_feat_flatten = lidar_feat.view(batch_size, lidar_feat.shape[1], -1)  #=>[BS, C, H*W]bev_pos = self.bev_pos.repeat(batch_size, 1, 1).to(lidar_feat.device)if self.fuse_img:img_feat = self.shared_conv_img(img_inputs)  # [BS * n_views, C, H, W]img_h, img_w, num_channel = img_inputs.shape[-2], img_inputs.shape[-1], img_feat.shape[1]# =>[B, C, H, n_views, W]raw_img_feat = img_feat.view(batch_size, self.num_views, num_channel, img_h, img_w).permute(0, 2, 3, 1, 4) # =>[B, C, H, n_views*W]img_feat = raw_img_feat.reshape(batch_size, num_channel, img_h, img_w * self.num_views)  # =>(B,C,n_view*W)img_feat_collapsed = img_feat.max(2).values# positional encoding for image guided query initializationif self.img_feat_collapsed_pos is None:img_feat_collapsed_pos = self.img_feat_collapsed_pos = self.create_2D_grid(1, img_feat_collapsed.shape[-1]).to(img_feat.device)else:img_feat_collapsed_pos = self.img_feat_collapsed_posbev_feat = lidar_feat_flattenfor idx_view in range(self.num_views):bev_feat = self.decoder[2 + idx_view](bev_feat, img_feat_collapsed[..., img_w * idx_view:img_w * (idx_view + 1)], bev_pos, img_feat_collapsed_pos[:, img_w * idx_view:img_w * (idx_view + 1)])

从if self.fuse_img条件判断进入的这段代码逻辑用于生成融合了的LiDAR-Camera BEV feature map F-lc。

图:展示如何操作一个batch中的6张Image feature map,形成高度压缩后的K,V。

图:展示Lidar features和6张Height Collapsed Image features融合的过程

使用Dense的Lidar BEV features作为Q,使用高度压缩后的Image Features作为K,V。为什么要对Image Features进行高度压缩,作者在论文中也做了解释。

关于如何融合lidar bev features和image features得到一个更具表达能力的bev feature map,在若干其它论文中都有涉及,较为著名的比如以下图所示的BEVFusion。

BEVFusion这种特征融合的方式很直观,但是他需要将multi-view的图像特征通过LSS或其它方式编码到BEV空间,然后使用一个Dynamic Fusion Module得到融合后的特征。这种融合简单粗暴,也是Hard-Association的。

考虑一个问题,如果使用BEVFusion这种多模态融合的bev feature map替换TransFusion-L中纯Lidar产生的bev featuremap会有什么效果呢?bevfusion的作者就做了这个实验。

从最后一列的nuScenes Validation上的结果来看mAP和NDS分别提了3.%和1.1%。怎么说呢,有用,但好像又觉得没赚到啥。毕竟费了大力气把不同视角下的image特征提取出来再编码到BEV空间,融合完成后mAP相比纯Lidar只是涨了3个点,基本上还是Lidar在支撑着。

        ################################## image guided query initialization#################################if self.initialize_by_heatmap:##=>[2, 10, 128, 128])dense_heatmap = self.heatmap_head(lidar_feat)dense_heatmap_img = Noneif self.fuse_img:dense_heatmap_img = self.heatmap_head_img(bev_feat.view(lidar_feat.shape))  # [BS, num_classes, H, W]heatmap = (dense_heatmap.detach().sigmoid() + dense_heatmap_img.detach().sigmoid()) / 2else:heatmap = dense_heatmap.detach().sigmoid()padding = self.nms_kernel_size // 2local_max = torch.zeros_like(heatmap)local_max_inner = F.max_pool2d(heatmap, kernel_size=self.nms_kernel_size, stride=1, padding=0)local_max[:, :, padding:(-padding), padding:(-padding)] = local_max_inner## for Pedestrian & Traffic_cone in nuScenesif self.test_cfg['dataset'] == 'nuScenes':local_max[:, 8, ] = F.max_pool2d(heatmap[:, 8], kernel_size=1, stride=1, padding=0)local_max[:, 9, ] = F.max_pool2d(heatmap[:, 9], kernel_size=1, stride=1, padding=0)elif self.test_cfg['dataset'] == 'Waymo':  # for Pedestrian & Cyclist in Waymolocal_max[:, 1, ] = F.max_pool2d(heatmap[:, 1], kernel_size=1, stride=1, padding=0)local_max[:, 2, ] = F.max_pool2d(heatmap[:, 2], kernel_size=1, stride=1, padding=0)##非max-heat的地方就被set为0了heatmap = heatmap * (heatmap == local_max)##torch.Size([2, 10, 16384]) <==heatmap = heatmap.view(batch_size, heatmap.shape[1], -1)# top #num_proposals among all classestop_proposals = heatmap.view(batch_size, -1).argsort(dim=-1, descending=True)[..., :self.num_proposals]top_proposals_class = top_proposals // heatmap.shape[-1]##index有什么用??top_proposals_index = top_proposals % heatmap.shape[-1]query_feat = lidar_feat_flatten.gather(index=top_proposals_index[:, None, :].expand(-1, lidar_feat_flatten.shape[1], -1), dim=-1)self.query_labels = top_proposals_classone_hot = F.one_hot(top_proposals_class, num_classes=self.num_classes).permute(0, 2, 1)query_cat_encoding = self.class_encoding(one_hot.float())query_feat += query_cat_encodingquery_pos = bev_pos.gather(index=top_proposals_index[:, None, :].permute(0, 2, 1).expand(-1, -1, bev_pos.shape[-1]), dim=1)else:query_feat = self.query_feat.repeat(batch_size, 1, 1)  # [BS, C, num_proposals]base_xyz = self.query_pos.repeat(batch_size, 1, 1).to(lidar_feat.device)  # [BS, num_proposals, 2]

回到TransFusion上面,在没有融合Image Features之前,heatmap需要从纯lidar feature map出。现在有了融合后的feature map,自然heatmap又多了一条出路。这就是代码中既有一个dense_heatmap,又多出来了一个dense_heatmap_img,他们最终通过以下代码进行了融合。

heatmap = (dense_heatmap.detach().sigmoid() + dense_heatmap_img.detach().sigmoid()) / 2

不看代码我还以为就只是利用了从dense_heatmap_img出的heatmap,作者这里还是做了一下结合,结合方式也比较简单,各自simgoid之后相加取平均。

        ret_dicts = []for i in range(self.num_decoder_layers):prefix = 'last_' if (i == self.num_decoder_layers - 1) else f'{i}head_'# Transformer Decoder Layer# :param query: B C Pq    :param query_pos: B Pq 3/6query_feat = self.decoder[i](query_feat, lidar_feat_flatten, query_pos, bev_pos)# Predictionres_layer = self.prediction_heads[i](query_feat) ##FFNres_layer['center'] = res_layer['center'] + query_pos.permute(0, 2, 1)first_res_layer = res_layerif not self.fuse_img:ret_dicts.append(res_layer)# for next level positional embeddingquery_pos = res_layer['center'].detach().clone().permute(0, 2, 1)

这段代码和单模态的TransFusion-L比,query_feat还是从纯lidar bev feature map取的,lidar_feat_flatten也还是原来那个展开了的lidar bev featuremap。但是,此时的query_feat所在的热点位置因为是从融合的bev featuremap出的,所以就有了"Image Guidance"的一说。

        ################################## transformer decoder layer (img feature as K,V)#################################if self.fuse_img:# positional encoding for image fusionimg_feat = raw_img_feat.permute(0, 3, 1, 2, 4) # [BS, n_views, C, H, W]img_feat_flatten = img_feat.view(batch_size, self.num_views, num_channel, -1)  # [BS, n_views, C, H*W]if self.img_feat_pos is None:(h, w) = img_inputs.shape[-2], img_inputs.shape[-1]img_feat_pos = self.img_feat_pos = self.create_2D_grid(h, w).to(img_feat_flatten.device)else:img_feat_pos = self.img_feat_posprev_query_feat = query_feat.detach().clone()query_feat = torch.zeros_like(query_feat)  # create new container for img query featurequery_pos_realmetric = query_pos.permute(0, 2, 1) * self.test_cfg['out_size_factor'] * self.test_cfg['voxel_size'][0] + self.test_cfg['pc_range'][0]query_pos_3d = torch.cat([query_pos_realmetric, res_layer['height']], dim=1).detach().clone()if 'vel' in res_layer:vel = copy.deepcopy(res_layer['vel'].detach())else:vel = Nonepred_boxes = self.bbox_coder.decode(copy.deepcopy(res_layer['heatmap'].detach()),copy.deepcopy(res_layer['rot'].detach()),copy.deepcopy(res_layer['dim'].detach()),copy.deepcopy(res_layer['center'].detach()),copy.deepcopy(res_layer['height'].detach()),vel,)on_the_image_mask = torch.ones([batch_size, self.num_proposals]).to(query_pos_3d.device) * -1for sample_idx in range(batch_size if self.fuse_img else 0):lidar2img_rt = query_pos_3d.new_tensor(img_metas[sample_idx]['lidar2img'])img_scale_factor = (query_pos_3d.new_tensor(img_metas[sample_idx]['scale_factor'][:2]if 'scale_factor' in img_metas[sample_idx].keys() else [1.0, 1.0]))img_flip = img_metas[sample_idx]['flip'] if 'flip' in img_metas[sample_idx].keys() else Falseimg_crop_offset = (query_pos_3d.new_tensor(img_metas[sample_idx]['img_crop_offset'])if 'img_crop_offset' in img_metas[sample_idx].keys() else 0)img_shape = img_metas[sample_idx]['img_shape'][:2]img_pad_shape = img_metas[sample_idx]['input_shape'][:2]boxes = LiDARInstance3DBoxes(pred_boxes[sample_idx]['bboxes'][:, :7], box_dim=7)query_pos_3d_with_corners = torch.cat([query_pos_3d[sample_idx], boxes.corners.permute(2, 0, 1).view(3, -1)], dim=-1)  # [3, num_proposals] + [3, num_proposals*8]# transform point clouds back to original coordinate system by reverting the data augmentationif batch_size == 1:  # skip during inference to save timepoints = query_pos_3d_with_corners.Telse:points = apply_3d_transformation(query_pos_3d_with_corners.T, 'LIDAR', img_metas[sample_idx], reverse=True).detach()num_points = points.shape[0]for view_idx in range(self.num_views):pts_4d = torch.cat([points, points.new_ones(size=(num_points, 1))], dim=-1)pts_2d = pts_4d @ lidar2img_rt[view_idx].t()##相机内参前面那个1/zpts_2d[:, 2] = torch.clamp(pts_2d[:, 2], min=1e-5)pts_2d[:, 0] /= pts_2d[:, 2]pts_2d[:, 1] /= pts_2d[:, 2]# img transformation: scale -> crop -> flip# the image is resized by img_scale_factorimg_coors = pts_2d[:, 0:2] * img_scale_factor  # Nx2img_coors -= img_crop_offset# grid sample, the valid grid range should be in [-1,1]coor_x, coor_y = torch.split(img_coors, 1, dim=1)  # each is Nx1if img_flip:# by default we take it as horizontal flip# use img_shape before padding for fliporig_h, orig_w = img_shapecoor_x = orig_w - coor_x##e.g. 200个proposal总共有200 + 200*8 = 1800个坐标点coor_x, coor_corner_x = coor_x[0:self.num_proposals, :], coor_x[self.num_proposals:, :]coor_y, coor_corner_y = coor_y[0:self.num_proposals, :], coor_y[self.num_proposals:, :]coor_corner_x = coor_corner_x.reshape(self.num_proposals, 8, 1)coor_corner_y = coor_corner_y.reshape(self.num_proposals, 8, 1)coor_corner_xy = torch.cat([coor_corner_x, coor_corner_y], dim=-1)h, w = img_pad_shapeon_the_image = (coor_x > 0) * (coor_x < w) * (coor_y > 0) * (coor_y < h)on_the_image = on_the_image.squeeze()# skip the following computation if no object query fall on current imageif on_the_image.sum() <= 1:continueon_the_image_mask[sample_idx, on_the_image] = view_idx# add spatial constraint#out_size_factor_img是什么out的factor?center_ys = (coor_y[on_the_image] / self.out_size_factor_img)center_xs = (coor_x[on_the_image] / self.out_size_factor_img)centers = torch.cat([center_xs, center_ys], dim=-1).int()  # center on the feature mapcorners = (coor_corner_xy[on_the_image].max(1).values - coor_corner_xy[on_the_image].min(1).values) / self.out_size_factor_img#gaosi geradius = torch.ceil(corners.norm(dim=-1, p=2) / 2).int()  # radius of the minimum circumscribed circle of the wireframesigma = (radius * 2 + 1) / 6.0"""The 2D gaussian weight mask M is generated  in a similar way as Center-Net,Mij = exp(((i-cx)^2+(j-cy)^2)/(sigma*radius^2)),where (i,j) is the spatial indices of the weight mask M,(cx,cy) is the 2D center computed by projecting the query prediction onto the image plane """distance = (centers[:, None, :] - (img_feat_pos - 0.5)).norm(dim=-1) ** 2gaussian_mask = (-distance / (2 * sigma[:, None] ** 2)).exp()gaussian_mask[gaussian_mask < torch.finfo(torch.float32).eps] = 0  ##太远的地方权重太小,直接给0attn_mask = gaussian_maskquery_feat_view = prev_query_feat[sample_idx, :, on_the_image]query_pos_view = torch.cat([center_xs, center_ys], dim=-1)query_feat_view = self.decoder[self.num_decoder_layers](query_feat_view[None], img_feat_flatten[sample_idx:sample_idx + 1, view_idx], query_pos_view[None], img_feat_pos, attn_mask=attn_mask.log())query_feat[sample_idx, :, on_the_image] = query_feat_view.clone()self.on_the_image_mask = (on_the_image_mask != -1)res_layer = self.prediction_heads[self.num_decoder_layers](torch.cat([query_feat, prev_query_feat], dim=1))res_layer['center'] = res_layer['center'] + query_pos.permute(0, 2, 1)for key, value in res_layer.items():pred_dim = value.shape[1]res_layer[key][~self.on_the_image_mask.unsqueeze(1).repeat(1, pred_dim, 1)] = first_res_layer[key][~self.on_the_image_mask.unsqueeze(1).repeat(1, pred_dim, 1)]ret_dicts.append(res_layer)

上面这段代码是TransFusion的高潮部分,只是现在的为K,V取自Image features。之所以说"取",自然就是每个object query取聚合所有视图下的Image Features,那样效率太低,也难以收敛。问题的关键是一个object query和哪些Image Features建立关联。有了第一阶段预测出的Initial Predict Boxes这个问题就好办一些了。关于怎么利用第一阶段的predict boxes以及Gaussian Circule作者在论文中已经写的很清楚了,应该算是诸多论文中的常规操作。

看到这里其实大该明白了作者所说的"soft-association",虽然由predict boxes到image features借助了标定关系。但是通过object query聚合对应局部image featues这里利用了TransFormer,尤其是利用其中的cross attention做了跟当前object query上下文相关的特征聚合,即使传感器之间没有严格对齐也更加鲁棒。

相关文章:

【多模态融合】TransFusion学习笔记(2)

接上篇【多模态融合】TransFusion学习笔记(1)。 从TransFusion-L到TransFusion ok,终于可以给出论文中那个完整的框架图了&#xff0c;我第一眼看到这个图有几个疑问: Q&#xff1a;Image Guidance这条虚线引出的Query Initialization是什么意思? Q&#xff1a;图像分支中的…...

Pyhon-每日一练(1)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…...

MySQL:数据库的物理备份和恢复-冷备份(3)

介绍 物理备份&#xff1a; 直接复制数据文件进行的备份 优点&#xff1a;不需要其他的工具&#xff0c;直接复制就好&#xff0c;恢复直接复制备份文件即可 缺点&#xff1a;与存储引擎有关&#xff0c;跨平台能力较弱 逻辑备份&#xff1a; 从数据库中导出数据另存而进行的备…...

功能比较:Redisson vs Jedis

Redis最流行的两个Java客户端库是Redisson和Jedis。Redisson提供内存中的数据网格功能&#xff0c;支持Redis的各种分布式对象和服务。另一方面&#xff0c;Jedis是一个更轻量级的产品&#xff0c;它缺乏其他库的某些功能。 如果你正在为Redis寻找一个Java客户端库&#xf…...

Spring web security

儅使用spring的web security時&#xff0c;默認會轉向自帶的spring security example page。而不會轉向error page。 TODO: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> &l…...

SpringCloud(二)Docker、Spring AMQP、ElasticSearch

文章目录 DockerDocker与虚拟机Docker架构镜像、容器、镜像托管平台Docker架构Docker实践 Spring AMQP简单使用案例工作队列- WorkQueue发布订阅服务FanoutExchangeDirectExchangeTopicExchange 消息转换器 ElasticSearch倒排索引IK分词器IK分词拓展与停用字典 操作索引库mappi…...

7.Tensors For Beginneers - Convector Components

介绍协向量时&#xff0c;曾说过它们有点像 行向量&#xff0c; 行向量确实以某种方式代表了协向量&#xff0c; 这里说明一下&#xff1a; 协向量是不变的&#xff1b; 协向量组件是可变的。 协向量不依赖坐标系&#xff0c;协向量的组件取决于坐标系。 当我们说协向量具有组…...

直线导轨坏了可以维修吗?

直线导轨是工业自动化设备中常用的零部件&#xff0c;其性能和使用寿命对设备的稳定运行和产能有着直接的影响&#xff0c;在生产中&#xff0c;由于各种原因&#xff0c;直线导轨会出现各种问题&#xff0c;那么&#xff0c;直线导轨的维修方法究竟是怎样的呢&#xff1f;我们…...

Java基础--泛型详解

一、背景 java推出泛型之前&#xff0c;集合元素类型可以是object类型&#xff0c;能够存储任意的数据类型对象&#xff0c;但是在使用过程中&#xff0c;如果不知道集合里面的各个元素的类型&#xff0c;在进行类型转换的时候就很容易引发ClassCastException异常。 二、概念 …...

学习搜狗的workflow,MacBook上如何编译

官网说可以在MacBook上也可以运行&#xff0c;但是编译的时候却有找不到openssl的错误&#xff1a; 看其他博客也有类似的错误&#xff0c;按照类似的思路去解决 问题原因和解决办法 cmake编译的时候&#xff0c;没有找到openssl的头文件&#xff0c;需要设置cmake编译环境下…...

Ubuntu使用cmake和vscode开发自己的项目,引用自己的头文件和openCV

创建文件夹 mkdir my_proj 继续创建include 和 src文件夹&#xff0c;形成如下的目录结构 用vscode打开项目 创建add.h #ifndef ADD_H #define ADD_Hint add(int numA, int numB);#endif add.cpp #include "add.h"int add(int numA, int numB) {return numA nu…...

2) dataset, dataloader

dataset, dataloader torchvision.datasets里面集成了一些常见的数据集,例如MNIST和CIFAR10 1) Dataset 以MNIST为例,其使用方式如下 import torch import torchvision from torchvision import transformstrain_dataset = torchvision.datasets.MNIST(root=../data,trai…...

阿里云PolarDB自研数据库详细介绍_兼容MySQL、PostgreSQL和Oracle语法

阿里云PolarDB数据库是阿里巴巴自研的关系型分布式云原生数据库&#xff0c;PolarDB兼容三种数据库引擎&#xff1a;MySQL、PostgreSQL、Oracle&#xff08;语法兼容&#xff09;&#xff0c;目前提供云原生数据库PolarDB MySQL版、云原生数据库PolarDB PostgreSQL版和云原生数…...

[软件工具]opencv-svm快速训练助手教程解决opencv C++ SVM模型训练与分类实现任务支持C# python调用

opencv中已经提供了svm算法可以对图像实现多分类&#xff0c;使用svm算法对图像分类的任务多用于场景简单且对时间有要求的场景&#xff0c;因为opencv的svm训练一般只需要很短时间就可以完成训练任务。但是目前网上没有一个工具很好解决训练问题&#xff0c;大部分需要自己编程…...

邮件注册(一)验证码发送

通过邮箱实现注册&#xff0c;用户请求验证码完成注册操作。 导入依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><g…...

【网络安全---sql注入(2)】如何通过SQL注入getshell?如何通过SQL注入读取文件或者数据库数据?一篇文章告诉你过程和原理。

前言 本篇博客主要是通过piakchu靶场来讲解如何通过SQL注入漏洞来写入文件&#xff0c;读取文件。通过SQL输入来注入木马来getshell等&#xff0c;讲解了比较详细的过程&#xff1b; 如果想要学习SQL注入原理以及如何进行SQL注入&#xff0c;我也写了一篇详细的SQL注入方法及…...

正点原子嵌入式linux驱动开发——TF-A移植

经过了之前的学习&#xff0c;除了TF-A的详细启动流程仍待更新&#xff0c;TF-A的使用和其对应的大致启动流程已经进行过了学习。但是当我们实际做产品时&#xff0c;硬件平台肯定会和ST官方的有区别&#xff0c;比如DDR容量会改变&#xff0c;自己的硬件没有使用到官方EVK开发…...

GB28181学习(六)——实时视音频点播(数据传输部分)

GB28181系列文章&#xff1a; 总述&#xff1a;https://blog.csdn.net/www_dong/article/details/132515446 注册与注销&#xff1a;https://blog.csdn.net/www_dong/article/details/132654525 心跳保活&#xff1a;https://blog.csdn.net/www_dong/article/details/132796…...

JMeter接口自动化测试(数据驱动)

之前我们的用例数据都是配置在HTTP请求中&#xff0c;每次需要增加&#xff0c;修改用例都需要打开JMeter重新编辑&#xff0c;当用例越来越多的时候&#xff0c;用例维护起来就越来越麻烦&#xff0c;有没有好的方法来解决这种情况呢&#xff1f;我们可以将用例的数据存放在cs…...

数据结构:二叉树(超详解析)

目录​​​​​​​ 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 1.3.1孩子兄弟表示法&#xff1a; 1.3.2双亲表示法&#xff1a;只存储双亲的下标或指针 两节点不在同一树上&#xff1a; 2.二叉树概念及结构 2.1.概念 2.2.特殊的二叉树&#xff1a; 2…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...