OpenPCDet安装、使用方式及自定义数据集训练

news2024/12/30 2:57:59

OpenPCDet安装、使用方式及自定义数据集训练

个人博客

OpenPCDet安装

# 先根据自己的cuda版本,安装对应的spconv
pip install spconv-cu113

# 下载OpenPCDet并安装
git clone https://github.com/open-mmlab/OpenPCDet.git
cd OpenPCDet
pip install -r requirements.txt 
python setup.py develop

# 安装open3d(可视化工具)(推荐)
pip install open3d

# (可选)安装mayavi(可视化工具)
pip install vtk
pip install mayavi
pip install PyQt5

安装完,就可以运行demo.py,测试一下。(需要准备好模型和数据文件)

python demo.py --cfg_file cfgs/kitti_models/pv_rcnn.yaml --ckpt pv_rcnn_8369.pth --data_path ../data/kitti/testing/velodyne/000001.bin

如果出现SharedArray相关的错误的话,可以适当的降低其版本。例如pip install -U SharedArray==3.1

注意,demo.py运行成功需要在具有显示设备的条件下,如果只有终端的话是无法运行成功的。

KITTI数据集训练

首先需要准备KITTI数据集,为了快速训练演示,选取100个数据进行训练。将数据集按照以下目录格式存放。

OpenPCDet
├── data
│   ├── kitti
│   │   │── ImageSets
│   │   │── training
│   │   │   ├──calib & velodyne & label_2 & image_2 & (optional: planes) & (optional: depth_2)
│   │   │── testing
│   │   │   ├──calib & velodyne & image_2
├── pcdet
├── tools

ImageSets中存在train.txt val.txt test.txt文本,其内容为训练、验证和测试使用的数据。

运行下面的代码以生成infos,生成的文件可在data/kitti找到。

python -m pcdet.datasets.kitti.kitti_dataset create_kitti_infos tools/cfgs/dataset_configs/kitti_dataset.yaml

openPCDet的可训练网络配置(KITTI数据集)存放在cfgs/kitti_models目录下。以pv_rcnn训练为例,由于本次没有使用planes数据,将pv_rcnn.yaml中的USE_ROAD_PLANE改成False。之后在tools目录下运行下面代码即可进行训练。

python train.py --cfg_file cfgs/kitti_models/pv_rcnn.yaml --batch_size 1 --workers 1 --epochs 10

在这里插入图片描述

训练结束后可以在output/kitti_models目录中找到模型文件。

