当前位置: 首页 > news >正文

Matlab通过GUI实现点云的ICP配准

        本节分享使用Matlab进行点云的ICP配准。点云配准是三维数据处理领域的核心技术之一,其目的是将不同视角、不同时间采集的点云数据统一到同一坐标系下,为后续的三维建模、目标检测、形变分析等任务提供基础。迭代最近点(Iterative Closest Point, ICP)算法是点云配准中最经典、最常用的方法之一,而 Matlab 凭借其强大的矩阵运算能力和完善的点云处理工具箱(PointCloud Toolbox),为 ICP 配准提供了高效、便捷的实现平台。本文将系统介绍 Matlab 中 ICP 配准的基本原理、主要流程及典型应用领域。

一、Matlab 点云 ICP 配准的基本介绍

1. 点云 ICP 配准的核心原理

        ICP 算法由 Besl 和 McKay 于 1992 年提出,其核心思想是通过迭代优化的方式,逐步最小化两组点云(源点云与目标点云)之间的距离误差,最终找到最优的刚体变换(平移 + 旋转),使源点云与目标点云实现精准对齐。

在 Matlab 中,ICP 配准的误差函数主要基于点到点(Point-to-Point)点到面(Point-to-Plane) 的距离度量:

  • 点到点误差:计算源点云中每个点与其在目标点云中最近点的欧氏距离,以所有距离的平方和作为优化目标;

  • 点到面误差:计算源点云中每个点到目标点云中对应最近点所在切平面的距离,该方法收敛速度更快、鲁棒性更强,尤其适用于噪声较多的点云数据。

        Matlab 的 PointCloud Toolbox 中,pcregistericp函数是实现 ICP 配准的核心接口,支持自定义误差度量、迭代次数、收敛阈值等参数,满足不同场景下的配准需求。

2. Matlab 点云处理的优势

        相比 C++(如 PCL 库)等其他平台,Matlab 在点云 ICP 配准中具有以下优势:

  • 易用性:无需复杂的代码编译,通过可视化界面(如 PointCloud Viewer)和简洁的函数调用即可完成配准流程;

  • 矩阵运算高效:ICP 算法涉及大量矩阵运算(如奇异值分解 SVD 求解变换矩阵),Matlab 的底层优化能显著提升计算效率;

  • 数据预处理与后处理工具完善:内置点云去噪(pcdenoise)、下采样(pcdownsample)、法向量估计(pcnormals)等工具,可直接为 ICP 配准提供高质量输入数据;

  • 结果可视化直观:支持实时显示配准过程中源点云与目标点云的对齐效果,便于分析误差来源(如pcshowpair函数)。

二、Matlab 点云 ICP 配准的主要流程

        Matlab 中 ICP 配准的完整流程可分为数据预处理ICP 迭代优化结果评估与后处理三个阶段,每个阶段需根据点云数据的特性(如噪声、密度、重叠度)选择合适的参数与工具。

1. 阶段 1:数据预处理(关键前提)

        原始点云通常存在噪声、冗余点或坐标系偏移,直接输入 ICP 算法会导致收敛缓慢或配准失败。预处理的核心目标是提升点云质量、减少计算量,主要步骤如下:

(1)点云读取与格式转换

        Matlab 支持读取 PLY、PCD、XYZ 等常见点云格式,通过pcread函数加载数据,并转换为pointCloud对象(ICP 算法的标准输入格式)。示例代码如下:

% 读取源点云(待配准)和目标点云(参考坐标系)source = pcread('source_cloud.ply'); % 源点云target = pcread('target_cloud.pcd'); % 目标点云% 查看点云基本信息(点数、坐标系、颜色等)disp('源点云信息:');disp(source);disp('目标点云信息:');disp(target);

(2)点云去噪与下采样

  • 去噪:针对噪声点(如测量误差导致的孤立点),使用pcdenoise函数基于统计滤波或半径滤波去除噪声,保留核心点云结构。例如:

% 统计滤波去噪(默认3倍标准差阈值)source_denoised = pcdenoise(source);target_denoised = pcdenoise(target);
  • 下采样:当点云密度过高时(如百万级点数),通过pcdownsample函数减少点数(如体素网格下采样),降低 ICP 迭代的计算成本,同时保持点云的几何特征。例如:

% 体素网格下采样(体素大小0.01m,根据实际尺度调整)source_downsampled = pcdownsample(source_denoised, 'gridAverage', 0.01);target_downsampled = pcdownsample(target_denoised, 'gridAverage', 0.01);

