Faster R-CNN源码解析(三)

news2024/11/17 1:28:16

目录

    • today
    • torch.meshgrid()函数

today

今天我们主要来捋一捋AnchorsGenerator这部分代码,对应在network_files文件夹中的rpn_function文件中,从RegionProposalNetwork()类的forward()函数开始看,首先会进入head部分在这里插入图片描述也就是我们看到的RPNHead部分,也就是比较小的虚线框框起来的那部分,可以看到是从backbone得到特征矩阵后传入到RPNHead部分,先直接看代码:

class RPNHead(nn.Module):
    def __init__(self, in_channels, num_anchors):
        super(RPNHead, self).__init__()
        # 3x3 滑动窗口
        self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
        # 计算预测的目标分数(这里的目标只是指前景或者背景)
        self.cls_logits = nn.Conv2d(in_channels, num_anchors, kernel_size=1, stride=1)
        # 计算预测的目标bbox regression参数
        self.bbox_pred = nn.Conv2d(in_channels, num_anchors * 4, kernel_size=1, stride=1)

        for layer in self.children():
            if isinstance(layer, nn.Conv2d):
                torch.nn.init.normal_(layer.weight, std=0.01)
                torch.nn.init.constant_(layer.bias, 0)

    def forward(self, x):
        # type: (List[Tensor]) -> Tuple[List[Tensor], List[Tensor]]
        logits = []
        bbox_reg = []
        for i, feature in enumerate(x):
            t = F.relu(self.conv(feature))
            logits.append(self.cls_logits(t))
            bbox_reg.append(self.bbox_pred(t))
        return logits, bbox_reg

首先初始化了一个3x3的滑动窗口,输入的channels就是backbone输出的channels(1280),输出的channels(1280)没变,卷积核大小,步长,padding就不用了说了吧。接下来初始化了1x1大小的类别卷积层和1x1大小的预测卷积层,对应输入的channels都是上一层的输出channels,注意类别卷积层输出的channels是anchors的个数(因为用的是二分类交叉熵损失,所以每个anchor只需要一个分数就够了),用于计算预测的目标分数(这里的目标只是指前景或者背景),预测卷积层的输出channels是4倍anchor的个数,每个anchor对应4个坐标(左上角两个坐标,右下角两个坐标),接着遍历children层存在conv2d层,对conv2d层进行均值为0,标准差为0.01的权重初始化,偏置全为0的初始化。最后看正向传播,初始化两个列表分别存放预测的目标分数和预测的目标bbox regression参数,
遍历经过backbone特征提取后的特征层,因为前面讲过用的是MobileNetv2进行的特征提取,所以最后只有一个特征层,那么只会循环一次,进过一系列卷积操作后得到最后的结果,debug我们看一下logits和bbox_reg列表的结果:在这里插入图片描述
可以看到最后logits和bbox_reg列表中都只有一个元素,我这里得到的形状分别是(8, 15, 25, 38), (8, 60, 25, 38),
8是一个batch有8张图片,15是最后输出15个anchor的结果,60是最后输出的15个anchor乘上4个坐标的结果,25x38就是卷积后的特征图的大小。注意:大家debug得到的形状最后两个维度可能跟我的不同,这是因为每次运行的时候dataloader选定的第一个batch的图片是随机的,尺寸也就可能变化了,backbone的输出的features尺寸自然也会变了

class AnchorsGenerator(nn.Module):
    # 注解组成的字典.注释下面两个变量里的元素类型
    __annotations__ = {
        "cell_anchors": Optional[List[torch.Tensor]],
        "_cache": Dict[str, List[torch.Tensor]]
    }

    """
    anchors生成器
    Arguments:
        sizes (Tuple[Tuple[int]]):
        aspect_ratios (Tuple[Tuple[float]]):
    """

    def __init__(self, sizes=(128, 256, 512), aspect_ratios=(0.5, 1.0, 2.0)):
        super(AnchorsGenerator, self).__init__()

        if not isinstance(sizes[0], (list, tuple)):
            # TODO change this
            sizes = tuple((s,) for s in sizes)
        if not isinstance(aspect_ratios[0], (list, tuple)):
            aspect_ratios = (aspect_ratios,) * len(sizes)

        assert len(sizes) == len(aspect_ratios)

        self.sizes = sizes
        self.aspect_ratios = aspect_ratios
        self.cell_anchors = None
        self._cache = {}

