各种滤波算法

各种滤波算法

  • 1. 半径离群点去除(Radius Outlier Removal,半径滤波)
  • 2. 统计离群点剔除(Statistical Outlier Removal, 统计滤波)
  • 3. 体素网格将采样(voxel grid downsampling)
  • 4. 最远点采样(Farthest Point Sampling, FPS)
  • 5. 正态空间将采样(Normal Space Sampling, NSS)
  • 6. 高斯滤波
  • 7. 双边滤波(Bilateral Filter)

这里整理了一些滤波算法,比如用于噪声点去除的Radius Outlier Removal和Statistical Outlier removal;用于降采样的voxel grid downsample,Farthest Point Sampling和Normal Space Sampling;用于上采样和平滑的Bilateral Filter;

1. 半径离群点去除(Radius Outlier Removal,半径滤波)

  • 算法思想
    半径离群点去除算法的原理主要涉及以下几个步骤:
    • 设定参数:首先需要设定半径 r r r 和最小邻居数k作为算法的参数。半径 r r r决定了在计算离群点时考虑的范围,最小邻居数 k k k 决定了一个点周围需要多少个邻居点才能将该点视为非离群点。
    • 建立数据结构:为了快速查找每个点的邻居点,通常会使用 KD 树或者球树等数据结构来存储点云数据,并加速邻居点的搜索。
    • 遍历点云:对于点云中的每个点 P i P_i Pi,在给定的半径 r r r内查找其邻居点,计算邻居点的数量 N i N_i Ni
    • 判断离群点:对于每个点 P i P_i Pi,如果其邻居点的数量小于设定的最小邻居数 k k k,则将 P i P_i Pi视为离群点。
    • 移除离群点:将被标记为离群点的点从点云中移除,得到去除离群点后的点云。

这种方法主要用于去除点云中的噪声点或者异常点,以提高点云数据的质量和准确性。

  • 代码1(使用PCL)
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/radius_outlier_removal.h>

int main() {
    // Load point cloud data
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile("input_cloud.pcd", *cloud);

    // Set parameters for radius outlier removal
    float radius = 0.1; // Radius search
    int min_neighbors = 10; // Minimum number of neighbors
    pcl::RadiusOutlierRemoval<pcl::PointXYZ> outrem;
    outrem.setInputCloud(cloud);
    outrem.setRadiusSearch(radius);
    outrem.setMinNeighborsInRadius(min_neighbors);

    // Apply radius outlier removal
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>);
    outrem.filter(*cloud_filtered);

    // Save filtered point cloud
    pcl::io::savePCDFile("output_cloud.pcd", *cloud_filtered);

    return 0;
}
  • 代码2(不使用PCL, 自己构建KDTree)
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>

struct Point {
    float x;
    float y;
    float z;
    int id;  // 用于标识点的ID

    Point(float x, float y, float z, int id) : x(x), y(y), z(z), id(id) {}
};

struct Node {
    Point point;
    Node* left;
    Node* right;

    Node(Point p) : point(p), left(nullptr), right(nullptr) {}
};

// 定义KD树
class KDTree {
private:
    Node* root;

    Node* buildTree(std::vector<Point>& points, int depth, int start, int end) {
        if (start > end) return nullptr;

        int axis = depth % 3;  // 在三维空间中进行分割

        std::sort(points.begin() + start, points.begin() + end + 1, [axis](const Point& a, const Point& b) {
            if (axis == 0) return a.x < b.x;
            else if (axis == 1) return a.y < b.y;
            else return a.z < b.z;
        });

        int mid = start + (end - start) / 2;
        Node* node = new Node(points[mid]);
        node->left = buildTree(points, depth + 1, start, mid - 1);
        node->right = buildTree(points, depth + 1, mid + 1, end);
        return node;
    }

