【代码能力提升 | 代码阅读学习】分析 VoxelNet 的 主干

文章目录

  • 前言
  • 代码分析
    • VoxelNet model
    • 2.数据处理
      • 2.1单个样本处理
      • 2.2处理成batch
  • 最后,附上我一步步调试代码,到3D-conv

前言

代码来自:https://github.com/skyhehe123/VoxelNet-pytorch
其中 测试数据来自:https://github.com/gakkiri/simple-pointpillar

代码分析

VoxelNet model

  1. model forward()就是模型的输入,voxel_features, voxel_coords是一个 batch 的数据。

    • voxel_features: [P, T, C], voxel_coords: [P, 4]。如何知道是 batch中的第几个样本? voxel_coords中的[P, 0]维度告诉了,其中的数据巧妙处理在 文件 kitti.pytrain.py, 稍后我们介绍
    • 转换为grid tensor: voxel_indexing(), 其中sparse_features.t()应该有.t(), 不然维度不匹配报错,原代码错了。经过这一步,输出维度为[B, C, D, H, W]。【这个函数妙啊,一个索引匹配就完成了 点云=> 规则张量,美中不足的是原代码写错了,需要加个转置.t()
      • 一个小样例测试这段代码 正确与否
    import torchsparse_features = torch.tensor([[1.0, 1, 1], [2, 2, 2]])
    coords = torch.IntTensor([[0,0,0,1], [1,0,0,2]]) # [P, 4(id,x,y,z)]dense_feature = torch.zeros(3, 2, 3,3,3) # [dim, B, D, H, W]
    dense_feature[:, coords[:,0], coords[:,1], coords[:,2], coords[:,3]]= sparse_features.t()
    print(dense_feature[:, 0, 0, 0, 1])
    print(dense_feature[:, 1, 0, 0, 2])输出, 刚好索引回来
    tensor([1., 1., 1.])
    tensor([2., 2., 2.])
    
    • 然后可以经过3D-Conv,后续接你需要的 任务头head,上面才是 VoxelNet的主干,也是最难理解的地方。
class VoxelNet(nn.Module):def __init__(self):super(VoxelNet, self).__init__()self.svfe = SVFE()self.cml = CML()# self.rpn = RPN()def voxel_indexing(self, sparse_features, coords):"""sparse_features: [P, C]coords:[P, 4]"""dim = sparse_features.shape[-1]device = sparse_features.devicedense_feature = Variable(torch.zeros(dim, cfg.N, cfg.D, cfg.H, cfg.W).to(device))dense_feature[:, coords[:,0], coords[:,1], coords[:,2], coords[:,3]]= sparse_features.t()dense_feature = dense_feature.transpose(0, 1)# print(f"dense_feature.shape = {dense_feature.shape}")# return dense_feature.transpose(0, 1) # dense_feature.transpose(0, 1):[ B, C, D, H, W]return dense_featuredef forward(self, voxel_features, voxel_coords):"""P: 一个batch合计的 voxel的数量T:一个voxel的点数 35C:每个点 的维度 7(=4 + 3)voxel_features: [P, T, C]voxel_coords: [P, 4(1+3)] => 每个voxel_coords 含4个维度,分别为 1+3,这个1就是指代这个 voxel 原来在 batch的第几个(就是这样来区分的),3表示grid的坐标"""print(f"forward=============>")print(f'voxel_features.shape = {voxel_features.shape}, voxel_coords.shape = {voxel_coords.shape}') # voxel_features.shape = (20284, 35, 7), voxel_coords.shape = (20284, 4)# feature learning networkvwfs = self.svfe(voxel_features) # vwfs:[P, C]print(f'=> vwfs.shape = {vwfs.shape}') # vwfs.shape = torch.Size([20284, 128])vwfs = self.voxel_indexing(vwfs,voxel_coords)print(f'voxel_indexing ==> vwfs.shape = {vwfs.shape}') # voxel_indexing ==> vwfs.shape = torch.Size([2, 128, 10, 400, 352]) [B, C, D, H, W]# convolutional middle networkcml_out = self.cml(vwfs)print(f"cml_out.shape = {cml_out.shape}") # cml_out.shape = torch.Size([2, 64, 2, 400, 352])# # region proposal network# # merge the depth and feature dim into one, output probability score map and regression map# psm,rm = self.rpn(cml_out.view(cfg.N,-1,cfg.H, cfg.W))# return psm, rm

