【ITK库学习】使用itk库进行图像配准:“Hello World”配准

news2025/1/19 3:09:21

目录

  • 1、itkImageRegistrationMethod / itkImageRegistrationMethodv4
  • 2、itkTranslationTransform
  • 3、itkMeanSquaresImageToImageMetric / itkMeanSquaresImageToImageMetric4
  • 4、itkRegularStepGradientDescentOptimizerv / itkRegularStepGradientDescentOptimizerv4

图像配准

将一幅图上的点映射到另一幅图像上同源点的空间转换过程;是同意吗目标的两幅图想在空间位置上的对准。

左图点P经过传递函数T,得到在右图的配准点位Q:

配准框架

基本组成:参考图像、待配准图像、传递函数、路劲选择、校对机、优化器
传递函数:从参考图像上的点到待配准图像上点的空间映射关系
校对机:评估待配准图象在非网格位置的程度
路劲选择:提供一种参考图像呗待配准图像配准的程度
优化器:路径选择形成数量上的标准,优化器通过寻找被传递参数定义的空间去最大优化此标准

1、itkImageRegistrationMethod / itkImageRegistrationMethodv4

该类为图像配准方法的基类。

该类定义配准方法的通用接口。它根据要配准的两个图像的类型进行模板化,使用通用 Transform,允许在运行时选择用于配准图像的特定转换类型。该方法使用通用度量来比较两个图像,配准方法的最终目标是找到优化度量的转换参数集。

此类中使用术语:固定图像和移动图像来指示变换正在映射什么图像。

此类使用固定图像的坐标系作为参考,并搜索将点从固定图像的空间映射到移动图像的空间变换。

为此,将连续应用度量来将固定图像与变换后的移动图像进行比较,此过程还需要从移动图像中插入值。

itkImageRegistrationMethodv是基于传统的优化方法,如梯度下降法等,算法复杂度相对简单一些,更容易上手。支持可以在运行时选择通用优化器,优化器的唯一限制是它应能在单值成本函数中运行,因为用于比较图像的指标提供单个值作为输出。

itkImageRegistrationMethodv4是基于图像类的优化方法,使用图像类提供的优化接口进行配准,算法更复杂,如多分辨率策略、局部变形场模型等,可以处理更复杂的配准问题,参数设置和使用稍微复杂一些,需要一定的图像处理和算法知识。允许多阶段配准,其中每个阶段的特征在于可能不同的变换和不同的图像度量。 例如,许多执行线性配准,然后执行变形配准,其中两个阶段都在多个级别中执行。

对算法和参数设置要求较高,可以选择itkImageRegistrationMethodv4,如果只需要简单的图像配准,可以选择itkImageRegistrationMethodv
共同的成员函数

  • SetFixedImage():设置/获取固定图像(参考图像)
  • Set/GetMovingImage():设置/获取移动图像(待配准图像)
  • SetOptimizer()/GetModifiableOptimizer():设置/获取优化器
  • SetMetric()/GetModifiableMetric():设置/获取度量(校对机)

itkImageRegistrationMethodv常用的成员函数

  • SetTransform()/GetModifiableTransform:设置/获取变换(传递函数)
  • Set/GetFixedImageRegion():将固定图像的区域设置/获取为在配准期间被视为感兴趣区域,该区域将被传递给ImageMetric,以限制度量计算仅考虑该区域
  • SetFixedImageRegionDefined():打开/关闭固定图像区域的使用,ImageMetric将限制其计算
  • GetFixedImageRegionDefined():如已为固定图像定义了ImageMetric将限制其计算的区域,则为 True
  • Set/GetInitialTransformParameters():设置/获取初始转换参数
  • SetInterpolator()GetModifiableInterpolator():设置/获取插值器
  • Set/GetLastTransformParameters():为派生类提供设置此私有变量的能力
  • StartOptimization():初始化
  • GetMTime():返回该对象或其任何缓存的 ivar 的最新修改时间的方法

