Home [3D scanner] 3D scanner 제작기 - 3
Post
Cancel

[3D scanner] 3D scanner 제작기 - 3

관련 포스팅


4. 소프트웨어 개발 과정

4.1 문제점 및 해결 과정

소프트웨어 개발 중 맞닥뜨린 문제점과 그 해결 과정을 위주로 서술하였습니다.

4.1.1 IR 거리 센서와 레이저 TOF 거리 센서 비교

구분GP2Y0A21YK0F IR 센서VL5310X Laser TOF 센서
통신아날로그 전압 신호I2C
제어핀아날로그 입력 핀A4(SCL), A5(SDA)
측정 원리적외선이 물체에서 반사된 지점에 따라
수신부에 도달하는 지점의 차이를 이용해 측정
레이저 빛이 발사되고 물체에 반사되어
수신될 때까지 시간을 측정

VL5310X 레이저 TOF 거리 센서 역시 10000원 대의 저렴한 가격으로 구매할 수 있는 거리 측정 센서입니다. 두 센서 중 어느 센서를 사용할지 결정하기 위해 사진과 같은 멀티미터를 한 slice 스캔하여 그 결과를 비교해 보았습니다. 별도의 데이터 후처리는 수행하지 않았습니다.

Multimeter_image Multimeter with edge

IR_sensor_result Scan result w/ IR sensor

Laser_TOF_sensor_result Scan result w/ Laser TOF sensor

IR 거리 센서 측정 결과를 보면 적외선의 입사각이 10도보다 작은 네 부분에서 규칙적으로 펄스가 관찰되었습니다. 반면 레이저 TOF 거리 센서의 측정 결과에서는 전체적인 정밀도는 높지만, 몸체가 얇게 왜곡되었습니다.

실험 결과 멀티미터의 주황색 케이스 부분의 엣지 부분이 원인으로 파악되었습니다. 직진성이 강한 레이저의 특성 상, 물체 표면의 방향이 레이저와 수직을 이루지 못할 경우 다른 곳으로 반사되므로 불규칙한 형상을 스캔할 시 정밀도가 크게 하락하였습니다.

이러한 제약을 감수할 수는 없으므로, 본 3D scanner 프로젝트에서는 IR 거리 센서를 사용하였습니다. 원래 레이저 TOF 거리 센서의 측정 범위는 2m 임을 생각하면 3mm 남짓의 오차는 센서 가격 대비 작은 편에 속하므로 이 센서는 추후에 자작 Lidar 프로젝트 때 사용해볼 계획입니다.


4.1.2 IR 거리 센서의 측정 오차 원인 분석

IR 거리 센서를 사용하기로 결심했지만, 발견된 오차를 무시할 수는 없습니다. 규칙적으로 발생하는 펄스를 좀 더 정량적으로 측정하기 위해 rotating plate 위에 회전축을 지나는 평면을 붙인 다음 180도 스캔하여 입사각에 따른 오차를 측정해보았습니다. 그 결과 다음과 같이 도출되었습니다.

Error_plot Incidence angle vs IR sensor distance error

이제 예상 오차 가설과 검증 과정을 정리해봅시다. 이 과정이 정말 오래 걸렸고 저에게 극심한 스트레스를 주었지만, 사실 결과는 허무하게 끝났습니다…

1) 회전 중심에서 거리에 따른 지점의 선속도 차이

회전 중심에서 먼 지점일수록 선속도가 커지므로 잔상 효과를 크게 받게 됩니다. 가장 먼저 떠올린 가설이었지만, 물체의 회전 방향을 바꾸어 측정하여도 펄스의 방향은 유지되었기 때문에 본 가설은 기각되었습니다.

2) IR 거리 센서의 방향과 회전 중심 사이의 편심

Eccentric_model Eccentric model of IR distance sensor

센서랑 모터 조립 시 오차가 발생하여 IR 거리 센서와 회전 중심 사이에 편심이 발생할 가능성이 존재합니다. 이를 이론적으로 분석해봅시다. 위의 그림과 같이 원래 빨간색으로 표시한 평면을 파란색으로 표시한 회전 중심을 지나는 센서로 측정해야하지만, 편심 s가 발생한 노란색 센서로 측정했다고 가정해봅시다.