(3)初始配准(可选但重要)

        ICP 算法是局部收敛算法,对初始位置敏感:若源点云与目标点云初始偏移过大(如重叠度低于 30%),ICP 易陷入局部最优解,导致配准失败。因此,需先通过初始配准将两组点云调整到 “近似对齐” 状态,常用方法包括:

  • 基于特征的配准:提取点云的关键特征(如 SIFT、FPFH 特征),通过pcregisterfeature函数实现粗配准;

  • 手动设置初始变换:若已知大致偏移(如平移量、旋转角度),可通过affinetform3d函数定义初始变换矩阵。

示例:通过特征匹配实现初始配准

% 估计目标点云的法向量(特征提取需法向量信息)target_normals = pcnormals(target_downsampled);target_with_normals = pointCloud(target_downsampled.Location, ...'Normals', target_normals);% 基于FPFH特征进行初始粗配准[initialTransform, ~] = pcregisterfeature(source_downsampled, target_with_normals);% 应用初始变换,得到粗配准后的源点云source_initial = pctransform(source_downsampled, initialTransform);

2. 阶段 2:ICP 迭代优化(核心步骤)

        在预处理的基础上,调用pcregistericp函数执行 ICP 迭代,核心是通过多轮 “寻找最近点→计算最优变换→更新源点云” 的循环,最小化距离误差。

(1)函数关键参数设置

        pcregistericp的参数需根据点云类型(如无序点云、有序点云)和配准精度需求调整,常用参数如下:

  • Metric:误差度量方式,'pointToPoint'(点到点,适用于密度均匀点云)或'pointToPlane'(点到面,适用于噪声较多或曲面点云);

  • MaxIterations:最大迭代次数(默认 200,复杂点云可增至 500);

  • TransformationTolerance:变换矩阵的收敛阈值(默认 1e-6,精度要求高时可设为 1e-8);

  • DistanceThreshold:最近点搜索的距离阈值(过滤异常匹配点,通常设为点云平均间距的 2-3 倍)。

(2)ICP 配准示例代码

% 估计目标点云法向量(点到面误差需法向量)target_normals = pcnormals(target_downsampled);target_with_normals = pointCloud(target_downsampled.Location, ...'Normals', target_normals);% 执行ICP配准(点到面误差,最大迭代300次)[finalTransform, registeredSource, error] = pcregistericp( ...source_initial, % 初始配准后的源点云target_with_normals, % 带法向量的目标点云'Metric', 'pointToPlane', ...'MaxIterations', 300, ...'TransformationTolerance', 1e-8, ...'DistanceThreshold', 0.02); % 距离阈值0.02m% 输出配准误差(平均距离误差,越小越好)disp(['ICP配准平均误差:', num2str(error), 'm']);

(3)ICP 迭代的核心逻辑

  1. 寻找最近点:对源点云中的每个点,在目标点云中搜索距离最近的点(基于 Kd 树加速搜索,Matlab 内置优化);

  1. 计算最优变换:通过最小二乘法求解变换矩阵(旋转矩阵 R + 平移向量 t),点到面误差需结合法向量构建线性方程组,通过 SVD 分解求解;

  1. 更新源点云:将计算出的变换矩阵应用于源点云,得到新的源点云位置;

  1. 收敛判断:若两次迭代的变换矩阵差异小于TransformationTolerance,或迭代次数达到MaxIterations,则停止迭代,输出最终变换矩阵。

3. 阶段 3:结果评估与后处理

        配准完成后,需通过可视化定量指标评估结果是否满足需求,若不满足则返回前序步骤调整参数。

(1)结果可视化

使用pcshowpair函数对比配准前后的点云对齐效果,通常用不同颜色区分源点云和目标点云(如红色表示源点云,蓝色表示目标点云):

% 显示配准前的点云(初始配准结果)figure;pcshowpair(source_initial, target_downsampled, 'ColorSource', [1,0,0], 'ColorTarget', [0,0,1]);title('ICP配准前(初始配准结果)');xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)');grid on; axis equal;% 显示配准后的点云(最终结果)figure;pcshowpair(registeredSource, target_downsampled, 'ColorSource', [1,0,0], 'ColorTarget', [0,0,1]);title('ICP配准后(最终结果)');xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)');grid on; axis equal;

