2025国赛-基于统计滤波的点云去噪
2025国赛-基于统计滤波的点云去噪

2025国赛-基于统计滤波的点云去噪

一、读取数据文件

1、设计点类

class Point:
    __slots__ = ('idx', 'x', 'y', 'z')
    def __init__(self, idx, x, y, z):
        self.idx = idx  # 存储点的序号(从1开始)
        self.x = x
        self.y = y
        self.z = z

2、静默读取

不将大量点数据显示到 Qt 界面,避免渲染卡顿。

(1)初始化路径

file_path = None

(2)打开文件

def open_file(self):
    global file_path
    path, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "文本文件(*.txt)")
    file_path = path
    self.original_text_area.setText("已正确读取并将点数据静默后台存储。")
    self.result_text_area.setText("等待数据处理....")
    self.status_label.setText("已打开数据文件。")

(3)逐行解析点数据

points = []
with open(file_path, "r", encoding="utf-8") as f:
    for idx, line in enumerate(f, start=1):
        part = line.strip().split(" ")
        p = Point(int(idx), float(part[0]), float(part[1]), float(part[2]))
        points.append(p)

二、算法实现

去噪实现

采用哈希思想,计算每个点的格网索引,以格网元组为键、该格网内的点列表为值,存入字典:

my_dict = {}  # key为元组(网格索引),value为列表(该网格内的点)
for p in points:
    i = int((p.x - x_min) / c)
    j = int((p.y - y_min) / c)
    k = int((p.z - z_min) / c)
    m_key = (i, j, k)
    if m_key not in my_dict:
        my_dict[m_key] = []
    my_dict[m_key].append(p)

进行 k 近邻点搜索时,先计算目标点所在格网,再通过字典查询相邻 27 个格网的候选点,时间复杂度 O(n)。

错误解法(O(n²))

for p in points:
    i, j, k = p.in_grid
    flag = []
    for i_ in [-1, 0, 1]:
        for j_ in [-1, 0, 1]:
            for k_ in [-1, 0, 1]:
                new_grid = (i + i_, j + j_, k + k_)
                flag.append(new_grid)
    for px in points:  # 遍历所有点,O(n²)
        ix, jx, kx = px.in_grid
        if (ix, jx, kx) in flag:
            p.neighbor_.append(px)

未利用格网索引的哈希思想,导致双重循环,时间复杂度 O(n²)。

三、源代码

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import math

file_path = None

class Point:
    __slots__ = ('idx', 'x', 'y', 'z')
    def __init__(self, idx, x, y, z):
        self.idx = idx # 存储点的序号(从1开始)
        self.x = x
        self.y = y
        self.z = z

def calculate_distance(p1, p2):
    return math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2 + (p1.z - p2.z) ** 2)

# 计算点p的候选点和邻近点
def calculate_neighbor(p, x_min, y_min, z_min, c, my_dict):
    x_lst = [-1, 0, 1]
    y_lst = [-1, 0, 1]
    z_lst = [-1, 0, 1]
    candidate = []
    x = p.x
    y = p.y
    z = p.z
    i = int((x - x_min) / c)
    j = int((y - y_min) / c)
    k = int((z - z_min) / c)
    for xx in x_lst:
        for yy in y_lst:
            for zz in z_lst:
                neighbor_ijk = (i + xx, j + yy, k + zz)
                if neighbor_ijk not in my_dict:
                    continue
                candidate.extend(my_dict[neighbor_ijk])
    # 计算p与候选点的欧氏距离,排序后取前6个作为邻近点
    candidate.sort(key = lambda p1: calculate_distance(p1, p))
    neighbor_points = candidate[1:7]  # 取前六个点作为邻近点(注意:排除自身点)
    idx_max = max([p.idx for p in neighbor_points])
    return len(candidate) - 1, idx_max, neighbor_points # 返回点p的候选点总数和其六个邻近点中序号的最大值