训练注意事项—train.py(以KITTI数据集为例)

  1. 如果不指定具体的ckpt,train.py中会默认加载最新的ckpt。换句话说,如果上一次训练pv_rcnn网络的epochs为5,得到了5个ckpt,下一次训练pv_rcnn的时候没有指定ckpt且epochs值小于等于5,那么就不会进入训练而是直接进入eval。如果本次epochs为大于5的值,则会接着epochs=5的ckpt训练。

    # trian.py中的相关代码
    # 如果ckpt不为None的话,就加载该ckpt,并从指定ckpt的epoch开始训练
    if args.ckpt is not None:
        it, start_epoch = model.load_params_with_optimizer(args.ckpt, to_cpu=dist_train, optimizer=optimizer, logger=logger)
        last_epoch = start_epoch + 1
    else:
        # 如果为None的话,会默认加载最新的ckpt
        ckpt_list = glob.glob(str(ckpt_dir / '*.pth'))
    
        if len(ckpt_list) > 0:
            # 按时间进行排序
            ckpt_list.sort(key=os.path.getmtime)
            while len(ckpt_list) > 0:
                try:
                    it, start_epoch = model.load_params_with_optimizer(
                        ckpt_list[-1], to_cpu=dist_train, optimizer=optimizer, logger=logger
                    )
                    last_epoch = start_epoch + 1
                    break
                except:
                    ckpt_list = ckpt_list[:-1]
    
  2. max_ckpt_save_num参数代表最大保存ckpt的数量。如果当前ckpt的数量多于最大保存ckpt数量,那么会删除几个时间最早的ckpt。默认为30.

    # train_utils.py中的相关代码
    ckpt_list = glob.glob(str(ckpt_save_dir / 'checkpoint_epoch_*.pth'))
    ckpt_list.sort(key=os.path.getmtime)
    
    # 如果当前ckpt的数量多于最大保存ckpt数量,那么会删除几个时间最早的ckpt
    if ckpt_list.__len__() >= max_ckpt_save_num:
        for cur_file_idx in range(0, len(ckpt_list) - max_ckpt_save_num + 1):
            os.remove(ckpt_list[cur_file_idx])
    
  3. ckpt_save_time_interval参数代表每隔{ckpt_save_time_interval}秒保存一次ckpt。默认为300

  4. train.py中的eval使用的数据集是kitti_dataset.yaml中的test值。默认配置下,kitti_dataset.yaml中test值为val,如下面代码所示:

    # kitti_dataset.yaml
    """
    总得来说,在训练的时候'train'和'test'分别对应训练集和验证集;在测试的时候'test'对应测试集。
    所以需要根据训练和测试任务更换test的配置。
    """
    
    # 需要加载的文件名称,默认为train.txt val.txt
    DATA_SPLIT: {
        'train': train,
        'test': val
    }
    
    # 需要加载的pkl文件 可以设置多个
    INFO_PATH: {
        'train': [kitti_infos_train.pkl],
        'test': [kitti_infos_val.pkl],
    }
    

如果想要修改val数据集,就需要修改DATA_SPLITINFO_PATH中的test值。

  1. num_epochs_to_eval参数代表只评估最后{num_epochs_to_eval}个epoch。比如当num_epochs_to_eval为1的时候,总epochs为5,那么只会评估后面4个epoch。默认为0,也就是每个epoch都评估。

  2. build_dataloader函数中

    # 使用变量 dataset_cfg.DATASET 中指定的数据集类型,创建一个数据集对象 dataset
    # 如果dataset_cfg.DATASET为KittiDataset,那么返回的dataset为kitti_dataset.py中的KittiDataset类型
    dataset = __all__[dataset_cfg.DATASET](
        dataset_cfg=dataset_cfg,
        class_names=class_names,
        root_path=root_path,
        training=training,
        logger=logger,
    )
    
  3. cfg_from_yaml_file函数将网络yaml和数据集yaml合并在一起,如果存在相同的key,则用网络yaml相应的val替换。因此如果网络yaml和数据集yaml中存在相同类型的配置,比如数据增强,那么最终训练使用的配置是网络yaml中的配置。实现这部分的相关代码可以在config.py中找到。

测试注意事项—test.py(以KITTI数据集为例)

  1. 运行test.py使用的测试数据集可以在kitti_dataset.yaml中的DATA_SPLITINFO_PATH找到相关配置。其中,它加载的测试数据集是INFO_PATH中的test值,这个值是一个列表,里面可以填多个.pkl文件,这部分的加载代码可以在kitti_dataset.pyinclude_kitti_data函数中找到;DATA_SPLIT中的test值默认为val,表示加载val.txt。

    所以说如果想要修改测试数据集就需要修改DATA_SPLITINFO_PATH中的test值。

    # kitti_dataset.yaml
    """
    总得来说,在训练的时候'train'和'test'分别对应训练集和验证集;在测试的时候'test'对应测试集。
    所以需要根据训练和测试任务更换test的配置。
    """
    # 需要加载的文件名称,默认为train.txt val.txt
    DATA_SPLIT: {
        'train': train,
        'test': val
    }
    
    # 需要加载的pkl文件 可以设置多个
    INFO_PATH: {
        'train': [kitti_infos_train.pkl],
        'test': [kitti_infos_val.pkl],
    }
    