(2)定量评估指标

        除了pcregistericp返回的平均距离误差,还可通过以下指标评估配准精度:

  • 均方根误差(RMSE):计算所有对应点对距离的均方根,更能反映极端误差;

  • 重叠度:统计配准后源点云与目标点云的重叠区域比例(通过pccompare函数计算);

  • 变换矩阵合理性:检查旋转矩阵是否满足正交性(行列式接近 1),避免出现物理上不可能的变换。

示例:计算 RMSE 和重叠度

% 计算对应点对的距离(使用Kd树搜索最近点)kdtree = KDTreeSearcher(target_downsampled.Location);[indices, distances] = knnsearch(kdtree, registeredSource.Location);% 计算RMSErmse = sqrt(mean(distances.^2));disp(['配准RMSE:', num2str(rmse), 'm']);% 计算重叠度(距离小于0.01m视为重叠)overlap_ratio = sum(distances < 0.01) / length(distances);disp(['点云重叠度:', num2str(overlap_ratio*100), '%']);

(3)后处理(可选)

        若配准结果满足需求,可将最终变换矩阵应用于原始高分辨率点云(而非下采样后的点云),得到高精度的配准结果:

% 将最终变换应用于原始去噪点云(保留细节)registeredSource_full = pctransform(source_denoised, finalTransform);% 保存配准后的点云pcwrite(registeredSource_full, 'registered_source.ply');

三、Matlab 点云 ICP 配准的应用领域

        ICP 配准作为三维数据融合的核心技术,在 Matlab 的工具链支持下,已广泛应用于工业检测、机器人导航、文物修复等多个领域,以下为典型应用场景:

1. 工业质量检测

在汽车、航空航天等精密制造领域,需通过点云配准对比产品的 “实际模型” 与 “设计模型”(CAD 模型),检测尺寸偏差或表面缺陷。例如:

  • 流程:使用 3D 扫描仪采集产品表面点云(源点云),导入 Matlab 后与 CAD 模型生成的点云(目标点云)进行 ICP 配准,通过pcshowpair显示偏差区域,计算最大偏差值;

  • 优势:Matlab 的可视化工具可直观标记超差区域,误差分析函数(如pcdevation)可自动生成检测报告,适用于批量产品的快速质检。

2. 机器人导航与环境建模

        移动机器人(如自动驾驶汽车、无人机)需通过多帧点云配准构建全局环境地图(SLAM,同步定位与地图构建),实现自主导航。例如:

  • 流程:机器人搭载激光雷达(LiDAR)实时采集周围环境点云,每帧点云作为源点云,与前一帧配准后的点云(目标点云)通过 ICP 对齐,逐步构建全局地图;

  • 优化:Matlab 支持将 ICP 与里程计数据融合(如robotLocalization工具箱),解决动态场景下的配准漂移问题,提升地图精度。

3. 文物修复与数字重建

        文物(如雕塑、古建筑)的数字重建需融合多视角点云数据,ICP 配准是实现数据融合的关键步骤。例如:

  • 流程:使用手持 3D 扫描仪从不同角度采集文物点云(如正面、侧面、顶部),在 Matlab 中依次将各视角点云与基准视角点云(目标点云)进行 ICP 配准,拼接成完整的三维模型;

  • 特色:Matlab 的点云平滑工具(如pcsmooth)可修复扫描过程中的缺失区域,结合meshgeneration工具箱生成高质量网格模型,为文物保护提供数字存档。

4. 医疗影像分析

        在医学领域,ICP 配准可用于 CT、MRI 等影像的三维重建与器官运动分析(如肺部呼吸运动跟踪)。例如:

  • 流程:将患者不同时间采集的肺部 CT 点云(源点云)与基准 CT 点云(目标点云)进行 ICP 配准,计算肺部在呼吸周期内的形变参数;

  • 价值:通过 Matlab 的数值分析工具(如curvefit)拟合形变规律,为放疗计划制定提供精准的器官运动数据,减少治疗误差。

5. 逆向工程

        逆向工程(如产品仿制、零件复刻)需将物理实体的点云数据转换为可编辑的三维模型,ICP 配准用于对齐多部件点云。例如:

  • 流程:扫描机械零件的多个组件(如齿轮、轴),在 Matlab 中通过 ICP 配准将各组件点云对齐到同一坐标系,再通过pcmerge函数合并为完整零件点云,为后续 CAD 建模提供基础。