빨간색으로 표시한 직선의 방정식은 극좌표계에서 다음과 같이 기술할 수 있습니다.

\[r(\theta) = \frac{d}{\cos{\theta}}\]

편심 $s$가 있는 센서로 평면을 스캔할 때의 측정 결과를 $r’(\theta)$라 합시다. 노란색 삼각형과 초록색으로 표시한 각도 $\phi$를 보면 다음이 성립합니다.

\[\begin{align*} \sin{\phi} &= \frac{s}{r(\theta - \phi)} \\ &= \frac{s}{d} \cos{(\theta - \phi)} \\ &= \frac{s}{d} (\cos{\theta} \cos{\phi} + \sin{\theta} \sin{\phi}) \end{align*}\] \[1 = \frac{s}{d} \left( \frac{\cos{\theta}}{\tan{\phi}} + \sin{\theta} \right)\] \[\therefore \tan{\phi} = \frac{s \cos{\theta}}{d - s \sin {\theta}}\]

따라서 $r’(\theta)$는 다음과 같이 유도됩니다.

\[\tan{\phi} = \frac{s}{r'(\theta)} = \frac{s \cos{\theta}}{d - s \sin{\theta}}\] \[\therefore r'(\theta) = \frac{d - s \sin{\theta}}{\cos{\theta}}\]

Eccentric_model_plot Eccentric model plot

위 그래프에서 빨간색 그래프는 원래 평면, 초록색 그래프는 편심 모델에 따라 왜곡된 측정 결과를 나타냅니다. 편심 수치를 과장하여 $d=20mm$, $s=5mm$를 대입하였음에도 불구하고 펄스를 닮지 않은걸 확인할 수 있습니다. 따라서 본 가설 역시 기각되었습니다.

3) 센서와 표면 재질에 따른 고유 특성

다른 가설은 없을지 머리를 싸매고 며칠간 고민했지만, 뾰족한 생각이 들지 않았습니다. 물체를 바꾸어가며 측정해보았지만 재질에 따라 정도의 차이만 있을 뿐 규칙적으로 펄스가 발생하였기 때문입니다. 결국 GP2Y0A21YK0F IR 센서의 고유한 특성으로 인정하였습니다. 이하 내용부터는 거리 센서의 오차를 입사각 $\phi_{inc}$에 대한 함수 $e(\phi_{inc})$로 두어 수치적인 모델로 해결하는 과정을 서술하였습니다.


4.1.3 IR 거리 센서의 측정 오차 해결 과정

1) 오차 모델 수립 및 모델 기반 해결

Incidence_angle_derivation Incidence angle in ploar coordinate

물체의 실제 표면을 $r(\theta)$라고 하면, 거리 벡터( $\vec{r} = \vec{OP}$ )와 물체의 표면이 이루는 각도 $\frac{\pi}{2} - \phi_{inc}$는 다음과 같이 구할 수 있습니다.

\[\tan{ \left( \frac {\pi}{2} - {\phi}_{inc} \right) } = \frac{r(\theta)}{\frac{\partial r}{\partial \theta} (\theta)}\] \[\phi_{inc} = \frac{\pi}{2} - \rm{atan2} \left( r(\theta), \frac{\partial r}{\partial \theta}(\theta) \right)\]

따라서 오차 모델을 다음과 같이 수립할 수 있습니다.

\[\begin{align*} r'(\theta) &= r(\theta) + e \left( \frac{\pi}{2} - \rm{atan2} \left( r(\theta), \frac{\partial r}{\partial \theta}(\theta) \right) \right) \\ &= r(\theta) + f \left( \theta; r, \frac{\partial r}{\partial \theta} \right) \end{align*}\]

이는 주어진 $f$와 측정한 $r’(\theta)$를 바탕으로 $r(\theta)$를 찾는 전형적인 inverse problem입니다. 그러나 오차 함수가 비선형 함수이고, 함수 $f$가 $r, \frac{\partial r}{\partial \theta}$에 동시에 의존하기 때문에 이를 풀기는 쉽지 않습니다. 저 역시 이를 해결할 수치적인 방법을 고안하지 못하였기 때문에, 아쉽게도 모델 기반 해결책은 기각하였습니다.

2) 모델 프리 해결

