點云數據處理--splat轉3dtiles

文章目錄

  • 處理流程簡介
  • 核心功能實現
    • 數據讀取與格式轉換
      • 定義Point類
      • 數據讀取
      • splat轉gltf
    • 點云數據分割
      • 定義四叉樹
      • 遞歸生成3dtiles瓦片
    • 生成tileset.json
      • 遞歸生成tileset.json
      • 計算box
    • 主函數調用
    • 渲染
  • 下一步工作
    • 性能優化
    • 渲染效果調優
    • 其他

源碼地址: github

處理流程簡介

基本流程:

  • 讀取點云數據。
  • 制作tile
    • 構建四叉樹
    • 分割點云
    • 將點云轉換為glTF格式。
  • 生成配置文件tileset.json。

前置知識:

  • glTF教程
  • glTF2.0 高斯擴展
  • 3dtiles 1.1 規范

核心功能實現

數據讀取與格式轉換

定義Point類

class Point:def __init__(self, position: Tuple[float, float, float], color: Tuple[int, int, int, int],scale: Tuple[float, float, float], rotation: Tuple[int, int, int, int]):self.position = positionself.color = colorself.scale = scaleself.rotation = rotationdef to_bytes(self) -> bytes:"""將點數據打包為二進制格式"""return struct.pack('3f4B3f4B', *self.position, *self.color, *self.scale, *self.rotation)@classmethoddef from_bytes(cls, data: bytes):"""從二進制數據解析為點"""unpacked = struct.unpack('3f4B3f4B', data)position = unpacked[:3]color = unpacked[3:7]scale = unpacked[7:10]rotation = unpacked[10:]return cls(position, color, scale, rotation)

數據讀取

def read_splat_file(file_path: str) -> List[Point]:"""讀取二進制格式的 Splat 文件:param file_path: Splat 文件路徑:return: 包含位置、縮放、顏色、旋轉數據的 Point 對象列表"""points = []with open(file_path, 'rb') as f:while True:position_data = f.read(3 * 4)  # 3個 Float32,每個4字節if not position_data:breakposition = struct.unpack('3f', position_data)scale = struct.unpack('3f', f.read(3 * 4))color = struct.unpack('4B', f.read(4 * 1))rotation = struct.unpack('4B', f.read(4 * 1))points.append(Point(position, color, scale, rotation))return points

splat轉gltf

遵循3dtiles 1.1 規范,在glTF 2.0 基礎上,增加高斯擴展。

def splat_to_gltf_with_gaussian_extension(points: List[Point], output_path: str):"""將 Splat 數據轉換為支持 KHR_gaussian_splatting 擴展的 glTF 文件:param points: Point 對象列表:param output_path: 輸出的 glTF 文件路徑"""# 提取數據positions = np.array([point.position for point in points], dtype=np.float32)colors = np.array([point.color for point in points], dtype=np.uint8)scales = np.array([point.scale for point in points], dtype=np.float32)rotations = np.array([point.rotation for point in points], dtype=np.uint8)normalized_rotations = rotations / 255.0# 創建 GLTF 對象gltf = GLTF2()gltf.extensionsUsed = ["KHR_gaussian_splatting"]# 創建 Bufferbuffer = Buffer()gltf.buffers.append(buffer)# 將數據轉換為二進制positions_binary = positions.tobytes()colors_binary = colors.tobytes()scales_binary = scales.tobytes()rotations_binary = normalized_rotations.tobytes()# 創建 BufferView 和 Accessordef create_buffer_view(byte_offset: int, data: bytes, target: int = 34962) -> BufferView:return BufferView(buffer=0, byteOffset=byte_offset, byteLength=len(data), target=target)def create_accessor(buffer_view: int, component_type: int, count: int, type: str, max: List[float] = None, min: List[float] = None) -> Accessor:return Accessor(bufferView=buffer_view, componentType=component_type, count=count, type=type, max=max, min=min)buffer_views = [create_buffer_view(0, positions_binary),create_buffer_view(len(positions_binary), colors_binary),create_buffer_view(len(positions_binary) +len(colors_binary), rotations_binary),create_buffer_view(len(positions_binary) +len(colors_binary) + len(rotations_binary), scales_binary)]accessors = [create_accessor(0, 5126, len(positions), "VEC3", positions.max(axis=0).tolist(), positions.min(axis=0).tolist()),create_accessor(1, 5121, len(colors), "VEC4"),create_accessor(2, 5126, len(normalized_rotations), "VEC4"),create_accessor(3, 5126, len(scales), "VEC3")]gltf.bufferViews.extend(buffer_views)gltf.accessors.extend(accessors)# 創建 Mesh 和 Primitiveprimitive = Primitive(attributes={"POSITION": 0, "COLOR_0": 1, "_ROTATION": 2, "_SCALE": 3},mode=0,extensions={"KHR_gaussian_splatting": {"positions": 0, "colors": 1, "scales": 2, "rotations": 3}})mesh = Mesh(primitives=[primitive])gltf.meshes.append(mesh)# 創建 Node 和 Scenenode = Node(mesh=0)gltf.nodes.append(node)scene = Scene(nodes=[0])gltf.scenes.append(scene)gltf.scene = 0# 將二進制數據寫入 Buffergltf.buffers[0].uri = "data:application/octet-stream;base64," + base64.b64encode(positions_binary + colors_binary + rotations_binary + scales_binary).decode("utf-8")gltf.save(output_path)print(f"glTF 文件已保存到: {output_path}")

