이 글은 LiDAR 스캐너 대형 로버 마운트 프레임 제작 과정을 다룬다. 프레임 제작 노력을 줄이기 위해 프로파일을 사용하기로 한다.
프로파일
로버에 Trimble LiDAR 장비를 마운트하고 사람이 진입하기 어려운 영역에 스캔을 해 본다. 좀 더 시간이 있다면, ROS를 이용해 로버를 제어하여, 주변 장애물을 피해가며 스캔을 할 수 있도록 할 계획이다.
Trimble 장비를 올리기 위해서는 로버 페이로드 성능이 좋아야 하며, 프레임도 매우 튼튼해야 한다. 아울러, 무게 중심도 낮아 쓰러지지 말아야 한다.
로버는 판매되는 키트나 제품을 최대한 활용해 개발한다.
추신 - 연말 과제 평가가 시작되어 1주일마다 평가와 자료제출이다. 연구자 담당 평균 과제 갯수가 4개 그 이상이다. 연구 일 안하는 사람들 동기부여한다고 PBS 각종 제도도입하더니 오히려 연구질은 계속 떨어지고 행정만 늘어난다. 일 벌이는 연구자만 손해인 구조. 행정 때문에 추석 이후에 연구 몰입할 수가 없다. 연구 행정 간소화 기대했다가 우리나라는 진짜 바뀌지 않는 다는 것을 알기까지 불과 1년도 걸리지 않았다.
앞서 모바일 스캔을 위한 스캔 백팩을 만들어 SLAM을 수행했었다. 이 스캔 팩백에는 임베디드 보드, 배터리, 스캐너 연결 보드 등이 들어 있어 이동시 편했다.
테스트 결과 다음과 같은 문제점이 있었다.
센서를 오래동안 들고 있을 만큼 가볍지 않음
화면을 보면서 SLAM 하기 힘듬
현재 공간 맵핑 진행 상태를 알기 힘듬
기타, 백팩에 든 보드와 배터리의 고정 문제 등
이런 문제를 고려해, 다음과 같이 좀 더 안정적인 스캔 백팩을 간단히 제작해 보았다.
스캔 백팩 개발 결과 다음과 같은 장점이 있었다.
센서를 손에 들고 다니는 것 보다 훨씬 편하다.
모니터를 다른 한손으로 보며 작업 과정을 관찰할 수 있었다.
1차 백팩에 비해 무게 중심을 잡는 밸트를 추가해 안전성있게 이동이 가능하다.
사용하는 배터리 성능이 좋아 몇 시간 이상 사용 가능하였다.
프레임이 안정적이고 수평을 맞추기 용이하여 SLAM 결과가 좋아진다.
개선점은 다음과 같다.
센서로 인해 무게중심이 높아 아직 급하게 이동하면 백팩이 뒤집힐까 조심스럽다.
임베디드 보드 패키징에 문제가 있다. 전선 등이 정리되어 있지 않아 급한 이동 시 문제가 될 수 있다.
처음 스캔 배낭을 맬때 높은 무게중심으로 인해 넘어질 수도 있다.
스캔 마운트가 폴대와 붙어 있어, 다른 장치 마운트 시 불편하다.
배터리 등 소모성 장비 장착, 탈착이 불편하다.
기타, 등산 배낭을 사용해 스타일이 그리 좋지 않다.
부록: 스캔 백팩 전시 기록
스캔 백팩 성능 및 사람들 피드백도 확인해 볼 요량으로 2019 메이커페어 서울에 전시해 보기로 함. 2019년 5월 샌프란시스코 메이커페어까지 아이들이 만든 작품만 쭉 전시했었기 때문에, 10월 메이커페어는 부스를 두개로 분리해 한쪽은 내 전공분야 관련 작업을 전시하기로 하였음. 컨셉은 다음 그림과 같이 해외 스캔 백팩과 비교해서 가성비 좋게 직접 만들 수 있음을 이야기하기로 함.
다음과 같이 부스 반을 짤라 한쪽편에 작업을 설치하고 전시함.
작품 특징 상 어른들, 주로 테크니션들이 관심을 많이 주고, 질문도 많이 함. 직접 만드는 방법에 관심이 많아 설명하였고, 정확도, 거리, 사용할 때 얼마나 가치가 있는 지 등에 대해 집중 질문. 해외 외국인들도 많이 보고 질문했는 데, 주로 대학원생 컴공, 전공, 기계과, 미디어아트 교환 학생들이었음.
교수, 교사 및 강사분들도 많았고, 메이커페어 특징 상 특이한 경력의 사람들도 많았음. 시간이 많지 않아 방문한 분들과 이런 저런 이야기를 못나눈 것이 아쉬움.
다음과 같이 스크립트를 작성한다.
cd /etc/init.d/
sudo gedit auto_bat.sh
다음과 같이 파일 편집한다. 여기서 &는 병렬 프로세스 실행 옵션이다. &&를 붙이면 앞의 명령이 제대로 실행된 후 조건에 따라 후속 실행된다.
#!/bin/bash
PATH=/usr/local/bin
sleep 3
time &
sleep 3
date &
파일을 저장한 후 다음 명령으로 부팅시 실행될 스크립트를 등록한다. 프로세스 <우선순위>는 생략가능하며, 99 이하 값을 준다.
cd /etc/init.d/
sudo chmod +x auto_bat.sh
sudo update-rc.d auto_bat.sh defaults <우선순위>
reboot
서비스 중인 상황을 다음 명령으로 확인한다.
initctl list
service --status-all
만약 제거하고자 한다면 다음처럼 하면 된다.
cd /etc/init.d/
sudo update-rc.d -f <script file name> remove
이전에 개발한 SLAM장비를 직접 들고 스캔할 목적으로 스캔 백팩을 만들었으나 무거워 가지고 다니며 스캔하기 힘들다. 이런 이유로, 연구용 야외 공간정보 맵핑 및 탐색이 가능한 간단한 로버를 만들 계획이다. 공간맵핑용 장비는 앞서 개발한 벨로다인 LiDAR 기반 SLAM 장치, 로봇 암 등을 활용한다. 이를 마운트하려면 최소한 1,794 g (VLP16 830 g + TX2 144 g + 배터리 310 g x 2 + 기타 200 g) 페이로드가 필요하다. 일반 장난감 수준의 장비로는 움직이지도 않기 때문에, 별도 로버를 제작하기로 하였다.
이 글에서 소개하는 로버는 최소 5 kg 이상의 페이로드를 지원하며, 야외 울퉁불퉁한 환경에서 공간정보 스캔을 할 수 있는 고정밀 센서 장치를 마운트 할 수 있다.
로버 임무
이 로버는 3차원 공간정보 스캔을 위한 탐색용 중량 로버(Exploration heavy weight rover for scanning 3D geo-information. 3D geo scan rover)이다. 프레임 상단에 벨로다인 VLP-16 및 RGBD 센서가 마운트될 수 있어야 하며, 통신 거리 100-200미터 범위에서 로버를 조정할 수 있어야 한다.
참고로, 로버를 개발할 때는 다음과 같은 사항을 고려해야 성공할 수 있다.
로버의 페이로드(운반무게) 결정. 페이로드 고려한 모터, 모터드라이버 및 배터리 필요
로버의 운영환경 결정. 만약, 습기가 있는 곳이라면 방수가 되는 패키징 필요. 야외라면 울퉁불퉁한 지면을 다닐 수 있는 큰 바퀴와 내구성있는 바디 프레임 필요
로버의 목적에 따른 센서 결정. 수색용이면 카메라, 적외선, LiDAR센서 및 이를 처리할 수 있는 임베디드 컴퓨터 필요. 물체취득이 필요하면 그립퍼, 로봇암등이 필요함
프레임 결정. 로버 목적에 따라 센서나 장비를 쉽게 마운트 할 수 있어야 함
로봇 통신 방법 결정. 유선인지 무선인지 결정 필요. 무선이면 몇 미터 거리 범위까지 통신 가능한지에 따라 통신장치 개발해야 함. 예를 들어 200미터 정도 거리 통신이 필요하다면 2.4GHz 송수신기를 사용해야 함
로버 파워
스펙 상으로 15kg 정도 페이로드를 지원하는 HEAVY 6WD rover 개발을 고려해 모터와 모터드라이버는 다음과 같이 결정한다.
BTS7960은 43A 모터 구동까지 가능하므로, 큰 힘이 필요한 상황에서 사용할 수 있다(예. #1, #2). 다음과 같이 배터리 팩 연결 단자를 남땜하고 모터드라이버와 연결한다. 참고로, 여기서는 지금 가지고 있는 전선을 이용했지만, 안전성을 고려해 30A 전류를 허용할 수 있는 직경을 가진 전선을 사용하는 것이 좋다(부록:AWG 참고).
프레임
프레임은 가공업체에 맡겨 제작하거나 모터만 부착된 로봇 로버 프레임(#)을 구매해 사용한다. 센서를 마운트하기 편하게 HOLE이 있어야 한다. 다만, 범용 프레임은 빠르게 개발할 수 있는 장점은 있으나, 특수목적 및 환경에서 사용할 때는 별도 개발이 필요하다.
여기서 사용할 프레임은 링크와 유사하다. 다만, 자체가 낮고, 가공이 쉬우며, 배터리나 모터 드라이버를 내부에 장착할 수 있는 프레임으로 결정하였다. 프레임 크기는 425 x 300 mm 이며, 바퀴를 제외하면 380 x 120 mm 이다.
센서
센서는 LiDAR와 RGBD를 지원한다. 이 결과로 고정밀 포인트 클라우드와 이미지를 획득할 수 있다. 사용 센서는 다음과 같다.
회로
회로는 다음과 같이 연결한다. 회로는 FS-R9B 수신기 CH2, CH3에서 DRIVE, STEER 신호를 각각 받아, 모터드라이버에 모터 구동신호를 전달한다. 배터리와 BTS7960은 2개를 사용해 모터에 전류를 충분히 공급한다. 참고로, L298N과 같이 2A미만 모터드라이버를 사용하면, 프레임 자체 자중도 견디기 어려울 것이다.
D22 - FS-R9B.CH2
D24 - FS-R9B.CH3
5V - FS-R9B.VCC
GND - FS-R9B.GND
D2 - RIGHT MOTOR BTS7960 RPWM
D3 - RIGHT MOTOR BTS7960 LPWM
D4 - RIGHT MOTOR BTS7960 L_EN
D5 - RIGHT MOTOR BTS7960 R_EN
D6 - LEFT MOTOR BTS7960 RPWM
D7 - LEFT MOTOR BTS7960 LPWM
D8 - LEFT MOTOR BTS7960 L_EN
D9 - LEFT MOTOR BTS7960 R_EN
VCC - BTS7960 VCC
GND - BTS6960 GND
11.1V BAT GND - BTS7960.GND
11.1V BAT VCC - BTS7960.VCC
RIGHT MOTOR BTS7960.M1 - RIGHT MOTOR +
RIGHT MOTOR BTS7960.M2 - RIGHT MOTOR -
LEFT MOTOR BTS7960.M1 - LEFT MOTOR +
LEFT MOTOR BTS7960.M2 - LEFT MOTOR -
회로 연결 과정
코딩
코딩은 다음과 같다(github 링크). 소스코드는 필드테스트 후에 다음과 같은 몇 가지 수정이 있었다.
로버 전후좌우 이동 이외에도 후진 방식 추가
송신기 전원 껴져 있는 경우, 최대속도 주행 오류 있어, 신호 없을 경우 멈춤기능 추가
특정 모터 드라이버 구동 코드 및 반복된 소스코드 함수화
모터 드라이버 회로 선 연결 착오로 핀 번호 수정
참고로, 아래 소스코드 사용할 경우, 수신기에서 얻은 채널별 디지털 신호의 범위는 테스트하여 구해야 하며, 이를 map, constrain 함수를 이용해 캘리브레이션해야 한다.
로버와 배터리를 연결하고, 송신기를 켠 후, 시리얼모니터를 확인하면, 다음과 같은 제어 신호 데이터를 확인할 수 있다.
송신기로 제어하면, 로버가 정상적으로 제어되는 것을 확인할 수 있다.
필드 테스트
로버 성능은 어떻게 되는 지, 어느 정도 중량을 견디는 지 등을 확인하고, 이슈가 있는 지 확인하기 위해 필드 테스트를 한다. 중량 테스트를 위해, 다음과 같이 로버 등의 하중을 측정한다.
로버 자중 = 4.0 kg
배터리 2개 + 에나멜 통 2개 중량 = 4.8 kg
큰 에나멜 통 1개 중량 - 2.8 kg
작은 에나멜 통 1개 = 1.1 kg
배터리 한개 501 g
로버 위 에나멜 통 2개 + 배터리 3개 = 5.3 kg
에나멜 통 1개 + 배터리 3개 = 2.7 kg
다양한 조건과 환경을 고려해 다음과 같이, 로버 테스트를 해 보았다.
1차 동작제어 및 주행속도 테스트 (자중 4 kg)
2차 중량 테스트 (하중 3.8 kg)
3차 장애물 테스트 (다양한 크기의 장애물 + 하중 2.8 kg)
4차 장애물 테스트 (다양한 크기의 장애물 + 하중 2.8 kg)
5차 중량 테스트 (하중 2.7 kg)
6차 중량+자유곡선 이동 테스트 (하중 2.7 kg)
7차 사면 등판테스트 (자중 4.0 kg)
테스트 해 본 결과 2.6kg 정도는 충분히 로버 프레임이 지지하며, 5kg은 로버의 서스펜션이 바닥에 닿는 문제를 제외하고는 큰 문제 없이 구동되었다. 서스펜션을 조정하면 5kg 페이로드도 가능하다. 경사 등판은 표면이 거친 경우, 40도 각도도 주행 가능하며, 무게중심이 낮다면 그 이상 각도도 등판 가능하다. 주먹만한 크기의 자갈길이나 작은 바위 위에서도 주행이 가능해 보인다.
마무리
앞의 개발 결과로 RF 제어가 되는 중량 센서 장치가 마운트될 수 있는 로버를 만들었다. 앞으로 로버 위에 개발된 LiDAR 및 RGBD 스캔 장치를 마운트하여, 공간정보 맵핑에 사용하려 한다. 여기에 비전, ROS, 딥러닝 등을 붙이면 장애물을 피해가며 지정된 구간을 스캔할 수 있는 장비를 개발할 수 있다.
부록: AWG 전선 규격표
AWG는 미국에서 만든 전산 사용 지침으로, 직경에 따라 저항 및 허용전류를 확인해 적절한 전선을 사용할 수 있도록 만든 가이드라인이다. 허용전류가 높은 데, 적은 직경의 전선을 사용하면, Joule 열이 발생하고, 전선 피복이 녹아 벗겨져 합선이나 화재가 발생할 수 있다. AWG는 이를 방지한다.
LOG 참고 Boot Info Script 8f991e4 + Boot-Repair extra info [Boot-Info 25oct2017] => No known boot loader is installed in the MBR of /dev/sda. => Grub2 (v2.00) is installed in the MBR of /dev/sdb and looks at sector 1 of the same hard drive for core.img. core.img is at this location and looks for (,msdos6)/grub. It also embeds following components: modules --------------------------------------------------------------------------- fshelp ext2 part_msdos biosdisk --------------------------------------------------------------------------- => Syslinux MBR (5.00 and higher) is installed in the MBR of /dev/sdc. sdb1: __________________________________________________________________________ File system: ntfs Boot sector type: Windows 8/2012: NTFS Boot sector info: No errors found in the Boot Parameter Block. Operating System: Boot files: sdb2: __________________________________________________________________________ File system: Extended Partition Boot sector type: Unknown Boot sector info: sdb5: __________________________________________________________________________ File system: swap Boot sector type: - Boot sector info: sdb6: __________________________________________________________________________ File system: ext4 Boot sector type: - Boot sector info: Operating System: Boot files: /grub/grub.cfg /grub/i386-pc/core.img sdb7: __________________________________________________________________________ File system: ext4 Boot sector type: - Boot sector info: Operating System: Ubuntu 18.04.1 LTS Boot files: /boot/grub/grub.cfg /etc/fstab /boot/grub/i386-pc/core.img sdc1: __________________________________________________________________________ File system: vfat Boot sector type: SYSLINUX 6.03 Boot sector info: Syslinux looks at sector 32776 of /dev/sdc1 for its second stage. The integrity check of Syslinux failed. No errors found in the Boot Parameter Block. Operating System: Boot files: /boot/grub/grub.cfg /syslinux.cfg /EFI/BOOT/grubx64.efi /ldlinux.sys
Potree 렌더링 서버 기반 대용량 스캔 데이터 가시화 결과(52,230,141 포인트. 1,775,825,021 byte, 248 x 266미터. 한국건설기술연구원 본관동 SLAM 데이터 일부)
대용량 점군 렌더링 실행 영상
소개
Potree는 오픈소스로 개발되었으며, nodejs 위에서 동작한다. Potree는 WebGL을 사용하며, 대용량 점군 렌더링을 위해 격자 구조로 LoD(Level of Detail) 생성하는 기능을 제공한다. 참고로, Potree는 TU faculty of Informatic의 Institute of Visual Computing & Human-Centered Technology, Research Division of Computer Graphics의 TU Wien Scanopy(Scan Data Organisaion, Processing and Display) project에서 시작되었고 Harvest4D project의 한 부분이었다. Harvest4D는 3차원 디지털 모델과 각종 센서 데이터를 연결해 도시 관리 등 다양한 곳에 사용하려는 목적으로 시작된 프로젝트이다.
Potree의 격자구조는 다음과 같이 옥트리(Octree) 형식의 공간인덱싱 기법을 사용한다. 이를 통해 렌더링 성능을 확보할 수 있는 LoD을 생성할 수 있다.
Octree를 이용해 생성된 LoD 피라미드(Martinez-Rubi, 2015)
참고로, 수백만 점군 이상의 데이터를 한번에 렌더링하면 아무리 성능 좋은 GPU와 CPU라 하더라도 속도 저하가 발생할 수 밖에 없고, 카메라에서 보는 시점에서 한 장면 렌더링할 때 최초 몇초에서 수십분 이상 시간이 걸릴 수도 있다. 참고로, Potree에 적용된 LoD와 카메라에 보이지 않는 객체를 제외해 렌더링하는 Culling 기법은 오래전부터 컴퓨터 그래픽스, 게임 프로그래밍에 써 왔던 기술이다.
사용방법 Potree rendering server
다음처럼 nodejs와 npm 최신버전을 설치한다(10.0 버전 이상. 오래된 버전으로 진행시 빌드 에러 날 수 있음).
node -v
sudo npm cache clean -f
sudo npm install -g n
n latest
npm -v
sudo npm install -g npm
potree와 PotreeConverter가 있다. potree를 방문한다. 그리고, 다음 순서로 nodejs 및 module을 설치한 후, 소스코드 다운로드 받고, 빌드한다. 참고로 gulp는 빌드 자동화 도구이다. rollup은 module bundler 도구로 자바스크립트 모듈의 재활용성과 유지보수성을 높이기 위해 조각난 로직간 의존성 등을 관리한다.
Potree converter
Potree가 사용하는 포인트 클라우드 자료 구조를 만들기 위해 제공하는 Potree converter를 빌드한다. 보통 변환에 입력되는 점군 포맷은 LAS 표준파일이다. Potree는 이 포맷을 읽고 쓰는 유틸리티를 사용한다. 다음과 같이 소스코드를 다운로드하고 빌드한다. 폴더가 없으면 mkdir로 만들고 다운로드한다.
cd ~/dev/workspaces/lastools
git clone https://github.com/m-schuetz/LAStools.git master
cd master/LASzip
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
LiDAR 스캔 데이터 포맷 변환 방법
PotreeConverter는 LAS, LAZ, PTX, PLY파일을 입력받는다. 이런 이유로, 스캔 데이터 포맷 변환 방법은 크게 3가지가 있다.
1. 상용 SW 를 이용한 LAS 파일 변환
고정밀 LiDAR를 사용하면, 표준파일포맷인 LAS, E57로 저장할 수 있는 소프트웨어를 개발사에서 함께 판매한다. 예를 들어, FARO는 Scene, Trimble은 Realworks 를 판매하고 있으며, 라이카는 사이클론을 판매한다(수천만원 이상의 가격은 안습). 이 소프트웨어를 이용해 LAS파일로 변환한 후 PotreeCoverter를 통해 Potree 렌더링용 데이터 구조를 생성할 수 있다.
2. PDAL 및 CloudCompare 이용한 파일 변환
이 방법은 보통, 개발용으로 실시간 LiDAR 센서(Velodyne, Quanergy systems 등)를 직접 이용할 때 사용한다. PDAL은 포인트 데이터를 변환하고 관리하는 기능을 제공하는 오픈소스이다. PDAL은 LAS 등 점군 형식 입출력을 지원한다. CloudCompare는 PDAL을 포함한 플러그인을 제공하고 있어 입출력 시 LAS포맷을 지원한다.
보통 라이다 센서는 ROS(Robot Operating System)에 모듈로 제공되는 경우가 많은 데, 이때 기록되는 파일 포맷은 ROS bag이나 PCD(Point Cloud Data)포맷이다. 그러므로 다음 순서로 LAS파일을 얻을 수 있다. 단, 파일 입출력 경로에 한글이 들어가면 에러 발생한다.
데이터 변환 과정(변환 결과 데이터는 대략 1.7GB로 원본 LAS파일에 비해 큰 증가는 없었음)
rendering viewer를 담을 html 및 관련 lib를 PotreeConverter/Poresources/page_template에서 cloud.js가 있는 폴더에 복사한다. 그리고, viewer_template.html 의 <script></script>에 다음 코드를 붙여넣고 저장한다.
<script> window.viewer = new Potree.Viewer(document.getElementById("potree_render_area")); viewer.setEDLEnabled(true); viewer.setFOV(60); viewer.setPointBudget(1*1000*1000); <!-- INCLUDE SETTINGS HERE --> viewer.loadSettingsFromURL(); viewer.loadGUI(() => { viewer.setLanguage('en'); $("#menu_appearance").next().show(); $("#menu_tools").next().show(); $("#menu_scene").next().show(); viewer.toggleSidebar(); }); // Load and add point cloud to scene Potree.loadPointCloud("./cloud.js", "r", e => { let scene = viewer.scene; let pointcloud = e.pointcloud; let material = pointcloud.material; material.size = 1; material.pointSizeType = Potree.PointSizeType.ADAPTIVE; material.shape = Potree.PointShape.SQUARE; scene.addPointCloud(pointcloud); viewer.fitToScreen(); }); </script>
이제 Potree폴더에서 Potree서버를 실행한다.
cd potree
gulp watch
인터넷 탐색기로 URL를 접속하고, 생성된 potree data폴더의 viewer_template을 선택한다. http://localhost:1234/<your folder>/viewer_template
그럼 다음과 같이 생성된 potree data를 확인할 수 있다. 렌더링이 매우 부드럽게 실행되는 것을 확인할 수 있다. 참고로, 뷰어 왼쪽 패널에 부착된 위젯으로 배경 등 설정할 수 있다.
example 폴더에 보면, 다양한 방식의 렌더링 기능이 script안에 코딩되어 있다. 다음은 annotation 등 기능을 적용한 모습이다.
Potree 서버 기반 포인트 클라우드 렌더링 실행 모습(SLAM, 한국건설기술연구원 본관 일부 스캔 데이터)
3. 직접 개발
PDAL, LAStools 라이브러리를 이용해 직접 개발하는 방법이다. 점군 자료구조를 직접 다루어야 한다면, 관련 예제를 참고해 개발해야 한다.
구조 분석
Potree 렌더링 서버는 다음 패키지를 사용한다.
다음은 주요 페키지 역할이다.
Cesuim: Google earth처럼 GIS 기반 공간정보를 가시화할 때 사용함. 다양한 스타일과 시각화효과를 제공함
openlayer3: 웹페이지에서 동적으로 지도를 생성하는 역할을 함. 데이터 소스에서 벡터 데이터, 마커 등 자동생성 지원
three.js: 경량화된 WebGL 기반 geometry 생성, 렌더링 및 애니메이션 지원
tween: 애니메이션 지원
d3; 자바스크립트 라이브러리로, 데이터를 화려한 차트, 그래픽, 인터렉티브한 UI로 DOM(Document Object Model)를 생성하는 역할을 함
jquery: DOM 탐색 및 수정, 이벤트 처리, 애니메이션, AJAX, JSON 파싱, 플러그인 등 지원.
i18next: 국제화를 지원하는 패키지
주요 클래스 구조는 다음과 같다.
각 클래스 역할은 다음과 같다. 제한된 웹브라우저 메모리에서 점군을 렌더링하기 위해 공간인덱싱, LoD 기법을 사용해서, Potree 이름처럼 전체적으로 point cloud를 tree 형태로 매달아 놓은 자료구조를 가진다. 참고로, 이 구조는 오래전부터 컴퓨터 그래픽스, 게임 개발 시 사용되었던 방식이다.
Potree: Potree 서버의 엔트리 포인트 제공. 기본 설정 및 포인트 클라우드 로딩 등 기본 작업
POCLoader: 포인트 클라우드 로딩
PointCloudOctreeGeometry: 옥트리 구조 제공. 점군 형식에 따른 로더 선택 및 실행
PointCloudOctreeGeometryNode: 옥트리 노드 제공. 각 노트는 LoD 파일이 연결되어 있으며, 렌더링에 필요할 경우 메모리에 loading 되는 메커니즘을 제공
<script> window.viewer = new Potree.Viewer(document.getElementById("potree_render_area")); <!-- 뷰어 생성 --> viewer.setEDLEnabled(true); <!-- 뷰어 속성 설정 --> viewer.setFOV(60); viewer.setPointBudget(1*1000*1000); <!-- 뷰어에 렌더링되는 점군 수 --> <!-- UI 패널 설정 --> viewer.loadSettingsFromURL(); viewer.loadGUI(() => { viewer.setLanguage('en'); $("#menu_appearance").next().show(); $("#menu_tools").next().show(); $("#menu_scene").next().show(); viewer.toggleSidebar(); }); <!-- 점군 로딩 --> Potree.loadPointCloud("./cloud.js", "r", e => { let scene = viewer.scene; let pointcloud = e.pointcloud; let material = pointcloud.material; material.size = 1; material.pointSizeType = Potree.PointSizeType.ADAPTIVE; material.shape = Potree.PointShape.SQUARE; scene.addPointCloud(pointcloud); <!-- 점군을 현재 렌더링 scene에 추가 --> viewer.fitToScreen(); <!-- 점군을 화면에 꽉차게 zoom extent 처리 --> }); </script>
Potree converter 구조는 다음과 같다.
각 패키지 역할은 다음과 같다.
LAStools: LAS 점군 표준파일 입출력 기능
PotreeConverter: LAS, LAZ, PTX, PLY 등 점군 파일을 읽어 Octree 격자로 구성된 렌더링용 LoD 파일들을 생성함
마무리
이 글에서는 Potree 설치 및 사용 방법을 간단히 소개해 보았다. Potree는 매우 큰 대용량 점군도 끊어지지 않고 부드럽게 데이터를 가시화해 주며, 이를 위한 유틸리티를 오픈소스로 제공한다. 최근 무인자율차, 드론 사진 측량, SLAM기반 자율로봇, 시설물 관리 및 운영 등에 스캔 데이터가 많이 사용되면서 Potree 같은 기술이 더욱 많은 관심을 받고 있다.
Potree를 사용하면 대용량 포인트 클라우드를 인터넷에서 원활한 서비스가 가능하다. 아울러, 다양한 java script library를 이용해 annotation, segmentation 등 목적에 맞게 개발할 수 있다.
레퍼런스
Martinez-Rubi, O., Verhoeven, S., Van Meersbergen, M., Van Oosterom, P., GonÁalves, R., & Tijssen, T. (2015). Taming the beast: Free and open-source massive point cloud web visualization. In Capturing Reality Forum 2015, 23-25 November 2015, Salzburg, Austria. The Servey Association.
문제정의는 천만개 수준의 점군의 실시간성 데이터 처리 속도를 보장하기 위한 Octree 생성 기술 개발이다.
연구목표 다이어그램
이 기술은 다음과 같이 입력 점군에 대한 청킹을 통해 옥트리로 인덱싱된 분산된 점군을 생성한다.
핵심 알고리즘은 다음과 같다. 여기서,
PCD = 점군
gridSize = 128
numTreads = thread count in CPU
chunkSize = sizeof chunk's point list
chunkFilename = r[0-7][x][y][z]
leafNoteMaxPoints = 10k
1. grid = PCD.divide_grid(gridSize)
2. grid.counting()
3. grid.merge_sparse_cells(max_count) # 병합
4. LUT = grid.make_LUT() # 인덱싱
5. chunk_files = grid.distribute_points(LUT)
인덱싱 알고리즘은 다음과 같다.
1. 각 청크에 대해 다중 해상도 옥트리 생성
2. 리프 노드는 포인트로 채워짐
3. 상위 노드는 자식노드를 샘플링해서 채움
점 좌표에서 해당 청크 파일까지 접근하기 위해 좌표 해쉬 기법을 사용할 수 있다. 예를 들어, 4바이트 정수(32 bit)는 4,294,967,296 값(2^32)을 가질 수 있다. 이는 meter 단위로 429억 km에 해당한다(참고로 지구 적도 둘레가 40,075km). 입력 점군 파일의 크기를 얻으면, 좌표의 특정 부분만 퀀타이즈(양자화. Quantized)로 마스크 처리한 후 해당 청크의 이름을 미리 계산해 놓는다(해쉬 인덱싱과 같은 원리).
병합 알고리즘은 다음과 같다.
1. 로컬 옥트리 청크가 모두 생성됨
2. 전역 옥트리 인덱싱 실행해 상향식으로 로컬 옥트리 청크를 개별 샘플링
3. 루트 노드까지 모든 노드가 octre.bin에 기록된 후 hierarchy.bin, metadatajson 파일 생성
렌더링 도중 필요한 scene의 렌더링 점군을 얻기 위해, 이 과정을 동적으로 수행할 수 있다(일종의 캐쉬 데이터를 동적으로 생성/해제).
좋은 점군 샘플링 알고리즘 특성은 다음과 같다.
1. 샘플링 점은 하위 노드의 점군 특징을 잘 반영(예. 포인트 경계, 점 분포 등)
2. 능선과 같은 브레이크 라인을 반영
샘플링은 포아송 디스크 접근 방식 등이 사용될 수 있다.
포아송 디스크 샘플링 예
청크를 노드별로 파일에 저장할 때, 쓰레드 기법을 사용하나, 디스크는 보통 하나이고, 각 쓰레드에서 쓰기하면 느려질 수 있다. 그래서, 메모리 캐쉬를 통해 버퍼에 점들을 저장하고, 버퍼가 다 차면, 물리적 디스크에 파일을 저장한다.
구현 결과는 다음과 같다.
참고: SimLoD. Simultaneous LOD Generation and Rendering for Point Clouds
문제 정의는 어떻게 실시간성 LoD 생성을 보장하며, 자연스러운 점군 렌더링을 할 수 있는 솔류션 개발이다. 특히, 희소한 복셀이 차지하는 메모리 용량을 최대한 줄일 수 있는 방법도 제시한다.
이 기술의 주요 용어는
청크 = 1k 점군 파일
청크수 = (현재 포인트 수 + 1000 - 1) / 1000
링크드 노드 = {청크*}
옥트리 = {링크드 리스트}
복셀 = 렌더링픽셀값(청크, 카메라정보)
이 연구는 앞의 기존 개발 기술과 CUDA(RTX 4090. 24GB)를 적극 사용한다. 자료구조는 Octree, linked list를 사용한다. 참고로, 기존 shader 방식도 링크드 리스트를 사용할 수 있으나, CUDA 코딩 방식보다 성능이 떨어진다.
각 리프노드는 50k 점군까지 저장된다. 내부 복셀은 128^3(2M)개 까지 늘어날 수 있으나 보통 객체의 내부는 텅텅 비어 있으므로, 128^2(16k. 객체 표면) 정도에 근접된 숫자로 유지된다. 이런 이유로 희소 격자는 링크드 리스트 형태로 저장될 것이다. 각 링크드 노드는 청크들을 관리한다. 이 연구에서는 각 청크는 1k 점군을 저장하기로 한다.
대용량 렌더링 데이터를 CUDA 커널에서 처리하기로 한다. LoD 생성은 수백만 프레임에 대해 처리되어야 한다. 실시간 생성될 LoD 점군은 시간 축으로 파이프라인을 통과해 처리되어야 한다. 다음은 이를 묘사한 것이다.
렌더링은 복셀로 이뤄진다. 각 격자 노드에 대한 복셀화 유무는 1 bit로 표현될 수 있는데, 이 논문의 경우, 128kb 만 사용해 이를 표현할 수 있었다. 복셀 좌표와 생삭은 링크드된 청크들에서 선형적인 형태로 미리 저장되어, 빠른 렌더링 성능을 가진다.
청크들(복셀들)은 다아나믹하게 관리되어 LoD를 렌더링하게 고안되어야 한다. 옥트리는 동적으로 확장될 수 있도록 관리된다. 각 옥트리 노드는 포함된 점군이 넘치지 않게(예. 50k이상 점군) 동적으로 분할된다. 전체 파이프라인은 다음과 같다.
옥트리 확장 > 복셀 생성 > 청크 할당(사전 할당 메모리) > 점/복셀 삽입
이는 GPU에서 실행될 수 있어야 한다. 이를 위해 두 개의 CUDA 커널(함수)를 준비한다.
래스터라이즈 커널: scene 렌더링
업데이트 커널: 배치된 포인트를 옥트리에 삽입/확장
다음은 이 상황을 묘사한다.
문제는 수천개 CUDA 스레드가 동시에 포인트를 삽입하려 할 때, 동기화로 인한 속도 저하 문제 발생한다. 이런 이유로, 각 격자 노드를 방문해 동적으로 생성할 점/복셀을 다 수집한 후 한꺼번에 업데이트함으로써 동기화 비용을 최소화한다. 이런 이유로, 특정 격자의 넘친 노드에 점군 스필 버퍼(spill buffer), 복셀용 임시버퍼(backlog buffer) 등을 사용해 한번에 갱신하는 전략 등을 사용한다.
이제 옥트리 노드마다 복셀을 계산했으므로, 렌더링을 위해 가시 노드들(visible nodes)을 계산해야 한다. 가시 노드는 뷰 프러스텀(view trustum) 내에 있는 노드들의 집합이다. 카메라 시점에서 보이는 노드만 선택되어야 한다. 뷰에서 부모 노드 방문 시 복셀이 128픽셀 이상이라면, 더 작은 노드의 복셀로 대체한다. 화면 가장자리는 카메라 원근 왜곡을 고려해 좀 더 높은 LoD 복셀이 선택되어야 한다.