一步一步学OAK之六:通过OAK相机实现特征检测

news2024/11/25 2:51:29

目录

  • 特征检测
  • Setup 1: 创建文件
  • Setup 2: 安装依赖
  • Setup 3: 导入需要的包
  • Setup 4: 创建pipeline
  • Setup 5: 创建节点
    • 创建相机节点
    • 创建特征检测节点
    • 创建数据交互的节点
  • Setup 6:设置相关属性
    • 设置相机的相关属性
    • 设置特征检测器的初始配置
  • Setup 7: 建立链接关系
    • 建立相机和特征跟踪器之间的数据连接
  • Setup 8: 连接设备并启动管道
  • Setup 9: 创建与DepthAI设备通信的输入队列和输出队列
  • Setup 10: 定义`drawFeatures`的函数,用于在图像帧上绘制特征点
  • Setup 11: 主循环
    • 获取帧数据并将其转换为彩色
    • 获取跟踪的特征,并在帧上绘制这些特征
    • 显示帧图像
    • 对键盘输入响应程序
  • Setup 12:运行程序

特征检测

特征检测是指在数字图像中自动寻找并定位具有特定特征的图像区域或图像点。这些特征可以是在图像中具有独特外观、结构或统计特性的对象、边缘、角点、纹理等。

特征检测在计算机视觉中起到至关重要的作用,它是许多计算机视觉任务的基础,如目标检测、跟踪、姿态估计、图像拼接、图像识别等。通过提取和匹配特征,可以实现图像对齐、物体识别、运动估计等计算机视觉任务。

传统的特征检测方法包括边缘检测(如Canny边缘检测)、角点检测(如Harris角点检测)、尺度不变特征变换(SIFT)、加速稳健特征(SURF)等。近年来,基于深度学习的特征检测方法,如卷积神经网络(CNN)的特征提取,也在图像特征检测领域取得了重要的突破和应用。

Setup 1: 创建文件

  • 创建新建6-feature-detector文件夹
  • 用vscode打开该文件夹
  • 新建一个main.py 文件

Setup 2: 安装依赖

安装依赖前需要先创建和激活虚拟环境,我这里已经创建了虚拟环境OAKenv,在终端中输入cd…退回到OAKenv的根目录,输入 OAKenv\Scripts\activate激活虚拟环境

安装pip依赖项:

pip install numpy opencv-python depthai blobconverter --user

Setup 3: 导入需要的包

在main.py中导入项目需要的包

import cv2
import depthai as dai

Setup 4: 创建pipeline

pipeline = dai.Pipeline()

Setup 5: 创建节点

创建相机节点

monoLeft = pipeline.createMonoCamera()
monoRight = pipeline.createMonoCamera()
  1. monoLeft = pipeline.createMonoCamera():创建一个单通道相机节点(MonoCamera),用于捕获左侧单色图像。
  2. monoRight = pipeline.createMonoCamera():创建一个单通道相机节点(MonoCamera),用于捕获右侧单色图像。

创建特征检测节点

featureTrackerLeft = pipeline.createFeatureTracker()
featureTrackerRight = pipeline.createFeatureTracker()

这段代码在pipeline中创建三个边缘检测节点。

  1. featureTrackerLeft = pipeline.createFeatureTracker():创建了一个特征检测节点用于处理左侧单色图像。
  2. featureTrackerRight = pipeline.createFeatureTracker():创建了一个特征检测节点用于处理右侧单色图像。

创建数据交互的节点

xoutPassthroughFrameLeft = pipeline.createXLinkOut()
xoutTrackedFeaturesLeft = pipeline.createXLinkOut()
xoutPassthroughFrameRight = pipeline.createXLinkOut()
xoutTrackedFeaturesRight = pipeline.createXLinkOut()
xinTrackedFeaturesConfig = pipeline.createXLinkIn()

xoutPassthroughFrameLeft.setStreamName("passthroughFrameLeft")
xoutTrackedFeaturesLeft.setStreamName("trackedFeaturesLeft")
xoutPassthroughFrameRight.setStreamName("passthroughFrameRight")
xoutTrackedFeaturesRight.setStreamName("trackedFeaturesRight")
xinTrackedFeaturesConfig.setStreamName("trackedFeaturesConfig")