2.数据处理

2.1单个样本处理

kitti.py文件的 def __getitem__(self, i):

可以看到,每帧lidar:[N, C],读取后经过这样处理:

  • 数据增强,
  • 范围过滤,
  • 处理成规则张量preprocess, 维度从 [N, C] 变为 [P, T, C], 其中,P不定,T=35,C=7

在这里插入图片描述

它是这样 处理的,可以学习代码:
其实,我不是很明白,这里为什么要 换维度?按理说3D-Conv都一样。

        # convert to  (D, H, W)voxel_coords = voxel_coords[:,[2,1,0]]

    def preprocess(self, lidar):# shuffling the pointsnp.random.shuffle(lidar)voxel_coords = ((lidar[:, :3] - np.array([self.xrange[0], self.yrange[0], self.zrange[0]])) / (self.vw, self.vh, self.vd)).astype(np.int32)# convert to  (D, H, W)voxel_coords = voxel_coords[:,[2,1,0]]voxel_coords, inv_ind, voxel_counts = np.unique(voxel_coords, axis=0, \return_inverse=True, return_counts=True)voxel_features = []for i in range(len(voxel_coords)):voxel = np.zeros((self.T, 7), dtype=np.float32)pts = lidar[inv_ind == i]if voxel_counts[i] > self.T:pts = pts[:self.T, :]voxel_counts[i] = self.T# augment the pointsvoxel[:pts.shape[0], :] = np.concatenate((pts, pts[:, :3] - np.mean(pts[:, :3], 0)), axis=1)voxel_features.append(voxel)return np.array(voxel_features), voxel_coords

2.2处理成batch

train.py文件中

处理流程:从batch中取出每个样本sample,给 每个样本sample 的coord的第0列添加序号,标记 是batch的第几个样本,方便后面转换为规则张量【主要是这个】。

    def detection_collate(batch):voxel_features = []voxel_coords = []for i, sample in enumerate(batch):voxel_features.append(sample[0])voxel_coords.append(np.pad(sample[1], ((0, 0), (1, 0)),mode='constant', constant_values=i))return np.concatenate(voxel_features), \np.concatenate(voxel_coords), \

最后,附上我一步步调试代码,到3D-conv