    void searchNeighbors(Node* node, const Point& query, float radius, std::vector<int>& neighbors) {
        if (node == nullptr) return;

        float dist = calculateDistance(node->point, query);
        if (dist <= radius) {
            neighbors.push_back(node->point.id);
        }

        int axis = 0;
        if (axis == 0) {
            if (query.x - radius <= node->point.x) searchNeighbors(node->left, query, radius, neighbors);
            if (query.x + radius > node->point.x) searchNeighbors(node->right, query, radius, neighbors);
        } else if (axis == 1) {
            if (query.y - radius <= node->point.y) searchNeighbors(node->left, query, radius, neighbors);
            if (query.y + radius > node->point.y) searchNeighbors(node->right, query, radius, neighbors);
        } else {
            if (query.z - radius <= node->point.z) searchNeighbors(node->left, query, radius, neighbors);
            if (query.z + radius > node->point.z) searchNeighbors(node->right, query, radius, neighbors);
        }
    }

public:
    KDTree(std::vector<Point>& points) {
        root = buildTree(points, 0, 0, points.size() - 1);
    }

    std::vector<int> getNeighbors(const Point& query, float radius) {
        std::vector<int> neighbors;
        searchNeighbors(root, query, radius, neighbors);
        return neighbors;
    }

    float calculateDistance(const Point& p1, const Point& p2) {
        float dx = p1.x - p2.x;
        float dy = p1.y - p2.y;
        float dz = p1.z - p2.z;
        return std::sqrt(dx * dx + dy * dy + dz * dz);
    }
};

int main() {
    std::vector<Point> points = {{1, 2, 3, 0}, {4, 5, 6, 1}, {7, 8, 9, 2}, {10, 11, 12, 3}};
    KDTree kd_tree(points);

    Point query_point(3, 4, 5, -1);
    float radius = 2.0;
    std::vector<int> neighbors = kd_tree.getNeighbors(query_point, radius);

    for (int id : neighbors) {
        std::cout << "Neighbor ID: " << id << std::endl;
    }

    return 0;
}

2. 统计离群点剔除(Statistical Outlier Removal, 统计滤波)

统计滤波基于一种假设: 点云中大部分点都是从同一平均分布中采样得到的,而离群点则来自一个不同的分布。

  • 算法思想步骤如下:

    • 设定参数:首先需要设定滤波算法的参数,包括点云中每个点的邻域范围(半径或K近邻),以及用于判断离群点的统计参数阈值,如均值和标准差的倍数。
    • 计算邻域统计参数:对于点云中的每个点,计算其邻域内点的统计参数,通常包括均值和标准差。可以选择使用固定大小的邻域或者自适应的 K 近邻来计算统计参数。
    • 判断离群点:根据设定的统计参数阈值,判断每个点是否为离群点。通常采用的判断方式是,如果点的某个统计参数(如距离邻域点的平均距离)超过了设定的阈值,则将该点标记为离群点。
    • 滤除离群点:将被标记为离群点的点从点云中移除,得到去除离群点后的点云。
  • 公式表达步骤如下:

    • 设置邻域r,得到点P周围邻域r内的若干点,记为k个(其实不是每个点在周围邻域内的邻居都有k个)
    • 计算所有点与自身邻域邻居的距离值 d i j , i ∈ [ 1 , 2 , . . . , m ] , j ∈ [ 1 , 2 , . . . , k ] d_{ij},i \in[1,2,...,m],j\in[1,2,...,k] dij,i[1,2,...,m],j[1,2,...,k]。m表示输入的点云个数,k表示每个点的邻居个数(只是方便表达,每个点的邻域邻居个数会有不同)
    • 通过 d i j d_{ij} dij,计算该组点云的距离高斯分布 d ~ N ( μ , σ ) d ~ N(\mu, \sigma) dN(μ,σ)也就是说这组点的每个点与其邻域内邻居的欧式距离均值都要遵循这个分布
      μ = 1 m × k ∑ i = 1 m ∑ j = 1 k d i j , σ = 1 m × k ∑ i = 1 m ∑ j = 1 k ( d i j − μ ) 2 \mu = {1 \over m \times k} \sum_{i=1}^m \sum_{j=1}^kd_{ij}, \sigma = \sqrt{{1 \over m \times k} \sum_{i=1}^m \sum_{j=1}^k(d_{ij}-\mu)^2} μ=m×k1i=1mj=1kdij,σ=m×k1i=1mj=1k(dijμ)2
    • 计算每个点与其邻域半径范围内点的距离均值 d d d
    • 剔除距离均值d不再置信度区间内的点,即如果不满足下边的公式,就要剔除
      ∑ j = 1 k d > μ + 3 σ  or  ∑ j = 1 k d < μ − 3 σ \sum_{j=1}^{k} d>\mu+3 \sigma \text { or } \sum_{j=1}^{k} d<\mu-3 \sigma j=1kd>μ+3σ or j=1kd<μ3σ
  • 代码