这段代码创建了五个与外部设备进行数据交互的节点。

  1. 使用pipeline.createXLinkOut()方法创建了四个用于与外部设备进行数据输出的节点。
  2. 使用pipeline.createXLinkIn()方法创建了一个用于与外部设备进行数据输入的节点。

为每个输出节点和输入节点设置了一个数据流名称

使用.setStreamName("name")方法为上面创建的五个节点设置数据流名称

Setup 6:设置相关属性

设置相机的相关属性

monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT)
monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT)

对于左侧的单目相机:

  1. monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P):设置了左侧单目相机的分辨率为400P。这意味着左侧单目相机将以400P的分辨率进行图像捕获。
  2. monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT):设置了左侧单目相机的板载插槽为LEFT。这表明左侧单目相机将通过与系统板的左侧接口连接。

对于右侧的单目相机:

  1. monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P):设置了右侧单目相机的分辨率为400P。这意味着右侧单目相机将以400P的分辨率进行图像捕获。
  2. monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT):设置了右侧单目相机的板载插槽为RIGHT。这表明右侧单目相机将通过与系统板的右侧接口连接。

设置特征检测器的初始配置

featureTrackerLeft.initialConfig.setMotionEstimator(False)
featureTrackerRight.initialConfig.setMotionEstimator(False)

禁用特征跟踪器实例中的运动估计器。

对于每个特征跟踪器实例,initialConfig是其初始化配置对象,可以用于设置不同的参数。

通过调用setMotionEstimator(False)函数并传递False作为参数,禁用运动估计器。这意味着特征跟踪器将不会对物体或特征点之间的运动进行估计,而只会跟踪它们的位置。

Setup 7: 建立链接关系

建立相机和特征跟踪器之间的数据连接

monoLeft.out.link(featureTrackerLeft.inputImage)
featureTrackerLeft.passthroughInputImage.link(xoutPassthroughFrameLeft.input)
featureTrackerLeft.outputFeatures.link(xoutTrackedFeaturesLeft.input)
xinTrackedFeaturesConfig.out.link(featureTrackerLeft.inputConfig)

monoRight.out.link(featureTrackerRight.inputImage)
featureTrackerRight.passthroughInputImage.link(xoutPassthroughFrameRight.input)
featureTrackerRight.outputFeatures.link(xoutTrackedFeaturesRight.input)
xinTrackedFeaturesConfig.out.link(featureTrackerRight.inputConfig)

featureTrackerConfig = featureTrackerRight.initialConfig.get()

print("Press 's' to switch between Harris and Shi-Thomasi corner detector!")
  1. monoLeft.out.link(featureTrackerLeft.inputImage):将左侧单目相机的输出链接到左侧特征跟踪器的输入图像。
  2. featureTrackerLeft.passthroughInputImage.link(xoutPassthroughFrameLeft.input):将左侧特征跟踪器的输入图像链接到左侧帧输出(用于传递原始输入图像到输出管道)。
  3. featureTrackerLeft.outputFeatures.link(xoutTrackedFeaturesLeft.input):将左侧特征跟踪器的输出特征链接到左侧被跟踪特征的输出。
  4. xinTrackedFeaturesConfig.out.link(featureTrackerLeft.inputConfig):将被跟踪特征的配置链接到左侧特征跟踪器的输入配置。
  5. monoRight.out.link(featureTrackerRight.inputImage):将右侧单目相机的输出链接到右侧特征跟踪器的输入图像。
  6. featureTrackerRight.passthroughInputImage.link(xoutPassthroughFrameRight.input):将右侧特征跟踪器的输入图像链接到右侧帧输出。
  7. featureTrackerRight.outputFeatures.link(xoutTrackedFeaturesRight.input):将右侧特征跟踪器的输出特征链接到右侧被跟踪特征的输出。
  8. xinTrackedFeaturesConfig.out.link(featureTrackerRight.inputConfig):将被跟踪特征的配置链接到右侧特征跟踪器的输入配置。
  9. featureTrackerRight.initialConfig.get():用于获取featureTrackerRight特征跟踪器实例的初始配置。