demo.py和open3d_vis_utils.py分析(以KITTI数据集为例)

  1. 使用Demo.py时,如果传入数据集参数时文件夹,则会获取文件夹所有符合后缀条件(.bin/.npy)的文件。

  2. 模型预测结果pred_dicts是一个列表,列表元素为字典,字典包含’pred_boxes’, ‘pred_scores’, 'pred_labels’三个键。

  3. 在open3d_vis_utils.py开头加入下面代码,目的是方式警告。不知道是什么原因,使用的时候一直会报颜色设置错误的警告。

    # 关闭警告
    open3d.utility.set_verbosity_level(open3d.utility.VerbosityLevel.Error)
    
  4. draw_box函数中可以得到box3d,它是OrientedBoundingBox类型,所以可以通过get_box_pointsget_center等函数获得相应的点坐标。

  5. 可以在draw_box函数中加入下面代码,功能是给方框标记中心点,中心点的颜色与方框相同。

    # 给方框标记中心点
    if center:
        sphere_center = open3d.geometry.TriangleMesh.create_sphere(radius=0.1)
        sphere_center.paint_uniform_color(box_colormap[ref_labels[i]])
        sphere_center.translate(box3d.get_center())
        vis.add_geometry(sphere_center)
    
  6. 可以在open3d_vis_utils.py中添加下面的代码用于绘制POINT_CLOUD_RANGE。如图所示,红色框为点云边界框。

    # 根据最大边界点和最小边界点画出方框---在这里是用于画POINT_CLOUD_RANGE的,该参数配置于voxset.yaml
    min_bound = [0, -39.68, -3]
    max_bound = [69.12, 39.68, 1]
    bbox = open3d.geometry.AxisAlignedBoundingBox(min_bound, max_bound)
    bbox_lines = open3d.geometry.LineSet.create_from_axis_aligned_bounding_box(bbox)
    bbox_lines.paint_uniform_color([1, 0, 0])
    vis.add_geometry(bbox_lines)
    

在这里插入图片描述

使用自定义数据集进行训练

官方教程

准备数据集

首先需要按照官方教程创建文件目录,如下所示。

OpenPCDet
├── data
│   ├── custom
│   │   │── ImageSets
│   │   │   │── train.txt
│   │   │   │── val.txt
│   │   │── points
│   │   │   │── 000000.npy
│   │   │   │── 999999.npy
│   │   │── labels
│   │   │   │── 000000.txt
│   │   │   │── 999999.txt
├── pcdet
├── tools

使用SUSTechPOINTS标注完后会得到json格式的标签文件,我们需要提取有用的内容再保存成txt格式。

OpenPCDet支持的自定义label文件格式如下所示

# format: [x y z dx dy dz heading_angle category_name]
1.50 1.46 0.10 5.12 1.85 4.13 1.56 Vehicle
5.54 0.57 0.41 1.08 0.74 1.95 1.57 Pedestrian

其中最后一个heading_angle,就是json标签文件中的rotation的z值。不过这个地方需要确保自己点云坐标系(激光雷达坐标系)与OpenPCDet中规定的坐标系是一致的,不然还需要转换成OpenPCDet坐标系。

在这里插入图片描述

可以使用下面这个函数实现将json格式的标签文件转换成符合OpenPCDet训练的标签文件。

def json_to_txt(json_path, txt_path):
    """
    将json文件转化成符合OpenPCDet训练的标签文件
    	json_path: json文件所在目录
    	txt_path: 生成txt文件目录
    """
    json_list = os.listdir(json_path)
    for json_name in json_list:
        json_file = os.path.join(json_path, json_name)
        
        with open(json_file, 'r') as f:
            data = json.load(f)
        label_list = []
        for obj_dict in data:
            label_name = obj_dict["obj_type"]
            pos_xyz = obj_dict["psr"]["position"]
            rot_xyz = obj_dict["psr"]["rotation"]
            scale_xyz = obj_dict["psr"]["scale"]
            
            temp = ""
            for xyz_dict in [pos_xyz, scale_xyz]:
                for key in ["x", "y", "z"]:
                    temp += str(xyz_dict[key])
                    temp += " "
            temp += str(rot_xyz["z"]) + " " + str(label_name) + "\n"
            label_list.append(temp)

        txt_name = json_name.split(".")[0] + ".txt"
        with open(os.path.join(txt_path, txt_name), "w") as f:
            for label in label_list:
                f.write(label)