點云數據分割

定義四叉樹

定義四叉樹類,包含基本方法,初始化、插入、分割、判斷點是否在邊界范圍內等等。

#四叉樹
class QuadTreeNode:def __init__(self, bounds: Tuple[float, float, float, float], capacity: int = 100000):"""初始化四叉樹節點。:param bounds: 節點的邊界 (min_x, min_y, max_x, max_y):param capacity: 節點容量(每個節點最多存儲的點數)"""self.bounds = boundsself.capacity = capacityself.points: List[Point] = []  # 存儲點數據self.children = Nonedef insert(self, point: Point) -> bool:"""將點插入四叉樹。:param point: 要插入的點:return: 是否插入成功"""if not self._contains(point.position):return Falseif len(self.points) < self.capacity:self.points.append(point)return Trueelse:if self.children is None:self._subdivide()return any(child.insert(point) for child in self.children)def _contains(self, position: Tuple[float, float, float]) -> bool:"""檢查點是否在節點邊界內。:param position: 點的位置 (x, y, z):return: 是否在邊界內"""x, y, _ = positionmin_x, min_y, max_x, max_y = self.boundsreturn min_x <= x < max_x and min_y <= y < max_ydef _subdivide(self):"""將節點劃分為四個子節點。"""min_x, min_y, max_x, max_y = self.boundsmid_x = (min_x + max_x) / 2mid_y = (min_y + max_y) / 2self.children = [QuadTreeNode((min_x, min_y, mid_x, mid_y), self.capacity),QuadTreeNode((mid_x, min_y, max_x, mid_y), self.capacity),QuadTreeNode((min_x, mid_y, mid_x, max_y), self.capacity),QuadTreeNode((mid_x, mid_y, max_x, max_y), self.capacity)]for point in self.points:for child in self.children:if child.insert(point):breakself.points = []  # 清空當前節點的點數據def get_all_points(self) -> List[Point]:"""獲取當前節點及其子節點中的所有點。:return: 所有點的列表"""points = self.points.copy()if self.children is not None:for child in self.children:points.extend(child.get_all_points())return points

遞歸生成3dtiles瓦片

def generate_3dtiles(node: QuadTreeNode, output_dir: str, tile_name: str):if node.children is not None:for i, child in enumerate(node.children):generate_3dtiles(child, output_dir, f"{tile_name}_{i}")elif len(node.points) > 0:points = node.get_all_points()splat_to_gltf_with_gaussian_extension(points, f"{output_dir}/{tile_name}.gltf")

生成tileset.json

遞歸生成tileset.json

generate_tileset_json