一样的先对AnchorsGenerator类,先看参数,sizes就是原论文当中的尺度scale,这里传的是((32, 64, 128, 256, 512),),aspect_ratios就是原论文中的三种比例(1:2,1:1,2:1),这里传的就是((0.5, 1.0, 2.0),),注意都是元组形式,不过没关系,传的时候不是元组也不会报错,因为传入非元组和非列表时下面两个if语句会自动帮你转换成元组的形式,(aspect_ratios,) * len(sizes)就是将(aspect_ratios,)重复len(sizes)次,如下图在这里插入图片描述
当然还要判断scale的长度是否等于比例的长度,因为每一组尺度都对应三个比例,所以需要进行判断,剩下的就是初始化各个变量,就不赘述了

 def forward(self, image_list, feature_maps):
     # type: (ImageList, List[Tensor]) -> List[Tensor]
     # 获取每个预测特征层的尺寸(height, width)
     grid_sizes = list([feature_map.shape[-2:] for feature_map in feature_maps])

     # 获取输入图像的height和width
     image_size = image_list.tensors.shape[-2:]

     # 获取变量类型和设备类型
     dtype, device = feature_maps[0].dtype, feature_maps[0].device

     # one step in feature map equate n pixel stride in origin image
     # 计算特征层上的一步等于原始图像上的步长
     strides = [[torch.tensor(image_size[0] // g[0], dtype=torch.int64, device=device),
                 torch.tensor(image_size[1] // g[1], dtype=torch.int64, device=device)] for g in grid_sizes]

     # 根据提供的sizes和aspect_ratios生成anchors模板
     self.set_cell_anchors(dtype, device)

     # 计算/读取所有anchors的坐标信息(这里的anchors信息是映射到原图上的所有anchors信息,不是anchors模板)
     # 得到的是一个list列表,对应每张预测特征图映射回原图的anchors坐标信息
     anchors_over_all_feature_maps = self.cached_grid_anchors(grid_sizes, strides)

     anchors = torch.jit.annotate(List[List[torch.Tensor]], [])
     # 遍历一个batch中的每张图像
     for i, (image_height, image_width) in enumerate(image_list.image_sizes):
         anchors_in_image = []
         # 遍历每张预测特征图映射回原图的anchors坐标信息
         for anchors_per_feature_map in anchors_over_all_feature_maps:
             anchors_in_image.append(anchors_per_feature_map)
         anchors.append(anchors_in_image)
     # 将每一张图像的所有预测特征层的anchors坐标信息拼接在一起
     # anchors是个list,每个元素为一张图像的所有anchors信息
     anchors = [torch.cat(anchors_per_image) for anchors_per_image in anchors]
     # Clear the cache in case that memory leaks.
     self._cache.clear()
     return anchors

老规矩,直接看正向传播过程,传入的image_list是ImageList类别,之前也说了,存储的是经过一个batch打包处理后的图片size和等比例缩放后的图片size,
feature_maps就是经过backbone特征提取后得到的一个特征层,
grid_sizes就是遍历特征层得到特征图的高和宽(debug得到的是25x38),
image_size是经过batch打包处理后的图片宽高(debug得到的是800x1216),
strides就是计算特征层上的一步等于原始图像上的步长,求得对应高宽的缩放因子(debug得到的是32),对应特征层上缩小了32倍,然后进入类方法set_cell_anchors()

def set_cell_anchors(self, dtype, device):
    # type: (torch.dtype, torch.device) -> None
    if self.cell_anchors is not None:
        cell_anchors = self.cell_anchors
        assert cell_anchors is not None
        # suppose that all anchors have the same device
        # which is a valid assumption in the current state of the codebase
        if cell_anchors[0].device == device:
            return

    # 根据提供的sizes和aspect_ratios生成anchors模板
    # anchors模板都是以(0, 0)为中心的anchor
    cell_anchors = [
        self.generate_anchors(sizes, aspect_ratios, dtype, device)
        for sizes, aspect_ratios in zip(self.sizes, self.aspect_ratios)
    ]
    self.cell_anchors = cell_anchors

set_cell_anchors()方法用于生成anchor模板,现在我们还没有cell_anchors,所以会进入类方法generate_anchors(),传入的参数分别是要生成不同anchor的尺度大小和比例以及数据类型和设备

def generate_anchors(self, scales, aspect_ratios, dtype=torch.float32, device=torch.device("cpu")):
    # type: (List[int], List[float], torch.dtype, torch.device) -> Tensor
    """
    compute anchor sizes
    Arguments:
        scales: sqrt(anchor_area)
        aspect_ratios: h/w ratios
        dtype: float32
        device: cpu/gpu
    """
    scales = torch.as_tensor(scales, dtype=dtype, device=device)
    aspect_ratios = torch.as_tensor(aspect_ratios, dtype=dtype, device=device)
    h_ratios = torch.sqrt(aspect_ratios)
    w_ratios = 1.0 / h_ratios

    # [r1, r2, r3]' * [s1, s2, s3]
    # number of elements is len(ratios)*len(scales)
    ws = (w_ratios[:, None] * scales[None, :]).view(-1)
    hs = (h_ratios[:, None] * scales[None, :]).view(-1)

    # left-top, right-bottom coordinate relative to anchor center(0, 0)
    # 生成的anchors模板都是以(0, 0)为中心的, shape [len(ratios)*len(scales), 4]
    base_anchors = torch.stack([-ws, -hs, ws, hs], dim=1) / 2

    return base_anchors.round()  # round 四舍五入

首先将尺度信息和比例信息转换为tensor格式

 h_ratios = torch.sqrt(aspect_ratios)
 w_ratios = 1.0 / h_ratios

这一步为什么这么做呢,因为我们传入的是三种比例(1:2, 1:1, 2:1),高的因子开根号,1除宽的因子,这样得到高宽的因子可以保证面积不变在这里插入图片描述

我们用第一种比例子:
2 × 1 = 1 × 2 2 × 2 × 2 = 2 2\times1=1\times\frac{\sqrt{2}}{2}\times2\times\sqrt{2}=2 2×1=1×22 ×2×2 =2
通过这两个因子就可以得到不同尺度的三种比例的anchor,w_ratios[:, None]就是添加一个维度,形状从[3]->[3, 1],scales[None, :]的形状就从[5]->[1, 5],矩阵相乘就会得到[3, 5]的矩阵,通过view(-1)转换成一维向量
生成的3x5矩阵,每一列对应每一种尺度的三种比例的值

( 2 2 1 2 ) × ( 32 64 128 256 512 ) \begin{pmatrix} \frac{\sqrt{2}}{2}\\ 1\\ \sqrt{2} \end{pmatrix}\times\begin{pmatrix} 32 & 64 & 128 & 256 & 512\\ \end{pmatrix} 22 12 ×(3264128256512)     这是对应生成的宽

( 2 1 2 2 ) × ( 32 64 128 256 512 ) \begin{pmatrix} \sqrt{2}\\ 1\\ \frac{\sqrt{2}}{2} \end{pmatrix}\times\begin{pmatrix} 32 & 64 & 128 & 256 & 512\\ \end{pmatrix} 2 122 ×(3264128256512)     这是对应生成的高

生成了15个高和15个宽值后,我们需要对应图像坐标系来将这些值拼接成左上角右下角的坐标形式
我们知道图像中的坐标系是下面这样的,为什么拼接之后每个坐标值要除2,看下面这张图就知道了在这里插入图片描述
这里只画了一种尺度的三种比例的anchor,因为生成的anchors模板都是以(0, 0)为中心的,我们把这些anchor模板放到坐标系中,anchor左上角右下角的坐标就对应着 [ − w s 2 , − h s 2 , w s 2 , h s 2 ] [\frac{-ws}{2},\frac{-hs}{2},\frac{ws}{2},\frac{hs}{2}] [2ws,2hs,2ws,2hs]对吧, dim=1是因为第0个维度是batch(多少张图片),所以从第一个维度拼接,最后四舍五入一下就得到最后的anchor模板对应着原点(0, 0)的坐标信息,我们可以看一下debug的结果在这里插入图片描述
每五行对应一种比例,刚好三种比例。这时候类方法set_cell_anchors()就讲完啦,应该很好理解吧!接下来就是类方法cached_grid_anchors(),

def cached_grid_anchors(self, grid_sizes, strides):
    # type: (List[List[int]], List[List[Tensor]]) -> List[Tensor]
    """将计算得到的所有anchors信息进行缓存"""
    key = str(grid_sizes) + str(strides)
    # self._cache是字典类型
    if key in self._cache:
        return self._cache[key]
    anchors = self.grid_anchors(grid_sizes, strides)
    self._cache[key] = anchors
    return anchors

grid_sizes是传入的是特征提取后的特征层高宽(25x38),strides就是上面讲到得特征图对应原图上的缩放倍数对应高宽,所以是[32, 32],self._cache初始化的是一个空字典,存储对应原图像上(经过打包处理后高宽固定的图像)的anchor坐标,直接进类方法grid_anchors()

def grid_anchors(self, grid_sizes, strides):
    # type: (List[List[int]], List[List[Tensor]]) -> List[Tensor]
    """
    anchors position in grid coordinate axis map into origin image
    计算预测特征图对应原始图像上的所有anchors的坐标
    Args:
        grid_sizes: 预测特征矩阵的height和width
        strides: 预测特征矩阵上一步对应原始图像上的步距
    """
    anchors = []
    cell_anchors = self.cell_anchors
    assert cell_anchors is not None

    # 遍历每个预测特征层的grid_size,strides和cell_anchors
    for size, stride, base_anchors in zip(grid_sizes, strides, cell_anchors):
        grid_height, grid_width = size
        stride_height, stride_width = stride
        device = base_anchors.device

        # For output anchor, compute [x_center, y_center, x_center, y_center]
        # shape: [grid_width] 对应原图上的x坐标(列)
        shifts_x = torch.arange(0, grid_width, dtype=torch.float32, device=device) * stride_width
        # shape: [grid_height] 对应原图上的y坐标(行)
        shifts_y = torch.arange(0, grid_height, dtype=torch.float32, device=device) * stride_height

        # 计算预测特征矩阵上每个点对应原图上的坐标(anchors模板的坐标偏移量)
        # torch.meshgrid函数分别传入行坐标和列坐标,生成网格行坐标矩阵和网格列坐标矩阵
        # shape: [grid_height, grid_width]
        shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
        shift_x = shift_x.reshape(-1)
        shift_y = shift_y.reshape(-1)

        # 计算anchors坐标(xmin, ymin, xmax, ymax)在原图上的坐标偏移量
        # shape: [grid_width*grid_height, 4]
        # 这里dim=1结果才是[grid_width*grid_height, 4],dim=0是batch的维度,如果dim=0结果就是[4, grid_width*grid_height]
        shifts = torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1)

        # For every (base anchor, output anchor) pair,
        # offset each zero-centered base anchor by the center of the output anchor.
        # 将anchors模板与原图上的坐标偏移量相加得到原图上所有anchors的坐标信息(shape不同时会使用广播机制)
        shifts_anchor = shifts.view(-1, 1, 4) + base_anchors.view(1, -1, 4)
        anchors.append(shifts_anchor.reshape(-1, 4))

    return anchors  # List[Tensor(all_num_anchors, 4)]

遍历所有特征图的高宽(25x38),由于mobilenetv2特征提取后只有一个特征层,所以只有25x34,放缩步长strides[32, 32],cell_anchors就是之前存储的anchor模板,

shifts_x = torch.arange(0, grid_width, dtype=torch.float32, device=device) * stride_width
# shape: [grid_height] 对应原图上的y坐标(行)
shifts_y = torch.arange(0, grid_height, dtype=torch.float32, device=device) * stride_height

这两步就是生成对应原图上的x坐标,y坐标,我们可以看一下在这里插入图片描述在这里插入图片描述

torch.meshgrid()函数

我们模拟一下上面那部分代码

import numpy as np
import matplotlib.pyplot as plt

size = [25, 38]
stride = [32, 32]
grid_height, grid_width = size
stride_height, stride_width = stride
x = np.arange(0, grid_width) * stride_width
y = np.arange(0, grid_height) * stride_height
y, x = np.meshgrid(y, x)
y = y.reshape(-1)
x = x.reshape(-1)
plt.figure()
plt.plot(x, y,
         color='limegreen',  # 设置颜色为limegreen
         marker='.',  # 设置点类型为圆点
         linestyle='')  # 设置线型为空,也即没有线连接点
plt.grid(True)
plt.show()
print(x)
print(y)

自己可以去试试,看看输出的x,y是什么,得到结果在这里插入图片描述
可以看到生成很多点,shifts = torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1)代码得到的结果就是[x, y, x, y]这样的形式,我们发现左上角右下角的坐标都是(x, y),说白了就是一个点,那么就将这些点都当作原点来看,即图上这些绿点,再将anchor模板放上去,,每个点放上15个,这样就会生成很多anchor,

shifts_anchor = shifts.view(-1, 1, 4) + base_anchors.view(1, -1, 4)
anchors.append(shifts_anchor.reshape(-1, 4))

这一步就是将anchors模板与原图上的坐标偏移量相加得到原图上所有anchors的坐标信息(shape不同时会使用广播机制),相当于下图在这里插入图片描述
三种颜色代表了三种尺度,这里我只画了三种,图上对应还有很多点,每个点都会得到5种尺度3比例3x5个anchor,将所有的anchor坐标存在一个列表中并返回
torch.jit.annotate()介绍
剩下的部分很简单,因为每张图片大小都是一样的,将刚刚得到的一张图上的所有anchor坐标重复一个batch(我设置的是8)的数量,最后再将一个batch的所有anchor坐标拼接到一起,debug结果如下在这里插入图片描述
这样就得到了一个batch每张图上的anchor坐标信息了,本次的源码解析就到这里啦,主要就是anchor模板的生成以及如何将anchor模板坐标放到原图上的过程,最后再将一个batch的图片所有的anchor信息放在一个列表中。我们下节见,谢谢大家能坚持看到结尾,可能是很多,很杂,但慢慢理一下就能有个大概的体系了,不懂的可以评论区留言,我们下节见!!

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

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

相关文章

SVD 最小二乘法解 亲测ok!

线性最小二乘问题 m个方程求解n个未知数&#xff0c;有三种情况&#xff1a; mn且A为非奇异&#xff0c;则有唯一解&#xff0c;xA.inverse()*bm>n&#xff0c;约束的个数大于未知数的个数&#xff0c;称为超定问题&#xff08;overdetermined&#xff09;m<n&#xff0…

2023.11.22 IDEA Spring Boot 项目热部署

目录 引言 操作步骤 1. 在 pom.xml 中添加热部署框架支持 2. Setting 开启项目自动编译 3. 以后创建的新项目进行同步配置 4. 重复 配置 步骤2 的内容 5. 开启运行中的热部署 引言 Spring Boot 的热部署是一种在项目正在运行的时候修改代码&#xff0c;却不需要重新启动…

数字孪生农村供水工程平台:为乡村振兴注入新活力

随着科技的不断进步&#xff0c;数字孪生技术逐渐成为各行业创新发展的重要驱动力。在水利领域&#xff0c;数字孪生农村供水平台以其独特的优势&#xff0c;为农村供水系统带来了革命性的变革。本文将为您详细介绍数字孪生农村供水平台的核心特点及优势&#xff0c;带您领略智…

【软件测试】技术不好?不学这几招你怎么跳槽?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、软件测试面试环…

解决DaemonSet没法调度到master节点的问题

最近在kubernetes部署一个springcloud微服务项目&#xff0c;到了最后一步部署边缘路由&#xff1a;使用nginx-ingress和traefik都可以&#xff0c;必须使用DaemonSet部署&#xff0c;但是发现三个节点&#xff0c;却总共只有两个pod。 换句话说&#xff0c; DaemonSet没法调度…

Google搜索广告图标详解

Google搜索广告图标是指在Google搜索结果页面中&#xff0c;广告结果前面显示的小图标。这些图标旨在帮助用户更容易地识别哪些结果是广告&#xff0c;并提供更直观的搜索体验。本文小编将对于Google搜索广告图标的类型、作用和设计原则进行介绍。 一、Google搜索广告图标的类型…

化学仿制药参比制剂目录-参比制剂查询网站

2015年以前&#xff0c;参比制剂对于仿制药的研究无关紧要&#xff0c;但推出了’仿制药一致性评价’后&#xff0c;参比制剂的选择成为了决定仿制药成功与否的关键因素&#xff0c;如今在进行仿制药研究时&#xff0c;首要任务就是确定仿制目标&#xff0c;也就是参比制剂。 …

PLC通过lora网关采集温室大棚温湿度数据

概述: 运用lora网关远程控制大棚内风机&#xff0c;日光灯&#xff0c;温湿度传感器等设备。可以实现远程获取现场环境的空气温湿度、土壤水分温度、二氧化碳浓度、光照强度可以自动控制温室湿帘风机、喷淋滴灌、加温补光等设备&#xff0c;并向远程计算机端推送实时数据&…

linux删除oracle数据库:如何在Linux系统中删除Oracle数据库

停止Oracle数据库服务&#xff1a;# su - oracle 1. 停止Oracle数据库服务&#xff1a; # su - oracle $ sqlplus / as sysdba SQL> SHUTDOWN IMMEDIATE; 2. 删除oracle安装目录&#xff1a; # rm -rf /u01/app/oracle 3. 删除oracle用户和组&#xff1a; # userdel…

关于前端上传

类似于 上面的传参form-data形式&#xff0c;第一个参数为上传的文件&#xff0c;第二个参数为json格式

企业建数仓的第一步是选择一个好用的ETL工具

当企业决定建立数据仓库&#xff08;Data Warehouse&#xff09;&#xff0c;第一步就是选择一款优秀的ETL&#xff08;Extract, Transform, Load&#xff09;工具。数据仓库是企业数据管理的核心&#xff0c;它存储、整合并管理各种数据&#xff0c;为商业决策和数据分析提供支…

【计算方法与科学建模】矩阵特征值与特征向量的计算(一):Jacobi 旋转法及其Python实现

文章目录 一、Jacobi 旋转法1. 基本思想2. 计算过程演示3. 注意事项 二、Python实现迭代过程&#xff08;调试&#xff09; 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数学和物理意义。Jacobi 旋转…

技术细分|推荐系统——推荐系统中的偏差

一、背景 推荐系统中大量使用用户行为数据&#xff0c;作为系统学习的标签或者说信号。但用户行为数据天生存在各式各样的偏差&#xff08;bias&#xff09;&#xff0c;如果直接作为信号的话&#xff0c;学习出的模型参数不能准确表征用户在推荐系统中的真实行为意图&#xff…

知虾:揭秘Shopee大数据采集及分析平台的全方位运营利器

Shopee是如今备受瞩目的电商平台之一&#xff0c;而要在这个竞争激烈的市场中脱颖而出&#xff0c;了解市场趋势、选择畅销商品、分析竞争对手等是至关重要的。这就是为什么Shopee推出了知虾&#xff0c;一个强大的大数据采集及分析平台。本文将详细介绍知虾的功能和优势&#…

如何解决跨国访问Microsoft 365网络卡顿问题?

作为主流的协调办公工具&#xff0c;Microsoft 365(旧称Office 365)是众多企业每天必须访问的应用&#xff0c;但由于多种原因&#xff0c;许多企业在跨区域访问Microsoft 365服务器时常面临卡顿、掉线等问题&#xff0c;对工作效率产生严重影响。 对此&#xff0c;连官方也专门…

python爬虫中 HTTP 到 HTTPS 的自动转换

前言 在当今互联网世界中&#xff0c;随着网络安全的重要性日益增加&#xff0c;越来越多的网站采用了 HTTPS 协议来保护用户数据的安全。然而&#xff0c;许多网站仍然支持 HTTP 协议&#xff0c;这就给我们的网络爬虫项目带来了一些挑战。为了应对这种情况&#xff0c;我们需…

双12电视盒子什么牌子好?数码小编力荐目前最强的电视盒子

最近想买电视盒子的网友非常多&#xff0c;小编收到了很多关于电视盒子方面的咨询&#xff0c;因此我特意整理了今年测评过的电视盒子&#xff0c;总结了五款目前最强的电视盒子&#xff0c;想知道双十二买电视盒子什么牌子好就赶紧收藏起来吧。 推荐一&#xff1a;泰捷WEBOX新…

【数据结构】深入浅出理解链表中二级指针的应用

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 (注:为方便演示本篇使用的x86系统,因此指针的大小为4个字节) 目录 &#x1f4cc;形参的改变不影响实参! 1.调用函数更改整型时传值调用与传址调用的区别 &#x1f38f;传值…

指令流信息记录

刚开始看指令流信息,还不太熟悉各个指令流表示的含义,这里做个记录 下面是用perf对一个简单的编译后的C程序进行的追踪结果 主体信息 上面都是16进制信息,先不用管 可以观察到每条指令流都由四部分组成,以分号作为分割(注意中间第三部分的时候他有冒号,但是不是分割) 真正追踪…