2021년 4월 14일 수요일

Torch Points3d 사용 및 개발 방법

이 글은 Torch Points3d 사용 및 개발 방법을 간단히 다룬다. 

텐서플로우 3D가 릴리즈되었지만, 아직 당장 사용하기에는 불완전하다. 이런 이유로 토치에서 제공하는 포인트 클라우드 세그먼테이션, 객체 분류 및 인식 패키지를 사용해 보기로 한다.


파이썬 패키지 설치

PIP 설치는 매우 간단하다. Ubuntu, NVIDIA driver, CUDA, Tensorflow 등 개발환경이 설치되어 있다는 가정하에 다음과 같이 터미널에서 명령 입력한다.

pip install torch
pip install torch-points3d

소스 및 예제 설치
소스 및 예제 설치는 다음 링크 참고해 진행한다.
튜토리얼
튜토리얼 실행은 다음 링크를 참고해 진행한다.
부록: 도커 기반 OpenPCDet 사용

sudo docker run -it -v /path/to/data/on/host:/usr/src/OpenPCDet/data/kitti --rm --gpus all opheliayuan/pcdet:3.0 nvidia-smi


레퍼런스

이 글은 아래 링크를 참고한다.

이 패키지는 다음 기술이 포함되어 있다.
  • PointNet from Charles R. Qi et al.: PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation (CVPR 2017)
  • PointNet++ from Charles from Charles R. Qi et al.: PointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space
  • RSConv from Yongcheng Liu et al.: Relation-Shape Convolutional Neural Network for Point Cloud Analysis (CVPR 2019)
  • RandLA-Net from Qingyong Hu et al.: RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds
  • PointCNN from Yangyan Li et al.: PointCNN: Convolution On X-Transformed Points (NIPS 2018)
  • KPConv from Hugues Thomas et al.: KPConv: Flexible and Deformable Convolution for Point Clouds (ICCV 2019)
  • MinkowskiEngine from Christopher Choy et al.: 4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural Networks (CVPR19)
  • VoteNet from Charles R. Qi et al.: Deep Hough Voting for 3D Object Detection in Point Clouds (ICCV 19)
  • FCGF from Christopher Choy et al.: Fully Convolutional Geometric Features (ICCV'19)
  • PointGroup from Li Jiang et al.: PointGroup: Dual-Set Point Grouping for 3D Instance Segmentation
  • PPNet (PosPool) from Ze Liu et al.: A Closer Look at Local Aggregation Operators in Point Cloud Analysis (ECCV 2020)
  • TorchSparse from Haotian Tang et al: Searching Efficient 3D Architectures with Sparse Point-Voxel Convolution
  • PVCNN model for semantic segmentation from Zhijian Liu et al:Point-Voxel CNN for Efficient 3D Deep Learning
  • MS-SVConv from Sofiane Horache et al: 3D Point Cloud Registration with Multi-Scale Architecture and Self-supervised Fine-tuning

2021년 4월 13일 화요일

딥러닝 기반 객체 인식 기술 우분투 20.04기반 YOLO v5 설치 및 실행하기

이 글은 우분투 20.04기반 YOLO v5 설치 및 실행하는 방법을 간단히 기술한다. YOLO v5는 기존 YOLO v3보다 객체 인식 속도 및 품질이 크게 개선되었다. 

YOLO v5 성능

개발환경 설치

다음 순서로 개발 환경을 설치한다.

이제, 텐서플로우나 케라스 예제 코드가 쥬피터 노트북에서 제대로 실행되면 환경 설정 성공한 것이다.
import tensorflow as tf
print(tf.reduce_sum(tf.random.normal([1000, 1000])))

import tensorflow as tf
print("# GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU')))


YOLO v5 설치
버전5는 원 개발자가 아닌 PyTorch로 YOLO개발했던 개발자가 맡아 진행한 것이다. 그러므로, PyTorch를 사용한다. 이에 대한 상세한 스토리는 여기를 참고한다. 다음을 참고해 욜로 버전5를 설치한다.
이제 YOLO 설치 폴더에서 다음 명령을 입력한다.

다음과 같이 객체 인식 처리가 되면 성공한 것이다.


주요 코드 분석
주요 코드를 분석해 본다. 동작은 다음과 같다. 
import torch.distributed as dist #파이토치 임포트
import torch.nn as nn
...
from models.yolo import Model  # 욜로 모델 임포트

def train(hyp, opt, device, tb_writer=None):
    # 설정
    if rank in [-1, 0]:
        opt.hyp = hyp  # 하이퍼 파라메터 추가
        run_id = torch.load(weights).get('wandb_id') if weights.endswith('.pt') and os.path.isfile(weights) else None
        if wandb_logger.wandb:
            weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp  # WandbLogger might update weights, epochs if resuming

    nc = 1 if opt.single_cls else int(data_dict['nc'])  # 클래스 수
    names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names']  # class names

    # 모델 정의
    pretrained = weights.endswith('.pt')
    if pretrained:
        with torch_distributed_zero_first(rank):
            attempt_download(weights)  # download if not found locally
        ckpt = torch.load(weights, map_location=device)  # 체크 모델 로드
        model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create
        model.load_state_dict(state_dict, strict=False)  # 모델 로딩
    else:
        model = Model(opt.cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create
    train_path = data_dict['train']
    test_path = data_dict['val']

    # 최적화
    nbs = 64  # nominal batch size
    accumulate = max(round(nbs / total_batch_size), 1)  # accumulate loss before optimizing
    hyp['weight_decay'] *= total_batch_size * accumulate / nbs  # scale weight_decay

    pg0, pg1, pg2 = [], [], []  # optimizer parameter groups

    if opt.adam:  # 최적화 함수
        optimizer = optim.Adam(pg0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999))  # adjust beta1 to momentum
    else:
        optimizer = optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)

    if opt.linear_lr:
        lf = lambda x: (1 - x / (epochs - 1)) * (1.0 - hyp['lrf']) + hyp['lrf']  # linear
    else:
        lf = one_cycle(1, hyp['lrf'], epochs)  # cosine 1->hyp['lrf']
    scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
    # plot_lr_scheduler(optimizer, scheduler, epochs)

    # EMA
    ema = ModelEMA(model) if rank in [-1, 0] else None

    start_epoch, best_fitness = 0, 0.0
    if pretrained:
        # Epochs
        start_epoch = ckpt['epoch'] + 1
        if opt.resume:
            assert start_epoch > 0, '%s training to %g epochs is finished, nothing to resume.' % (weights, epochs)

        del ckpt, state_dict

    # Image sizes
    imgsz, imgsz_test = [check_img_size(x, gs) for x in opt.img_size]  # verify imgsz are gs-multiples

    # 모델 파라메터
    hyp['box'] *= 3. / nl  # scale to layers
    hyp['cls'] *= nc / 80. * 3. / nl  # scale to classes and layers
    hyp['obj'] *= (imgsz / 640) ** 2 * 3. / nl  # scale to image size and layers
    hyp['label_smoothing'] = opt.label_smoothing
    model.nc = nc  # attach number of classes to model
    model.hyp = hyp  # attach hyperparameters to model
    model.gr = 1.0  # iou loss ratio (obj_loss = 1.0 or iou)
    model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc  # attach class weights
    model.names = names

    for epoch in range(start_epoch, epochs):  # epoch 
        model.train()  # 모델 훈련

        for i, (imgs, targets, paths, _) in pbar:  # 배치 -------------------------------------------------------------
            ni = i + nb * epoch  # number integrated batches (since train start)
            imgs = imgs.to(device, non_blocking=True).float() / 255.0  # uint8 to float32, 0-255 to 0.0-1.0


detect.py 주요 코드는 다음과 같다. 
def detect(save_img=False):
    source, weights, view_img, save_txt, imgsz = opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size
    save_img = not opt.nosave and not source.endswith('.txt')  # save inference images
    webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith(
        ('rtsp://', 'rtmp://', 'http://', 'https://'))

    # 초기화
    device = select_device(opt.device)
    half = device.type != 'cpu'  # half precision only supported on CUDA

    # 모델 로드
    model = attempt_load(weights, map_location=device)  # load FP32 model
    stride = int(model.stride.max())  # model stride
    imgsz = check_img_size(imgsz, s=stride)  # check img_size

    # 클래스 이름 및 색상 
    names = model.module.names if hasattr(model, 'module') else model.names
    colors = [[random.randint(0, 255) for _ in range(3)] for _ in names]

    # 예측 실행
    if device.type != 'cpu':
        model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())))  # run once
    t0 = time.time()
    for path, img, im0s, vid_cap in dataset:
        img = torch.from_numpy(img).to(device)
        img = img.half() if half else img.float()  # uint8 to fp16/32
        img /= 255.0  # 0 - 255 to 0.0 - 1.0
        if img.ndimension() == 3:
            img = img.unsqueeze(0)

        # Inference
        t1 = time_synchronized()
        pred = model(img, augment=opt.augment)[0]

        # Apply Classifier
        if classify:
            pred = apply_classifier(pred, modelc, img, im0s)  # 객체 인식 예측

        # 객체 탐지 프로세스
        for i, det in enumerate(pred):  # detections per image
            p = Path(p)  # to Path
            save_path = str(save_dir / p.name)  # img.jpg
            gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
            if len(det):
                # Rescale boxes from img_size to im0 size
                det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()

            # Stream results
            if view_img:
                cv2.imshow(str(p), im0)
                cv2.waitKey(1)  # 1 millisecond