import torch.nn as nn
import torch.nn.functional as F
import torch
from torch.autograd import Variable
from config import config as cfg##### from my_kitti.py
import sys
sys.path.append(r"D:\workspace\【代码能力提升-深度学习】\voxel_and_pillar_code\VoxelNet-pytorch-master\VoxelNet-pytorch-master")import os
import os.path
import torch.utils.data as data
import utils
from utils import box3d_corner_to_center_batch, anchors_center_to_corner, corner_to_standup_box2d_batch
from data_aug import aug_data
# from box_overlaps import bbox_overlaps
import numpy as np
import cv2#### conv2d + bn + relu
class Conv2d(nn.Module):def __init__(self,in_channels,out_channels,k,s,p, activation=True, batch_norm=True):super(Conv2d, self).__init__()self.conv = nn.Conv2d(in_channels,out_channels,kernel_size=k,stride=s,padding=p)if batch_norm:self.bn = nn.BatchNorm2d(out_channels)else:self.bn = Noneself.activation = activationdef forward(self,x):x = self.conv(x)if self.bn is not None:x=self.bn(x)if self.activation:return F.relu(x,inplace=True)else:return x# conv3d + bn + relu
class Conv3d(nn.Module):def __init__(self, in_channels, out_channels, k, s, p, batch_norm=True):super(Conv3d, self).__init__()self.conv = nn.Conv3d(in_channels, out_channels, kernel_size=k, stride=s, padding=p)if batch_norm:self.bn = nn.BatchNorm3d(out_channels)else:self.bn = Nonedef forward(self, x):x = self.conv(x)if self.bn is not None:x = self.bn(x)return F.relu(x, inplace=True)# Fully Connected Network
class FCN(nn.Module):def __init__(self,cin,cout):super(FCN, self).__init__()self.cout = coutself.linear = nn.Linear(cin, cout)self.bn = nn.BatchNorm1d(cout)def forward(self,x):# KK is the stacked k across batchkk, t, _ = x.shape # x:[P, T, C]x = self.linear(x.view(kk*t,-1))x = F.relu(self.bn(x))return x.view(kk,t,-1)# Voxel Feature Encoding layer
class VFE(nn.Module):def __init__(self,cin,cout):super(VFE, self).__init__()assert cout % 2 == 0self.units = cout // 2self.fcn = FCN(cin,self.units)def forward(self, x, mask):# point-wise feauturepwf = self.fcn(x) # pwf:[P, T, C]#locally aggregated featurelaf = torch.max(pwf,1)[0].unsqueeze(1).repeat(1,cfg.T,1)# point-wise concat featurepwcf = torch.cat((pwf,laf),dim=2) # point-wise and locally, cat起来# apply maskmask = mask.unsqueeze(2).repeat(1, 1, self.units * 2)pwcf = pwcf * mask.float()return pwcf# Stacked Voxel Feature Encoding
class SVFE(nn.Module):def __init__(self):super(SVFE, self).__init__()self.vfe_1 = VFE(7,32)self.vfe_2 = VFE(32,128)self.fcn = FCN(128,128)def forward(self, x): # x:[P, T, C]mask = torch.ne(torch.max(x,2)[0], 0)x = self.vfe_1(x, mask)x = self.vfe_2(x, mask)x = self.fcn(x)# element-wise max poolingx = torch.max(x,1)[0] # x: [P, C], 相当于 只取每个voxel 的max featurereturn x# Convolutional Middle Layer
class CML(nn.Module):def __init__(self):super(CML, self).__init__()self.conv3d_1 = Conv3d(128, 64, 3, s=(2, 1, 1), p=(1, 1, 1))self.conv3d_2 = Conv3d(64, 64, 3, s=(1, 1, 1), p=(0, 1, 1))self.conv3d_3 = Conv3d(64, 64, 3, s=(2, 1, 1), p=(1, 1, 1))def forward(self, x):x = self.conv3d_1(x)x = self.conv3d_2(x)x = self.conv3d_3(x)return x# Region Proposal Network
# class RPN(nn.Module):
#     def __init__(self):
#         super(RPN, self).__init__()
#         self.block_1 = [Conv2d(128, 128, 3, 2, 1)]
#         self.block_1 += [Conv2d(128, 128, 3, 1, 1) for _ in range(3)]
#         self.block_1 = nn.Sequential(*self.block_1)#         self.block_2 = [Conv2d(128, 128, 3, 2, 1)]
#         self.block_2 += [Conv2d(128, 128, 3, 1, 1) for _ in range(5)]
#         self.block_2 = nn.Sequential(*self.block_2)#         self.block_3 = [Conv2d(128, 256, 3, 2, 1)]
#         self.block_3 += [nn.Conv2d(256, 256, 3, 1, 1) for _ in range(5)]
#         self.block_3 = nn.Sequential(*self.block_3)#         self.deconv_1 = nn.Sequential(nn.ConvTranspose2d(256, 256, 4, 4, 0),nn.BatchNorm2d(256))
#         self.deconv_2 = nn.Sequential(nn.ConvTranspose2d(128, 256, 2, 2, 0),nn.BatchNorm2d(256))
#         self.deconv_3 = nn.Sequential(nn.ConvTranspose2d(128, 256, 1, 1, 0),nn.BatchNorm2d(256))#         self.score_head = Conv2d(768, cfg.anchors_per_position, 1, 1, 0, activation=False, batch_norm=False)
#         self.reg_head = Conv2d(768, 7 * cfg.anchors_per_position, 1, 1, 0, activation=False, batch_norm=False)#     def forward(self,x):
#         x = self.block_1(x)
#         x_skip_1 = x
#         x = self.block_2(x)
#         x_skip_2 = x
#         x = self.block_3(x)
#         x_0 = self.deconv_1(x)
#         x_1 = self.deconv_2(x_skip_2)
#         x_2 = self.deconv_3(x_skip_1)
#         x = torch.cat((x_0,x_1,x_2),1)
#         return self.score_head(x),self.reg_head(x)class VoxelNet(nn.Module):def __init__(self):super(VoxelNet, self).__init__()self.svfe = SVFE()self.cml = CML()# self.rpn = RPN()def voxel_indexing(self, sparse_features, coords):"""sparse_features: [P, C]coords:[P, 4]"""dim = sparse_features.shape[-1]device = sparse_features.devicedense_feature = Variable(torch.zeros(dim, cfg.N, cfg.D, cfg.H, cfg.W).to(device))dense_feature[:, coords[:,0], coords[:,1], coords[:,2], coords[:,3]]= sparse_features.t()dense_feature = dense_feature.transpose(0, 1)# print(f"dense_feature.shape = {dense_feature.shape}")# return dense_feature.transpose(0, 1) # dense_feature.transpose(0, 1):[ B, C, D, H, W]return dense_featuredef forward(self, voxel_features, voxel_coords):"""P: 一个batch合计的 voxel的数量T:一个voxel的点数 35C:每个点 的维度 7(=4 + 3)voxel_features: [P, T, C]voxel_coords: [P, 4(1+3)] => 每个voxel_coords 含4个维度,分别为 1+3,这个1就是指代这个 voxel 原来在 batch的第几个(就是这样来区分的),3表示grid的坐标"""print(f"forward=============>")print(f'voxel_features.shape = {voxel_features.shape}, voxel_coords.shape = {voxel_coords.shape}') # voxel_features.shape = (20284, 35, 7), voxel_coords.shape = (20284, 4)# feature learning networkvwfs = self.svfe(voxel_features) # vwfs:[P, C]print(f'=> vwfs.shape = {vwfs.shape}') # vwfs.shape = torch.Size([20284, 128])vwfs = self.voxel_indexing(vwfs,voxel_coords)print(f'voxel_indexing ==> vwfs.shape = {vwfs.shape}') # voxel_indexing ==> vwfs.shape = torch.Size([2, 128, 10, 400, 352]) [B, C, D, H, W]# convolutional middle networkcml_out = self.cml(vwfs)print(f"cml_out.shape = {cml_out.shape}") # cml_out.shape = torch.Size([2, 64, 2, 400, 352])# # region proposal network# # merge the depth and feature dim into one, output probability score map and regression map# psm,rm = self.rpn(cml_out.view(cfg.N,-1,cfg.H, cfg.W))# return psm, rmif __name__ == '__main__':class My_dataset(data.Dataset):def __init__(self, cfg):self.T = cfg.Tself.vd = cfg.vdself.vh = cfg.vhself.vw = cfg.vwself.xrange = cfg.xrangeself.yrange = cfg.yrangeself.zrange = cfg.zrangedef preprocess(self, lidar):# shuffling the pointsnp.random.shuffle(lidar)voxel_coords = ((lidar[:, :3] - np.array([self.xrange[0], self.yrange[0], self.zrange[0]])) / (self.vw, self.vh, self.vd)).astype(np.int32)# convert to  (D, H, W)voxel_coords = voxel_coords[:,[2,1,0]]voxel_coords, inv_ind, voxel_counts = np.unique(voxel_coords, axis=0, \return_inverse=True, return_counts=True)voxel_features = []for i in range(len(voxel_coords)):voxel = np.zeros((self.T, 7), dtype=np.float32)pts = lidar[inv_ind == i]if voxel_counts[i] > self.T:pts = pts[:self.T, :]voxel_counts[i] = self.T# augment the pointsvoxel[:pts.shape[0], :] = np.concatenate((pts, pts[:, :3] - np.mean(pts[:, :3], 0)), axis=1)voxel_features.append(voxel)return np.array(voxel_features), voxel_coords def detection_collate(batch):voxel_features = []voxel_coords = []for i, sample in enumerate(batch):voxel_features.append(sample[0])voxel_coords.append(np.pad(sample[1], ((0, 0), (1, 0)),mode='constant', constant_values=i))return np.concatenate(voxel_features), \np.concatenate(voxel_coords), \from config import config as cfg    my_dataset = My_dataset(cfg)        # lidar_file = self.lidar_path + '/' + self.file_list[i] + '.bin'lidar_file = r"D:\workspace\【代码能力提升-深度学习】\voxel_and_pillar_code\simple-pointpillar-main\simple-pointpillar-main\kitti_test_data\000000.bin"lidar = np.fromfile(lidar_file, dtype=np.float32).reshape(-1, 4) # (N, 4)print(f'lidar.shape = {lidar.shape}') # lidar.shape = (115384, 4)# 对单个样本lidar进行数据处理,包括数据增强,限制范围,预处理# 这里我们没有 gt_box3d ,所以忽略 aug_data# data augmentation# lidar, gt_box3d = aug_data(lidar, gt_box3d) # specify a rangelidar = utils.get_filtered_lidar(lidar )print(f'get_filtered_lidar => lidar.shape = {lidar.shape}') # get_filtered_lidar => lidar.shape = (62853, 4)voxel_features, voxel_coords = my_dataset.preprocess(lidar)print(f'voxel_features.shape = {voxel_features.shape}, voxel_coords.shape = {voxel_coords.shape}') print(f'type(voxel_features) = {type(voxel_features)}, type(voxel_coords) = {type(voxel_coords)}')# voxel_features.shape = (10142, 35, 7) = (P, T, C(4 + 3)), voxel_coords.shape = (10142, 3) (P, C(xyz))# type(voxel_features) = <class 'numpy.ndarray'>, type(voxel_coords) = <class 'numpy.ndarray'># 模拟一个 batch 两个样本batch = []batch.append((voxel_features, voxel_coords))batch.append((voxel_features, voxel_coords))batch_voxel_features, batch_voxel_coords = detection_collate(batch)# batch_voxel_features = Variable(torch.cuda.FloatTensor(batch_voxel_features)) # 变为 tensor类型batch_voxel_features = Variable(torch.FloatTensor(batch_voxel_features)) # 变为 tensor类型# print(f'batch_voxel_features.shape = {batch_voxel_features.shape}')# print(batch_voxel_coords[-5:])    model = VoxelNet()y = model(batch_voxel_features, batch_voxel_coords)

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

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

