스캔 데이터는 일반적으로 많은 노이즈를 가진다. 이런 데이터는 3차원 특징점을 계산하기 어렵게 한다. 이러한 몇몇 아웃라이어는 필터링 기법으로 삭제가 가능하다. 점군에 속한 각 점들은 이웃점을 가지며, 이와 관련된 평균 거리 등을 계산할 수 있다. 이에 대한 분포는 평균, 분산과 함께 가우시안 분포를 가진다. 이런 특징을 이용해 통계적으로 필터링을 할 수 있다.
필터링된 점군은 각 점마다 특징(피처, Feature)을 계산할 수 있다. 피쳐 모듈은 3차원 특징 예측을 위한 자료 구조와 알고리즘을 포함한다. 공간을 스캔한 점군은 점 주변에 기하학적인 패턴을 가진다. 자료 공간을 검색할 때 사용하는 k-이웃검색(k-neighborhood)를 통해, 특정 주변 점군을 선택할 수 있을 것이다. 이 점군들은 특정 표면의 측정 결과일 것이며, 표면에 대한 곡률과 법선 정보가 내포된 것이다.
질의점 p에서 획득한 이웃점 k개를 획득할 때는 대용량 점군을 그대로 사용할 수 없다. 계산 성능을 개선하기 위해 공간인덱싱 기법 중 하나인 옥트리(octree), kD-tree를 사용한다. 그러므로, 이웃점을 질의하는 연산은 그리 간단한 문제가 아니다. 보통 이웃점 검색에 사용되는 파라메터는 반경 r혹은 이웃점 갯수 k이다.
획득된 이웃점들을 이용해, 법선 벡터를 구하기 위해서, 고유벡터(eigen vector)를 계산해야 한다. 이를 위해, 고유값분해(digen decomposition)을 수행한다. 법선벡터는 이렇게 계산된 고유벡터 중 가장 작은 고유치를 가진 벡터가 된다.
각 3차원 점에 대한 법선벡터 n을 획득하였다면, 각 점에 대한 곡률벡터는 이웃점의 법선벡터로 계산할 수 있다. 여기서 획득한 값들은 곡면이나 객체를 분류할 때 유용하게 사용될 수 있다.
우선 다음 링크를 참고해, PCL 코드를 다운로드 한 후 빌드한다. 빌드 중 다양한 이유로 에러가 발생할 수 있다. 하나씩 해결해 나가며 빌드해 본다.
빌드를 성공했다면, PCL루트 폴더에서 다음 명령을 입력해 법선계산 프로그램 코드를 찾아본다.
이 중에 compute 함수를 찾아본다. 함수의 핵심 루틴은 다음과 같다.
NormalEstimation<PointXYZ, Normal> ne;
ne.setInputCloud (xyz);
ne.setSearchMethod (search::KdTree<PointXYZ>::Ptr (new search::KdTree<PointXYZ>));
ne.setKSearch (k);
ne.setRadiusSearch (radius);
ne.compute (normals);
각 코드의 의미는 다음과 같다.
1. 법선벡터 계산 템플릿 클래스 생성
2. 3차원 점군 설정
3. 이웃점 탐색 공간인덱싱 기법 설정
4. 이웃점 탐색 개수 설정
5. 이웃점 탐색 반경 설정
6. 법선벡터 계산
빌드된 법선벡터 계산 프로그램을 실행해 보자.
참고로, 사용된 점군은 라이다에서 얻은 점군을 SLAM처리하여 얻은 대학교 캠퍼스(
UNF) 데이터이다.
pcl_viewer를 실행해 법선벡터 값을 아래와 같이 확인해 본다. 곡률이 작으면 적색, 많으면 녹색이다. 이 값은 앞서 언급한 파라메터에 따라 달라질 수 있다.
곡률 계산 결과(PCL)
다음 명령을 이용해 las 데이터로 변환한 후 CloudCompare에서 확인해 본다.
for f in *.pcd; do pcl_convert_pcd_ascii_binary $f ./ascii/$f 0; done
for f in *.pcd; do pdal translate ./$f $f.las; done
곡률 계산 결과(CloudCompare)
PCL 패키지 모듈 분석
PCL모듈을 좀 더 명확히 이해하기 위해, Make파일을 통해 구조를 분석해 본다.
PCL은 매우 모듈화된 구조를 제공한다. 예를 들어, 가시화 모듈인 pcl_visualization을 이용해, 다양한 목적의 3차원 점군 데이터 뷰어를 개발할 수 있다. 다음 그림과 같이, 해당 모듈 폴더에서 소스파일을 갱신한 후 메이크해본다.
touch pcl_visualizer.cpp
cd ..
cd build
make
Make 결과
이 결과, lib 폴더 아래에 libpcl_visualization.so 라이브러리가 빌드되고, pcl_outofcore_viewer, pcl_oni_viewer, pcl_image_grabber_saver, pcl_vlp_viewer, pcl_pcd_image_viewer, pcl_viewer, pcl_octree_viewer 도구 등이 이 라이브러리를 이용하는 것을 확인할 수 있다.
소스파일 의존관계를 좀 더 깊게 확인해 보기 위해,
make 파일을 분석해 본다. PCL은
CMake를 사용한다. CMake 파일은 아래와 같이 구성된다(참고.
graphviz). 행렬수치해석 Eigen 등 사용하는 패키지를 포함하고, 루트 폴더 내 모듈 폴더들을 획득해, add_subdirectory 명령으로 하위 CMakeFile을 추가하고 있음을 확인할 수 있다.
...
# Eigen (required)
find_package(Eigen 3.1 REQUIRED)
include_directories(SYSTEM ${EIGEN_INCLUDE_DIRS})
# Boost (required)
include("${PCL_SOURCE_DIR}/cmake/pcl_find_boost.cmake")
...
collect_subproject_directory_names("${PCL_SOURCE_DIR}" "CMakeLists.txt" PCL_MODULES_NAMES PCL_MODULES_DIRS doc)
foreach(subdir ${PCL_MODULES_DIRS})
add_subdirectory("${PCL_SOURCE_DIR}/${subdir}")
endforeach()
하위 폴더는 모듈을 빌드하는 CMake 파일이 존재한다. Common 모듈의 경우, 아래와 같다.
set(SUBSYS_NAME common)
set(SUBSYS_DESC "Point cloud common library")
set(SUBSYS_DEPS)
set(build TRUE)
PCL_SUBSYS_OPTION(build "${SUBSYS_NAME}" "${SUBSYS_DESC}" ON)
PCL_SUBSYS_DEPEND(build "${SUBSYS_NAME}" DEPS ${SUBSYS_DEPS} EXT_DEPS eigen boost)
set(range_image_incs
include/pcl/range_image/bearing_angle_image.h
include/pcl/range_image/range_image.h
include/pcl/range_image/range_image_planar.h
include/pcl/range_image/range_image_spherical.h
)
set(range_image_incs_impl
include/pcl/range_image/impl/range_image.hpp
include/pcl/range_image/impl/range_image_planar.hpp
include/pcl/range_image/impl/range_image_spherical.hpp
)
set(range_image_srcs
src/bearing_angle_image.cpp
src/range_image.cpp
src/range_image_planar.cpp
)
set(srcs
src/point_types.cpp
src/pcl_base.cpp
src/PCLPointCloud2.cpp
src/io.cpp
...
set(LIB_NAME "pcl_${SUBSYS_NAME}")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")
PCL_ADD_LIBRARY(${LIB_NAME} COMPONENT ${SUBSYS_NAME} SOURCES ${srcs} ${kissfft_srcs} ${incs} ${common_incs} ${impl_incs} ${tools_incs} ${kissfft_incs} ${common_incs_impl} ${range_image_incs} ${range_image_incs_impl})
PCL_MAKE_PKGCONFIG(${LIB_NAME} COMPONENT ${SUBSYS_NAME} DESC ${SUBSYS_DESC})
...
생성된 make 파일의 주요 부분은 다음과 같다. 적색 표시 부분은 각 폴더의 Make파일을 실행한 결과 생성되는 라이브러리 패키지이다.
...
# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/ktw/Projects/pcl-1.12
# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/ktw/Projects/pcl-1.12/build
...
# Special rule for the target list_install_components
list_install_components:
@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"pcl_2d\" \"pcl_common\" \"pcl_features\" \"pcl_filters\" \"pcl_geometry\" \"pcl_io\" \"pcl_kdtree\" \"pcl_keypoints\" \"pcl_ml\" \"pcl_octree\" \"pcl_outofcore\" \"pcl_people\" \"pcl_recognition\" \"pcl_registration\" \"pcl_sample_consensus\" \"pcl_search\" \"pcl_segmentation\" \"pcl_stereo\" \"pcl_surface\" \"pcl_tools\" \"pcl_tracking\" \"pcl_visualization\" \"pclconfig\""
.PHONY : list_install_components
# The main clean target
clean/fast: clean
# Build rule for target.
uninstall: cmake_check_build_system
$(MAKE) -f CMakeFiles/Makefile2 uninstall
...
# Build rule for target.
pcl_common: cmake_check_build_system
$(MAKE) -f CMakeFiles/Makefile2 pcl_common
.PHONY : pcl_common
...
# Build rule for target.
pcl_sample_consensus: cmake_check_build_system
$(MAKE) -f CMakeFiles/Makefile2 pcl_sample_consensus
.PHONY : pcl_sample_consensus
# fast build rule for target.
pcl_sample_consensus/fast:
$(MAKE) -f sample_consensus/CMakeFiles/pcl_sample_consensus.dir/build.make sample_consensus/CMakeFiles/pcl_sample_consensus.dir/build
.PHONY : pcl_sample_consensus/fast
PCL ROOT 폴더 구조를 보면, 각 모듈이 위치한 폴더들을 확인할 수 있다. 각 모듈은 include, src 소스파일 폴더로 구성된다.
유지관리하기 편리하게, 모듈 패키지 별로 폴더를 구성했음을 확인할 수 있다.
모듈 패키지에서 제공하는 함수 역할을 다음과 같다.
- cuda: CUDA 가속 계산 함수
- features: 특징 계산
- filters: 점군 필터링
- geometry: 기하 형상 자료 구조 제공. polygon, mesh 등.
- gpu: GPU 가속 계산 함수
- io: 센서 및 파일 데이터 입출력 함수
- kdtree: kD-tree 공간 인덱싱 함수
- keypoints: 키포인트 계산 함수
- ml: SVM(Support Vector Machine)등 기계학습 지원 함수
- octree: Octree 공간 인덱싱 함수
- ontoforce: 디스크 기반 공간 인덱싱 함수
- recognition: 객체 인식 함수
- registration: 3차원 정합 함수
- sample_consensus: RANSAC(Random sample consensus) 처리 함수
- search: 점군 검색 함수
- segmentation: 점군 세그먼테이션 함수
- simulation: 3차원 점군 시뮬레이션
- stereo: 스테레오 이미지 생성 함수
- surface: 표면 재구성 함수
- test: 테스트 실행 함수
- tools: PCL 도구
- tracking: 3차원 점군 추적 함수
- visualization: 3차원 점군 시각화 함수
이 중에 주요 패키지인 common, feature를 중심으로 코드를 역설계해본다. 핵심적인 코드는 common 패키지의 pcl_base, PCLHeader.h, point_cloud_h, feature의 feature.h이다.
주요 코드
PCL 아키텍처는 처음부터 재활용성, 확장성과 유지보수성을 크게 염두해두고, 객체지향 소프트웨어 공학적으로 개발되었다. 이런 이유로, 새로운 모듈을 계속 확장하면서도, 복잡도를 줄일 수 있었다. 예를 들어, 3차원 점군은 다음과 같이 매우 다양한 유형을 가질 수 있다.
- 정수형 X, Y, Z
- 실수형 X, Y, Z
- RGBD
- 반사강도
- 사용자 데이터 등
이를 모두 표현하면서, 개발된 다양한 입출력 포맷, 알고리즘은 코드 수정없이 재활용할 수 있도록, 객체지향의 템프릿 클래스를 사용한다.
template <typename PointT>
class PCL_EXPORTS PointCloud
핵심적인 클래스인 점군 자료구조, 입출력, 특징 계산과 관련된 아키텍처는 다음과 같다.
역설계된 PCL 주요 아키텍처(UML)
PCL은 이렇게 신뢰성, 확장성이 매우 뛰어나, ROS등 다양한 프레임웍에 기본으로 사용된다.
PCL 사용 회사
소프트웨어 개발
PCL을 이용한 간단한 개발 방법을 살펴보자. 우선, 프로젝트 파일을 담을 폴더를 생성하고, PCD파일을 읽을 코드를 작성한다.
mkdir pcd_io
cd pcd_io
pcd_io.cpp 파일 코드는 다음과 같다.
#include <stdio.h>
#include <iostream>
#include <pcl/PCLPointCloud2.h>
#include <pcl/io/pcd_io.h>
#include <pcl/features/normal_3d.h>
using namespace pcl;
using namespace pcl::io;
using namespace pcl::console;
int main(int argc, char** argv)
{
// Load in the point cloud
PointCloud<PointXYZ>::Ptr cloud_in (new PointCloud<PointXYZ> ());
if (loadPCDFile (argv[1], *cloud_in) != 0)
{
print_error ("Could not load input file %s\n", argv[1]);
return (-1);
}
std::cout << "Loaded " << cloud_in->width * cloud_in->height << " fields: " << std::endl;
for (const auto& point: *cloud_in)
std::cout << " " << point.x << " " << point.y << " " << point.z << std::endl;
return 0;
}
# set default env varibles
cmake_minimum_required(VERSION 3.16.1)
set(SUBSYS_NAME pcd_io)
set(SUBSYS_DESC "Useful PCD io command")
set(DEFAULT ON)
set(REASON "")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
set(PCL_ROOT "/home/ktw/Projects/pcl-1.12")
message("BIN PATH = " ${CMAKE_BINARY_DIR})
message("PCL ROOT = " ${PCL_ROOT}/common/include)
# set project
project(pcd_io)
# set include and library
include_directories(PUBLIC ${PCL_ROOT}/common/include ${PCL_ROOT}/features/include ${PCL_ROOT}/filters/include ${PCL_ROOT}/geometry/include ${PCL_ROOT}/io/include ${PCL_ROOT}/kdtree/include ${PCL_ROOT}/keypoints/include ${PCL_ROOT}/octree/include ${PCL_ROOT}/recognition/include ${PCL_ROOT}/registration/include ${PCL_ROOT}/sample_consensus/include ${PCL_ROOT}/search/include ${PCL_ROOT}/segmentation/include ${PCL_ROOT}/visualization/include ${PCL_ROOT}/build/include /usr/include/eigen3)
link_directories(${PCL_ROOT}/build/lib/)
link_libraries(libpcl_common.so libpcl_features.so libpcl_io.so libpcl_kdtree.so libpcl_common.so libpcl_ml.so libpcl_keypoints.so libpcl_octree.so libpcl_outofcore.so libpcl_recognition.so libpcl_registration.so libpcl_sample_consensus.so libpcl_segmentation.so libpcl_search.so libpcl_surface.so libpcl_visualization.so)
# program
add_executable(pcd_io pcd_io.cpp)
다음과 같이 빌드한다.
mkdir build
cd build
cmake ..
make
그럼, 다음 같이 bin 폴더 아래 실행파일이 생성된다. 스캔된 PCD 파일을 테스트해 본다.
LDD명령을 사용해, 실행파일의 라이브러리 의존성을 체크해 본다. 제대로 링크되었음을 확인할 수 있다.
참고로, 아래와 같이 cmake --graphviz 옵션을 사용하면, 사용된 라이브러리 의존성을 그래픽으로 확인할 수 있다.
cmake .. --graphviz=dep.dot
dot -Tpng dep.dot -o dep.png
이런 방식으로 필요한 패키지를 사용해, 원하는 소프트웨어를 개발할 수 있다.