itkImageRegistrationMethodv4常用的成员函数

  • Set/GetOptimizerWeights():设置/获取优化器权重,允许设置每个局部参数的加权数组,如果未设置,权重将被视为同一性, 权重用于在优化期间屏蔽特定参数以保持其恒定, 或者它们可以用来应用另一种先验知识, 权重的大小必须等于局部变换参数的数量
  • Set/GetFixedPointSet():设置/获取固定点集
  • Set/GetFixedInitialTransformInput():设置/获取初始固定变换
  • SetMovingPointSet():设置移动点集
  • Set/GetMovingInitialTransformInput():设置/获取初始移动变换
  • Set/GetNumberOfLevels():设置/获取多分辨率级别的数量, 在设置级别数时,需要为每个级别设置以下内容:a)虚拟域的收缩因子,b)Sigma平滑参数,c)使用指定级别的特定参数转换适配器
  • Set/GetMetricSamplingStrategy():设置/获取指标采样策略
  • SetMetricSamplingPercentagePerLevel():设置指标采样百分比,有效值范围[0.0,1.0]
  • SetMetricSamplePoints():设置度量采样点
  • SetInPlace():请求将 InitialTransform 移植到输出上,而不创建副本
  • Set/GetInitialTransformInput():设置/获取要优化的初始变换,该变换与MovingInitialTransform 一起指定从运动图像到虚拟图像的初始变换,它用于默认参数,可用于指定变换类型,如果过滤器设置了 “InPlace” ,则此变换将是输出变换对象或“嫁接”到输出,否则,此 InitialTransform 将被深度复制或“克隆”到输出,如果未设置此参数,则使用默认构造的输出变换
  • SetInitializeCenterOfLinearOutputTransform():使用队列中前一个变换的中心初始化要优化的当前线性变换,这提供了比默认原点更好的初始化
  • Set/GetTransformParametersAdaptorsPerLevel():设置/获取转换适配器
  • Set/GetSmoothingSigmasPerLevel():设置/获取每个级别的平滑Sigma值,在每个分辨率级别,都会应用高斯平滑滤波器( itkDiscreteGaussianImageFilter ),Sigma值根据选项 m_SmoothingSigmasAreSpecifiedInPhysicalUnits 指定
  • Set/GetSmoothingSigmasAreSpecifiedInPhysicalUnits():设置/获取是否以物理单位(默认)或体素指定每个级别的平滑Sigma
  • SetShrinkFactorsPerLevel():设置每个级别的收缩因子,其中每个级别的每个维度都有一个恒定的收缩因子,例如,输入到 Factors = [4,2,1] 函数将在第一级将每个维度的图像缩小 4,然后在第二级缩小 2,最后一级的1原始分辨率(使用 itkShrinkImageFilter )
  • SetShrinkFactorsPerDimension():为每个维度设置特定级别的收缩因子

2、itkTranslationTransform

向量空间的平移变换(例如空间坐标)

使用映射变换可以获得相同的功能,但性能差异很大。

常用的成员函数

  • Set/GetParameters():设置/获取指定的变换值的参数
  • SetIdentity():将参数设置为 IdentityTransform
  • SetFixedParameters():设置固定参数并更新内部变换,Translation Transform 不需要固定参数,因此该方法的实现是空操作
  • Set/GetOffset():设置平移变换的偏移量,此方法将 TranslationTransform 的偏移量设置为用户指定的值
  • IsLinear():表明该变换是线性的,也就是说,给定两个点 P 和 Q,以及标量系数 a 和 b,则T(aP+bQ)=aT§+bT(Q)
  • GetInverseTransform():返回该变换的逆变换

3、itkMeanSquaresImageToImageMetric / itkMeanSquaresImageToImageMetric4

这两个类用于计算图像间均方差的度量类。

指标越小,表示两幅图像的相似度越高。

使用该指标需要调用SetFixedImage()SetMovingImage()函数设置待比较的两幅图像,并可以使用SetFixedImageRegion()函数进一步指定比较区域,然后调用Initialize()函数进行初始化,然后可以通过调用GetValue()函数计算出指标值。

itkMeanSquaresImageToImageMetric是ITK中较早的版本的度量类,它使用的是传统的像素级别的计算方法。它使用的计算公式为:sum((pixel1 - pixel2)^2) / N,其中pixel1pixel2分别表示两个图像中的像素值,N表示图像中的像素总数。这种计算方法简单直观,但是对于图像配准等应用可能不够灵活,无法适应复杂的数据类型、图像的变换等。