相关文章

春节寄快递贵?想要便宜寄快递?那是你没找到寄快递便宜的渠道!

春节将至&#xff0c;公司会发放一大批的年货礼品给员工们&#xff0c;来聊表这一年来的勤恳工作的心意。但是想要拿走这么多的年货&#xff0c;可不是一件容易的事情啊&#xff0c;这时候我们可以通过邮寄的方式把东西邮寄走&#xff0c;是不是省了很多事呢&#xff0c;不仅回…

如何过有「松弛感」的生活?

最近网上有一个词很流行&#xff0c;叫做「松弛感」。最早的出处似乎是这么一条微博&#xff1a;博主见到一家人出门旅游&#xff0c;行李全部被退回&#xff0c;空手抵达目的地&#xff0c;竟然没人紧张和生气&#xff0c;而是重新安排好行李后继续开心聊天&#xff0c;全程非…

Redis -- 前置知识

目录 简要 分布式系统 负载均衡 引入缓存 数据库分表 微服务 小结 简要 redis是存储数据在内存中, 定义变量就是在内存中, 但是redis是在分布式系统中, 才能真正发挥威力, 如果只是单机程序, 那么直接通过变量来存储数据的方式将是最优的选择. …

Unity 解释器模式(实例详解)

文章目录 示例1&#xff1a;基础解释器结构示例2&#xff1a;小于表达式&#xff08;LessThanExpression&#xff09;示例3&#xff1a;逻辑或表达式&#xff08;OrExpression&#xff09;示例4&#xff1a;逻辑非表达式&#xff08;NotExpression&#xff09;示例5&#xff1a…