def generate_tileset_json(output_dir: str, root_node: QuadTreeNode, bounds: List[float], geometric_error: int = 100):def build_tile_structure(node: QuadTreeNode, tile_name: str, current_geometric_error: int) -> Dict:bounding_volume = {"region": compute_region([point.position for point in node.get_all_points()])} if is_geographic_coordinate else {"box": compute_box([point.position for point in node.get_all_points()])}content = {"uri": f"{tile_name}.gltf"} if not node.children else Nonechildren = [build_tile_structure(child, f"{tile_name}_{i}", current_geometric_error / 2)for i, child in enumerate(node.children)] if node.children else []tile_structure = {"boundingVolume": bounding_volume,"geometricError": current_geometric_error,"refine": "ADD","content": content}if children:tile_structure["children"] = childrendel tile_structure["content"]return tile_structuretileset = {"asset": {"version": "1.1", "gltfUpAxis": "Z"},"geometricError": geometric_error,"root": build_tile_structure(root_node, "tile_0", geometric_error)}with open(f"{output_dir}/tileset.json", "w") as f:json.dump(tileset, f, cls=NumpyEncoder, indent=4)

數據格式轉換

class NumpyEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, (np.int_, np.intc, np.intp, np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64)):return int(obj)elif isinstance(obj, (np.float_, np.float16, np.float32, np.float64)):return float(obj)elif isinstance(obj, np.ndarray):return obj.tolist()return json.JSONEncoder.default(self, obj)

計算box

def compute_box(points: np.ndarray) -> List[float]:center = np.mean(points, axis=0)half_size = (np.max(points, axis=0) - np.min(points, axis=0)) / 2return [center[0], center[1], center[2], half_size[0], 0, 0, 0, half_size[1], 0, 0, 0, half_size[2]]

主函數調用

def main(input_path: str, output_dir: str):# 讀取 .splat 文件points = read_splat_file(input_path)# 創建四叉樹根節點positions = np.array([point.position for point in points])min_x, min_y = np.min(positions[:, :2], axis=0)max_x, max_y = np.max(positions[:, :2], axis=0)root = QuadTreeNode((min_x, min_y, max_x, max_y), capacity=100000)# 將點插入四叉樹for point in points:root.insert(point)# 生成 3D Tilesgenerate_3dtiles(root, output_dir, "tile_0")# 生成 tileset.jsonbounds = [min_x, min_y, np.min(positions[:, 2]), max_x, max_y, np.max(positions[:, 2])]generate_tileset_json(output_dir, root, bounds)if __name__ == "__main__":# 解析命令行參數parser = argparse.ArgumentParser(description="將 Splat 文件轉換為 3D Tiles。")parser.add_argument("input_path", type=str, help="輸入的 .splat 文件路徑")parser.add_argument("output_dir", type=str, help="輸出的 3D Tiles 目錄路徑")args = parser.parse_args()# 調用主函數main(args.input_path, args.output_dir)

渲染

編譯cesium的splat-shader版本,參考示例代碼3D Tiles Gaussian Splatting.html實現。

async function loadTileset() {try {const tileset = await Cesium.Cesium3DTileset.fromUrl("http://localhost:8081/data/outputs/model/tileset.json",{modelMatrix:computeModelMatrix(),maximumScreenSpaceError: 1,}).then((tileset) => {CesiumViewer.scene.primitives.add(tileset);setupCamera();});} catch (error) {console.error(`Error creating tileset: ${error}`);}
}

下一步工作

性能優化

  • 支持LOD 。
  • 支持多線程、多任務,分批處理 。
  • 切片方案優化,嘗試構建其他空間索引,例如八叉樹 。

渲染效果調優

目前渲染效果不理想,橢圓的某個軸長過大,問題排查中。

其他

其他待優化項。本文輸出的是一個簡易版的splat轉3dtiles工具,供學習和交流使用,待優化的地方,若有精力后續會持續完善。

參考資料:
[1] https://github.com/KhronosGroup/glTF-Tutorials/tree/main/gltfTutorial
[2] https://github.com/CesiumGS/3d-tiles
[3] https://github.com/CesiumGS/glTF/tree/proposal-KHR_gaussian_splatting/extensions/2.0/Khronos/KHR_gaussian_splatting
[4] https://github.com/CesiumGS/cesium/tree/splat-shader

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72939.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72939.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72939.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

