地平线 3D 目标检测 Bevformer 参考算法-V2.0

该示例为参考算法,仅作为在 征程 6 上模型部署的设计参考,非量产算法

简介

BEVFormer 是当前热门的自动驾驶系统中的 3D 视觉感知任务模型。BEVFormer 是一个端到端的框架,BEVFormer 可以直接从原始图像数据生成 BEV 特征,无需依赖于传统的图像处理流程。它通过利用 Transformer 架构和注意力机制,有效地从多摄像头图像中学习生成高质量的鸟瞰图(Bird’s-Eye-View, BEV)特征表示。相较于其他的 BEV 转换方式:

  1. 时空注意力机制:模型结合了空间交叉注意力(Spatial Cross-Attention, SCA)和时间自注意力(Temporal Self-Attention, TSA),使网络能够同时考虑空间和时间维度上的信息。融合历史 bev 特征来提升预设的 BEV 空间中的 query 的自学能力,得到 bev 特征。
  2. Deformable attn:通过对每个目标生成几个采样点和采样点的 offset 来提取采样点周围的重要特征,即只关注和目标相关的特征,减少计算量。
  3. transformer 架构:能够有效捕捉序列中的长期依赖关系,适用于处理图像序列。

性能精度指标

模型参数:

在这里插入图片描述

性能精度表现:

在这里插入图片描述

模型介绍

在这里插入图片描述

·公版 BEVFormer 模型主要可以分为以下几个关键部分:

  1. Backbone 网络:用于从多视角摄像头图像中提取特征,本文为 tiny 版本,因此为 ResNet50。
  2. 时空特征提取:BEVFormer 通过引入时间和空间特征来学习 BEV 特征。具体来说,模型包括:
  3. Temporal Self-Attention(时间自注意力):利用前一时刻的 BEV 特征作为历史特征,通过自注意力机制来计算当前时刻的 BEV 特征。
  4. Spatial Cross-Attention(空间交叉注意力):进行空间特征注意力,融合多视角图像特征。
  5. Deformable Attention(可变形注意力):BEVFormer 使用可变形注意力机制来加速运算,提高模型对不同视角图像特征的适应性。
  6. BEV 特征生成:通过时空特征的融合,完成环视图像特征向 BEV 特征的建模。
  7. Decoder:设计用于 3D 物体检测的端到端网络结构,基于 2D 检测器 Deformable DETR 进行改进,以适应 3D 空间的检测任务。

地平线部署说明

公版 bevformer 在 征程 6 上部署相比于 征程 5 来说更简单了,需要考虑的因素更少。征程 6 对非 4 维的支持可以和 4 维的同等效率,因此 征程 6 支持公版的注意力实现,不再限制维度,因此无需对维度做 Reshape,可直接支持公版写法。但需注意的是公版的 bev_mask 会导致动态 shape。征程 6 不支持动态输入,因此 bev_mask 无法使用。在精度上,我们修复了公版的 bug 已获得了精度上的提升,同时通过对关键层做 int16 的量化精度配置以保障 1%以内的量化精度损失。

下面将部署优化对应的改动点以及量化配置依次说明。

性能优化

改动点 1:

将 attention 层的 mean 替换为 conv 计算,使性能上获得提升。

/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/attention.py

self.query_reduce_mean = nn.Conv2d(
    self.num_bev_queue * self.reduce_align_num,
    self.reduce_align_num,
    1,
    bias=False,)

# init query_reduce_mean weight
query_reduce_mean_weight = torch.zeros(
    self.query_reduce_mean.weight.size(),       
    dtype=self.query_reduce_mean.weight.dtype,
)
for i in range(self.reduce_align_num):
    for j in range(self.num_bev_queue):
        query_reduce_mean_weight[i, j * self.reduce_align_num + i] = (
            1 / self.num_bev_queue
        )
self.query_reduce_mean.weight = torch.nn.Parameter(
    query_reduce_mean_weight, requires_grad=False
)

改动点 2:

公版中,在 Encoder 的空间融合模块,会根据 bev_mask 计算有效的 query 和 reference_points,输出 queries_rebatch 和 reference_points_rebatch,作用为减少交互的数据量,提升模型运行性能。对于稀疏的 query 做 crossattn 后再将 query 放回到 bev_feature 中。