if __name__ == '__main__':
    parser = argparse.ArgumentParser()  # 객체 탐지 예측 파라메터 정의
    parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
    parser.add_argument('--source', type=str, default='data/images', help='source')  # file/folder, 0 for webcam
    parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--view-img', action='store_true', help='display results')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--update', action='store_true', help='update all models')
    parser.add_argument('--project', default='runs/detect', help='save results to project/name')
    parser.add_argument('--name', default='exp', help='save results to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    opt = parser.parse_args()
    print(opt)
    check_requirements(exclude=('pycocotools', 'thop'))

    with torch.no_grad():
        if opt.update:  # update all models (to fix SourceChangeWarning)
            for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']:  # 가중치에 따른 예측
                detect()
                strip_optimizer(opt.weights)
        else:
            detect()


간단한 AWS EC2 클라우드 서비스 사용, 터미널 접속 및 웹서버 개발

이 글은 간단한 AWS EC2 클라우드 서비스 사용 방법을 정리한것이다. 이 글은 아마존 AWS EC2 클라우드 서비스 사용, 터미널 접속 및 웹서버 개발을 보여준다.

AWS 서비스 신청 및 인스턴스 생성

AWS에서 EC2 서비스 신청한 후, 서비스에서 적당한 인스턴스를 선택하여 생성한다. 

이런 모든 작업은 다음 AWS 데쉬보드의 각 메뉴에서 제공해준다. 


데쉬보드 메뉴 리스트(왼쪽)에서 인스턴스 메뉴 선택하고, 아래 화면에서 인스턴스 시작 버튼을 선택한다.

다음과 같이 이미 저장된 인스턴스 이미지들이 리스트된다. 이 글에서는 우분투 18.04 이미지를 인스턴스로 생성한다. 생성 시 순서는 화면을 읽어보며 따라가면 된다. 인스턴스 생성시 보안키를 생성할 수 있다. 보안키 파일 pem파일을 생성하고 다운로드받아 보관한다.

인스턴스 실행하면, 자동으로 해당 이미지가 아마존 서버에 로딩되고, 운영체제가 실행된다. 네트워크는 자동으로 아마존에서 제공되는 공개IP와 연결된다. 

터미널 접속 및 패키지 설치

실행 후, 인스턴스에 관련 패키지를 설치하기 위하여, 명령창을 관리자 모드로 실행한다. 명령창에서 ssh 터미널을 다음과 같이 실행한다. 

ssh -i "???.pem" ubuntu@ec2-##-##-##-###.ap-northeast-2.compute.amazonaws.com

???부분은 인스턴스 시작 시 생성되는 보안키 파일이다. ###은 해당 인스턴스의 공개 DNS주소이다. 이 주소는 다음과 같이 인스턴스 데쉬보드의 인스턴스에 연결 메뉴에서 얻을 수 있다. pem은 인스턴스 생성 시 다운로드받은 보안키 파일명 경로이다. pem 설정 뒤에 있는 부분은 인스턴스 접속 DNS 공개 주소이다.  

실행하면 다음과 같이 ssh 터미널에 접속된다. 혹시, "Permissions for 'private-key' are too open" 에러 발생 시 pem파일 속성에서 불필요한 파생 계정을 삭제하고 실행한다(상세 내용 참고).


이제 원격으로 접속된 아마존 서버에 기본적인 node.js 등 설치하여 사용한다(설치 참고).
sudo apt-get install npm

간단한 웹서버 코딩 및 실행
다음과 같이 express를 설치한다. 
npm install express

다음과 같이 js 파일을 생성한다. 
nano server.js

다음과 같이 코딩한다.
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

3000번 포트에서 접속 가능하도록, AWS 인스턴스 데쉬보드 보안 탭에서 보안그룹 링크를 클릭하여, 방화벽의 인바우드 규칙을 다음과 같이 설정한다(참고. 아마존 CORS 설정).

이제 노드 서버를 실행해보고, 외부 네트워크에서 접속되는 지 확인한다(참고. 터미널 종료 후 서비스 계속 실행 방법).
screen
node server.js &

다음과 같이 각자 공개 DNS 주소에 접속되면 성공한 것이다.

마무리
AWS 서비스는 서버, 운영체제, 네트워크 설치 노가다를 크게 줄여준다. 이런 것들은 해본 사람도 설치에만 몇일 이상이 걸린다. AWS는 가입 후 AWS에서 제공하는 콘솔 데쉬보드에서 몇번의 클릭으로 웹서버, 데이터베이스서버, 딥러닝 패키지, IoT 패키지 등 이미지를 서버에 업로드해 실행시킨다. AWS의 가장 큰 단점은 비싼 비용과, AWS에 너무 특화된 서비스이다. 특히, 데이터 저장소, 네트워크 밴드폭 사용 비용 등이 비싼데, 아무 생각없이 신청했다가는 요금 포탄 맞을 수 있으니 주의가 필요하다.


2021년 4월 10일 토요일

가장 쉬운 Ubuntu 18.04 NVIDIA driver, CUDA and Tensorflow installation

이 글은 Ubuntu 18.04 NVIDIA driver, CUDA and Tensorflow 설치 방법에 대한 것을 간략히 정리한 것이다. 그동안 18.04 버전 설치 테크트리가 약간 변경되어 다시 재정리한다. 다만, 딥러닝 등 최신 라이브러리 사용 목적이라면 우분투 20.04를 권장한다.
  • Ubuntu 18.04 설치 이미지 부팅 가능 USB 만들기
  • Install Ubuntu 18.04. 만약, 화면이 안보이거나 멈추면 GRUB에서 NOMODESET 추가 후 설치 진행
  • Booting Ubuntu
  • Ubuntu에서 Terminal을 열어 아래 명령어 입력 실행함 
    • ubuntu-drivers devices
    • sudo ubuntu-drivers autoinstall
    • reboot
    • nvidia-smi
  • Install CUDA 10.2 and 11.2 or 11.0. 주의: 이 단계에서 GPU 드라이버 재 설치하면 안됨.
  • Install cuDNN SDK 8.0.4
  • Install Tensorflow
  • Install Keras
  • Install Jupyter Notebook
  • reboot
이제, 다음과 같이 딥러닝 예제들이 정상 실행되면 성공한 것이다.
결과

참고로, Ubuntu 18.04 현재 버전은 ROS 설치를 완벽히 지원하지 않는다. 디펜던시 에러가 발생하므로 주의한다.

2021년 3월 30일 화요일

Tensorflow 3D 설치 및 포인트 클라우드 객체 인식

 이 글은 얼마전 발표된 Tensorflow 3D 설치 및 포인트 클라우드 객체 인식 방법을 간략히 다룬다. 텐서플로우 3D버전에 대한 소개 내용은 아래 링크를 참고한다.

요구사항
다음 개발 환경이 설치되어 있어야 한다.
  • 우분투 20.04
  • NVIDIA 드라이버
  • CUDA 10.1, 11.2

tf3d 설치
우분투 터미널에서 다음 명령을 입력해 TF3D를 설치한다.

sudo apt update
sudo apt install subversion git virtualenv

git clone https://github.com/google-research/google-research.git --depth=1
cd google-research
virtualenv -p python3 tf3d_env
source ./tf3d_env/bin/activate

pip install -r tf3d/requirements.txt

sudo apt update
sudo apt install protobuf-compiler python3-dev

cd tf3d/utils/protos
protoc *.proto --python_out=.

pip install tf3d

python "import tf3d.ops.tensorflow_sparse_conv_ops as sparse_conv_ops"

3D scan dataset 다운로드 (Waymo)
다음 개발 환경을 설치한다.
터미널에서 다음 명령을 입력한다.
curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-334.0.0-linux-x86_64.tar.gz
unzip google-cloud-sdk-334.0.0-linux-x86_64.tar.gz
./google-cloud-sdk/install.sh
./google-cloud-sdk/bin/gcloud init

다음과 같이 설치된 구글 SDK 경로를 추가한다.
add ./google-cloud-sdk/bin/ path to ~/.bashrc file

스캔 데이터셋 다운로드를 위해, https://waymo.com/open/ 에 로그인한다. 그리고, 다음 명령을 입력한다.
gsutil cp gs://waymo_open_dataset_tf_example_tf3d/original_tfrecords/train-00000-of-01212.tfrecords .
gsutil ls gs://waymo_open_dataset_tf_example_tf3d/original_tfrecords

sudo mkdir -p /tmp/dataset/waymo/
sudo mv train-00000-of-01212.tfrecords /tmp/dataset/waymo

이제 다음 명령으로 쉘 안에 있는 데이터셋 경로를 /tmp/dataset/waymo 로 수정한다.
vim tf3d/semantic_segmentation/scripts/waymo/run_train_locally.sh

다음과 같이 쉘 파일을 실행한다.
bash tf3d/semantic_segmentation/scripts/waymo/run_train_locally.sh

마무리
사용해 본 결과, 구글의 3차원 모델 딥러닝 투자 결정은 바람직해 보니다. 다만, 아직은 구글에서 제공하는 코드가 불완전하다. 개선이 필요해 보인다.

레퍼런스