四、总结与注意事项

        Matlab 凭借其便捷的函数接口、高效的矩阵运算和直观的可视化工具,成为点云 ICP 配准的理想平台。在实际应用中,需注意以下关键点:

        1. 预处理是关键:噪声、冗余点会严重影响 ICP 收敛性,需优先进行去噪、下采样;若初始偏移过大,必须通过特征匹配等方法实现粗配准;

        2. 参数按需调整:点到面误差适用于曲面或噪声点云,点到点误差适用于密度均匀的结构化点云;迭代次数、距离阈值需根据点云规模和精度需求调整;

        3. 结果需验证:除了平均误差和 RMSE,还需通过可视化检查重叠区域是否对齐,避免因局部最优解导致的配准失败。

        随着三维扫描技术的普及,点云 ICP 配准在工业、医疗、机器人等领域的应用将更加广泛,而 Matlab 的持续更新(如 R2024b 版本增强了大场景点云的配准效率)也将为该技术的落地提供更强大的支持。

本次使用的数据是——————兔砸,还是兔砸!

一、点云ICP配准的程序

1、最简版

%% 1. 读取点云
[file, path] = uigetfile({'*.ply;*.pcd;*.xyz','点云文件 (*.ply,*.pcd,*.xyz)'},...'请选择点云');
if file==0; return; end
fname = fullfile(path,file);
ptCloudSrc = pcread(fname);%% 2. 平移
t      = [0.1 0.15 0.2];                 % x,y,z 偏移
pts    = ptCloudSrc.Location;           
ptCloudTgt = pointCloud(pts + t, ...'Normal', ptCloudSrc.Normal, ...'Color',  ptCloudSrc.Color);  % 保留法向/颜色%% 3. 加噪声
noise = 0.001 * randn(size(ptCloudTgt.Location));  % N(0,0.001^2)
ptCloudTgt = pointCloud(ptCloudTgt.Location + noise, ...'Normal', ptCloudTgt.Normal, ...'Color',  ptCloudTgt.Color);  % 保留法向/颜色maxDistance = 1;
tformInit   = eye(4);
%% 4. ICP 配准(2020a 语法)
tform = pcregistericp(ptCloudSrc, ptCloudTgt);   % 只能传两个参数%% 5. 可视化(2020a)
% 给点云上色
ptCloudSrc.Color = repmat(uint8([255 0 0]), size(ptCloudSrc.Location,1), 1);  % 红
ptCloudTgt.Color = repmat(uint8([0 255 0]), size(ptCloudTgt.Location,1), 1);  % 绿figure('Name','初始位置','Position',[50 50 1200 800]);
pcshow(ptCloudSrc); hold on;
pcshow(ptCloudTgt);
title('初始相对位置');%% 6. 配准后
ptCloudReg = pctransform(ptCloudSrc, tform);   % 仍保持红色
figure('Name','配准后','Position',[50 50 1200 800]);
pcshow(ptCloudReg); hold on;
pcshow(ptCloudTgt);   % 绿色
title('ICP 结果');%% 7. 控制台输出
fprintf('配准矩阵:\n');
disp(tform);

2、GUI版本