这段代码片段主要用于创建数据流水线,将图像数据从相机传递到特征跟踪器,并将跟踪的特征输出到相应的目标。

Setup 8: 连接设备并启动管道

with dai.Device(pipeline) as device:

Setup 9: 创建与DepthAI设备通信的输入队列和输出队列

    passthroughImageLeftQueue = device.getOutputQueue("passthroughFrameLeft", 8, False)
    outputFeaturesLeftQueue = device.getOutputQueue("trackedFeaturesLeft", 8, False)
    passthroughImageRightQueue = device.getOutputQueue("passthroughFrameRight", 8, False)
    outputFeaturesRightQueue = device.getOutputQueue("trackedFeaturesRight", 8, False)

    inputFeatureTrackerConfigQueue = device.getInputQueue("trackedFeaturesConfig")
    
    leftWindowName = "left"
    rightWindowName = "right"
  1. passthroughImageLeftQueue = device.getOutputQueue("passthroughFrameLeft", 8, False):从设备中获取名为"passthroughFrameLeft"的输出队列。该队列将用于接收左侧特征跟踪器的帧图像数据,队列大小为8,不使用阻塞模式。
  2. outputFeaturesLeftQueue = device.getOutputQueue("trackedFeaturesLeft", 8, False):从设备中获取名为"trackedFeaturesLeft"的输出队列。该队列将用于接收左侧特征跟踪器的跟踪特征数据,队列大小为8,不使用阻塞模式。
  3. passthroughImageRightQueue = device.getOutputQueue("passthroughFrameRight", 8, False):从设备中获取名为"passthroughFrameRight"的输出队列。该队列将用于接收右侧特征跟踪器的帧图像数据,队列大小为8,不使用阻塞模式。
  4. outputFeaturesRightQueue = device.getOutputQueue("trackedFeaturesRight", 8, False):从设备中获取名为"trackedFeaturesRight"的输出队列。该队列将用于接收右侧特征跟踪器的跟踪特征数据,队列大小为8,不使用阻塞模式。
  5. inputFeatureTrackerConfigQueue = device.getInputQueue("trackedFeaturesConfig"):从设备中获取名为"trackedFeaturesConfig"的输入队列。该队列将用于向特征跟踪器发送配置数据。

Setup 10: 定义drawFeatures的函数,用于在图像帧上绘制特征点

    def drawFeatures(frame, features):
        pointColor = (0, 0, 255)
        circleRadius = 2
        for feature in features:
            cv2.circle(frame, (int(feature.position.x), int(feature.position.y)), circleRadius, pointColor, -1, cv2.LINE_AA, 0)

定义了一个名为drawFeatures的函数,用于在图像帧上绘制特征点。

  1. pointColor = (0, 0, 255):定义了绘制特征点时使用的颜色。这里使用的是RGB颜色空间中的红色。

  2. circleRadius = 2:定义了绘制特征点时使用的圆的半径大小。

  3. for feature in features::对特征列表中的每个特征进行循环遍历。

  4. cv2.circle(frame, (int(feature.position.x), int(feature.position.y)), circleRadius, pointColor, -1, cv2.LINE_AA, 0):使用OpenCV的cv2.circle函数,在图像帧frame上绘制一个圆。圆心的位置由特征的位置(feature.position.x, feature.position.y)确定,圆的半径为circleRadius,颜色为pointColor,线宽为-1表示填充整个圆,cv2.LINE_AA指定使用抗锯齿线段,最后一个参数为0表示以默认的图像坐标系绘制。

Setup 11: 主循环

    while True:

获取帧数据并将其转换为彩色

        inPassthroughFrameLeft = passthroughImageLeftQueue.get()
        passthroughFrameLeft = inPassthroughFrameLeft.getFrame()
        leftFrame = cv2.cvtColor(passthroughFrameLeft, cv2.COLOR_GRAY2BGR)

        inPassthroughFrameRight = passthroughImageRightQueue.get()
        passthroughFrameRight = inPassthroughFrameRight.getFrame()
        rightFrame = cv2.cvtColor(passthroughFrameRight, cv2.COLOR_GRAY2BGR)
  1. inPassthroughFrameLeft = passthroughImageLeftQueue.get():从名为passthroughImageLeftQueue的队列获取一个帧。

  2. passthroughFrameLeft = inPassthroughFrameLeft.getFrame():从获取到的帧对象inPassthroughFrameLeft中提取帧数据。

  3. leftFrame = cv2.cvtColor(passthroughFrameLeft, cv2.COLOR_GRAY2BGR):使用OpenCV的cv2.cvtColor函数将灰度图像passthroughFrameLeft转换为彩色图像。这里使用的颜色转换代码cv2.COLOR_GRAY2BGR将灰度图像转换为BGR彩色图像。

  4. inPassthroughFrameRight = passthroughImageRightQueue.get():从名为passthroughImageRightQueue的队列获取另一个帧。

  5. passthroughFrameRight = inPassthroughFrameRight.getFrame():从获取到的帧对象inPassthroughFrameRight中提取帧数据。

  6. rightFrame = cv2.cvtColor(passthroughFrameRight, cv2.COLOR_GRAY2BGR):使用OpenCV的cv2.cvtColor函数将灰度图像passthroughFrameRight转换为彩色图像。这里使用的颜色转换代码cv2.COLOR_GRAY2BGR将灰度图像转换为BGR彩色图像。

获取跟踪的特征,并在帧上绘制这些特征

        trackedFeaturesLeft = outputFeaturesLeftQueue.get().trackedFeatures
        drawFeatures(leftFrame, trackedFeaturesLeft)

        trackedFeaturesRight = outputFeaturesRightQueue.get().trackedFeatures
        drawFeatures(rightFrame, trackedFeaturesRight)

这段代码从输出特征队列中获取跟踪的特征,并在帧上绘制这些特征。

  1. trackedFeaturesLeft = outputFeaturesLeftQueue.get().trackedFeatures:从名为outputFeaturesLeftQueue的输出特征队列中获取一个特征跟踪结果对象,并从中提取跟踪的特征。

  2. drawFeatures(leftFrame, trackedFeaturesLeft):调用名为drawFeatures的函数,并将左侧帧leftFrame和跟踪的左侧特征trackedFeaturesLeft作为参数传递进去,以在左侧帧上绘制特征点。

  3. trackedFeaturesRight = outputFeaturesRightQueue.get().trackedFeatures:从名为outputFeaturesRightQueue的输出特征队列中获取另一个特征跟踪结果对象,并从中提取跟踪的特征。

  4. drawFeatures(rightFrame, trackedFeaturesRight):调用名为drawFeatures的函数,并将右侧帧rightFrame和跟踪的右侧特征trackedFeaturesRight作为参数传递进去,以在右侧帧上绘制特征点。

显示帧图像

        cv2.imshow(leftWindowName, leftFrame)
        cv2.imshow(rightWindowName, rightFrame)

使用OpenCV的cv2.imshow函数显示左右两个帧。

  1. cv2.imshow(leftWindowName, leftFrame):使用OpenCV的cv2.imshow函数显示左侧帧。leftWindowName是左侧窗口的名称,用于标识窗口,leftFrame是要显示的帧。

  2. cv2.imshow(rightWindowName, rightFrame):使用OpenCV的cv2.imshow函数显示右侧帧。rightWindowName是右侧窗口的名称,用于标识窗口,rightFrame是要显示的帧。

对键盘输入响应程序

        key = cv2.waitKey(1)
        if key == ord('q'):
            break
        elif key == ord('s'):
            if featureTrackerConfig.cornerDetector.type == dai.FeatureTrackerConfig.CornerDetector.Type.HARRIS:
                featureTrackerConfig.cornerDetector.type = dai.FeatureTrackerConfig.CornerDetector.Type.SHI_THOMASI
                print("Switching to Shi-Thomasi")
            else:
                featureTrackerConfig.cornerDetector.type = dai.FeatureTrackerConfig.CornerDetector.Type.HARRIS
                print("Switching to Harris")

            cfg = dai.FeatureTrackerConfig()
            cfg.set(featureTrackerConfig)
            inputFeatureTrackerConfigQueue.send(cfg)