itkMeanSquaresImageToImageMetricv4在ITK在新版本中被引入,它使用的是基于像素间距离场的计算方法。它使用的计算公式为:sum(weight * (distance1 - distance2)^2) / N,其中weight是像素间的权重值,distance1distance2分别表示两个图像中的像素距离值,N表示图像中的像素总数。这种计算方法可以更灵活地处理图像间的变换和重采样,并且可以用于处理多通道和向量图像等复杂数据。

常用的成员函数

  • GetValueThreadProcessSample():获取匹配测量值
  • GetValueAndDerivative():获取单值优化器的值和导数
  • GetValue():获取值
  • GetDerivative():获取匹配度量的导数

4、itkRegularStepGradientDescentOptimizerv / itkRegularStepGradientDescentOptimizerv4

二者均为梯度下降优化器,ITK新旧版本的不同。

itkRegularStepGradientDescentOptimizerv基于固定步长的梯度下降算法,它有固定的步长参数,需要手动设置。

常用的成员函数

  • StepAlongGradient():考虑由因子表示的步长,沿着校正的梯度前进一步,该方法由 AdvanceOneStep 调用,它预计会被非向量空间中的优化方法覆盖

itkRegularStepGradientDescentOptimizerv4基于线搜索的梯度下降算法,它的步长在每次迭代时根据目标函数的变化情况进行动态调整,在许多情况下也具有更好的优化性能和更快的收敛速度。为防止采取太大的步骤,在每次迭代中,该优化器将沿着度量导数的方向采取一步,每次导数的方向突然改变时,优化器都会假设已通过局部极值,并通过将步长减小默认设置为 0.5 的松弛因子来做出反应,初始步长的默认值为 1,并且该值只能通过 SetLearningRate() 手动更改,因为此优化器不使用 ScaleEstimator来自动估计学习率,另请注意,与itkRegularStepGradientDescentOptimizerv不同,它 没有“最大化/最小化”选项来修改度量导数的效果,假定指定的度量返回“改进”优化的参数导数结果。

常用的成员函数

  • Set/GetRelaxationFactor():设置/获取松弛因子值
  • Set/GetMinimumStepLength():设置/获取用于收敛检查的最小步长(学习率)值,当通过大步越过局部极小值时,将通过松弛因子调整(减小)步长,以便朝着最小值点(收敛)采取更小的步,当步长值达到较小值时,视为收敛,默认值设置为 1e-4 以通过所有测试
  • Set/GetGradientMagnitudeTolerance():设置/获取梯度幅度容差值以进行收敛检查
  • Set/GetCurrentLearningRateRelaxation():设置/获取学习率的当前比例
  • EstimateLearningRate():根据当前梯度估计学习率
  • AdvanceOneStep():沿着梯度方向前进一步,包括变换更新
  • StartOptimization():启动并运行优化

示例代码

#include "itkImageRegistrationMethodv4.h"
#include "itkTranslationTransform.h"
#include "itkMeanSquaresImageToImageMetricv4.h"
#include "itkRegularStepGradientDescentOptimizerv4.h"
#include "itkImage.h"

using namespace itk;

const unsigned int  Dim2D = 2;       //数据的Dimension
typedef itk::Image<float, Dim2D> FixedImageType;
typedef itk::Image<float, Dim2D> MovingImageType;

class CommandIterationUpdate : public itk::Command
{
public:
	using Self = CommandIterationUpdate;
	using Superclass = itk::Command;
	using Pointer = itk::SmartPointer<Self>;
	itkNewMacro(Self);

protected:
	CommandIterationUpdate() = default;

public:
	using OptimizerType = itk::RegularStepGradientDescentOptimizerv4<double>;
	using OptimizerPointer = const OptimizerType*;

	void
		Execute(itk::Object* caller, const itk::EventObject& event) override
	{
		Execute((const itk::Object*)caller, event);
	}