결국 외부 소프트웨어의 도움을 빌렸습니다. “Meshlab”이라는 오픈 소스 3D mesh 툴을 이용하여 pointcloud를 mesh로 변환한 다음, 적용할 수 있는 surface smoothing 알고리즘을 비교하였습니다. 그 결과, Laplace smoothing 알고리즘이 가장 적절하게 펄스 노이즈를 제거하는걸 확인했습니다. Meshlab 프로그램과 Laplace smoothing에 대한 설명은 다음을 참고하시면 좋을 것 같습니다.

참고 자료


4.1.4 SRAM 메모리 부족 문제 및 해결

아두이노 UNO를 사용하기로 결심한 후 메모리 용량이 부족하지 않을까 고민했는데… 걱정이 현실이 되었습니다. 개발 후반후에 접어들면서 아두이노 IDE 상에서 메모리 부족 경고가 뜨더니 결국 컴파일해도 작동을 하지 않더라고요. 아두이노 UNO의 SRAM 용량은 2048Bytes밖에 되지 않기 때문에 항상 주의를 기울여야 합니다. 저는 다음과 같은 해결책을 적용하여 이러한 문제를 해결할 수 있었습니다.

  • Hyperparameter의 경우 PROGMEM 명령어를 사용하여 flash 메모리에 데이터를 저장합니다. 대표적으로 속도와 정밀도 세팅값, LCD에 출력할 custom character가 있습니다.
  • 문자열을 함수의 인수로 대입할 시 F() macro를 사용하여 flash 메모리에 저장합니다.
  • 변수의 범위를 고려하여 알맞은 변수 type를 선택합니다. 예를 들어 속도나 해상도 세팅은 3~4가지가 있으므로 byte 변수로 저장하고, flag 변수는 0, 1만 가지므로 bool, z slices 값은 기하학적으로 1000 이하이기 때문에 uint16_t로 저장합니다.
  • 아두이노 IDE에서 지역 변수가 사용하는 메모리는 추적하지 못하기 때문에 메모리 사용량을 가늠하기 쉽도록 지역 변수를 최소화하였습니다. 또한 일부 변수는 재사용하였습니다.

참고 자료


4.2 class 설명

각 class별 역할과 기능을 설명합니다.

4.2.1 hyperparameter.h

NEMA17의 한 회전 당 스텝 수 200, A4988에 설정해둔 분주 수 16, IR 거리 센서에서 plate 중심까지 거리 158mm 등 3D scanner 기본 세팅 값이 저장되어있습니다. PROGMEM을 이용하여 flash 메모리에 저장하는 테크닉이 여기에 많이 적용되었습니다.

4.2.2 lcd.h & lcd.cpp

아두이노 LCD I2C 모듈을 제어하는 라이브러리 LiquidCrystal_I2C를 상속하여 작성하였습니다. 특정 줄의 문장을 스크롤하는 custom_scroll()와 막대 그래프를 출력하는 bar_graph(), 아두이노 flash 메모리에 저장된 특수 문자를 불러오는 createChar_P() 함수가 구현되어 있습니다.

4.2.3 A4988_stepper.h & A4988_stepper.cpp

A4988 모터 드라이버로 스텝 모터를 제어하는 클래스입니다. A4988은 pulse의 duration을 통해 모터의 속도를 조절하는데, 이러한 duration을 설정하는 set_period() 함수가 구현되어 있습니다. 또한 모터의 방향을 조절하는 set_dir(), 입력한 스텝만큼 회전하는 rotate_step() 등의 함수가 구현되어 있습니다.

4.2.4 button.h & button.cpp

Rotary encoder의 버튼과 limit switch를 제어하는 클래스입니다. 버튼이 눌렸는지 확인하는 is_pushed() 함수와 버튼이 한 번 클릭되었는지 더블 클릭되었는지 판단하는 is_singled_or_double_ clicked() 함수가 구현되어 있습니다.

4.2.5 distance_sensor.h & distance_sensor.cpp