上面的代码实现了等待用户按下键盘上的键,并根据按下的键执行不同的操作。

  1. key = cv2.waitKey(1):等待用户按下键盘上的键,1代表等待1毫秒。此函数会返回按键的ASCII值。

  2. if key == ord('q')::如果按下的键是字母q对应的ASCII值,即用户按下q键:

    • break:跳出循环,结束程序。
  3. elif key == ord('s')::如果按下的键是字母s对应的ASCII值,即用户按下s键:

    • if featureTrackerConfig.cornerDetector.type == dai.FeatureTrackerConfig.CornerDetector.Type.HARRIS::检查当前角点检测器的类型是否是Harris角点检测器。

      • featureTrackerConfig.cornerDetector.type = dai.FeatureTrackerConfig.CornerDetector.Type.SHI_THOMASI:如果是Harris角点检测器,则将角点检测器类型切换为Shi-Thomasi角点检测器。

        • print("Switching to Shi-Thomasi"):打印消息,表示已切换到Shi-Thomasi角点检测器。
      • else::如果当前角点检测器的类型不是Harris角点检测器。

        • featureTrackerConfig.cornerDetector.type = dai.FeatureTrackerConfig.CornerDetector.Type.HARRIS:将角点检测器类型切换回Harris角点检测器。

        • print("Switching to Harris"):打印消息,表示已切换回Harris角点检测器。

    • cfg = dai.FeatureTrackerConfig():创建一个新的dai.FeatureTrackerConfig对象。

    • cfg.set(featureTrackerConfig):将当前的特征跟踪器配置featureTrackerConfig复制到新创建的对象中。

    • inputFeatureTrackerConfigQueue.send(cfg):将特征跟踪器配置对象发送到名为inputFeatureTrackerConfigQueue的队列中。

Setup 12:运行程序

在终端中输入如下指令运行程序

python main.py

运行程序,看下效果
在这里插入图片描述

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

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

相关文章

iOS 16 版本适配