	void
		Execute(const itk::Object* object, const itk::EventObject& event) override
	{
		auto optimizer = static_cast<OptimizerPointer>(object);

		if (!itk::IterationEvent().CheckEvent(&event))
		{
			return;
		}

		std::cout << optimizer->GetCurrentIteration() << " = ";
		std::cout << optimizer->GetValue() << " : ";
		std::cout << optimizer->GetCurrentPosition() << std::endl;
	}
};

bool imageRegistration1(FixedImageType* fixImage, MovingImageType* moveImage, bool useEstimator)
{
    typedef itk::LinearInterpolateImageFunction<FixedImageType, double> FixedLinearInterpolatorType;
	typedef itk::LinearInterpolateImageFunction<MovingImageType, double>  MovingLinearInterpolatorType;

	typedef itk::TranslationTransform<double, Dim2D> TransformType;
	typedef itk::MeanSquaresImageToImageMetricv4<FixedImageType, MovingImageType> MetricType;
	typedef itk::RegularStepGradientDescentOptimizerv4<double> OptimizerType;
	typedef itk::ImageRegistrationMethodv4<FixedImageType, MovingImageType, TransformType> RegistrationType;

	typename TransformType::Pointer movingInitialTransform = TransformType::New();
	typename TransformType::ParametersType initialParameters(movingInitialTransform->GetNumberOfParameters());
	initialParameters[0] = 0.0;   // Initial offset in mm along X
	initialParameters[1] = 0.0;   // Initial offset in mm along Y
	//initialParameters[2] = 0.0;   // Initial offset in mm along Z
	movingInitialTransform->SetParameters(initialParameters);

	typename TransformType::Pointer identityTransform = TransformType::New();
	identityTransform->SetIdentity();

	typename FixedLinearInterpolatorType::Pointer fixedInterpolator = FixedLinearInterpolatorType::New();
	typename MovingLinearInterpolatorType::Pointer movingInterpolator = MovingLinearInterpolatorType::New();
	typename MetricType::Pointer metric = MetricType::New();
	metric->SetFixedInterpolator(fixedInterpolator);
	metric->SetMovingInterpolator(movingInterpolator);

	typename OptimizerType::Pointer optimizer = OptimizerType::New();
	optimizer->SetLearningRate(4);
	optimizer->SetMinimumStepLength(0.001);
	optimizer->SetRelaxationFactor(0.5);
	if (useEstimator)
	{
		using ScalesEstimatorType =
			itk::RegistrationParameterScalesFromPhysicalShift<MetricType>;
		auto scalesEstimator = ScalesEstimatorType::New();
		scalesEstimator->SetMetric(metric);
		scalesEstimator->SetTransformForward(true);
		optimizer->SetScalesEstimator(scalesEstimator);
		optimizer->SetDoEstimateLearningRateOnce(true);
	}
	optimizer->SetNumberOfIterations(200);
	auto observer = CommandIterationUpdate::New();
	optimizer->AddObserver(itk::IterationEvent(), observer);

	constexpr unsigned int numberOfLevels = 1;

	typename RegistrationType::Pointer registration = RegistrationType::New();
	typename RegistrationType::ShrinkFactorsArrayType shrinkFactorsPerLevel;
	shrinkFactorsPerLevel.SetSize(1);
	shrinkFactorsPerLevel[0] = 1;

	typename RegistrationType::SmoothingSigmasArrayType smoothingSigmasPerLevel;
	smoothingSigmasPerLevel.SetSize(1);
	smoothingSigmasPerLevel[0] = 0;

	
	registration->SetFixedImage(fixImage);              //参考图像
	registration->SetMovingImage(moveImage);            //待配准图像
	registration->SetMetric(metric);                    //校对机
	registration->SetOptimizer(optimizer);              //优化器
	registration->SetMovingInitialTransform(movingInitialTransform);    //初始移动变换
	registration->SetFixedInitialTransform(identityTransform);     //初始固定变换
	registration->SetNumberOfLevels(numberOfLevels);    //多分辨率级别的数量 
	registration->SetSmoothingSigmasPerLevel(smoothingSigmasPerLevel);   //每个级别的平滑Sigma值
	registration->SetShrinkFactorsPerLevel(shrinkFactorsPerLevel);       //每个级别的收缩因子
	try
	{
		registration->Update();
	}
	catch (itk::ExceptionObject& ex)
	{
		//读取过程发生错误
		std::cerr << "Error: " << ex << std::endl;
		return false;
	}
	TransformType::ConstPointer transform = registration->GetTransform();
	TransformType::ParametersType finalParameters = transform->GetParameters();
	const double  TranslationAlongX = finalParameters[0];
	const double  TranslationAlongY = finalParameters[1];
	const unsigned int numberOfIterations = optimizer->GetCurrentIteration();    //抵达收敛的迭代的实际次数
	const double bestValue = optimizer->GetValue();       //参数集合的图像量规值

	return true;
}

