Python实现计算点云投影面积
本次我们分享一种基于 Open3D 的快速、稳健方法,用于从激光点云中自动提取“地面”并计算其投影面积。算法先自适应估计地面高程,再将地面点投影至水平面,随后用凸包或最小外接矩形求取面积。整个流程无需人工干预,单文件即可运行,已封装成 Python 模块与图形界面,可直接嵌入地形测绘、建筑规划、农林监测、灾害评估等应用。
一、实现流程
1. 加载点云:支持 PCD/PLY/XYZ 等常见格式。
2. 地面分割:采用类 Otsu 自适应阈值,自动分离地面与非地面点。
3. 二维投影:将地面点正射投影至 XY 平面。
4. 边界提取:提供凸包与最小旋转矩形两种策略。
5. 面积输出:返回平方米数值,可选可视化边界与文件报告。二、应用场景
- 地形制图:一键估算裸露地表面积,辅助 DEM 生产。
- 城市规划:批量计算建筑底面轮廓,快速统计容积率。
- 农林生态:量测植被或农田冠层投影,评估生长状况与产量。
- 灾害应急:震后/滑坡区域快速圈定受灾范围,指导救援。
- 土方计量:开挖前后两次点云对比,自动计算实际填挖面积与体积。三、特点
- 零依赖 GUI:拷贝即用,无需编写代码。
- 自动阈值:对高低起伏、植被遮挡数据依然稳健。
- 双模式面积:凸包(精确边界)与 Min-BBox(规则轮廓)任意切换。
- 结果可溯源:同步输出边界矢量(GeoJSON),方便导入 GIS 平台。
本次我们使用的数据——————————————兔砸!!!(当然,任意点云都可以)
一、投影面积计算程序
import tkinter as tk
from tkinter import filedialog, messagebox
import open3d as o3d
import numpy as np
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg# ---------- 业务逻辑 ----------
def read_pcd():# """弹出文件选择框并读取点云"""# file = filedialog.askopenfilename(# title="选择点云文件",# filetypes=[("点云", "*.pcd *.ply *.xyz"), ("所有文件", "*.*")])# if not file:# return Nonefile = "E:/CSDN/规则点云/bunny.pcd"pcd = o3d.io.read_point_cloud(file)if pcd.is_empty():messagebox.showerror("错误", "点云读取失败!")return Nonereturn pcddef auto_height_threshold(points):"""简单 Otsu 思想取地面阈值"""z = points[:, 2]z_min, z_max = z.min(), z.max()best_th, best_var = z_min, 0for th in np.linspace(z_min, z_max, 100):back, fore = z[z <= th], z[z > th]if fore.size == 0 or back.size == 0:continuew0, w1 = back.size / z.size, fore.size / z.sizemean0, mean1 = back.mean(), fore.mean()mean_all = w0 * mean0 + w1 * mean1inter_var = w0 * (mean0 - mean_all) ** 2 + w1 * (mean1 - mean_all) ** 2if inter_var > best_var:best_var, best_th = inter_var, threturn best_thdef calc_area(pcd, draw=False):"""返回面积,可选弹出投影图"""pts = np.asarray(pcd.points)th = auto_height_threshold(pts)ground = pts[pts[:, 2] <= th, :2]if ground.shape[0] < 3:messagebox.showerror("错误", "地面点不足 3 个!")return Nonehull = ConvexHull(ground)area = hull.volume # 2D 凸包 volume = areaif draw:# 弹出新窗口画投影win = tk.Toplevel()win.title("地面投影与凸包")fig, ax = plt.subplots(figsize=(4, 4))ax.scatter(ground[:, 0], ground[:, 1], s=1, c='g')for simplex in hull.simplices:ax.plot(ground[simplex, 0], ground[simplex, 1], 'r-')ax.set_aspect('equal')ax.set_title(f"Convex-Hull Area = {area:.3f}")canvas = FigureCanvasTkAgg(fig, master=win)canvas.draw()canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)return area# ---------- 按钮回调 ----------
def on_convex_hull():pcd = read_pcd()if pcd is None:returnarea = calc_area(pcd, draw=False)if area is not None:messagebox.showinfo("结果", f"凸包占地面积:{area:.4f} 平方单位")def on_convex_hull_with_plot():pcd = read_pcd()if pcd is None:returnarea = calc_area(pcd, draw=True)if area is not None:# 信息已在弹窗标题passdef on_exit():root.quit()root.destroy()# ---------- GUI ----------
root = tk.Tk()
root.title("点云地面面积计算")
root.geometry("300x160")
root.resizable(False, False)btn1 = tk.Button(root, text="1. 凸包面积", width=25, command=on_convex_hull)
btn2 = tk.Button(root, text="2. 凸包面积+投影图", width=25,command=on_convex_hull_with_plot)
btn3 = tk.Button(root, text="3. 退出", width=25, command=on_exit)btn1.pack(pady=10)
btn2.pack(pady=10)
btn3.pack(pady=10)root.mainloop()
二、投影面积计算结果
这次依然沿用以前的GUI风格(主要是太好用了,谁用谁知道)。可以看到,两种投影面积计算方法得到的投影面积差不多。因为我们选用了自适应参数,所以效果基本上是最佳的。同学们如果有兴趣,可以自己调调参数试试,可以更加记忆深刻哦。就酱,下次见^-^