以上提取稀疏 query 步骤的主要算子为 gather,放回 bev_feature 步骤的主要算子为 scatter。由于工具链对这两个算子暂未支持(gather 算子 930 已支持)而且 bev_mask 为动态的,为了提升模型的运行性能,工具链提供了 gridsample 算子的替换方式,index 计算只与内外参有关,因此作为前处理,将计算好的 index 作为模型输入即可。

gather

gather 为根据 bevmask 来提取稀疏 query,降低 cross attn 的数据量,提升运行效率。

代码路径:<code>/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/<span style="caret-color: #000000; color: #000000; font-family: monospace; font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: #e8e8e8; text-decoration: none; display: inline !important; float: none;">view_transformer.py</span>

        reference_points_cam = torch.clamp(
            reference_points_cam, min=-2.1, max=2.1
        )
        reference_points_cam = reference_points_cam.permute(2, 1, 3, 0, 4)
        bev_mask = bev_mask.permute(2, 1, 3, 0, 4).squeeze(-1)
        bev_mask_ori = bev_mask.clone()
        max_len = self.virtual_bev_h * self.virtual_bev_w
        queries_rebatch_grid = reference_points_cam.new_zeros(
            [B * self.numcam, self.virtual_bev_h, self.virtual_bev_w, 2]
        )
        for camera_idx, mask_per_img_bs in enumerate(bev_mask):
            for bs_id, mask_per_img in enumerate(mask_per_img_bs):
                temp_grid = (
                    torch.zeros(
                        (max_len, 2),
                        device=queries_rebatch_grid.device,
                        dtype=torch.float32,
                    )
                    - 1.5
                )
                index_query_per_img = (
                    mask_per_img.sum(-1).nonzero().squeeze(-1)
                )
                num_bev_points = index_query_per_img.shape[0]
                camera_idx_tensor_x = index_query_per_img % self.bev_w
                camera_idx_tensor_y = index_query_per_img // self.bev_w
                index_grid = torch.stack(
                    [
                        camera_idx_tensor_x / (self.bev_w - 1),
                        camera_idx_tensor_y / (self.bev_h - 1),
                    ],
                    dim=-1,
                )
                index_grid = index_grid * 2 - 1
                temp_grid[:num_bev_points] = index_grid
                temp_grid = temp_grid.reshape(
                    self.virtual_bev_h, self.virtual_bev_w, 2
                )
                queries_rebatch_grid[
                    bs_id * self.numcam + camera_idx
                ] = temp_grid
        reference_points_rebatch = (
            reference_points_cam.flatten(-2)
            .permute(1, 0, 3, 2)
            .flatten(0, 1)
            .reshape(B * self.numcam, D * 2, self.bev_h, self.bev_w)
        )
        reference_points_rebatch = (
            F.grid_sample(
                reference_points_rebatch,
                queries_rebatch_grid,
                mode="nearest",
                align_corners=True,
            )
            .flatten(-2)
            .permute(0, 2, 1)
            .reshape(B * self.numcam, max_len, D, 2)
        )

query_rebatch

        queries_rebatch = (
            query.unsqueeze(1)
            .repeat(1, self.num_cams, 1, 1)
            .reshape(
                bs * self.num_cams, self.bev_h, self.bev_w, self.embed_dims
            )
            .permute(0, 3, 1, 2)
        )
        queries_rebatch = F.grid_sample(
            queries_rebatch,
            queries_rebatch_grid,
            mode="nearest",
            align_corners=True,
        )
        queries_rebatch = queries_rebatch.flatten(-2).permute(0, 2, 1)
        reference_points_rebatch = reference_points_rebatch.flatten(
            -2
        ).unsqueeze(-2)

scatter

scatter 操作对经过 deformable_attention 后的 query 放入到 bevfeature 中,然后求平均。