注:调试过程中报错:itkStatisticsAlgorithm.hxx的533行报: ”InsertSort”: 找不到标识符,是4.13版本的InsertSort引用在函数定义前,修改文件内容,将InsertSort函数定义放在引用函数前bug解决

bug解决办法参考自博客:https://blog.csdn.net/assjaa/article/details/111317338

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

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

相关文章

0基础学习VR全景平台篇第130篇:曝光三要素—感光度

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 众所周知&#xff0c;摄影是一门用光的艺术。随着天气、地点、时间的变化&#xff0c;我们所处环境的光线也随之发生改变。而在不同的环境下该如何去正确的调节曝光&#xff0c;是…

Spring security之授权

前言 本篇为大家带来Spring security的授权&#xff0c;首先要理解一些概念&#xff0c;有关于&#xff1a;权限、角色、安全上下文、访问控制表达式、方法级安全性、访问决策管理器 一.授权的基本介绍 Spring Security 中的授权分为两种类型&#xff1a; 基于角色的授权&…

【Angular】Angular中的最差实践

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

4.4【共享源】克隆实战开发之截屏(二)

三,显示器截图 screen_read_display() 函数则用于捕获显示器的屏幕截图。我们需要在特权上下文中工作,以便可以完全访问系统的显示属性。我们可以通过调用具有 SCREEN_DISPLAY_MANAGER_CONTEXT 上下文类型的 screen_create_context() 来创建特权上下文。进程必须具有 root 的…

使用minio实现大文件断点续传

部署 minio 拉取镜像 docker pull minio/minio docker images新建映射目录 新建下面图片里的俩个目录 data(存放对象-实际的数据) config 存放配置开放对应端口 我使用的是腾讯服务器所以 在腾讯的安全页面开启 9000&#xff0c;9090 两个端口就可以了&#xff08;根据大家实际…

使用python对windows/win11进行属性设置

有一个个人的需求&#xff0c;针对windows系统进行属性设置&#xff0c;这里以对鼠标的左右键主键进行切换为例&#xff0c;进行了研究&#xff0c;以当前win11系统为基础进行了更动。 首先是对于如果打开windows系统下的鼠标设置&#xff0c;有以下几种办法&#xff1a; 添加…

十大经典排序算法(个人总结C语言版)

文章目录 一、前言二、对比1.排序算法相关概念1.1 时间复杂度1.2 空间复杂度1.3 排序方式1.4 稳定度 2.表格比较3.算法推荐3.1 小规模数据3.2 中等规模数据3.3 大规模数据3.4 特殊需求 三、排序算法1.冒泡排序&#xff08;Bubble Sort&#xff09;1.1 简介1.2 示例代码&#xf…

使用互斥锁(Mutex)管理共享资源

在Go中确保并发安全性 并发是Go中的一个强大功能&#xff0c;它允许多个Goroutines&#xff08;并发线程&#xff09;同时执行。然而&#xff0c;伴随着强大的功能也带来了大量的责任。当多个Goroutines并发地访问和修改共享资源时&#xff0c;可能会导致数据损坏、数据竞争&a…

网络爬虫之Ajax动态数据采集

动态数据采集 规则 有时候我们在用 requests 抓取页面的时候&#xff0c;得到的结果可能和在浏览器中看到的不一样&#xff0c;在浏览器中可以看到正常显示的页面教据&#xff0c;但是使用 requests 得到的结果并没有&#xff0c;这是因为requests 获取的都是原始的 HTML 文档…

【小白攻略】php 小数转为百分比,保留两位小数的函数