class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("基于统计滤波的点云去噪")
        self.setGeometry(100,100,850,600)
        self._init_ui()
    def _init_ui(self):
        menu_bar = self.menuBar()
        file_menu = menu_bar.addMenu("文件")
        file_menu.addAction("📂打开",self.open_file)
        file_menu.addAction("🗃️保存",self.save_file)
        file_menu.addSeparator()
        file_menu.addAction("❌关闭",self.close)
        menu_bar.addMenu("运行").addAction("⛷️运行算法",self.process_data)
        menu_bar.addMenu("显示").addAction("🖥️显示结果", self.process_data)
        toolbar = self.addToolBar("工具栏")
        toolbar.addAction("📂打开",self.open_file)
        toolbar.addAction("⛷️运行算法",self.process_data)
        self.status_label = QLabel("✅就绪")
        self.statusBar().addPermanentWidget(self.status_label, 1)
        self.statusBar().setStyleSheet("background:#e8f4f0;")
        tab = QWidget()
        layout = QVBoxLayout(tab)
        layout.setContentsMargins(5,5,5,5)
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(self._make_panel("原始数据"))
        splitter.addWidget(self._make_panel("运行结果"))
        splitter.setSizes([425,425])
        layout.addWidget(splitter)
        notebook = QTabWidget()
        notebook.addTab(tab,"数据处理")
        self.setCentralWidget(notebook)
        self.original_text_area = self.panels[0]
        self.result_text_area = self.panels[1]
    def _make_panel(self,title):
        frame = QFrame()
        frame.setFrameShape(QFrame.StyledPanel)
        layout = QVBoxLayout(frame)
        layout.addWidget(QLabel(title))
        text = QTextEdit()
        text.setStyleSheet("font-family:Consolas;font-size:10pt;")
        layout.addWidget(text)
        if not hasattr(self,'panels'):
            self.panels = []
        self.panels.append(text)
        return frame
    def open_file(self):
        global file_path
        path,_ = QFileDialog.getOpenFileName(self, "打开文件", "", "文本文件(*.txt)")
        file_path = path
        self.original_text_area.setText("✅已正确读取并将点数据静默后台存储。")
        self.result_text_area.setText("⌛️等待数据处理....")
        self.status_label.setText(f"✅已打开数据文件。")
    def save_file(self):
        path, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "文本文件(*.txt)")
        with open(path,"w",encoding="utf-8") as f:
            f.write(self.result_text_area.toPlainText())
        self.status_label.setText(f"✅已保存结果至:{path}")
    def process_data(self):
        try:
            processed_lines = []
            # 一、读取数据文件
            points = []
            with open(file_path, "r", encoding="utf-8") as f:
                for idx, line in enumerate(f, start = 1):
                    part = line.strip().split(" ")
                    p = Point(int(idx), float(part[0]), float(part[1]), float(part[2]))
                    points.append(p)
            processed_lines.append(f"1、点P1的x坐标为:{points[0].x:.3f}")
            processed_lines.append(f"2、点P6的y坐标为:{points[5].y:.3f}")
            processed_lines.append(f"3、点P789的z坐标为:{points[788].z:.3f}")
            # 二、程序算法
            # 1、数据统计与初始化
            n = len(points)
            processed_lines.append(f"4、原始点云的总点数:{n}")
            # 2、去噪实现
            # 2.1格网划分与点云分配
            c = 3
            x_max = max([p.x for p in points])
            y_max = max([p.y for p in points])
            z_max = max([p.z for p in points])
            x_min = min([p.x for p in points])
            y_min = min([p.y for p in points])
            z_min = min([p.z for p in points])
            x_max1 = int((x_max - x_min) / c + 1) * c + x_min
            y_max1 = int((y_max - y_min) / c + 1) * c + y_min
            z_max1 = int((z_max - z_min) / c + 1) * c + z_min

            cnt = 0
            for p in points:
                x = p.x
                y = p.y
                z = p.z
                i = int((x-x_min) / c)
                j = int((y-y_min) / c)
                k = int((z-z_min) / c)
                if i == 0 and j == 0 and k == 0:
                    cnt += 1

            p1_i = int((points[0].x - x_min) / c)
            p6_j = int((points[5].y - y_min) / c)
            processed_lines.append(f"5、点云数据x最大值xmax:{x_max:.3f}")
            processed_lines.append(f"6、点云数据y最大值ymax:{y_max:.3f}")
            processed_lines.append(f"7、点云数据z最大值zmax:{z_max:.3f}")
            processed_lines.append(f"8、格网xyz最小最大值xmax1:{x_max1:.3f}")
            processed_lines.append(f"9、格网xyz最小最大值ymax1:{y_max1:.3f}")
            processed_lines.append(f"10、格网xyz最小最大值zmax1:{z_max1:.3f}")
            processed_lines.append(f"11、网格(0,0,0)内的点个数:{cnt}")
            processed_lines.append(f"12、点P1的网格索引(i,j,k)中的i分量(第一分量):{p1_i}")
            processed_lines.append(f"13、点P6的网格索引(i,j,k)中的j分量(第二分量):{p6_j}")
            # 2.2k邻近点搜索(k=6)
            # (1)存储每个网格包括哪些点
            my_dict = {} # key为元组,表示网格。value为列表,表示在该网格的点类。
            for p in points:
                x = p.x
                y = p.y
                z = p.z
                i = int((x - x_min) / c)
                j = int((y - y_min) / c)
                k = int((z - z_min) / c)
                m_key = (i, j, k)
                if m_key not in my_dict:
                    my_dict[m_key] = []
                my_dict[m_key].append(p)
            # (2)计算点P1的候选点总数,点P6的候选点总数,点P1的6个邻近点序号中最大值,点P6的六个邻近点序号中最大值
            p1_candidate_points_cnt, p1_max_idx,_ = calculate_neighbor(points[0], x_min, y_min, z_min, c, my_dict)
            p6_candidate_points_cnt, p6_max_idx,_ = calculate_neighbor(points[5], x_min, y_min, z_min, c, my_dict)
            processed_lines.append(f"14、点P1的候选点总数:{p1_candidate_points_cnt}")
            processed_lines.append(f"15、点P6的候选点总数:{p6_candidate_points_cnt}")
            processed_lines.append(f"16、点P1的6个邻近点序号中最大值:{p1_max_idx}")
            processed_lines.append(f"17、点P6的6个邻近点序号中最大值:{p6_max_idx}")
            # 2.3统计特征计算
            all_u = []
            all_cc = []
            # (1)对于每个点,找到其k个邻近点Ni,k=6
            for p in points:
                _, _, neighbor_points = calculate_neighbor(p, x_min, y_min, z_min, c, my_dict)
                # (2)计算邻域点的平均距离u和标准差cc
                u = sum(calculate_distance(neighbor_point, p) for neighbor_point in neighbor_points) / 6
                cc = math.sqrt(sum((calculate_distance(neighbor_point, p)-u)**2 for neighbor_point in neighbor_points) / 6)
                all_u.append(u)
                all_cc.append(cc)
                if p.idx == 1:
                    processed_lines.append(f"18、点P1的邻域平均距离:{u:.3f}")
                    processed_lines.append(f"19、点P1的邻域距离标准差:{cc:.3f}")
                if p.idx == 6:
                    processed_lines.append(f"20、点P6的邻域平均距离:{u:.3f}")
                    processed_lines.append(f"21、点P6的邻域距离标准差:{cc:.3f}")
            # (3)计算所有点的平均距离的均值u和标准差cc
            all_u_mean = sum(all_u) / n
            all_cc_mean = sum(all_cc) / n
            processed_lines.append(f"22、全局平均距离均值u:{all_u_mean:.3f}")
            processed_lines.append(f"23、全局距离标准差:{all_cc_mean:.3f}")
            # 2.4去噪结果
            flag_lst = [0] * len(all_u)
            flag = all_u_mean + 2 * all_cc_mean
            for i, ui in enumerate(all_u):
                if ui > flag:
                    flag_lst[i] = 1 # 噪声点标记为1
            noised_point_cnt = sum(flag_lst)
            remain_cnt = len(all_u) - noised_point_cnt
            processed_lines.append(f"24、点P1是否为噪声点:{flag_lst[0]}")
            processed_lines.append(f"25、点P6是否为噪声点:{flag_lst[5]}")
            processed_lines.append(f"26、去噪的噪声点总数:{noised_point_cnt}")
            processed_lines.append(f"27、去噪后保留的点云总数:{remain_cnt}")
            self.result_text_area.setText("\n".join(processed_lines))
            self.status_label.setText("✅数据处理成功!")
        except Exception as e:
            self.status_label.setText(f"❌数据处理失败:{e}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = App()
    window.show()
    sys.exit(app.exec_())

四、个人理解

这道题应该学会:格网索引哈希优化技巧、点云去噪思想。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注