除了需要转换标签文件,还需要将pcd格式的点云转换成npy格式。可以使用下面的函数实现。

def pcd_to_npy(pcd_path, npy_path):
    """
        将pcd文件转换成npy文件
        pcd_path: pcd格式点云目录
        npy_path: npy格式点云输出目录
    """
    pcd_list = os.listdir(pcd_path)
    for pcd_name in pcd_list:
        pcd_file = os.path.join(pcd_path, pcd_name)
        lidar = []
        pcd = o3d.io.read_point_cloud(pcd_file)
        points = np.array(pcd.points)

        for linestr in points:
            if len(linestr) == 3:  # only x,y,z
                linestr_convert = list(map(float, linestr))
                linestr_convert.append(0)
                lidar.append(linestr_convert)
            if len(linestr) == 4:  # x,y,z,i
                linestr_convert = list(map(float, linestr))

        lidar = np.array(lidar).astype(np.float32)
        np.save(os.path.join(npy_path, pcd_name[:-4]+".npy"), lidar)

可以使用下面的函数快速生成train.txt和val.txt

def get_train_val_txt(src_path, dst_path, num_of_train):
    """
    	src_path: 标签文件目录
    	dst_path: 输出文件目录
    	num_of_train: 训练集样本数量
    """
    src_list = os.listdir(src_path)
    random.shuffle(src_list)
    with open(os.path.join(dst_path, "train.txt"), 'w') as f:
        for index in range(num_of_train):
            f.write(src_list[index].split(".")[0])
            f.write('\n')
    with open(os.path.join(dst_path, "val.txt"), 'w') as f:
        for index in range(num_of_train, len(src_list)):
            f.write(src_list[index].split(".")[0])
            f.write('\n')

将npy点云、txt标签、train.txt和val.txt放到指定目录下。修改custom_dataset.py,需要根据自己数据集修改分类类别。

在这里插入图片描述

修改custom_dataset.yaml。主要关注以下内容:

