????????本次我們分享一種基于 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風格(主要是太好用了,誰用誰知道)。可以看到,兩種投影面積計算方法得到的投影面積差不多。因為我們選用了自適應參數,所以效果基本上是最佳的。同學們如果有興趣,可以自己調調參數試試,可以更加記憶深刻哦。就醬,下次見^-^