#include <iostream>
#include <vector>
#include <cmath>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>

typedef pcl::PointXYZ PointType;
typedef pcl::PointCloud<PointType> PointCloud;

// Statistical outlier removal filter function using KDTree
PointCloud::Ptr statisticalOutlierRemovalKDTree(const PointCloud::Ptr& cloud, float radius) {
    pcl::KdTreeFLANN<PointType> kdtree;
    kdtree.setInputCloud(cloud);

    PointCloud::Ptr filtered_cloud(new PointCloud);
    std::vector<int> pointIdxRadiusSearch;
    std::vector<float> pointRadiusSquaredDistance;

    for (size_t i = 0; i < cloud->points.size(); ++i) {
        if (kdtree.radiusSearch(cloud->points[i], radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0) {
            std::vector<float> distances;
            for (size_t j = 0; j < pointIdxRadiusSearch.size(); ++j) {
                distances.push_back(std::sqrt(pointRadiusSquaredDistance[j]));
            }

            float sum_distances = 0.0;
            for (float dist : distances) {
                sum_distances += dist;
            }

            float mean_distance = sum_distances / distances.size();

            float sum_sq_diff = 0.0;
            for (float dist : distances) {
                sum_sq_diff += (dist - mean_distance) * (dist - mean_distance);
            }
            float std_dev = std::sqrt(sum_sq_diff / distances.size());

            float confidence_interval = 3 * std_dev;

            float sum_filtered_distances = 0.0;
            for (float dist : distances) {
                if (dist <= (mean_distance + confidence_interval) && dist >= (mean_distance - confidence_interval)) {
                    sum_filtered_distances += dist;
                }
            }

            float mean_filtered_distance = sum_filtered_distances / distances.size();

            if (sum_filtered_distances > (mean_distance + confidence_interval) || 
                sum_filtered_distances < (mean_distance - confidence_interval)) {
                // Point is an outlier, do not add to filtered cloud
            } else {
                // Point is within confidence interval, add to filtered cloud
                filtered_cloud->points.push_back(cloud->points[i]);
            }
        }
    }

    filtered_cloud->width = filtered_cloud->points.size();
    filtered_cloud->height = 1;
    return filtered_cloud;
}

int main() {
    PointCloud::Ptr cloud(new PointCloud);
    // Fill 'cloud' with point data (assuming loaded from file)

    float neighborhood_radius = 1.0;

    PointCloud::Ptr filtered_cloud = statisticalOutlierRemovalKDTree(cloud, neighborhood_radius);

    std::cout << "Filtered Cloud:" << std::endl;
    for (size_t i = 0; i < filtered_cloud->points.size(); ++i) {
        std::cout << "(" << filtered_cloud->points[i].x << ", " << filtered_cloud->points[i].y << ", " << filtered_cloud->points[i].z << ")" << std::endl;
    }

    return 0;
}

3. 体素网格将采样(voxel grid downsampling)

体素网格降采样的本质就是,设置体素网格的大小,对于包含点云的体素栅格,从栅格中提取一个点。如果只是针对坐标值来说,可以是随机提取,可以求均值,也可以直接使用栅格的中心。但是对于携带的物理信息,比如说反射强度等属性,可以采用投票的方式

  • 算法原理

    • 设定体素大小:首先需要设定体素网格的大小,即立方体的边长。这个大小决定了最终采样后点云的密度和分辨率。
    • 创建体素网格:将点云中的所有点按照体素大小划分到对应的立方体网格中。这一步可以使用一个三维数组或者类似的数据结构来表示体素网格。
    • 采样过程:对于每个立方体网格,选择其中包含点的网格作为采样后的点,可以选择点的均值或者其他统计值作为采样点的位置。
    • 降采样:根据体素网格的大小,可以选择保留每个体素网格中的一个采样点,或者按照一定规则保留部分采样点,实现点云的降采样。
  • 代码(PCL版本)

#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>

int main() {
    // Load point cloud data
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
    pcl::io::loadPCDFile<pcl::PointXYZ>("input_cloud.pcd", *cloud);

    // Voxel grid downsampling
    pcl::VoxelGrid<pcl::PointXYZ> voxel_grid;
    voxel_grid.setInputCloud(cloud);
    voxel_grid.setLeafSize(0.1, 0.1, 0.1);  // Set voxel size
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_downsampled(new pcl::PointCloud<pcl::PointXYZ>);
    voxel_grid.filter(*cloud_downsampled);

    // Save downsampled cloud
    pcl::io::savePCDFile<pcl::PointXYZ>("output_downsampled.pcd", *cloud_downsampled);

    std::cout << "PointCloud before downsampling: " << cloud->size() << " points" << std::endl;
    std::cout << "PointCloud after downsampling: " << cloud_downsampled->size() << " points" << std::endl;

    return 0;
}

4. 最远点采样(Farthest Point Sampling, FPS)

最远点采样(Farthest Point Sampling,FPS)算法是一种常用于点云数据采样的方法,其原理如下:

  • 初始化:随机选择一个点作为起始点,并将其加入到采样点集合中。
  • 迭代采样:对于剩余的点,计算它们到已选取采样点集合中所有点的最短距离(或者最远距离),选取距离最大的点作为下一个采样点,并将其加入到采样点集合中。
  • 重复直到结束:重复上述过程,直到采样点集合达到所需的数量或者所有点都被选取。

这个算法的关键是在每次迭代中高效地找到距离已选取采样点集合最远的点。这通常可以通过优先级队列(如堆)来实现,以快速找到最远点。

在这里插入图片描述

5. 正态空间将采样(Normal Space Sampling, NSS)

普通的采样,对于表面非均匀的场景不太友好。如果点云曲面有小的凹陷,如果使用均匀采样,就会遗失这种特征,所以引入了NSS的方法,NSS可以保证所有的法向量范围都可以采样的到。

  • NSS算法原理:
    • 在法向量空间中建立一些容器,每个容器对应一个不同的法向量方向。
    • 遍历点云中的每个点,根据点的法向量将点放入对应的法向量容器中。
    • 从每个不同的法向量容器中取出一定数量的点作为采样点,可以根据需求调整采样点的数量。
      这种方法能够有效地在法向量空间内对点云进行采样,保留了不同法向量方向上的特征点,有助于保持点云的形状信息。
      在这里插入图片描述

6. 高斯滤波

在这里插入图片描述
这里的S代表像素p的位置邻域空间,q为邻域内的某个位置, G σ ( x ) G_\sigma(x) Gσ(x)代表像素p和像素q两个像素点在某个尺度上的高斯权重(这里两者在该尺度上的距离越远,权重越小,表示像素点q给像素点p的贡献越小), I q I_q Iq这里可以指代像素点的RGB通道值。也就是说这里是使用高斯滤波来求像素点p的rgb通道值(借助像素p周围邻域内的所有像素点的rgb通道值完成滤波)

具体来说,高斯滤波器在处理图像或信号时,通过将每个像素点或采样点与周围像素点或采样点的值按照高斯函数的权重进行加权平均,从而达到平滑、去噪或者边缘保留的效果。

高斯滤波的本质可以用以下几点来总结:

加权平均:高斯滤波器对每个像素或采样点的值进行加权平均,权重由高斯函数决定。
平滑效果:高斯函数在中心值附近有较高的权重,而在距离中心值较远的位置权重逐渐减小,因此对信号进行高斯滤波可以实现平滑效果,去除噪声。
边缘保留:由于高斯函数的权重在中心值附近较高,在边缘处权重急剧减小,因此高斯滤波在一定程度上能够保留图像或信号的边缘特征,避免过度平滑导致细节丢失

7. 双边滤波(Bilateral Filter)

在这里插入图片描述

双边滤波是说同时使用两个尺度的高斯距离来给权重

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

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

相关文章

最优算法100例之30-表示数值的字符串

专栏主页&#xff1a;计算机专业基础知识总结&#xff08;适用于期末复习考研刷题求职面试&#xff09;系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 请实现一个函数用来判断字符串是否表示数值&#xff08;包括整数和小数&#xff09;。例如&a…

【35分钟掌握金融风控策略3】场景概述3

目录 ​编辑 场景概述 贷前、贷中、贷后的划分及对应的风控场景 贷前风控场景简介 预授信 授信审批 定额 定价 人工审核 场景概述 贷前、贷中、贷后的划分及对应的风控场景 在金融风控全生命周期中&#xff0c;贷前主要是指授信成功及之前的阶段、贷中主要是指授信成…

链表的应用

⽬录 1. 单链表经典算法OJ题⽬ 1.1 单链表相关经典算法OJ题1&#xff1a;移除链表元素 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ typedef …

基于opencv的猫脸识别模型

opencv介绍 OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发&#xff0c;以BSD许可证授权发行&#xff0c;可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、计算机视觉以及…

ctf刷题记录2(更新中)

因为csdn上内容过多编辑的时候会很卡&#xff0c;因此重开一篇&#xff0c;继续刷题之旅。 NewStarCTF 2023 WEEK3 Include &#x1f350; <?phperror_reporting(0);if(isset($_GET[file])) {$file $_GET[file];if(preg_match(/flag|log|session|filter|input|data/i, $…

解锁金融数据中心场景,实现国产化AD替代,宁盾身份域管为信创电脑、应用提供统一管理

随着信创国产化改造持续推进&#xff0c;越来越多的金融机构不断采购信创服务器、PC、办公软件等&#xff0c;其 IT 基础设施逐渐迁移至国产化 IT 架构下。为支撑国产化 IT 基础设施的正常使用和集中管理运维&#xff0c;某金融机构数据中心的微软Active Directory&#xff08;…

二、GitLab相关操作

GitLab相关操作 一、组、用户、项目管理1.创建组2.创建项目3.创建用户并分配组3.1 创建用户3.2 设置密码3.3 给用户分配组 二、拉取/推送代码1.配置ssh(第一次需要)1.1 创建一个空文件夹1.2 配置本地仓账号和邮箱1.3 生成ssh公钥密钥1.4 gitlab配置公钥 2.拉取代码3.推送代码3.…

FastAPI Web框架教程 第10章 APIRouter

10-1 APIRouter基本使用 需求场景 如果我们写一个网站&#xff0c;或者写一个APP&#xff0c;那整个项目应该是比较复杂的&#xff0c;此时不应该把所有代码放在一个文件中。 前几节课&#xff0c;我们通过把代码拆分到不同文件的方式&#xff0c;可以解决一些代码混乱的问题…

SD-WAN帮助企业实现对分布式网络的集中管理和控制

在当今数字化时代&#xff0c;企业网络越来越分散和复杂&#xff0c;分布在全球不同地点的分支机构和远程办公地点需要高效的网络连接来支持业务运营。传统的广域网&#xff08;WAN&#xff09;架构已经无法满足企业对网络灵活性、可靠性和安全性的需求。而SD-WAN的出现为解决这…

Phpstorm配置Xdebug

步骤 1、先去官网找到对应的php xdebug的版本 2、配置phpstorm断点调试 网址&#xff1a;https://xdebug.org/ 查看php对应的xdebug版本&#xff1a;Xdebug: Support — Tailored Installation Instructions 1.1查看对应php xdebug版本 全选&#xff0c;复制到目标网址 我…

C++要点细细梳理(上)(函数与面向对象)

之前我们讨论了C语言一些基础的细节&#xff0c;下面我们开始讨论C&#xff0c;&#xff0c;后面我打算接着谈C&#xff0c;也就是C#&#xff0c;先在此留个坑。 注意&#xff0c;本文有素材来自中国大学MOOC的C课程&#xff0c;本文也是该课程的听课笔记【这是链接】 1. 从C…

【HarmonyOS】ArkUI - 动画

利用属性动画、显示动画、组件转场动画实现组件动画效果。 一、属性动画 属性动画是通过设置组件的 animation 属性来给组件添加动画&#xff0c;当组件的 width、height、Opacity、backgroundColor、scale、rotate、translate 等属性变更时&#xff0c;可以实现渐变过渡效果。…

模块化编程:AMD 和 CMD 的魅力

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

力扣刷题部分笔记

Leetcode 力扣刷题笔记&#xff0c;记录了几个月来的题目记录&#xff0c;将会继续保持刷题~ 2024.01 1768.交替合并字符串 创建字符串不需要声明长度&#xff08;动态分配内存&#xff09;&#xff0c;push_back()可以加入某个字符&#xff0c;append()一般用于添加字符串…

js的qq换肤效果

文章目录 1. 演示效果2. 分析思路3. 代码实现3.1. 方式一3.2. 方式二3.3. 整体代码 1. 演示效果 2. 分析思路 先编写样式&#xff0c;弄好布局和排版。遍历这个集合&#xff0c;对每个图片元素&#xff08;img&#xff09;添加一个点击事件监听器。可以使用 for 或者 forEach …

IT外包服务:企业数据资产化加速利器

随着数字化时代的兴起&#xff0c;数据成为企业最为重要的资源之一。数据驱动创新对于企业的竞争力和可持续发展至关重要。在这一进程中&#xff0c;IT外包服务发挥着关键作用&#xff0c;加速企业数据资产化进程&#xff0c;为企业提供了重要支持。 首先&#xff0c;IT外包服务…

c++11 标准模板(STL)本地化库 - 平面类别 - (std::ctype) 定义字符分类表(四)

本地化库 本地环境设施包含字符分类和字符串校对、数值、货币及日期/时间格式化和分析&#xff0c;以及消息取得的国际化支持。本地环境设置控制流 I/O 、正则表达式库和 C 标准库的其他组件的行为。 平面类别 定义字符分类表 std::ctype template< class CharT > clas…

计算机网络(五) 传输层

传输层 一、传输层概述二、TCP1.报文段格式2.连接管理3.可靠传输4.流量控制5.拥塞控制 三、UDP1.报文段格式2.校验 一、传输层概述 从通信和信息处理的角度看&#xff0c;传输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功…

微信小程序纯CSS实现好看的加载动画

进入下面小程序可以体验效果&#xff1a; WXML: <wxs module"img" src"./loading.wxs"></wxs> <view class"loading-container {{show?:loading-container-hide}}"><view class"loading-mask" wx:if"{{ma…

LNMP环境:揭秘负载均衡与高可用性设计

lb1: 192.168.8.5 lb2: 192.168.8.6 web1:192.168.8.7 web2:192.168.8.8 php-fpm: 192.168.8.9 mysql: 192.168.8.10 nfs:192.168.8.11 分别插入镜像 8.5-8.8 分别安装nginx,并设置启动 8.9 安装php 8.10 安装mysql 先配置一台web服务器然后同步 设置网站根目录 cp -…