# 与KITTI数据集映射   左边是自己数据集   右边是KITTI数据集
# 这个地方只会在eval阶段会用到,所以如果自己不需要eval的话可以不加
MAP_CLASS_TO_KITTI: {
    # 'Vehicle': 'Car'
    'Pedestrian': 'Pedestrian',
    'BicycleRider': 'Cyclist',
}
# 需要与自己的点云数据格式对应,一般不需要改
POINT_FEATURE_ENCODING: {
    encoding_type: absolute_coordinates_encoding,
    used_feature_list: ['x', 'y', 'z', 'intensity'],
    src_feature_list: ['x', 'y', 'z', 'intensity'],
}
DATA_AUGMENTOR:
    DISABLE_AUG_LIST: ['placeholder']
    AUG_CONFIG_LIST:
        - NAME: gt_sampling
          USE_ROAD_PLANE: False
          DB_INFO_PATH:
              - custom_dbinfos_train.pkl
          PREPARE: {
          	# 需要改成自己的数据集类别
            filter_by_min_points: ['Pedestrian:5', 'BicycleRider:5'],
            # filter_by_difficulty: [-1], # 这个地方如果不注释的话训练可能会报错,可以自己尝试一下
          }
		  # 需要改成自己的数据集类别
          SAMPLE_GROUPS: [Pedestrian:15', 'BicycleRider:15']
          NUM_POINT_FEATURES: 4
          DATABASE_WITH_FAKELIDAR: False
          REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
          LIMIT_WHOLE_SCENE: True

最后在命令行运行下面命令。如果不报错的话就可以得到训练数据集了。

python -m pcdet.datasets.custom.custom_dataset create_custom_infos tools/cfgs/dataset_configs/custom_dataset.yaml

修改网络yaml配置文件

# 要改成自己的类别
CLASS_NAMES: ['Pedestrian', 'BicycleRider']
# 需要修改成custom_dataset.yaml
_BASE_CONFIG_: cfgs/dataset_configs/custom_dataset.yaml
# 点云范围 [x_min, y_min, z_min, x_max, y_max, z_max]
# 需要和VOXEL_SIZE满足倍数关系。X和Y轴与体素需要满足16倍的关系。详细配置可以看官方教程。
POINT_CLOUD_RANGE: [0, -15.36, -2, 15.36, 15.36, 2]
# 按照自己数据集修改
DATA_AUGMENTOR:
    DISABLE_AUG_LIST: ['placeholder']
    AUG_CONFIG_LIST:
        - NAME: gt_sampling
        # 该数据增强方法起源于SECOND网络,作者将其他帧ground truth矩形框内的点云抽取出来放在当前帧的空余位置,
        # 以此来形成“新”一帧的训练数据,达到数据增强的目的。
          USE_ROAD_PLANE: False
          DB_INFO_PATH:
              - custom_dbinfos_train.pkl
          PREPARE: {
            # 保留至少5个点的车辆、行人和骑行者
            filter_by_min_points: ['Pedestrian:5', 'BicycleRider:5'],
            # filter_by_difficulty: [-1],
          }
            # 指定需要采样的物体类别和数量
          SAMPLE_GROUPS: ['Pedestrian:15', 'BicycleRider:15']
          NUM_POINT_FEATURES: 4
          DATABASE_WITH_FAKELIDAR: False
          REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
          LIMIT_WHOLE_SCENE: False
# anchor配置,需要适配自己的数据集
ANCHOR_GENERATOR_CONFIG: [
    {
        'class_name': 'Pedestrian',
        # 尺寸 长宽高 单位为米
        'anchor_sizes': [[0.75, 0.66, 1.73]],
        'anchor_rotations': [0, 1.57],    # 旋转角度 0°和90°(弧度π/2=1.57)表示anchor可以沿水平和垂直方向旋转
        'anchor_bottom_heights': [-0.6],    # 底部高度,离地面的高度
        'align_center': False,    # 居中对齐
        'feature_map_stride': 1,    # 特征图步幅
        'matched_threshold': 0.5,    # 匹配阈值    高于这个阈值的被认为是正样本
        'unmatched_threshold': 0.35    # 不匹配阈值    低于这个阈值的被认为是负样本
    },
    {
        'class_name': 'BicycleRider',
        'anchor_sizes': [[1.83, 0.74, 1.64]],
        'anchor_rotations': [0, 1.57],
        'anchor_bottom_heights': [-0.6],
        'align_center': False,
        'feature_map_stride': 1,
        'matched_threshold': 0.5,
        'unmatched_threshold': 0.35
    }
]

可以使用下面的代码获取自己类别的平均anchor

# 获取每个类别的评价lwh,以此来设置anchor

import os

if __name__ == "__main__":
    label_path = "data/custom/labels"
    label_list = os.listdir(label_path)
    # l w h
    P_counts = 0
    Pedestrian = [0.0, 0.0, 0.0]
    B_counts = 0
    BicycleRider = [0.0, 0.0, 0.0]

    for label_name in label_list:
        label_file = os.path.join(label_path, label_name)
        with open(label_file, 'r') as f:
            data = f.readlines()
            for line in data:
                temp_list = line.split(" ")
                cls_name = temp_list[-1][:-1]
                if cls_name == "Pedestrian":
                    Pedestrian[0] += float(temp_list[3])
                    Pedestrian[1] += float(temp_list[4])
                    Pedestrian[2] += float(temp_list[5])
                    P_counts += 1
                else:
                    BicycleRider[0] += float(temp_list[3])
                    BicycleRider[1] += float(temp_list[4])
                    BicycleRider[2] += float(temp_list[5])
                    B_counts += 1

    print(f"P l{Pedestrian[0]/P_counts} w{Pedestrian[1]/P_counts} h{Pedestrian[2]/P_counts}")
    print(f"B l{BicycleRider[0]/B_counts} w{BicycleRider[1]/B_counts} h{BicycleRider[2]/B_counts}")

按照上述要求修改完就可以训练了。

最后分享一下我自己拍摄标注的数据集:点我下载

这个数据集是以树为目标的,已经处理成自定义数据集的格式了,可以直接使用。

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

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

相关文章

Jetpack Compose 中安全地消耗Flow

Jetpack Compose 中安全地消耗Flow 以 Lifecycle 为周期的方式收集流是在 Android 上收集流的推荐方式。如果您正在使用 Jetpack Compose 构建 Android 应用程序,则使用 collectAsStateWithLifecycle API 可以在 Lifecycle 为周期的方式下从您的 UI 中收集流。 co…

利用Jmeter做接口测试(功能测试)全流程分析

利用Jmeter做接口测试怎么做呢?过程真的是超级简单。 明白了原理以后,把零碎的知识点填充进去就可以了。所以在学习的过程中,不管学什么,我一直都强调的是要循序渐进,和明白原理和逻辑。这篇文章就来介绍一下如何利用…

CP2102 USB转UART国产桥接芯片 DPU02

芯片概述: DPU02是一个高度集成的USB转UART的桥接控制器,该产品提供了一个简单的解决方案,可将RS-232设计更新为USB设计,并简化PCB组件空间。该DPU02包括了一个USB2.0全速功能控制器、USB收发器、振荡器、EEPROM和带有完整调制解调控制信号的…

通过宝塔辅助部署本地Python爬虫项目到阿里云轻量服务器

文章目录 一、 上传项目文件二、准备项目环境2.1、安装 requirements.txt 依赖2.2、安装 node.js 环境2.3、阿里云服务器MySQL 8.0开启远程连接2.4、本地远程连接MySQL测试2.4.1、navicat 远程连接测试2.4.2、python 代码连接测试 笔记:最近想把本地的一套爬虫项目给…

SpringBoot——启动源码(一)

SpringBootApplication注解 前言SpringBootApplicationSpringBootConfigurationEnableAutoConfigurationimport注解 ComponentScan 总结 前言 Springboot作为Spring的脚手架,其本质核心并不提供Spring核心功能,作用就是开发者快速构建,预置三…

怎么转换英文音频成文字?英文音频转文字app分享

两位朋友正在讨论如何将一段英文讲座的音频转换成文字,以便于学习和理解。 Sophia:嗨,我最近听了一段非常精彩的英文讲座,但是对于我来说,理解听到的内容有些困难。你知道有什么方法可以将英文音频转换成文字吗&#…

SpringBoot内置Logback日志的学习

一、日志级别 日志级别有TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF。 TRACE级别最低 DEBUG 的级别低,当需要打印调试信息的话,就用这个级别,不建议在生产环境下使用。 INFO 的级别高一些,当一些重要的信息需要打印的时候&#x…

虹科多功能电流电压采集方案

01电流电压采集基础概念和应用 电流采集、电压采集、电能充电量测试和功率测试在不同领域都有着广泛的应用 ▲汽车电子:电池管理系统BMS、发动机控制系统、车身电子系统 ▲航空航天:飞行控制系统,航空电源管理系统、航空电子设备 ▲消费电…

如何学习和提升使用编程语言的能力? - 易智编译EaseEditing

学习编程语言并提升编程能力需要一定的学习方法和实践。以下是一些方法可以帮助你提升编程语言能力: 学习基本语法: 了解编程语言的基本语法和关键概念。可以通过阅读官方文档、教程、书籍或在线资源来学习。 编写代码: 编写实际的代码是提…

入门孪生网络Siamese Network,我将会分几个博客来逐步阐述我对孪生网络的理解和应用---初步介绍0

文章目录 前言一、孪生网络(Siamese Network)的基本概念二、孪生网络(Siamese Network)的优点三、利用孪生网络进行故障诊断/分类的思路假如我有一堆数据,它可以是轴承故障数值数据,也可以是图像数据,我想进行二分类&a…

ESP32-C2-12模组 AT固件例程

ESP32C2 AT固件使用 ESP32 C2模组,如图1-1所示 图1-1 ESP32 C2模组 ESP32 C2开发板,如图1-2所示 图1-2 ESP32 C2开发 方案亮点 1、完整的 WiFi 子系统,符合 IEEE 802.11b/g/n 协议,具有 Station 模式、SoftAP 模式、SoftAP Stat…

Vue中如何只传递一个人员Name同时把人员Id也传递过去

前言: 根据项目需求,在修改功能中,要求展示一个人员的下拉框,但是又要把人员ID在点击提交时传递过去,一般这种情况有2种解决方法:一是 通过map遍历匹配;二是 在选中人员时将人员ID获取到&#x…

编辑和校对魔法:让文字焕发生机的秘诀

编辑和校对是写作过程中的关键环节,可以让你的文字更加精炼、清晰、引人入胜。以下是一些编辑和校对的秘诀,可以让你的文字焕发生机。 1.保持客观 从读者的角度审视文章,保持客观和中立。确保内容清晰、观点明确,同时避免主观情感…

运筹系列81:LKH代码分析

1. 基本数据结构 基础的node定义在LKH.h中 用于2-level tree的segment定义如下: LKH可以使用3种数据结构,默认是2-level tree: 2-level tree的flip操作(即2-opt算子),在Flip_SL.c中,特殊的地…

请问python如何处理url带有“?”参数的接口?

在Python中处理带有"?"参数的URL接口,可以使用urllib.parse库中的urlencode()函数来进行编码。以下是一些示例代码 如果你想学习自动化测试,我这边给你推荐一套视频,这个视频可以说是B站百万播放全网第一的自动化测试教程&#x…

详解python排序的5种高级用法

来源:投稿 作者:Fairy 编辑:学姐 排序是编程中常用的操作之一。Python提供了多种排序方法,可以适用于不同的排序需求。 那么,今天我们将介绍Python中常用的 5 种列表排序方法。 「1.使用sorted()函数和lambda表达式」…

常见的JS存储方式及其特点

在前端开发中,经常需要在浏览器端存储和管理数据。为了实现数据的持久化存储和方便的访问,JavaScript提供了多种数据存储方式。本文将介绍几种常见的前端JS数据存储方式及其特点。 1. Cookie Cookie是一种小型的文本文件,由浏览器保存在用户…

VMware Workstation下载与安装(适用于在官网注册好账号的朋友,许可证秘钥请自行网上搜索获取)

一、VMware Workstation下载 第一步:点击下方“Resources”链接,进入Vmware官网(本教程适用于已经在官网注册好账号并已经进行登陆的朋友): Resources 第二步:点击“Product Downloads”,进入…

GIT分支管理(随笔)

目录 前言 一、概念原理 1、分支 2、原理 说明1: 说明2: 二、分支操作 1、查看分支 2、创建分支 3、切换分支 4、删除分支 5、合并分支 6、合并冲突 7、重命名分支 8、编制分支的介绍 三、标签 1、概念 2、操作 总结 前言 分支&#xff0…

C语言---关键字static的初步剖析

🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:凡人修C传 💬总结:希望你看完之后&…