php 小数转为百分比 首先&#xff0c;最简单直观的方法是利用PHP内置的number_format函数。该函数可以对一个数字进行格式化&#xff0c;并可以设置小数点后的精度。通过将小数乘以100&#xff0c;再用number_format函数将结果格式化为百分比形式&#xff0c;即可达到将小数转为…

uniapp怎么动态渲染导航栏的title?

直接在接口请求里面写入以下&#xff1a; 自己要什么参数就写什么参数 本人仅供参考&#xff1a; this.name res.data.data[i].name; console.log(名字, res.data.data[i].name); uni.setNavigationBarTitle({title: this.name}) 效果&#xff1a;

图论 | 网络流的基本概念

文章目录 流网路残留网络增广路径割最大流最小割定理最大流Edmonds-Karp 算法算法步骤程序代码时间复杂度 流网路 流网络&#xff1a; G ( V , E ) G (V, E) G(V,E) 有向图&#xff0c;不考虑反向边s&#xff1a;源点t&#xff1a;汇点 c ( u , v ) c(u, v) c(u,v)&#xff…

CSS:浮动

CSS&#xff1a;浮动 浮动效果浮动方式 float浮动特性标准流脱标脱标的影响脱标的影响范围 清除浮动清除浮动原理 clear基于clear的清除浮动方式额外标签法:afert伪元素法双伪元素法 清除浮动原理 BFCBFC定义BFC布局规则创建一个BFC基于BFC的清除浮动方式父级添加overflow法 浮…

物理模拟重力 斜抛运动计算 抛物线计算

物理模拟重力 斜抛运动计算 抛物线计算 一、介绍二、原理三、实现如下PhysicsUtil.cs 工具类Missile.cs 四、资源分享 一、介绍 模拟Unity原始重力系统进行重写&#xff0c;可是实现发射到指定目标位置并能继续当前力进行自身的弹力与摩擦继续运动 二、原理 将Unity原始不受控…

鸿蒙开发4.0-ArkTS与H5的交互

ArkTS侧与H5的交互 首先在开发H5页面&#xff08;输入框和金额选择部分&#xff09;前需要实现JSBridge桥接打通两侧的交互。开发者可以在ArkTS侧定义一个JSBridge类&#xff0c;在类中封装call方法以及initJsBridge方法。 准备异步执行脚本&#xff0c;在脚本中声明一个JSBri…

工具系列:PyCaret介绍_Fugue 集成_Spark、Dask分布式训练

工具系列&#xff1a;PyCaret介绍_Fugue 集成_Spark、Dask分布式训练 Fugue 是一个低代码的统一接口&#xff0c;用于不同的计算框架&#xff0c;如 Spark、Dask。PyCaret 使用 Fugue 来支持分布式计算场景。 目录 1、分布式计算示例&#xff1a; (1)分类 (2)回归 (3)时间…

7.5组合总和②(LC40-M)

算法&#xff1a; 相比于上一题&#xff0c;数组candidates有重复元素&#xff0c;而要求不能有重复的组合&#xff0c;所以相对于39.组合总和 (opens new window)难度提升了不少。 如何去重&#xff1f; 先把candidates排序&#xff0c;让重复的元素都在一起 单层递归时&a…

MATLAB - 读取双摆杆上的 IMU 数据

系列文章目录 前言 本示例展示了如何从安装在双摆杆上的两个 IMU 传感器生成惯性测量单元 (IMU) 读数。双摆使用 Simscape Multibody™ 进行建模。有关使用 Simscape Multibody™ 构建简易摆的分步示例&#xff0c;请参阅简易摆建模&#xff08;Simscape Multibody&#xff09…

深度学习(七):bert理解之输入形式

传统的预训练方法存在一些问题&#xff0c;如单向语言模型的局限性和无法处理双向上下文的限制。为了解决这些问题&#xff0c;一种新的预训练方法随即被提出&#xff0c;即BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;。通过在大规模…

python通过JS逆向采集艺恩电影数据, 并制作可视化

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 如果有什么疑惑/资料需要的可以点击文章末尾名片领取源码 环境使用: 版 本&#xff1a; python 3.10 编辑器&#xff1a;pycharm 2022.3.2 nodejs 模块使用: requests -> pip install requests execjs -> pip install…