DeepLearningSystem:开启深度学习之旅的全方位指南,一款深度学习系统性开源课程项目!

在当今这个数据驱动的时代&#xff0c;深度学习技术 已经成为了科技领域的一个重要分支。 对于有志于掌握这一技术的专业人士和学生来说&#xff0c;有一个项目值得特别关注——由chenzomi12开发的“深度学习系统”课程。 Star&#xff1a;6.2k GitHub&#xff1a;https://gi…

美睫师睫毛嫁接零基础学习,日式美睫与开花嫁接实战教学

一、教程描述 大家都说女人的钱好挣&#xff0c;这是因为每个女人在每年&#xff0c;都要花很多钱来打扮自己。本套教程是关于日式美睫和开花嫁接的&#xff0c;从零基础学习到店铺经营都有涉及&#xff0c;就做美睫和睫毛嫁接这两项业务&#xff0c;月收入万元以上应该问题不…

系统引导程序 Boot Loader——学习笔记

基于嵌入式Linux 的完整系统软件由三个部分组成&#xff1a;系统引导程序、Linux 操作系统内核和文件系统。 系统引导程序 Boot Loader 是系统加电后运行的第一段软件代码&#xff0c;它的作用是加载操作系统或者其他程序到内存中&#xff0c;并将控制权交给它们。 Boot Load…

JMeter性能测试实战

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

C语言——O / 动态内存管理

一、为什么要有动态内存分配 我们已经掌握的内存开辟⽅式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的⽅式有两个特点&#xff1a; • 空间开辟⼤⼩是固定的。 • 数组在申明的时候&am…

RabbitMQ之死信交换机

