2019년 9월 15일 일요일

대용량 포인트 클라우드 오픈소스 렌더링 서버 Potree 설치, 사용 및 분석기

이 글은 대용량 포인트 클라우드 렌더링 서버 Potree 설치, 사용 및 분석기이다.

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에 대한 좀 더 상세한 내용은 다음 링크나 github 를 참고하길 바란다.

사용방법
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

다음 링크를 방문한다.
https://github.com/potree

potree와 PotreeConverter가 있다. potree를 방문한다. 그리고, 다음 순서로 nodejs 및 module을 설치한 후, 소스코드 다운로드 받고, 빌드한다. 참고로 gulp는 빌드 자동화 도구이다. rollup은 module bundler 도구로 자바스크립트 모듈의 재활용성과 유지보수성을 높이기 위해 조각난 로직간 의존성 등을 관리한다.

mkdir potree
cd potree
npm install
npm install -g gulp
npm install -g rollup

git clone https://github.com/potree/potree
mkdir ./build/potree
gulp watch

이렇게 하면 Potree 렌더링 서버가 실행된다. 다음 링크를 클릭하면 예제들을 확인할 수 있다.
http://localhost:1234/examples/
http://localhost:1234/examples/cesium_retz.html

다음은 Potree 실행 화면이다.

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

cd ~/dev/workspaces/PotreeConverter
git clone https://github.com/potree/PotreeConverter.git master
cd master
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DLASZIP_INCLUDE_DIRS=~/dev/workspaces/lastools/master/LASzip/dll -DLASZIP_LIBRARY=~/dev/workspaces/lastools/master/LASzip/build/src/liblaszip.so ..
make

이제 다음과 같이 Potree 변환기를 사용할 수 있다. 입력파일포맷은 LAS, LAZ, PTX, PLY을 지원한다. 
# convert data.las and generate web page.
./PotreeConverter ./data.las -o ./potree_converted -p pageName --output-attributes RGB INTENSITY CLASSIFICATION

# generate compressed LAZ files instead of the default BIN format.
./PotreeConverter ./data.las -o ./potree_converted --output-format LAZ

# convert all files inside the data directory
./PotreeConverter ./data -o ./potree_converted


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) rosrun pcl_ros bag_to_pcd [input bag] /laser_cloud_surround pcd

  2) open pcd file in PDAL,  CloudComapre
회사 본관 1, 2동 스캔 데이터(전체 52,230,141 포인트. 1.65GB)

  3) select point cloud nodes. edit > merge in CloudComapre
  4) save las file in PDAL,  CloudComapre
LAS 변환

다음은 이렇게 변환된 LAS이다.
CloudCompare

이 LAS파일을 PotreeConverter 로 변환하다. 변환 후 폴더에는 cloud.js와 data폴더가 생성되어 있을 것이다.
./PotreeConverter ./data.las -o ./potree_converted -p <pageName> --output-attributes RGB INTENSITY CLASSIFICATION