代码路径为:/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/attention.py

        slots = self.restore_outputs(
            restore_bev_grid,
            queries_out,
            bev_pillar_counts,
            bs,
            queries_rebatch_grid,
        )
    def restore_outputs(
        self,
        restore_bev_grid: Tensor,
        queries_out: Tensor,
        counts: Tensor,
        bs: int,
        queries_rebatch_grid: Tensor,
    ):
        """Restore outputs to bev feature."""
        queries_out = queries_out.reshape(
            bs, self.num_cams, self.embed_dims, -1
        )
        queries_out = queries_out.permute(0, 2, 1, 3)
        queries_out = queries_out.reshape(
            bs,
            self.embed_dims,
            self.num_cams * queries_rebatch_grid.shape[1],
            queries_rebatch_grid.shape[2],
        )
        bev_queries = F.grid_sample(
            queries_out, restore_bev_grid, mode="nearest", align_corners=True
        )
        bev_queries = bev_queries.reshape(bs, -1, self.bev_h, self.bev_w)
        slots = self.query_reduce_sum(bev_queries).flatten(-2).permute(0, 2, 1)
        slots = self.mul_pillarweight.mul(slots, counts)
        return slots

其中 restore_bev_grid,根据 bevmask 反算回 bev_feature 的位置:

        restore_bev_grid = (
            reference_points_cam.new_zeros(
                B, self.max_camoverlap_num * self.bev_h, self.bev_w, 2
            )
            - 1.5
        )
        for bs_id, bev_mask_ in enumerate(bev_mask):
            bev_pillar_num_map = torch.zeros(
                (self.bev_h, self.bev_w), device=bev_mask_.device
            )
            count = bev_mask_.sum(-1) > 0
            camera_idxs, bev_pillar_idxs = torch.where(count)
            camera_idx_offset = 0
            for cam_id in range(self.numcam):
                camera_idx = torch.where(camera_idxs == cam_id)
                bev_pillar_idx_cam = bev_pillar_idxs[camera_idx[0]]
                num_camera_idx = len(camera_idx[0])
                camera_idx_tmp = camera_idx[0] - camera_idx_offset
                camare_tmp_idx_x = camera_idx_tmp % self.virtual_bev_w
                camare_tmp_idx_y = camera_idx_tmp // self.virtual_bev_w
                grid_x = camare_tmp_idx_x
                grid_y = cam_id * self.virtual_bev_h + camare_tmp_idx_y
                bev_pillar_idx_cam_x = bev_pillar_idx_cam % self.bev_w
                bev_pillar_idx_cam_y = bev_pillar_idx_cam // self.bev_w
                bev_pillar_num_map_tmp = bev_pillar_num_map[
                    bev_pillar_idx_cam_y, bev_pillar_idx_cam_x
                ]
                grid_h = (
                    bev_pillar_num_map_tmp * self.bev_h + bev_pillar_idx_cam_y
                ).to(torch.int64)
                grid_w = (bev_pillar_idx_cam_x).to(torch.int64)
                restore_bev_grid[bs_id, grid_h, grid_w, 0] = grid_x / (
                    self.virtual_bev_w - 1
                )
                restore_bev_grid[bs_id, grid_h, grid_w, 1] = grid_y / (
                    self.numcam * self.virtual_bev_h - 1
                )
                bev_pillar_num_map[
                    bev_pillar_idx_cam_y, bev_pillar_idx_cam_x
                ] = (
                    bev_pillar_num_map[
                        bev_pillar_idx_cam_y, bev_pillar_idx_cam_x
                    ]
                    + 1
                )
                camera_idx_offset = camera_idx_offset + num_camera_idx
        restore_bev_grid = restore_bev_grid * 2 - 1

精度优化

浮点精度

改动点 3:

公版通过 can_bus 初始化 ref 来做时序融合,然而这个时候 bev feat 并没有对齐,在 attention 计算时不能简单的 concat 起来。因此我们换了一种时序对齐的方式,通过前后两帧的 ego2global 坐标系转换矩阵将当前帧的 bev 特征和上一帧对齐,此时 ref 都是一样的。(非 征程 6 不支持,为公版 bug),精度上获得提升。

/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/view_transformer.py` `get_prev_bev` `get_fusion_ref
pre_scene = prev_meta["scene_token"]
for i in range(bs):
    if pre_scene[i] != cur_meta["meta"][i]["scene"]:
        prev_bev[i] = torch.zeros(
            (self.bev_h * self.bev_w, self.embed_dims),
            dtype=torch.float32,
            device=device,
        )