OneM2M:全球性的物聯網標準-可應用于物聯網中

OneM2M 是一個全球性的物聯網(IoT)標準,旨在為物聯網設備和服務提供統一的框架和接口,以實現設備之間的互操作性、數據共享和服務集成。OneM2M 由多個國際標準化組織(如 ETSI、TIA、TTC、ARIB 等)共同制定,目標是解決物聯網領域的碎片化問題,提供一個通用的標準,支持跨…

【Python 入門基礎】—— 人工智能“超級引擎”,AI界的“瑞士軍刀”,

歡迎來到ZyyOvO的博客?&#xff0c;一個關于探索技術的角落&#xff0c;記錄學習的點滴&#x1f4d6;&#xff0c;分享實用的技巧&#x1f6e0;?&#xff0c;偶爾還有一些奇思妙想&#x1f4a1; 本文由ZyyOvO原創??&#xff0c;感謝支持??&#xff01;請尊重原創&#x1…

Java爬蟲獲取淘寶商品詳情數據的完整指南

在電商領域&#xff0c;獲取商品詳情數據對于市場分析、價格監控、用戶體驗優化等場景具有重要意義。淘寶作為國內領先的電商平臺&#xff0c;提供了豐富的API接口供開發者使用&#xff0c;其中item_get和item_get_pro接口可以用來獲取商品的詳細信息。本文將詳細介紹如何使用J…

Ubuntu 下 nginx-1.24.0 源碼分析 - ngx_init_cycle 函數