데이터 변환 과정(변환 결과 데이터는 대략 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 되는 메커니즘을 제공
  • Loader: 점군 파일 형식에 맞는 로더를 구현해 노음
viewer template 소스코드 구조는 다음과 같다. 
<!DOCTYPE html>
<html lang="en">
<head>
   <!-- meta tag 와 css style link 선언-->
</head>

<body>
   <!-- 패키지 라이브러리 선언 -->
   <div class="potree_container" style="position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; ">
     <div id="potree_render_area">
         <div id="cesiumContainer" style="position: absolute; width: 100%; height: 100%; background-color:green"></div>
      </div>
      <div id="potree_sidebar_container"> </div>
   </div>

이렇게 DOM 구조를 정의해 놓은 후 <script> 를 코딩한다. 
 <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 등 목적에 맞게 개발할 수 있다.

레퍼런스
  1. 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.


로보틱스 라이브러리 소개

이 글은 ROS이외의 로보틱스 관련 라이브러리를 정리한다.

Industrial Robots Mimic

HAL | ROBOT PROGRAMMING & CONTROL
Move IT

RCML programming toolkit

Mobile robotics C++ libraries

2019년 9월 12일 목요일

딥러닝 기반 3차원 포인트 클라우드 학습 및 객체 인식 방법

이 글은 대표적인 딥러닝 기반 3차원 포인트 클라우드 학습 및 객체 인식 모델 사용방법 대한 내용을 간단히 정리한다.

JSIS3D
JSIS3D는 3차원 포인트 클라우드 데이터에서 시멘틱 기반 객체 인식을 위한 딥러닝 학습 및 예측 모델이다.

JSIS3D 모델

다음은 이 모델의 github 링크이다.

사용 순서는 다음과 같다.

1. 파이썬 및 파이토치 설치
Python 3.5 이상. Pytorch 0.4 이상

CUDA를 사용할 경우, CUDA도 설치해야 한다. 버전체크는 여기를 참고한다.

2. 소스코드 & data set 다운로드
git clone https://github.com/pqhieu/jsis3d

download S3DIS dataset https://drive.google.com/open?id=1s1cFfb8cInM-SNHQoTGxN9BIyNpNQK6x
copy S3DIS dataset file to data/s3dis/h5 folder

3. 소스 빌드
cd external/densecrf
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=Release ..
make
cd ../../.. # You should be at the root folder here
make

4. 딥러닝 학습
python train.py --config configs/s3dis.json --logdir logs/s3dis

그럼, 훈련할 점군을 다운로드한 후 학습을 시작한다. 만약 CUDA 메모리 에러가 발생할 경우, config/s3dis.json 파일에 설정된 batch_size 수를 16 > 8 > 4 > 2 > 1 씩 줄여보길 바란다.

단, pytorch "ValueError: Expected more than 1 value per channel" 에러가 발생하면, batch_size를 좀 더 높여야 한다(넉넉한 GPU 메모리가 장착된 컴퓨터 권장함).

학습데이터는 이미 라벨링된 3차원 점군이 저장된 S3DIS 데이터셋이다. 참고로, S3DIS는 건물 6개 영역을 RGBD로 스캔하여 1,413 스캔 위치에서 25,434 RGBD이미지와 695,878,620 포인트 클라우드를 제공하고 있다. 스캔 데이터는 ceiling, floor, wall, beam, column, door, window, table, chair, sofa, bookcase, board 등으로 구분해 라벨링되었다.

5. 딥러닝 예측
python pred.py --logdir logs/s3dis --mvcrf

평가를 위해서는 다음 명령을 입력한다.
python eval.py --logdir logs/s3dis

Point cloud semantic segmentation using 3D CNN
이 모델은 CNN(convolutional neural network)를 이용한 시멘틱 기반 세그먼테이션 딥러닝 학습 모델이다. github 링크는 다음과 같다.

레퍼런스

딥러닝 기반 3차원 객체 인식

이 글은 딥러닝 기반 3차원 객체를 인식하는 방법들을 간단히 조사해 정리한다.

PointRCNN
PointRCNN(Shaoshuai Shi, 2019)

PointSIFT
PointSIFT(Mingyang Jiang, 2018)

Votenet
Votenet(Charles R. Qi, 2019) (Version #2)

Superpoint graph
Superpoint graph(Loic Landrieu, 2018)

Complex-YOLO
Complex-YOLO(Liu, 2018)

Semantic3dnet
Semantic3dnet(hackel, 2017)

LiDAR-Bonnetal
LiDAR-Bonnetal(Andres Milioto, 2019)

JSIS3D
JSIS3D(Quang-Hieu Pham, 2019)

3D point cloud generation
3D point cloud generation(Chen-Hsuan Lin, 2018)

Multiple object tracking lidar
Multiple object tracking lidar(Praveen palanisamy, 2015)

OpenDetection
Open detection(Kripasindhu Sarkar, 2015)

레퍼런스

2019년 9월 11일 수요일

모바일 LiDAR 스캔 준비과정 및 SLAM 테스트 결과

이 글은 모바일(mobile.이동식) LiDAR(Laser Detection And Ranging. 라이다) 스캔(scan)을위한 준비 과정과 SLAM(Simultaneous localization and mapping) 테스트 결과를 나눔한다. 참고로, 테스트에 사용된 SLAM 알고리즘 설명은 다음 링크를 참고한다.
SLAM 개발 및 실행 환경은 NVIDIA TX2, 우분투 18.04, ROS melodic 이다. 다음은 모바일 스캔 및 실시간 SLAM 테스트 결과이다. 건물들을 스캔한 포인트 클라우드(point cloud. 점군)이 실시간으로 정합된 것을 확인할 수 있다.
SLAM 과정 영상

SLAM 결과

모바일 스캔 Backpack 만들기
이동하며 스캔을 하기 위해서는 사람이 장비를 가지고 다녀야 하므로, 장비를 넣을 방법이 필요하다. 그래서, 간단히 백팩(Backpack)을 이용해 보기로 한다. 테스트를 위해서는 사용한 LiDAR, NVIDIA TX2 등 보드에 LIPO 배터리를 연결해야 한다. 연결잭은 기성품이 없으므로 직접 절단해 납땜하여 다음과 같이 만든다. 

각 장치에 배터리를 연결한 모습은 다음과 같다. 참고로 TX2 에 붙은 개발보드는 모든 테스트 완료 후 제거하고 캐리어 보드를 부착할 계획이다.

이동을 위해 다음과 같이 백팩을 준비하고, 장비를 넣는다.

다음은 간단히 만든 전체 라이다 스캔 및 SLAM 장비 모습이다.
완성된 모바일 스캔 백팩

현장 테스트
이제 필드로 나가서 테스트한다. 스캔 지역은 지도에서 다음과 같다.

스캔에 사용할 LiDAR 센서와 SLAM 진행 결과 확인용 모니터를 준비한다.

슬램 데이터 기록을 위해 rosbag 명령을 실행해 토픽을 실시간으로 저장하도록 한다. 나머지 실행에 필요한 패키지들은 그 전에 모두 실행해 놓는다. 

참고로, bag 파일이 아닌 .active 로 저장되어 있으면 ros bag파일을 reindex후 fix하면 된다.
rosbag reindex *.bag.active
rosbag fix *.bag.active repaired.bag

현장에서 다음 그림과 같이 이동하면서 스캔 및 슬램을 진행한다.

다음은 회사 본관 2동을 중심으로 전체를 걸어 이동하며 SLAM한 결과이다.

테스트 결과
저장된 ROS bag 파일을 재생해 보며, SLAM 결과를 분석해 보았다.
  1. 스캔 데이터의 특징점이 될만한 건물 벽, 모서리, 바닥이 있는 넓은 지역에서는 실시간으로 잘 처리된다. 
  2. 파티션 칸막이가 많아 전체 바닥이 잘 안보이는 실내에서는 SLAM이 쉽지 않다. 이는 스캔 데이터에서 안정적인 특징점을 획득을 고려하지 못한 알고리즘의 원인인 듯 하다. 이는 IMU장치를 이용해도 마찬가지이다. 실내에서 길게 이동하며 스캔할 때 오차가 발생하는 경우가 많은 데, 이는 현재 판매되고 있는 상용 SLAM에서 발생하는 문제와 비슷하다. 이 경우에는 알고리즘 개선이 필요하다.
  3. 유리, 반사 재질 대리석, 화이트 보드 등에서는 노이즈가 만들어진다. 노이즈는 SLAM에 안좋은 영향을 준다.
  4. 실외에서는 큰 문제가 없이 진행되나, 점군이 대용량이라 기록파일이 메모리 용량을 넘치는 경우가 발생하였다. 보통 10-20분 이동시 SLAM bag 파일은 거의 1GB를 넘어간다. 이 상황에서 RVIZ의 포인트 클라우드를 관찰하는 것은 쉽지 않다.
  5. 사용한 LiDAR 자체가 무겁다. 가능한 안정적으로 이동해야 했기 때문에 팔을 90도 각도로 하고, 라이다를 든 상태로 10-20분 이동, 이를 6회 시험하였다. 팔은 이때 벨로다인 센서(VLP-16) 830g + 모니터 무게 = 1 kg을 캔틸래버 구조로 지지하고 있으므로, 관절에 무리가 간다. 20분 정도 고정자세로 들고 있으면 팔이 떨어져 나갈 것 같았다.

백팩에 센서를 부착하거나, 카트 같은 이동식 LiDAR 센서 지지대, 로버(rover) 등이 있다면 좋을 듯 하다. 다음 테스트에서는 로버에 부착한 형태로 테스트할 계획이다. 아울러, NVIDIA NANO에서도 잘 수행되는 지 확인해 보려 한다.


참고사항
스캔 점군을 ROS bag에 저장하고, 다시 재생하는 방법은 다음 영상을 참고하기를 바란다.


추신 - 이번 추석은 길다. 연휴에 가족과 좀 더 많은 시간을 보낼 수 있어 좋다. 하지만, 추석이 지나면 어김없이 찾아오는 참여 과제들 연구 증빙, 행정과 여러번에 걸친 연말 평가도 함께 떠오른다. 연초 과제 협약하느라 시간 보내고, 내 과제와 소환된 참여 과제들 회의, 협의하고, 정량지표 논문, 특허, 홍보 건수 작업하다 보면 정말 빨리 해도 실제 연구할 시간은 1년에 몇 개월도 안되는 듯 하다(연구비에 비례하는 평가용 정량지표. 대형 연구단일수록 제사 떡에 관심 많고 실제 기술은 개발하기 더 어려워짐. 누수 많음. 펀드 많다고 좋은게 아니다). 국가 연구 행정 효율화한다고 하나 상황은 더 나빠지는 듯.. 아쉬운 사람만 우물파는 형국. 어쩔 수 없이 틈틈히 시간만들어 해 보는 수 밖에 없다.

갑질없는 공평 사회 시급하다(일하지 않으면 먹지도 말아야)