##公版:
shift_ref_2d = ref_2d.clone()
shift_ref_2d += shift[:, None, None, :]
bs, len_bev, num_bev_level, _ = ref_2d.shape
hybird_ref_2d = torch.stack([shift_ref_2d, ref_2d], 1).reshape(
    bs*2, len_bev, num_bev_level, 2)
##地平线版本
shift_ref_2d = ref_2d.clone()
bs, len_bev, num_bev_level, _ = ref_2d.shape
hybird_ref_2d = torch.stack([shift_ref_2d, ref_2d], 1).reshape(
    bs * 2, len_bev, num_bev_level, 2
)

改动点 4:

修复了个 tsa 公版的 batchsize 不等于 1 的 bug。BEVFormer/projects/mmdet3d_plugin/bevformer/modules/temporal_self_attention.py at master · fundament

量化精度

为量化精度保证,我们将以下的算子配置为 int16 或 int32 输出:

**view_transformer:**输入节点做 int16 量化:

        int16_models = [
            self.quant_hybird_ref_2d,
            self.quant_norm_coords,
            self.quant_restore_bev_grid,
            self.quant_reference_points_rebatch,
            self.quant_queries_rebatch_grid,
        ]
        for m in int16_models:
            m.qconfig = qconfig_manager.get_qconfig(
                activation_qat_qkwargs={"dtype": qint16},
                activation_calibration_qkwargs={
                    "dtype": qint16,
                },
                activation_calibration_observer="mix",
            )

**attention 层:**最后两个 conv 和 add 开启 int16

    def set_qconfig(self) -> None:
        """Set the quantization configuration."""
        from hat.utils import qconfig_manager
        int16_module = [
            self.output_proj,
            self.add_res,
        ]

**decoder 层:**cls_branches、reg_branches 的 conv 配置为 int32 输出;sigmoid 和 reference_points 配置为 int16

    def set_qconfig(self) -> None:
        """Set the quantization configuration."""
        from hat.utils import qconfig_manager
        for _, m in enumerate(self.cls_branches):
            m[0].qconfig = qconfig_manager.get_qconfig(
                activation_qat_qkwargs={"dtype": qint16},
                activation_calibration_qkwargs={
                    "dtype": qint16,
                },
                activation_calibration_observer="mix",
            )
            m[3].qconfig = qconfig_manager.get_qconfig(
                activation_qat_qkwargs={"dtype": qint16},
                activation_calibration_qkwargs={
                    "dtype": qint16,
                },
                activation_calibration_observer="mix",
            )
            m[-1].qconfig = qconfig_manager.get_default_qat_out_qconfig()
        self.reg_branches[-1][
            -1
        ].qconfig = qconfig_manager.get_default_qat_out_qconfig()
        self.query_embedding.qconfig = None
        int16_module = [
            self.reference_points,
            self.sigmoid,
        ]
        for m in int16_module:
            m.qconfig = qconfig_manager.get_qconfig(
                activation_qat_qkwargs={"dtype": qint16},
                activation_calibration_qkwargs={
                    "dtype": qint16,
                },
                activation_calibration_observer="mix",
            )

总结与建议

训练建议

  • 浮点和公版一致即可
  • qat 训练需要将 lr 降低,下降策略建议使用 StepDecayLrUpdater。

部署建议

  • 建议 bev size 的选择考虑性能影响。征程 6 相比于 征程 5 带宽增大,但仍需注意 bevsize 过大导致访存时间过长对性能的影响,建议考虑实际部署情况选择合适的 bevsize 做性能验证。
  • 使用 bevmask 来提升运行性能,可参考 4.1 章节使用 gridsample 替换不支持的 scatter。
  • 在注意力机制中存在一些 ElementWise 操作,对于导致性能瓶颈的可以考虑 conv 替换,对于造成量化风险的可以根据敏感度分析结果合理选择更高的量化精度,以确保注意力机制的部署。

本文通过对 Bevformer 在地平线征程 6 上量化部署的优化,使得模型在该计算方案上用低于 1%的量化精度损失,得到 latency 为 45.74ms 的部署性能,同时,通过 Bevformer 的部署经验,可以推广到其他模型部署优化,例如包含 MSDA 模型结构、transformer-based BEV 的部署。