Sharp 사의 GP2Y0A21YK0F IR 거리 센서로 측정한 거리를 mm 단위로 반환하는 get_distance() 함수가 구현되어 있습니다. 거리 측정 시 정밀도를 향상하기 위해 5회 측정한 평균값을 반환합니다.


4.3 3D scanning 흐름도

3D_scanning_flow_chart 3D scanning flow chart

각 단계는 rotary encoder 버튼을 1번 클릭 시 다음으로 넘어갈 수 있고, 더블 클릭 시 이전 화면으로 되돌아갑니다. LCD 화면을 통해 세팅 화면을 출력합니다.

4.3.1 시작 화면

SD 카드를 장착했는지 확인합니다. 확인은 5초마다 수행됩니다. SD 카드가 장착되어 있는 채로 버튼을 클릭하면 다음 화면으로 이동합니다.

4.3.2 모터 속도 세팅

Rotating plate의 회전 속도를 결정합니다. 회전 속도가 빨라질 시 전체 scanning 시간은 감소하지만, IR 거리 센서로 물체의 표면을 스캔할 때 잔상 효과가 발생하고 물체의 진동이 커져 정확도가 감소합니다. Rotary encoder를 시계, 반시계 방향으로 회전하여 SLOW, MEDIUM, FAST, VERY FAST 4 단계로 조절할 수 있습니다.

4.3.3 θ-해상도 세팅

한 회전에 측정하는 점의 수를 결정합니다. LOW, MEDIUM, HIGH 3단계로 조절할 수 있으며, LOW의 경우 100pts/rev, MEDIUM은 200pts/rev, HIGH는 400pts/rev의 해상도로 스캔합니다.

4.3.4 z-해상도 세팅

물체를 스캔하는 z 방향 간격을 결정합니다. LOW, MEDIUM, HIGH 3단계로 조절할 수 있으며, LOW의 경우 3mm 간격, MEDIUM은 2mm 간격, HIGH는 1mm 간격으로 스캔합니다.

4.3.5 원점 세팅

거리 센서 carriage를 limit switch 위치까지 내렸다가 사용자가 z=0으로 둘 높이를 설정하는 단계입니다. Rotary encoder를 시계, 반시계 방향으로 돌려 z=0 위치를 조절할 수 있습니다.

4.3.6 물체 확인

물체를 plate 위에 올렸는지 확인하는 단계입니다.

4.3.7 Pre-scanning

사용자가 설정한 z=0 지점부터 물체의 최고점까지 높이를 측정하여 z-slices 개수를 계산합니다. 거리 센서 carriage를 z=0부터 일정 거리만큼 올리면서 측정값과 미리 정해둔 최대 거리값을 비교합니다. 최대 거리값보다 작게 측정될 경우 물체가 존재한다는 의미이므로 다시 carriage를 1 slice 상승시킵니다. 최대 거리값보다 크게 측정될 경우 이 각도에는 물체가 없지만 다른 각도에는 물체가 존재할 수 있으므로 plate를 1바퀴 회전하면서 계속 비교를 수행합니다. 모든 각도에서 물체가 존재하지 않다고 판단될 경우 pre-scanning을 종료하고 z-slices 개수를 LCD에 출력합니다. 이는 scanning 단계에서 진행도를 계산하는데 사용됩니다.

4.3.8 Scanning

스캔을 시작하기 전에 SD 카드가 슬롯에 있는지 다시 한번 확인하고, SD 카드가 존재하지 않으면 시작 화면으로 돌아갑니다. SD 카드가 정상적으로 존재할 경우 SD 카드의 “(root 폴더에 있는 파일 개수)+1.txt”의 이름으로 파일을 만들고 스캔한 점의 좌표를 기록합니다. 스캔 과정에서, 스캔을 완료한 z-slices 개수와 전체 z-slices 개수로부터 진행도를 계산하여 LCD에 막대 그래프로 출력합니다. 스캔이 완료되면 파일 쓰기를 종료하고 시작 화면으로 돌아갑니다.

4.3.9 데이터 후처리

Python open3d 라이브러리를 사용하여 pointcloud 데이터를 시각화할 수 있습니다. Meshlab 프로그램을 사용하여 pointcloud를 mesh로 변환할 수 있습니다.

This post is licensed under CC BY 4.0 by the author.