1、iOS 16 真机调试时需要在设备的设置 —> 隐私与安全 —> 开发者模式 中打开开发者模式。 2、隐私权限增强,如通过 UIDevice 获取设备名称时,无法获取用户的信息,只能获取设备对应的名称([UIDevice currentDevice].name返…

基于Python所写的影视作品分析设计

点击以下链接获取源码资源: https://download.csdn.net/download/qq_64505944/87964875 《开心麻花影视作品分析》程序使用说明 在PyCharm中运行《开心麻花影视作品分析》即可进入如图1所示的系统主界面。在该界面中,选择要分析的电影名称,然…

android:RadioGroup的使用

一、前言:工作中会遇到勾选不同的类型,获得不同的数据。仅以此笔记记录。 二、上代码: 新建一个Activity public class RadioHorizontalActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {private Text…

线程并发同步--条件变量--conditional_variable

同步:我的线程完成之后,你在进行下一个线程。可以理解为进货和卖货,即只有进或的线程结束后才可以执行卖货的这个线程。 c中提供了一个工具:conditional_variable。实现有两种方式,一种是condition_variable和conditon…

[java]Redis

关于Redis Redis是一款基于内存的,使用K-V结构存储数据的NoSQL非关系型数据库。 基于内存的:Redis读写数据时,都是在内存中进行读写的,所以,读写效率非常高!另外,Redis会自动的将所管理的数据同…

mmdetection自定义数据集训练

目录 1. 源码下载,本文基于v3.0版本 2.选模型训练 2.1 先生成后面用于编译的配置文件 2.2.1 修改coco.py 2.2.2 修改class_names.py 3. 训练配置 4. 训练过程展示 1. 源码下载,本文基于v3.0版本 GitHub - open-mmlab/mmdetection: OpenMMLab Detec…

UART-GD32

UART-GD32 通信的概念 同步通信和异步通信 数据帧格式 波特率 使用步骤 引脚分布

图-深度优先搜索与广度优先搜索

图 在现实生活中,有许多应用场景会包含很多点以及点点之间的连接,而这些应用场景我们都可以用即将要学习的图 这种数据结构去解决 地图 我们生活中经常使用的地图,基本上是由城市以及连接城市的道路组成,如果我们把城市看做是一…

西安石油大学 C++期末考试 重点知识点+题目复习(上)

第一章 “const”关键字的用法 当使用 const 修饰变量、函数参数、成员函数以及指针时,以下是一些代码示例: 声明只读变量: const int MAX_VALUE 100; const float PI 3.14;保护函数参数: void printArray(const int arr[]…

解决vmware虚拟机,克隆修改ip后,xshell连接不上问题

1、查看网卡 ifconfig2、修改网卡配置 vim /etc/sysconfig/network-scripts/ifcfg-ens32 改成与上图一样 修改后 3、重启reboot则解决

STM32F407 串口配置步骤

介绍STM32F407串口配置步骤,完成串口的数据发送与接收、实现中断接收,支持printf重定向。 STM32F407 串口配置说明 STM32F4 的串口资源相当丰富的,功能也相当强劲,STM32F407ZGT6 最多可提供 6 路串口,有分数波特率发…

【Junit 单元测试】

Junit 单元测试 笔记记录 1. Junit介绍2. 使用Junit3. 常用注解4. 断言使用 1. Junit介绍 2. 使用Junit 1.导入依赖 <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>tes…

C语言-基础语法学习-4 字符串

目录 字符串C语言字符串及相关函数定义字符数组和字符串的区别sizeof()和strlen()的区别动态开辟字符串野指针常用字符串函数 字符串 C语言字符串及相关函数 在C语言中&#xff0c;字符串是由字符数组表示的一系列字符序列。C语言提供了一些函数来处理字符串&#xff0c;使我…

centos磁盘扩容

解释 PE - 物理块&#xff08;Physical Extent&#xff09; 硬盘上有很多实际物理存在的存储块PV - 物理卷 &#xff08;Physical Volume&#xff09; 物理卷处于最底层&#xff0c;它可以是实际物理硬盘上的分区&#xff0c;也可以是整个物理硬盘(相当于单独做一个分区)&…

CommonJS 和 ES6 module

本文主要自己觉得要记录的点记录下来,不耽误大家时间&#xff0c;会持续更新。 Module对象 Module {id: xxx/demo/1.js, //加载文件的绝对路径path: xxx/demo,// 加载文件所在目录的绝对路径exports: [Function (anonymous)],filename: xxx/demo/1.js,加载文件的绝对路径load…

B/S和C/S详解(嵌入式学习)

B/S和C/S详解 1. C/S1.1 概念1.2 C/S架构的特点 2. B/S2.1 概念2.2 B/S架构的特点2.3 相对于传统的C/S架构的优势 3. B/S架构详解4. B/S架构怎么用&#xff08;CGI、Lighttpd&#xff09; 1. C/S 1.1 概念 C/S&#xff08;Client/Server&#xff09;是一种计算机网络架构模式…

【C++】 Qt-页面布局

文章目录 布局组件和布局按钮练习-用户信息页面布局准备工作设置性别设置年龄设置生日设置邮箱后缀 设置头像创建文件写入文件清空表单信息Buddy&#xff08;伙伴&#xff09;关系Tab顺序 布局组件和布局按钮 Qt的UI设计器中提供了丰富的布局管理功能&#xff0c;组件面板里有…

高效简单解决滑动验证码

前言 做爬虫总会遇到各种各样的反爬限制&#xff0c;其中移动验证码是很重要且常见的一环&#xff0c;今天总结下如何高效破解他的方法&#xff0c;例如下图&#xff1a; 解决思路与方法 首先先来分析下&#xff0c;核心问题其实是要怎么样找到目标缺口的位置&#xff0c;一…

Windows服务启动exe无界面终极解决方案

1、前言 我这个方案&#xff08;C#操作&#xff09;是彻底解决【从Windows服务启动程序exe&#xff0c;程序无界面】问题的终极解决方案&#xff0c;终极方案&#xff0c;绝对的终极方案&#xff0c;本来打算收钱的&#xff0c;还是算了&#xff0c;你们也不容易&#xff0c;关…

网络安全(自学笔记)

如果你真的想通过自学的方式入门web安全的话&#xff0c;那建议你看看下面这个学习路线图&#xff0c;具体到每个知识点学多久&#xff0c;怎么学&#xff0c;自学时间共计半年左右&#xff0c;亲测有效&#xff08;文末有惊喜&#xff09;&#xff1a; 1、Web安全相关概念&am…