附录

  1. 论文:https://arxiv.org/pdf/2203.17270
    的 bevsize 做性能验证。
  • 使用 bevmask 来提升运行性能,可参考 4.1 章节使用 gridsample 替换不支持的 scatter。
  • 在注意力机制中存在一些 ElementWise 操作,对于导致性能瓶颈的可以考虑 conv 替换,对于造成量化风险的可以根据敏感度分析结果合理选择更高的量化精度,以确保注意力机制的部署。

本文通过对 Bevformer 在地平线征程 6 上量化部署的优化,使得模型在该计算方案上用低于 1%的量化精度损失,得到 latency 为 45.74ms 的部署性能,同时,通过 Bevformer 的部署经验,可以推广到其他模型部署优化,例如包含 MSDA 模型结构、transformer-based BEV 的部署。

附录

  1. 论文:https://arxiv.org/pdf/2203.17270
  2. 公版代码:https://github.com/fundamentalvision/BEVFormer

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Deepseek 【大模型】之 Ollama 与 Ollama Web UI Lite 本地部署 Deepseek 可视化UI 访问模型的简单整理

Deepseek 【大模型】之 Ollama 与 Ollama Web UI Lite 本地部署 Deepseek 可视化UI 访问模型的简单整理 目录 Deepseek 【大模型】之 Ollama 与 Ollama Web UI Lite 本地部署 Deepseek 可视化UI 访问模型部署简单整理 一、简单介绍 二、 Ollama 下载安装 三、Ollama 下载 LLM…

Excel 融合 deepseek

效果展示 代码实现 Function QhBaiDuYunAIReq(question, _Optional Authorization "your api key", _Optional Qhurl "https://qianfan.baidubce.com/v2/chat/completions")Dim XMLHTTP As ObjectDim url As Stringurl Qhurl 这里替换为你实际的URLDim …

SpringBoot开发(六)SpringBoot整合MyBatis

1. SpringBoot整合MyBatis 1.1. MyBatis介绍 MyBatis 是一款优秀的持久层Dao框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息&#xff0c…

[数据结构] Set的使用与注意事项