function icpAlignGUI
% ICP 配准 GUI(三栏:原始 | 目标 | 配准后)—— 2020a 兼容
fig = figure('Name','ICP 配准工具','NumberTitle','off',...'MenuBar','none','ToolBar','none','Position',[100 100 1280 720]);%% ==================== 界面布局 ====================
imgWidth = 0.78;
panelW   = imgWidth/3 - 0.005;pnlSrc = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,'Position',[0.02 0.02 panelW 0.96],'Title','原始点云(红)');
pnlTgt = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,'Position',[0.02+panelW+0.005 0.02 panelW 0.96],'Title','目标点云(绿)');
pnlReg = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,'Position',[0.02+2*(panelW+0.005) 0.02 panelW 0.96],'Title','配准后(红→绿)');axSrc = axes('Parent',pnlSrc,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axTgt = axes('Parent',pnlTgt,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);
axReg = axes('Parent',pnlReg,'Units','normalized','Position',[0.05 0.05 0.90 0.90]);pnlCtrl = uipanel('Parent',fig,'Units','normalized',...'FontSize',16,'Position',[0.78 0 0.22 1],'Title','控制');%% ==================== 控件 ====================
txtH  = 0.04;  btnH = 0.06;  gap = 0.02;  yTop = 0.94;uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','浏览…',...'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...'Callback',@loadCloud);
yTop = yTop - btnH - gap;lblInfo = uicontrol('Parent',pnlCtrl,'Style','text','String','未加载点云',...'FontSize',10,'Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...'HorizontalAlignment','left');
yTop = yTop - txtH - gap;% 平移 X/Y/Z
uicontrol('Parent',pnlCtrl,'Style','text','String','平移偏移 / mm',...'FontSize',12,'FontWeight','bold','Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...'HorizontalAlignment','left');
yTop = yTop - txtH - gap;uicontrol('Parent',pnlCtrl,'Style','text','String','X',...'FontSize',10,'Units','normalized','Position',[0.05 yTop-txtH 0.15 txtH]);
editX = uicontrol('Parent',pnlCtrl,'Style','edit','String','100',...'FontSize',14,'Units','normalized','Position',[0.20 yTop-txtH 0.20 txtH],...'Callback',@refreshTarget);
uicontrol('Parent',pnlCtrl,'Style','text','String','Y',...'FontSize',10,'Units','normalized','Position',[0.45 yTop-txtH 0.15 txtH]);
editY = uicontrol('Parent',pnlCtrl,'Style','edit','String','150',...'FontSize',14,'Units','normalized','Position',[0.60 yTop-txtH 0.20 txtH],...'Callback',@refreshTarget);
uicontrol('Parent',pnlCtrl,'Style','text','String','Z',...'FontSize',10,'Units','normalized','Position',[0.05 yTop-2*txtH 0.15 txtH]);
editZ = uicontrol('Parent',pnlCtrl,'Style','edit','String','200',...'FontSize',14,'Units','normalized','Position',[0.20 yTop-2*txtH 0.20 txtH],...'Callback',@refreshTarget);
yTop = yTop - 2*txtH - gap;% 噪声强度
uicontrol('Parent',pnlCtrl,'Style','text','String','噪声强度 / mm',...'FontSize',12,'FontWeight','bold','Units','normalized','Position',[0.05 yTop-txtH 0.90 txtH],...'HorizontalAlignment','left');
yTop = yTop - txtH - gap;sliderSig = uicontrol('Parent',pnlCtrl,'Style','slider','Min',0,'Max',20,'Value',1,...'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.65 btnH],...'Callback',@refreshTarget);
txtSig    = uicontrol('Parent',pnlCtrl,'Style','edit','String','1',...'FontSize',16,'Units','normalized','Position',[0.75 yTop-btnH 0.20 btnH],...'Callback',@editSigCB);
yTop = yTop - btnH - gap;% ICP 按钮
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','运行 ICP',...'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...'Callback',@runICP);
yTop = yTop - btnH - gap;% 保存配准后
uicontrol('Parent',pnlCtrl,'Style','pushbutton','String','保存配准结果',...'FontSize',16,'Units','normalized','Position',[0.05 yTop-btnH 0.90 btnH],...'Callback',@(s,e)saveCloud(ptCloudReg));%% ==================== 数据 ====================
ptCloudSrc = pointCloud.empty;
ptCloudTgt = pointCloud.empty;
ptCloudReg = pointCloud.empty;
tform      = eye(4);%% ==================== 回调 ====================function loadCloud(~,~)[file,path] = uigetfile({'*.ply;*.pcd;*.xyz','点云文件'},'选择点云');if isequal(file,0), return; endtryptCloudSrc = pcread(fullfile(path,file));catch MEerrordlg(ME.message,'读取失败'); return;endset(lblInfo,'String',sprintf('已加载:%s  (%d 点)',file,ptCloudSrc.Count));refreshTarget();endfunction refreshTarget(~,~)% 检查数据if isempty(ptCloudSrc)return;end% 1. 读取平移量(mm → m)t = [str2double(get(editX,'String'));str2double(get(editY,'String'));str2double(get(editZ,'String'))] / 1000;   % 3×1if any(isnan(t))t = [0.1 0.15 0.2];                         % 默认偏移endt = t.';                                        % 3×1 → 1×3 便于广播% 2. 平移xyz = ptCloudSrc.Location + t;                  % N×3 + 1×3% 3. 加噪声(改名避免 sigma 函数冲突)noiseSigma = get(sliderSig,'Value') / 1000;     % mm → mxyz = xyz + noiseSigma * randn(size(xyz));% 4. 组装目标点云(绿色)ptCloudTgt = pointCloud(xyz, ...'Normal', ptCloudSrc.Normal, ...'Color',  ptCloudSrc.Color);ptCloudTgt.Color = repmat(uint8([0 255 0]), size(xyz,1), 1);  % 绿% 5. 显示showPointCloud(axTgt, ptCloudTgt);% 6. 同步原始点云(红色)ptCloudSrc.Color = repmat(uint8([255 0 0]), size(ptCloudSrc.Location,1), 1);showPointCloud(axSrc, ptCloudSrc);% 7. 清空上次配准结果ptCloudReg = pointCloud.empty;cla(axReg); title(axReg, '配准后(红→绿)');% 8. 刷新文本框set(txtSig, 'String', num2str(get(sliderSig,'Value')));endfunction editSigCB(src,~)v = str2double(get(src,'String'));if isnan(v), v = 1; endv = max(0,min(20,v));set(sliderSig,'Value',v);refreshTarget();endfunction runICP(~,~)if isempty(ptCloudSrc) || isempty(ptCloudTgt)errordlg('请先加载点云','提示'); return;endh = waitbar(0,'ICP 配准中…');tform = pcregistericp(ptCloudSrc,ptCloudTgt);   % 2020a 语法ptCloudReg = pctransform(ptCloudSrc,tform);     % 红色close(h);% 显示showPointCloud(axReg,ptCloudReg); hold(axReg,'on');pcshow(ptCloudTgt,'Parent',axReg);              % 绿色叠加hold(axReg,'off');title(axReg,'ICP 结果:红→绿');% 控制台输出fprintf('配准矩阵:\n'); disp(tform);endfunction saveCloud(cloud)if isempty(cloud)errordlg('请先运行 ICP','提示'); return;end[file,path] = uiputfile({'*.pcd','PCD';'*.ply','PLY';'*.xyz','XYZ'},'保存配准点云');if isequal(file,0), return; endtrypcwrite(cloud,fullfile(path,file),'Precision','double');msgbox('保存成功!','提示');catch MEerrordlg(ME.message,'保存失败');endendfunction showPointCloud(ax,pc)cla(ax); set(ax,'Color','w');pcshow(pointCloud(nan(0,3)),'Parent',ax,'MarkerSize',1); drawnow; % 2020a 暖启动pcshow(pc,'Parent',ax,'MarkerSize',35);axis(ax,'tight'); grid(ax,'on'); view(ax,3); drawnow;end
end

二、点云ICP配准的结果

        目标点云使用的是兔砸平移和加噪声处理的,框里面的数字也是动态字体,可以修改。可以看到,ICP配准后,原始点云和目标点云基本上重合了,也说明本节的ICP配准算法是有效的,感兴趣的小伙伴可以再优化下啊。

就酱,下次见^-^

http://www.dtcms.com/a/418671.html

相关文章:

  • Ubuntu 部署 ClickHouse:高性能分析型数据库(附shell脚本一键部署↓)
  • 【GUI自动化测试】菜单控件操作与记事本自动化测试实践
  • FFmpeg-vflip滤镜使用
  • 有没有做线播放网站合肥百度快照优化排名
  • 友链交换网站源码中信建设有限责任公司唐万哩
  • 具有品牌的上海网站建设山西运城网站开发
  • 网约车架构
  • K8s StorageClass配置实战:从入门到精通
  • 鼻毛修剪器MCU方案开发设计
  • 为什么LLM会使用到向量这种数学工具?
  • LocalStorage Token vs HttpOnly Cookie 认证方案
  • ArkUI V2中Repeat组件使用注意事项总结
  • 自动字幕翻译避坑指南
  • Go vs. PHP:核心优势劣势对比
  • Go 语言中的**数组 (Array)*用法
  • php 网站部署虚拟主机安装wordpress
  • 浙江省旅游企业网站建设情况做最最优秀的视频网站有哪些
  • 设计模式第五章(门面模式)
  • 海康相机SDK封装
  • 大模型应用:一个基于AI大模型的自动邮件简报系统 - Flask + HTML 方案
  • 开源 C# 快速开发(八)通讯--Tcp服务器端
  • MTK调试-电池识别
  • 网站目标网页制作下载图片代码
  • 钱站网站如何建设手机移动网站
  • Vue调用浏览器打印
  • 捷讯官网 网站建设网站到期只续域名不续空间能打开吗
  • CS231n学习笔记1-4: Image Features
  • DragonBalls_One009*
  • extern关键字
  • 捷为科技亮相新能源汽车产业对接会,数智化平台赋能汽车行业高质量发展