境配置指南)
簡介面向需要GPU加速點云處理的中高級開發(fā)者提供一套PCL 1.12.0配合CUDA 11.1與cuDNN 8.0.4的Windows 64位預(yù)編譯包由MSVC 2019生成專門用于發(fā)布版x64架構(gòu)的C項目。包內(nèi)總共1221個文件壓縮后僅為30.6MB核心是638個頭文件、362個模板實現(xiàn)文件與31個靜態(tài)鏈接庫同時附帶動態(tài)庫、CMake配置腳本和示例程序可供工程直接調(diào)用也能輔助項目完成依賴查找與鏈接集成。配合其中的CUDA加速模塊可以快捷啟用特征提取、樣本一致性分割、目標(biāo)跟蹤、表面重建等常用功能的GPU實現(xiàn)省去在Windows上自行編譯PCL GPU組件的繁瑣流程適合已有一定PCL基礎(chǔ)并追求算法提速的工程師。已有267人學(xué)習(xí)下載用戶只需預(yù)先安裝CUDA 11.1及PCL 1.12.0的第三方依賴再按發(fā)布版x64模式配置即可正常使用。1. PCL 1.12.0 CUDA 11.1 編譯包GPU 點云處理不再卡在環(huán)境上點云配準(zhǔn)和分割跑 CPU 版本有多痛苦做過的人都懂。一個 20 萬點的點云做歐式聚類CPU 版要跑幾十秒換成 GPU 加速模塊后能壓到 3 秒以內(nèi)這個數(shù)量級的提升直接改變了開發(fā)節(jié)奏。但 PCL 的 GPU 模塊一直不好碰自己從源碼編譯要處理 CUDA 算力架構(gòu)、MSVC 工具集版本、第三方依賴庫的匹配關(guān)系隨便一個環(huán)節(jié)不對就整周白費。這套pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64編譯包把最麻煩的編譯環(huán)節(jié)做完了你拿到手只需要配好 3rdparty 依賴在 Release x64 模式下鏈接對應(yīng)的 lib就能直接跑 GPU 加速的聚類、分割、法線估計和八叉樹搜索。適合項目已經(jīng)跑在 Windows 上、顯卡是 N 卡且算力在 6.0 以上、不想花一周時間折騰源碼編譯的開發(fā)者。2. 環(huán)境準(zhǔn)備CUDA 11.1、3rdparty 與 MSVC 2019 的匹配關(guān)系2.1 為什么必須是 CUDA 11.1 和 MSVC 2019PCL 1.12.0 的 GPU 模塊是在 CUDA 11.1 環(huán)境下編譯出來的。CUDA 的 runtime API 在 11.x 之后變化不大但 MSVC 的版本和標(biāo)準(zhǔn)庫實現(xiàn)直接影響 C ABI。如果你用 VS2022 打開這個包編譯器版本是 v143而包內(nèi) lib 是用 v142 工具集生成的混用會出現(xiàn)一堆 link 錯誤報錯在pcl_common.lib里比如LNK2038 mismatch detected for _MSC_VER。MSVC 2019 對應(yīng)的是 v142 工具集這是這個編譯包能正常鏈接的前提。CUDA 11.1 官方支持的最高 MSVC 版本是 19.28所以用 VS2019 16.8 及以上但不要超過 16.11這是編譯時的工具鏈版本約束。還需要裝好 CUDA 11.1 的完整安裝包不能只裝 driver。GPU 模塊編譯時用的是cudart64_110.dll和cublas64_11.dll如果只裝了最新 CUDA 12.xPCL 包內(nèi)鏈接的符號版本對不上運行時直接報找不到入口點。常見做法是機器上同時裝 CUDA 11.1 和更高版本用環(huán)境變量CUDA_PATH_V11_1來區(qū)分。我一般會這樣檢查nvcc --version echo %CUDA_PATH% echo %CUDA_PATH_V11_1%nvcc --version顯示的版本必須是 11.1CUDA_PATH環(huán)境變量在安裝 CUDA 11.1 后會被設(shè)置成C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1。2.2 3rdparty 目錄的配置順序這個編譯包默認(rèn)不附帶 PCL 的第三方依賴庫需要你手動安裝或解壓到指定位置。3rdparty 包含 FLANN、Qhull、Boost、Eigen、OpenNI2、VTK 等組件。注意里面的FindFLANN.cmake、FindQhull.cmake這些查找腳本是寫好了的但前提是你的 3rdparty 目錄結(jié)構(gòu)恰好是它們期望的樣子。我先說目錄結(jié)構(gòu)PCL_ROOT 環(huán)境變量指向編譯包解壓根目錄PCL_ROOT\3rdParty\ 下放 Boost、Eigen、FLANN、Qhull、VTK3rdParty 下的庫文件需要和這個包同時編譯不能直接用 vcpkg 裝的版本因為 vcpkg 默認(rèn)用動態(tài)鏈接 CRT而這個包用的是/MD運行時我把 3rdparty 的配置方式說一下。假設(shè)你把第三方庫放在D:\libs\pcl_3rdparty下用 CMake 配置項目時有幾個關(guān)鍵變量要設(shè)置否則FindPCL.cmake找不到庫。具體來說set(PCL_ROOT D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64) set(PCL_DIR ${PCL_ROOT}/cmake) set(BOOST_ROOT D:/libs/pcl_3rdparty/boost) set(EIGEN_INCLUDE_DIR D:/libs/pcl_3rdparty/eigen)這里PCL_DIR指向編譯包內(nèi)的cmake目錄里面放著PCLConfig.cmake和PCLConfigVersion.cmake。CMake 通過find_package(PCL 1.12 REQUIRED)時會先找PCL_DIR指定的位置而不是去系統(tǒng) PATH 里碰運氣。2.3 環(huán)境變量的持久化設(shè)置命令行里臨時設(shè)置環(huán)境變量只對當(dāng)前終端有效重新打開 VS2019 的開發(fā)者命令提示符就丟了。我建議寫到系統(tǒng)環(huán)境變量里setx PCL_ROOT D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64 setx PCL_DIR %PCL_ROOT%/cmake setx BOOST_ROOT D:/libs/pcl_3rdparty/boost設(shè)置后要重開終端窗口因為setx不會影響當(dāng)前進(jìn)程的環(huán)境變量。重開后可以用echo %PCL_ROOT%驗證。3. 鏈接配置lib 清單、CMakeLists 與 Release x64 模式3.1 lib 文件清單解讀摘要里列出的 lib 清單是 GPU 加速核心模塊的產(chǎn)物。我把關(guān)鍵的幾個分成組lib 分組對應(yīng)模塊作用pcl_cuda_features.libcuda_featuresGPU 加速的特征提取比如法線估計、FPFHpcl_cuda_segmentation.libcuda_segmentationGPU 加速的區(qū)域生長分割、歐式聚類pcl_gpu_octree.libgpu_octreeGPU 八叉樹構(gòu)建與最近鄰搜索pcl_gpu_tracking.libgpu_tracking基于 GPU 的跟蹤算法pcl_gpu_utils.libgpu_utilsGPU 模塊公共工具函數(shù)pcl_gpu_containers.libgpu_containersGPU 端的數(shù)據(jù)容器如 DeviceArraypcl_io.libio點云文件讀寫PCD/PLY 格式這些 lib 全部對應(yīng) Release x64 模式。Debug 模式下它們不工作因為編譯產(chǎn)物的運行庫和迭代器調(diào)試級別不同。項目配置里平臺必須選 x64字符集選多字節(jié)或 Unicode 都可以但_ITERATOR_DEBUG_LEVEL必須為 0這是 Release 的默認(rèn)值。3.2 一份可用的 CMakeLists.txtPCL 官方的 CMake 配置方式在 Windows 非標(biāo)準(zhǔn)安裝路徑時經(jīng)常失效所以建議手動指定路徑。下面這份 CMakeLists 我在自己項目里跑通過cmake_minimum_required(VERSION 3.16) project(pcl_gpu_demo) set(CMAKE_PREFIX_PATH D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64 D:/libs/pcl_3rdparty ) set(PCL_DIR D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64/cmake) find_package(PCL 1.12 REQUIRED COMPONENTS common io filters features kdtree segmentation cuda_features cuda_segmentation gpu_octree gpu_utils ) include_directories(${PCL_INCLUDE_DIRS}) add_definitions(${PCL_DEFINITIONS}) link_directories(${PCL_LIBRARY_DIRS}) add_executable(pcl_gpu_demo main.cpp) target_link_libraries(pcl_gpu_demo ${PCL_LIBRARIES})這里有幾個容易翻車的地方。find_package的 COMPONENTS 列表必須同時包含 CPU 模塊和 GPU 模塊如果只寫 GPU 相關(guān)組件CMake 會報PCL_LIBRARIES為空。link_directories需要指向編譯包內(nèi)的lib目錄否則鏈接器找不到.lib文件。3.3 手動鏈接方式的 Visual Studio 配置如果不走 CMake直接在 VS2019 里建空項目也可以。項目屬性頁里的配置步驟如下C/C → 附加包含目錄D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64/include/pcl-1.12和D:/libs/pcl_3rdparty/boost/include鏈接器 → 附加庫目錄D:/libs/pcl1.12.0-cuda11.1-cudnn8.0.4-msvc2019-win64/lib鏈接器 → 附加依賴項按依賴關(guān)系逐個添加 lib依賴順序有講究。pcl_gpu_octree.lib依賴pcl_gpu_utils.libpcl_gpu_utils.lib又依賴pcl_common.lib和 CUDA 的cudart.lib。如果不按依賴順序排會出現(xiàn)unresolved external symbol報錯位置在pcl_gpu_octree.lib里引用pcl::gpu::utils::...的符號。常見做法是把 CUDA 的庫目錄也加到附加庫目錄里C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1\lib\x64然后在附加依賴項里加上cudart.lib cudart_static.lib4. 實戰(zhàn)演示GPU 加速的歐式聚類與法線估計4.1 GPU 模塊的代碼結(jié)構(gòu)差異PCL 的 GPU 模塊在 API 設(shè)計上和 CPU 版本很像但有幾個本質(zhì)區(qū)別。先看 CPU 版本的常規(guī)寫法#include pcl/segmentation/euclidean_cluster_comparator.h #include pcl/segmentation/extract_clusters.h #include pcl/point_types.h #include pcl/point_cloud.h pcl::search::KdTreepcl::PointXYZ::Ptr tree(new pcl::search::KdTreepcl::PointXYZ); pcl::EuclideanClusterExtractionpcl::PointXYZ ec; ec.setClusterTolerance(0.02); ec.setMinClusterSize(100); ec.setMaxClusterSize(25000); ec.setSearchMethod(tree); std::vectorpcl::PointIndices cluster_indices; ec.extract(cluster_indices);而 GPU 版本用的是pcl::gpu::EuclideanClusterExtraction數(shù)據(jù)結(jié)構(gòu)也換成pcl::gpu::OctreePointCloudSearch。差別在于GPU 版本的輸入點云需要先上傳到顯存搜索結(jié)構(gòu)用 GPU octree輸出結(jié)果直接是聚類索引。下面是 GPU 版本的典型寫法#include pcl/gpu/octree/octree.hpp #include pcl/gpu/segmentation/euclidean_cluster.h #include pcl/gpu/containers/device_array.h pcl::gpu::OctreePointCloudSearchpcl::PointXYZ octree; octree.setInputCloud(cloud_gpu); octree.build(); pcl::gpu::EuclideanClusterExtractionpcl::PointXYZ ec_gpu; ec_gpu.setClusterTolerance(0.02); ec_gpu.setMinClusterSize(100); ec_gpu.setMaxClusterSize(25000); ec_gpu.setSearchMethod(octree); std::vectorpcl::PointIndices cluster_indices_gpu; ec_gpu.extract(cluster_indices_gpu);注意cloud_gpu不是pcl::PointCloudpcl::PointXYZ而是pcl::gpu::DeviceArraypcl::PointXYZ。從 CPU 點云上傳到 GPUpcl::gpu::DeviceArraypcl::PointXYZ cloud_gpu; cloud_gpu.upload(cpu_cloud-points);這里的upload是 CUDA 的cudaMemcpy封裝把std::vector格式的點數(shù)據(jù)拷貝到顯存。參數(shù)上setClusterTolerance的單位和 CPU 版本一致都是米對于雷達(dá)點云常用 0.02 到 0.05。setMinClusterSize和setMaxClusterSize的語義也和 CPU 版本相同用于過濾掉噪聲簇和過大簇。4.2 法線估計的 GPU 實現(xiàn)法線估計是另一個典型的 GPU 加速場景。CPU 版本用pcl::NormalEstimation加 KdTreeGPU 版本用pcl::gpu::NormalEstimation。關(guān)鍵在于法線估計需要在每個點的鄰域內(nèi)做 PCA這個計算在 GPU 上可以做到極致并行。#include pcl/gpu/features/features.hpp pcl::gpu::NormalEstimationpcl::PointXYZ, pcl::Normal ne_gpu; ne_gpu.setInputCloud(cloud_gpu); ne_gpu.setRadiusSearch(0.03); pcl::gpu::DeviceArraypcl::Normal normals_gpu; ne_gpu.compute(normals_gpu);setRadiusSearch控制鄰域搜索半徑單位是米。這個值選太大會導(dǎo)致法線過度平滑選太小會出現(xiàn)空洞。對于室內(nèi)場景 0.03 到 0.05 比較穩(wěn)妥室外雷達(dá)數(shù)據(jù)建議 0.08 到 0.1。結(jié)果在normals_gpu里需要下載回 CPU 才能繼續(xù)用pcl::visualization顯示std::vectorpcl::Normal normals_cpu; normals_gpu.download(normals_cpu);這里有個細(xì)節(jié)pcl::gpu::DeviceArray的download方法會做cudaMemcpy從設(shè)備到主機如果不調(diào)用這個后續(xù) CPU 端處理拿不到數(shù)據(jù)。忘了download是我見過最頻繁的翻車點。4.3 依賴的 CUDA 運行時庫GPU 模塊運行時需要 cudart 和 cublas。在代碼里不需要顯式#include這些頭文件但在鏈接時需要對應(yīng)的.lib。如果 CMake 配置時用了find_package(CUDA)生成的項目會自動帶上 CUDA 依賴但手動配置的項目必須自己加。cudart64_110.dll cublas64_11.dll cufft64_10.dll這三個 DLL 在 CUDA 11.1 安裝目錄的bin文件夾下。運行時如果報找不到 DLL把這兩條路徑加到系統(tǒng) PATH 里C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1\bin C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1\libnvvp5. 避坑指南配置 PCL GPU 編譯包時的 5 個常見問題5.1 Debug 模式下鏈接報錯現(xiàn)象編譯通過鏈接時報LNK2038 mismatch detected for RuntimeLibrary: value MTd_StaticDebug doesnt match value MD_DynamicRelease。原因這個編譯包只生成了 Release x64 版本庫內(nèi)_ITERATOR_DEBUG_LEVEL0而 Debug 模式要求_ITERATOR_DEBUG_LEVEL2兩者 ABI 不兼容。解決把項目配置切換到 Release x64。如果必須用 Debug 調(diào)試只能自己從源碼編譯 PCL 的 Debug 版本無法用這個包將就。5.2 CMake 找不到 PCLConfig.cmake現(xiàn)象find_package(PCL 1.12 REQUIRED)報錯Could not find a package configuration file provided by PCL。原因CMake 默認(rèn)搜索路徑是CMAKE_PREFIX_PATH和當(dāng)前目錄但編譯包沒有安裝到系統(tǒng)默認(rèn)路徑。解決要么把編譯包的根目錄加到CMAKE_PREFIX_PATH要么顯式設(shè)置PCL_DIR變量指向cmake子目錄。注意設(shè)置PCL_DIR后需要重新運行 CMake 的 Configure不能只改緩存。5.3 顯卡算力不匹配導(dǎo)致內(nèi)核啟動失敗現(xiàn)象程序編譯鏈接都正常運行到 GPU 相關(guān)代碼時崩潰或報錯invalid device function。原因CUDA 11.1 默認(rèn)生成的 ptx 和 cubin 是為特定算力架構(gòu)編譯的但這個包編譯時指定的compute_xx和你顯卡的算力不匹配。比如包內(nèi)是compute_75對應(yīng) RTX 20 系列你用的是 RTX 30 系列算力 86運行時找不到兼容的 cubin。解決把 PCL 的 GPU 模塊源碼用你顯卡對應(yīng)的算力重新編譯一次或者確認(rèn)你的顯卡算力被包內(nèi)包含的 cubin 覆蓋。更簡單的驗證方式是用nvidia-smi看驅(qū)動和 CUDA 版本再用deviceQuery看顯卡算力。5.4 鏈接時重定義符號現(xiàn)象鏈接報錯LNK2005 xxx already defined in pcl_common.lib和LNK2005 xxx already defined in pcl_gpu_utils.lib同時出現(xiàn)。原因CPU 版本和 GPU 版本模塊都包含了同名的函數(shù)實現(xiàn)但符號在兩者中都被導(dǎo)出。比如pcl::PointCloud的某些模板實例化在兩個庫里都有。解決在 Visual Studio 的項目屬性里把鏈接器 → 命令行 → 其他選項加上/FORCE:MULTIPLE。注意這個選項可能掩蓋真實問題不建議長期使用。更好的方案是檢查是否同時鏈接了 CPU 和 GPU 的同名模塊比如同時加了pcl_segmentation.lib和pcl_cuda_segmentation.lib如果功能重復(fù)只保留 GPU 版本。5.5 運行時提示找不到 pcl_gpu_utils.dll現(xiàn)象程序啟動報錯The code execution cannot proceed because pcl_gpu_utils.dll was not found。原因這個編譯包生成的是動態(tài)庫版本運行時需要把bin目錄下的 DLL 路徑加到 PATH或者把 DLL 復(fù)制到 exe 同目錄。解決把編譯包根目錄下bin文件夾的完整路徑加入系統(tǒng) PATH然后重啟 VS 和終端。注意已有的 CMD 窗口不會自動刷新 PATH必須新開窗口。6. 性能驗證從 40 秒到 3 秒的調(diào)優(yōu)細(xì)節(jié)6.1 數(shù)據(jù)規(guī)模和管線設(shè)計的影響GPU 加速的收益和點云規(guī)模強相關(guān)。當(dāng)點云只有幾千個點時GPU 版的優(yōu)勢幾乎可以忽略甚至因為上傳下載的時間開銷比 CPU 慢。我的經(jīng)驗閾值是單幀超過 5 萬個點GPU 版才有明顯收益。當(dāng)點云到了 20 萬級別CPU 版的歐式聚類耗時在 40 秒左右GPU 版跑到 3 秒收益超過 10 倍。管線設(shè)計上最忌諱的是每幀都重新上傳整個點云。如果點云在連續(xù)幀間只有小幅變化可以復(fù)用DeviceArray的顯存空間只更新變化部分。但 PCL 的upload接口不支持局部更新所以數(shù)據(jù)量如果超過顯存容量就得換策略比如分塊處理或者降采樣后再上傳。6.2 兩個值得跑的驗證實驗第一個實驗驗證法線估計的加速比。用同一個 PCD 文件分別跑 CPU 版和 GPU 版的法線估計各跑 5 次取平均時間。注意首幀通常包含 CUDA context 初始化和 kernel 編譯時間要排除掉。我用的方法是先跑一次預(yù)熱再記錄后面 4 次的耗時。第二個實驗驗證 octree 構(gòu)建與最近鄰搜索。CPU 版構(gòu)建一個 20 萬點云的 KdTree 大約 1.2 秒GPU 版構(gòu)建 octree 大約 0.15 秒。搜索階段查詢 10 萬個點找最近鄰CPU 版 2.4 秒GPU 版 0.4 秒。加速比在 6 倍左右不如聚類那么夸張但也值得優(yōu)化。6.3 一個容易被忽略的并行坑不要在同一臺機器上同時跑多個 GPU 任務(wù)搶顯存。我的項目之前同時開了法線估計和聚類兩個 GPU 模塊顯存占用超過 2GB 后CUDA driver 會在內(nèi)核啟動時等待空閑顯存時間消耗反而比 CPU 版更差。解決辦法是串行執(zhí)行 GPU 任務(wù)或者控制點云上傳量。從那以后我每次配置 PCL CUDA 環(huán)境都會先做一次 GPU 版的基準(zhǔn)測試確認(rèn)加速比符合預(yù)期后再繼續(xù)調(diào)算法參數(shù)。如果加速比低于 5 倍說明配置有問題早點排查比后期再回頭找原因成本低得多。這套流程跑順之后處理點云數(shù)據(jù)的效率提升是實實在在的希望幫到你。本文還有配套的精品資源點擊獲取