nei聲明在 src/core/ngx_cycle.h ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle);實現在 src/core/ngx_cycle.c ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) {void *rv;char **senv;ngx_uint_t i, n;ngx_log_t …

qt 操作多個sqlite文件

qt 操作多個sqlite文件 Chapter1 qt 操作多個sqlite文件1. 引入必要的頭文件2. 創建并連接多個SQLite數據庫3. 代碼說明4. 注意事項 Chapter2 qt 多線程操作sqlite多文件1. 引入必要的頭文件2. 創建數據庫操作的工作線程類3. 在主線程中創建并啟動多個工作線程4. 代碼說明5. 運…

最新版本WebContext構造函數-避坑

import org.thymeleaf.context.IWebContext; import org.thymeleaf.context.WebContext; 當你想把頁面信息全部獲取出來存到redis緩存中使用時&#xff0c;SpringWebContext在Spring5中報錯 SpringWebContext ctx new SpringWebContext(request, response,request.getServlet…

用Python分割并高效處理PDF大文件

在處理大型PDF文件時&#xff0c;將它們分解成更小、更易于管理的塊通常是有益的。這個過程稱為分區&#xff0c;它可以提高處理效率&#xff0c;并使分析或操作文檔變得更容易。在本文中&#xff0c;我們將討論如何使用Python和為Unstructured.io庫將PDF文件劃分為更小的部分。…

neo4j隨筆-將csv文件導入知識圖譜

目錄 導入前的準備 導入csv文件 導入nodes1.1.csv并動態為節點添加標簽 ?編輯導入relations1.1.csv并動態為關系添加標簽 結果 導入前的準備 我有兩個csv文件 nodes1.1.csv存放節點信息,用記事本打開&#xff0c;能正常顯示&#xff0c;且編碼為UTF-8&#xff0c;就可以…

cpu 多級緩存L1、L2、L3 與主存關系

現代 CPU 的多級緩存&#xff08;L1、L2、L3&#xff09;和主存&#xff08;DRAM&#xff09;構成了一個層次化的內存系統&#xff0c;旨在通過減少內存訪問延遲和提高數據訪問速度來優化計算性能。以下是對多級緩存和主存的詳細解析&#xff1a; 1. 緩存層次結構 現代 CPU 通…

C++:入門詳解(關于C與C++基本差別)

目錄 一.C的第一個程序 二.命名空間&#xff08;namespace&#xff09; 1.命名空間的定義與使用&#xff1a; &#xff08;1&#xff09;命名空間里可以定義變量&#xff0c;函數&#xff0c;結構體等多種類型 &#xff08;2&#xff09;命名空間調用&#xff08;&#xf…

Python的學習篇(七)--網頁結構

七、網頁&#xff08;HTML&#xff09;結構 7.1 HTML介紹 HTML(Hyper Text Markup Language)&#xff0c;超文本標記語言。 超文本&#xff1a;比文本的功能要強大&#xff0c;通過鏈接和交互式的方式來組織與呈現信息的文本形式。不僅僅有文本&#xff0c;還可以包含圖片、…

*VulnHub-FristiLeaks:1.3暴力解法、細節解法,主打軟硬都吃,隧道搭建、尋找exp、提權、只要你想沒有做不到的姿勢

*VulnHub-FristiLeaks:1.3暴力解法、細節解法&#xff0c;主打軟硬都吃&#xff0c;隧道搭建、尋找exp、提權、只要你想沒有做不到的姿勢 一、信息收集 1、掃靶機ip 經典第一步&#xff0c;掃一下靶機ip arp-scan -l 掃描同網段 nmap -sP 192.168.122.0/242、指紋掃描、端口…

PHP:格式化JSON為PHP語法格式

1. 原生函數 $arr [1,2,3,4]; $str var_export($a,true); var_dump($str); 2. 自定義方法 class Export{private static $space;private static function do($a, string $prev){$res ;$next $prev . self::$space;if (is_array($a)) {$res . [;foreach ($a as $k > $…

【Python 數據結構 9.樹】

我裝作漠視一切&#xff0c;其實我在乎的太多&#xff0c;但我知道抓得越緊越容易失去 —— 25.3.6 一、樹的基本概念 1.樹的定義 樹是n個結點的有限集合&#xff0c;n0時為空樹。當n大于0的時候&#xff0c;滿足如下兩個條件&#xff1a; ① 有且僅有一個特定的結點&#xff…

pyqt聯合designer的運用和設置

PyQt Designer 簡介 PyQt Designer 是一個用于創建和設計 PyQt 應用程序用戶界面的可視化工具。它允許用戶通過拖放方式添加和排列各種控件,如按鈕、文本框、滑塊等,并設置它們的屬性和樣式,從而快速構建出美觀且功能完整的 UI 界面。 Windows版本:【免費】安裝包別管啊啊…

純html文件實現目錄和文檔關聯

目錄結構 效果圖 代碼 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>項目結題報告</title><style lang"scss">::-webkit-scrollbar {width: 6px;height: 6px;}::-webkit-scro…

python用戶圖形界面wxpython庫安裝與使用

要開始使用 wxPython 庫來創建 Python 用戶圖形界面&#xff0c;首先需要安裝這個庫。在大多數情況下&#xff0c;你可以通過 pip 來安裝 wxPython。下面我會指導你完成安裝過程&#xff0c;并給出一個簡單的例子來展示如何使用 wxPython 創建一個基本的窗口應用程序。 安裝 w…

MongoDB winx64 msi包安裝詳細教程

首先我們可以從官網上選擇對應版本和對應的包類型進行安裝&#xff1a; 下載地址&#xff1a;Download MongoDB Community Server | MongoDB 這里可以根據自己的需求&#xff0c; 這里我選擇的是8.0.5 msi的版本&#xff0c;采用的傳統裝軟件的方式安裝。無需配置命令。 下載…

如何借助 ArcGIS Pro 高效統計基站 10km 范圍內的村莊數量?

在當今數字化時代&#xff0c;地理信息系統&#xff08;GIS&#xff09;技術在各個領域都發揮著重要作用。 特別是在通信行業&#xff0c;對于基站周邊覆蓋范圍內的地理信息分析&#xff0c;能夠幫助我們更好地進行網絡規劃、資源分配以及市場分析等工作。 今天&#xff0c;就…

saltstack通過master下發腳本批量修改minion_id,修改為IP

通過master下發腳本批量修改minion_id&#xff0c;以修改為IP為例 通過cmd.script遠程執行shell腳本修改minion_id&#xff0c;步驟如下: # 下發腳本并執行 >> salt old_minion_id cmd.script salt://modify_minion_id.sh saltenvdev #輸出結果 old_minion_id:Minion di…