前言 消息队列是分布式系统中常用的组件&#xff0c;用于异步通信、解耦和提高系统可靠性。然而&#xff0c;在实际应用中&#xff0c;难免会遇到一些异常情况&#xff0c;例如消息处理失败、超时等。为了更好地处理这些异常情况&#xff0c;死信交换机&#xff08;Dead Lette…

AS自治系统中的路由协议---RIP、OSPF、BGP

一、AS --- 自治系统 将网络分块管理 --- 由单一的机构或组织所管理 的一系列IP网络及其设备的集合 AS的管理&#xff1a;为了方便对AS进行管理&#xff0c;我们给AS设计了一个编号称为AS 号 --- 16位二进制构成 --- 0 - 65535 ---- 目前也存在拓展版的AS 号 --- 32位二进制构…

C++-类和对象(1)

目录 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 关于C中struct和class的区别&#xff1a; 4.2 封装 1.面向过程和面向对象初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&…

【论文阅读|半监督小苹果检测方法S3AD】

论文题目 &#xff1a; : Semi-supervised Small Apple Detection in Orchard Environments 项目链接&#xff1a;https://www.inf.uni-hamburg.de/en/inst/ab/cv/people/wilms/mad.html 摘要&#xff08;Abstract&#xff09; 农作物检测是自动估产或水果采摘等精准农业应用不…

代码迁移,合并双仓库

文章目录 前言一、如何对两个仓库进行合并二、具体操作步骤A仓库新分支第一步 移除controller以及接口第二步 移除service以及实现类impl第三步 移除dao层&#xff08;mapper以及xml&#xff09;第四步 移除pojo、cache、config、enum以及util等&#xff0c;并复查service第五步…

python 基础知识点(蓝桥杯python科目个人复习计划27)

今日复习内容&#xff1a;基础算法中的递归 1.介绍 递归&#xff1a;通过自我调用来解决问题的函数递归通常把一个复杂的大问题层层转化为一个与原问题相似的规模较小的问题来解决 递归要注意&#xff1a;&#xff08;1&#xff09;递归出口&#xff1b;&#xff08;2&#x…

一招搞定msvcr120.dll丢失怎样修复的问题,快速修复msvcr120.dll文件

msvcr120.dll丢失的问题时电脑中常见的几种问题&#xff0c;关于msvcr120.dll丢失的问题都有什么办法可以有效的解决文件丢失的问题&#xff0c;今天我们就来聊聊关于msvcr120.dll丢失怎样修复的方法都有哪些&#xff1f; 一.一招搞定msvcr120.dll丢失问题 为了解决msvcr120.d…

05 SB3之Spring Initializr+运行方式+自动配置原理(TBD)

1. 使用IDEA内置Spring Initializr 生成SB项目 最上方Server URL可以选择借助哪个平台生成, 可选阿里云 作为对比 , 官方可选版本最旧为3.1.18 ; 阿里云可选最新版本为3.0.2 本次选择3.1.8版本, 并且添加Spring Web依赖(包括RESTful / Spring MVC/)和Lombok依赖 生成后端项目…

野火霸道V2学习笔记

STM32F103学习笔记 STM32F103学习笔记说明基础配置配置KeilMDK配置串口下载程序美化Keil界面配置VScode 理论知识STM32命名方式例子 置位与清零GPIOGPIO简介GPIO和引脚的区别引脚的分类 GPIO 框图讲解保护二极管推挽输出推挽输出的含义推挽输出的原理推挽输出的特点推挽输出的应…

TCO AF488,AF488 反式环辛烯,可以用于各种生物实验中,如免疫荧光染色、细胞追踪、核酸检测等

AF488 TCO&#xff0c;Alexa Fluor TCO&#xff0c;TCO AF488&#xff0c;AF488 反式环辛烯&#xff0c;可以用于各种生物实验中&#xff0c;如免疫荧光染色、细胞追踪、核酸检测等 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;AF488 TCO&#xff0c;Alexa Fluo…

如何使用Docker部署火狐浏览器并实现无公网ip远程访问

文章目录 1. 部署Firefox2. 本地访问Firefox3. Linux安装Cpolar4. 配置Firefox公网地址5. 远程访问Firefox6. 固定Firefox公网地址7. 固定地址访问Firefox Firefox是一款免费开源的网页浏览器&#xff0c;由Mozilla基金会开发和维护。它是第一个成功挑战微软Internet Explorer浏…