目录 Set的说明 常见方法说明 注意事项 TreeSet使用案例 Set的说明 Set与Map主要的不同有两点: Set是继承自Collection的接口类,Set中只存储了Key. 常见方法说明 方法解释boolean add(E e)添加元素,但重复元素不会被添加成功void clear()清空集合boolean contains(Object…

JumpServer堡垒机管理服务器与数据库资产

第一次接触JumpServer是一位老师借给我的&#xff0c;当时想部署oceanbase 企业版V3 &#xff0c;苦于笔记本内存太小&#xff0c;后来在JumpServer上部署成功了&#xff0c;后来一直对JumpServer比较感兴趣&#xff0c;年后有时间对JumpServer进行了系统的学习 一.使用场景 我…

汽车免拆诊断案例 | 2015款奔驰R320车行驶中偶尔多个故障灯异常点亮

故障现象  一辆2015款奔驰R320车&#xff0c;搭载276 826 发动机&#xff0c;累计行驶里程约为18万km。该车行驶中&#xff0c;组合仪表上的ABS警告灯、防侧滑警告灯、发动机故障灯等多个故障灯偶尔异常点亮&#xff08;图1&#xff09;&#xff0c;且车速表不指示&#xff0…

实验3 词法分析(二)

实验3 词法分析(二) [实验目的]&#xff1a; 1 . 熟悉给定的词法分析程序&#xff1b; 2 . 改进词法分析程序。 [实验内容]&#xff1a; 1.尝试多方面改进TEST语言的文法&#xff0c;参考教材附录B词法分析程序TESTscan.c&#xff0c;在此词法分析程序的基础上改进程序&#x…

[创业之路-286]:《产品开发管理-方法.流程.工具 》-2- 人的管理是任何组织首要解决的问题 - 企业与研发组织组成、构架、组织分工

目录 一、产品开发的部门组成&#xff08;系统关键组成要素&#xff09; 1、产品开发中的市场规划部门与研发内部的市场/产品/技术预研部门的职责区别&#xff1a; 2、研发的分类&#xff1a;技术预研、平台开发、产品开发 相同点 差异点 相互联系 二、研发的组织架构 1…

使用jmeter进行压力测试

使用jmeter进行压力测试 jmeter安装 官网安装包下载&#xff0c;选择二进制文件&#xff0c;解压。 tar -xzvf apache-jmeter-x.tgz依赖jdk安装。 yum install java-1.8.0-openjdk环境变量配置&#xff0c;修改/etc/profile文件&#xff0c;添加以下内容。 export JMETER/…

matlab simulink LNG广义预测控制

1、内容简介 略 matlab simulink 120-LNG广义预测控制 可以交流、咨询、答疑 2、内容说明 略 模型分为2部分&#xff0c;一部分是simulink的结果&#xff0c;用的是pid和模糊pid控制&#xff0c;第二个模型考虑到代码计算的方便性&#xff0c;采用的m文件做仿真&#xff0…

git submodule使用

git submodule 用于关联其他独立的仓库。 它有着几点好处&#xff1a; 代码复用&#xff1a;可以将工具代码放到单独的仓库&#xff0c;再通过 submodule 关联。模块化开发&#xff1a;可以将项目拆分成多个模块&#xff0c;每个模块设置单独仓库独立开发&#xff0c;再通过 su…

python怎么求 一个数是否包含3

python求一个数包含3的方法&#xff1a; 1、使用“for i in 列表名”循环遍历列表中的每一个元素并将每个元素用str()函数转换成字符串格式 2、用“if str(3) in i”判断该元素中是否含有3 完整代码如下&#xff1a; 执行结果如下&#xff1a;

数据库系统概念第六版记录 三

外码约束&#xff08;Foreign Key Constraint&#xff09; 外码&#xff08;Foreign Key, FK&#xff09;是关系数据库中的一个约束&#xff0c;它用于保证表之间的引用完整性。外码的值必须&#xff1a; 要么存在于被引用表的主键列中&#xff0c;要么为空&#xff08;NULL&…

修改SSH登录密码,只需要登录SSH,之后输入命令即可,这里登录使用的软件为 MobaXterm1

在登入终端之后输入命令 passwd {用户名} 即可进行修改。需要注意的是&#xff0c;输入的密码不会有星号代替&#xff0c;也不会出现明文。 如果想要修改SSH的登录端口&#xff0c;比如修改为1433&#xff0c;则只需要执行以下命令即可&#xff1a; /usr/sbin/sshd -p 1433…

电商平台的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统商品交易信息管理难度大&#xff0c;容错率低&#xff0…

【R语言】plyr包和dplyr包

一、plyr包 plyr扩展包主要是实现数据处理中的“分割-应用-组合”&#xff08;split-apply-combine&#xff09;策略。此策略是指将一个问题分割成更容易操作的部分&#xff0c;再对每一部分进行独立的操作&#xff0c;最后将各部分的操作结果组合起来。 plyr扩展包中的主要函…

oscp备考,oscp系列——VulnOSv2靶场,两种方法获取低权限shell

前言 oscp备考&#xff0c;oscp系列——VulnOSv2靶场&#xff0c;两种方法获取低权限shell 难度简单 对于低权限shell获取涉及&#xff1a;drupal 7 getshell漏洞&#xff0c;opendocman sql注入&#xff0c;ssh连接对于提权&#xff1a;内核提权 下载地址&#xff1a; http…

东方财富股吧发帖与评论爬虫

东方财富股吧发帖与评论爬虫 东方财富股吧爬虫 写在开头项目介绍主要功能文件介绍爬取逻辑 a. 爬取帖子信息b. 爬取评论信息 使用步骤 1. 下载代码2. MongoDB 安装3. Webdriver 安装4. 运行 main.py5. 查看数据 踩过的坑附录&#xff08;运行结果&#xff09; 东方财富股吧爬…

wxWidgets生成HTML文件,带图片转base64数据

编译环境大家可以看我之前的文章,CodeBlocks + msys2 + wx3.2,win10 这里功能就是生成HTML文件,没用HTML库,因为是自己固定的格式,图片是一个vector,可以动态改变数量的。 效果如下: #include <wx/string.h> #include <wx/file.h> #include <wx/ima…

网络编程 day2

题目 代码 服务器 typedef char DataType[32]; //普通节点数据类型typedef struct NODE {union{DataType data; //普通节点数据域int len; //头节点数据域};struct NODE *next; //指针域 }node,*nodePtr;struct PACK {int size; //告知 通信传输的数据的大小int type; //决定…