본 글은 하기 블로그의 리뷰 글임.

https://deepbaksuvision.github.io/Modu_ObjectDetection/

 

모두를 위한 Object Detection(Object Detection for All) · GitBook

No results matching ""

deepbaksuvision.github.io

 

 

Object Detection이란?

이미지로부터 객체를 판단하는 기술. 

 

Computer Vision에는 주요 3가지 Task가 있는데

  • Classification
  • Single Classfication & Localization & Detection
  • Multi Object Detection & Localization & Classification

이 있다고 한다.

 

이는 목적에 따라 다른듯. 주어진 이미지로부터 어떤 category의 이미지인지 반드시 찾아야하는 문제가 있을 것이고, 주어진 이미지에서 배경과 객체의 구분 / 그리고 그 객체가 '어떤' 이미지인지를 판단하는 문제가 있을 것이고 / 만일 객체라 한 개가 아닌 여러 개라면 여러개에 대한 구분과 각각에 대한 Classification이 필요할 것이다.

 

-> Recognition과 Object Detection이 있는데

  • Object가 어떤 것인지 구분
  • Recognition보다 더 작은 범위로써 Object의 존재 유무만 판단

의 차이라고 한다.

 

-> Recognition 전에 Detection이 선행되어야 함.

 

전통적으로 사용했던 Object Detection 알고리즘은 Feature Engineering 기법을 통해 특징을 추출하여 특징들의 분포에서 경계를 결정하여 찾는 방법을 주로 사용.

 

전통적인 Feature Extraction 방법 예시 : Haar-like feature, HOG(Histogram of Oriented Gradient), SIFT(Scale Invariant Feature Transform), LBP(Local Binary Pattern), MCT(Modified Census Transform) 등 )

전통적인 Boundary Decision 알고리즘 예시 : SVM(Support Vector Machine), Adaboost와 같은 검출 알고리즘(Classifier)

 

결론적으로 전처리 -> 특징 추출 -> 분류 의 파이프라인을 따름.

 

Datasets for Object Detection

사용할 데이터 셋들

 

PASCAL VOC Dataset

위 페이지에서 어찌저찌 다운을 받았다.

근데 다양한 연도의 데이터가 있었지만 여러 사이트들을 보니 2007이 국룰(?)인 것 같아 07년도의 데이터로 받았다.

tar 파일로 되어있는 것을 압축을 풀면 다음과 같다.

 

각 폴더별 내용물은 다음과 같다고 한다.

  • Annotations : JPEGImages 폴더 속 원본 이미지와 같은 이름들의 xml파일들이 존재합니다. Object Detection을 위한 정답 데이터이 됩니다.
  • ImageSets : 어떤 이미지 그룹을 test, train, trainval, val로 사용할 것인지, 특정 클래스가 어떤 이미지에 있는지 등에 대한 정보들을 포함하고 있는 폴더입니다.
  • JPEGImages : *.jpg확장자를 가진 이미지 파일들이 모여있는 폴더입니다. Object Detection에서 입력 데이터가 됩니다.
  • SegmentationClass : Semantic segmentation을 학습하기 위한 label 이미지
  • SegmentationObject : Instance segmentation을 학습하기 위한 label 이미지

 

해당 코드로 이미지를 불러오라고 했지만 나는 ipynb를 사용할 거기에 image_path에다가 이미지 경로를 넣어준다.

그리고 아마 데이터가 조금씩 다른 듯 하다. 저기선 68번 이미지가 들어와졌지만 나는 68번은 없고 숫자가 조금 띄엄띄엄 있었다.

image_path = sys.argv[1]

image = Image.open(image_path).convert("RGB")

plt.figure(figsize=(25,20))
plt.imshow(image)
plt.show()
plt.close()

 

XML 파일 구조

<annotation>
	<folder>VOC2007</folder>
	<filename>000005.jpg</filename>
	<source>
		<database>The VOC2007 Database</database>
		<annotation>PASCAL VOC2007</annotation>
		<image>flickr</image>
		<flickrid>325991873</flickrid>
	</source>
	<owner>
		<flickrid>archintent louisville</flickrid>
		<name>?</name>
	</owner>
	<size>
		<width>500</width>
		<height>375</height>
		<depth>3</depth>
	</size>
	<segmented>0</segmented>
	<object>
		<name>chair</name>
		<pose>Rear</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>263</xmin>
			<ymin>211</ymin>
			<xmax>324</xmax>
			<ymax>339</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>165</xmin>
			<ymin>264</ymin>
			<xmax>253</xmax>
			<ymax>372</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>1</truncated>
		<difficult>1</difficult>
		<bndbox>
			<xmin>5</xmin>
			<ymin>244</ymin>
			<xmax>67</xmax>
			<ymax>374</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>0</truncated>
		<difficult>0</difficult>
		<bndbox>
			<xmin>241</xmin>
			<ymin>194</ymin>
			<xmax>295</xmax>
			<ymax>299</ymax>
		</bndbox>
	</object>
	<object>
		<name>chair</name>
		<pose>Unspecified</pose>
		<truncated>1</truncated>
		<difficult>1</difficult>
		<bndbox>
			<xmin>277</xmin>
			<ymin>186</ymin>
			<xmax>312</xmax>
			<ymax>220</ymax>
		</bndbox>
	</object>
</annotation>

Annotation에 있는 파일들은 xml 형식의 파일들인데 이는 JPEG이미지들과 1대1 mapping되는 듯 하다.

여기서 각 이미지들이 어떤 정보를 가지고 있는 지 전달해주는 것 같다.

다음은 xml 파일들이 가지고 있는 정보이다.

 

  • <size> : xml파일과 대응되는 이미지의 width, height, channels 정보에 대한 tag입니다.
    • <width> : xml파일에 대응되는 이미지의 width값
    • <height> : xml파일에 대응되는 이미지의 height값
    • <depth> : xml파일에 대응되는 이미지의 channels값
  • <object> : xml파일과 대응되는 이미지속에 object의 정보에 대한 tag입니다.
    • <name> : 클래스 이름을 의미합니다.
    • <bndbox> : 해당 object의 바운딩상자의 정보에 대한 tag입니다.
      • xmin : object 바운딩상자의 왼쪽상단의 x축 좌표값
      • ymin : object 바운딩상자의 왼쪽상단의 y축 좌표값
      • xmax : object 바운딩상자의 우측하단의 x축 좌표값
      • ymax : object 바운딩상자의 우측하단의 y축 좌표값

바운딩 박스는 다음의 박스를 의미함.

 

xml파일을 load하고 xml package를 이용하여 label을 파싱해보자.

 

xml_path = '~~'
print("XML parsing Start\n")

xml = open(xml_path, "r")
tree = Et.parse(xml)
root = tree.getroot()

size = root.find("size")

width = size.find("width").text
height = size.find("height").text
channels = size.find("depth").text

print("Image properties\nwidth : {}\nheight : {}\nchannels : {}\n".format(width, height, channels))

objects = root.findall("object")
print("Objects Description")
for _object in objects:
    name = _object.find("name").text
    bndbox = _object.find("bndbox")
    xmin = bndbox.find("xmin").text
    ymin = bndbox.find("ymin").text
    xmax = bndbox.find("xmax").text
    ymax = bndbox.find("ymax").text

    print("class : {}\nxmin : {}\nymin : {}\nxmax : {}\nymax : {}\n".format(name, xmin, ymin, xmax, ymax))

print("XML parsing END")

다음과 같이 불러지게 된다.

 

#필요한 거 import
import os
import sys
import matplotlib.pyplot as plt
import xml.etree.ElementTree as Et
from xml.etree.ElementTree import Element, ElementTree

from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw

#Annotations와 JPEGImages의 상위 폴더에 위치를 잡아준다.
dataset_path = '/Users/kbj110/Desktop/workspace/ml_dl/VOCdevkit/VOC2007'

#각각의 폴더 이름을 잡아주
IMAGE_FOLDER = "JPEGImages"
ANNOTATIONS_FOLDER = "Annotations"

#각 폴더에 대한 정보를 받아온다. (하위 파일들 리스트 또한 불러옴)
ann_root, ann_dir, ann_files = next(os.walk(os.path.join(dataset_path, ANNOTATIONS_FOLDER)))
img_root, amg_dir, img_files = next(os.walk(os.path.join(dataset_path, IMAGE_FOLDER)))

#그래서 해당 폴더서 각 파일별로 loop를 돌리며
for xml_file in ann_files:

    # XML파일와 이미지파일은 이름이 같으므로, 확장자만 맞춰서 찾습니다.
    img_name = img_files[img_files.index(".".join([xml_file.split(".")[0], "jpg"]))]
    img_file = os.path.join(img_root, img_name)
    image = Image.open(img_file).convert("RGB")
    draw = ImageDraw.Draw(image)

    #xml을 파싱하고 사이즈를 찾아서 각 width, height, channels 값을 받아온다.
    xml = open(os.path.join(ann_root, xml_file), "r")
    tree = Et.parse(xml)
    root = tree.getroot()

    size = root.find("size")

    width = size.find("width").text
    height = size.find("height").text
    channels = size.find("depth").text

    objects = root.findall("object")

    #그러고 objects들의 정보를 받아 loop를 돌리는데 이는 하나의 객체만을 담고 있지 않을 수 있기 때문인듯
    for _object in objects:
        name = _object.find("name").text
        bndbox = _object.find("bndbox")
        xmin = int(bndbox.find("xmin").text)
        ymin = int(bndbox.find("ymin").text)
        xmax = int(bndbox.find("xmax").text)
        ymax = int(bndbox.find("ymax").text)

        # Box를 그릴 때, 왼쪽 상단 점과, 오른쪽 하단 점의 좌표를 입력으로 주면 됩니다.
        draw.rectangle(((xmin, ymin), (xmax, ymax)), outline="red")
        draw.text((xmin, ymin), name)
    
    #그리고 그린다
    plt.figure(figsize=(25,20))
    plt.imshow(image)
    plt.show()
    plt.close()

주어진 코드를 잘 실행하면 다음과 같은 귀여운 고양이 사진도 찾을 수 있다.

 

convert2Yolo

https://github.com/ssaru/convert2Yolo

 

GitHub - ssaru/convert2Yolo: This project purpose is convert voc annotation xml file to yolo-darknet training file format

This project purpose is convert voc annotation xml file to yolo-darknet training file format - ssaru/convert2Yolo

github.com

 

 

convert2Yolo란? :  각종 datasets들을 YOLO저자가 만든 darknet 프레임워크가 사용하는 label format으로 변경해주는 프로젝트

뒤에 나올 내용에서 모든 Object Detection의 구현체의 Dataloader는 이를 사용하므로 해당 프로젝트가 어떻게 구성되어있고 사용하는지를 숙지해아하 Dataloader 이해 가능.

 

---작성 및 공부중---

이론만 공부하면 지치니 실습 코드 리뷰를 하나 진행하자.

손글씨 Dataset으로 유명한 mnist 데이터 셋이다.

 

1. Module Import

import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms, datasets

 

필요한 모듈들 import한다.

- numpy, plt 는 수치 계산과 시각화를 위하여.

- nn은 신경망 모델을 위해, F는 함수들 따오기 위해 등

 

2. 딥러닝 모델을 설계할 때 활용되는 장비 확인

if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')

print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)

 

- cuda를 지원하는 gpu가 사용가능한지 확인, 만일 가능하면 gpu를, 그렇지 않다면 cpu를 사용하도록 설정.

 

BATCH_SIZE = 32
EPOCHS = 10

- hyperparameter 설정. 이전에 확인했듯 batch size는 2의 거듭승이 좋음.

 

3. MNIST 데이터 다운로드

train_dataset = datasets.MNIST(root = "../data/MNIST",
                               train = True,
                               download = True,
                               transform = transforms.ToTensor())

test_dataset = datasets.MNIST(root = "../data/MNIST",
                              train = False,
                              transform = transforms.ToTensor())

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False)

 

- train에 대한 param 설정을 통해 train set / test set 구분하여 다운

- transforms.ToTensor(): PIL 이미지나 넘파이 배열을 PyTorch의 FloatTensor로 변환하고, 픽셀 값을 [0, 1] 범위로 스케일링합니다. 

 

- 그 후 받아진 dataset을 dataloader로 변환(변환이 맞는 표현인진 모르겠다).

- 이전에 설정한 BATCH_SIZE (하이퍼파라미터)를 받아오며, train data에 대해선 shuffle을 사용하여 bias를 방지.

 

4.  데이터 확인하기 (1)

for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break

결과는 다음과 같다.

 

for-loop를 바로 break 때리는 것을 보아 하나의 배치만 확인하고자 하는 용도로 사용한듯.

-> 데이터 형태와 타입이 제대로 들어오는 지 단순 확인 용도.

 

X train set에 대해서는

 

  • 배치 크기(batch size): 32
  • 채널 수(channels): 1 (MNIST는 흑백 이미지이므로 채널이 1개)
  • 이미지 높이(height): 28 픽셀
  • 이미지 너비(width): 28 픽셀

 

와 같이 이루어지며, float tensor로 지정되게 됨.

반면 y train 데이터는 1차원 텐서로 label 값인 '정수'를 표현하게 됨.

 

5.  데이터 확인하기 (2)

pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(28, 28), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))

- figsize (10,1)로 설정.

- 위에서 for loop을 돌려서 X_train과 y_train에 첫번째 batch에 대한 데이터가 할당됨.

- 그 중 1~10번째 데이터들만 가져오고 y_train[i]를 통해 같이 표기하여 붙여줌.

 

--------------------- 여기까진 기본적인 data load 및 세팅 ---------------------

6. Multi Layer Perceptron (MLP) 모델 설계하기

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(28 * 28, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 10)

    def forward(self, x):
        x = x.view(-1, 28 * 28)
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)
        x = F.sigmoid(x)
        x = self.fc3(x)
        x = F.log_softmax(x, dim = 1)
        return x

- 여기서 다층 퍼셉트론에 대한 모델 정의

- MLP는 입력층, 하나 이상의 은닉층, 출력층으로 구성된 feed-forward 신경망

- MNIST 데이터셋의 이미지(28x28 픽셀)를 입력으로 받아서, 10개의 클래스(숫자 0~9)에 대한 예측을 출력

 

여기서 PyTorch의 장점 -> feed-forward function만 정의를 하여도 backward function에 대해서는 자동으로 정의가 됨.

(물론 function들은 nn.functional에서 제공해주는 함수들을 사용하여하여함.)

 

Net 클래스는 nn.Module을 상속하여 모델 정의. 부모 클래스인 nn.Module의 생성자를 호출하여 초기화.

 

fc1, fc2, fc3은 레이어 정의.

fc1은 28*28 픽셀 데이터를 1차원 벡터로 펼침. (MNIST 이미지 사이즈가 28*28 임)

fc3의 출력 노드수는 당연히 10으로 맞춰준다. (0~9까지의 10가지 숫자)

 

forward 메서드를 정의하며

입력 이미지를 flatten한 후 first fully-connected layer를 통과 시킨 후 sigmoid적용. 이하 반복.

그리고 마지막에 fc3까지 적용된 출력 값에 log softmax 함수를 적용하여 클래스별 로그 확률 계산

 

 

 

7. Optimizer, Objective Function 설정하기

model = Net().to(DEVICE)
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.5)
criterion = nn.CrossEntropyLoss()

print(model)

- 위에서 생성한 Net class의 객체 생성.

- 옵티마이저 설정. SGD 사용하며 learning rate은 0.01.

- momentum은 처음 봤는데 '이전 업데이트의 방향을 일정 부분 유지하여 학습 속도를 향상시키는 역할'이라고 한다.

- loss function (criterion) 설정. 크로스 엔트로피 손실 함수 사용.

 

print한 결과값은 다음과 같다.

 

8. MLP 모델 학습을 진행하며 학습 데이터에 대한 모델 성능을 확인하는 함수 정의

def train(model, train_loader, optimizer, log_interval):
    model.train()
    for batch_idx, (image, label) in enumerate(train_loader):
        image = image.to(DEVICE)
        label = label.to(DEVICE)
        optimizer.zero_grad()
        output = model(image)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()

        if batch_idx % log_interval == 0:
            print("Train Epoch: {} [{}/{} ({:.0f}%)]\tTrain Loss: {:.6f}".format(
                epoch, batch_idx * len(image), 
                len(train_loader.dataset), 100. * batch_idx / len(train_loader), 
                loss.item()))

 

- train이라는 function 정의. 뒤에 10에서 보면 알겠지만 한 epoch에 대해 학습 / 학습에 대한 성능 확인하는 함수.

- 큰 틀에서 함수는 parameter로 모델과 훈련 데이터(엄밀하게 말하면 배치 단위로 제공되는), 옵티마이저, 학습 상태를 출력할 간격 총 4개를 받는다.

 

- model.train() : 모델의 학습 모드를 설정하는 메소드.

- for loop에서 enumerate을 통해 batch index를 출력하며 돌림.

- image와 label로 가져오며 이는 X(우리가 학습시킬 이미지), y(label) 정답을 의미한다.

- pytorch의 옵티마이저는 gradient가 누적되므로 계속 초기화 해준다.

- 모델에 image를 집어넣어 output을 계산해주고 이를 loss function에 넣어 오차를 구한다.

- 역전파가 계산되며 각 파라미터에 gradient가 저장된다.

- 이를 optimizer.step()을 통해 모델의 파라미터들을 업데이트 시킨다.

 

그 아래는 batch를 돌리며 모델이 어느 정도 성능을 내는지, 배치가 얼마나 돌아갔는지 등을 표기한다.

 

9. 학습되는 과정 속에서 검증 데이터에 대한 모델 성능을 확인하는 함수 정의

def evaluate(model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0

    with torch.no_grad():
        for image, label in test_loader:
            image = image.to(DEVICE)
            label = label.to(DEVICE)
            output = model(image)
            test_loss += criterion(output, label).item()
            prediction = output.max(1, keepdim = True)[1]
            correct += prediction.eq(label.view_as(prediction)).sum().item()
    
    test_loss /= (len(test_loader.dataset) / BATCH_SIZE)
    test_accuracy = 100. * correct / len(test_loader.dataset)
    return test_loss, test_accuracy

모델의 성능을 평가할 evaluate이라는 function을 정의하자.

model.eval()을 호출하면 모델이 평가 모드로 전환됨.

- 평가 모드로 전환되면 드롭아웃 비활성화, 배치 정규화의 이동 평균과 이동 분산이 업데이트 되지 않음.

- test_loss와 correct을 0으로 초기화.

- test_loss : 평가할 데이터의 총 loss 수치. correct : 모델이 정확하게 맞춘 숫자.

- with torch.no_grad(): 이 블럭 내에서는 자동 미분 비활성화. 불필요한 계산 안하니 메모리 할당(사용량?) 이점.

- 이전 train function과 대략적 비슷한 흐름. test_loader를 인자로 받아 for문을 돌리며, loss값을 계속 test_loss에 누적시켜 더함.

 

- output의 가장 큰 값을의 Index를 출력하여 prediction과 label의 동일한지 여부 확인. 맞으면 더하고 아님말고.

 

10. MLP 학습 실행하며 Train, Test set의 Loss 및 Test set Accuracy 확인하기

for epoch in range(1, EPOCHS + 1):
    train(model, train_loader, optimizer, log_interval = 200)
    test_loss, test_accuracy = evaluate(model, test_loader)
    print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
        epoch, test_loss, test_accuracy))

맨 마지막 부분이다.

위에서 정의한 함수들을 사용하며 맨 앞에서 정의한 epoch만큼 for문을 돌리며 train, evaluate을 실행한다.

 

Notice

모든 코드는 하기 링크에서 가져왔으며 내 이해를 위한 글임.

https://github.com/Justin-A/DeepLearning101/blob/master/2-1_MNIST_MLP.ipynb

 

DeepLearning101/2-1_MNIST_MLP.ipynb at master · Justin-A/DeepLearning101

Code about DeepLearning101 Book. Contribute to Justin-A/DeepLearning101 development by creating an account on GitHub.

github.com

 

위 코드를 설명한 https://velog.io/@gr8alex/PyTorch%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-MNIST-%EB%B6%84%EB%A5%98-%EB%AA%A8%EB%8D%B8-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0 도 참고함.

 

 

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

이제 모델을 만들고 학습을 했으면 실제 상황에 써봐야하지 않겠는가.

 

1. 테스트 할 데이터 생성

파워포인트로 직접 그렸다. 물론 데이터는 하나하나 저장해주어야 한다.

 

2. Preprocessing

기존의 이미지

우리가 이전에 데이터셋으로 쓴 이미지는 다음과 같이

1. 검은색 배경에 흰색 글씨

2. 28*28 pixel의 데이터이다.

 

def preprocess_image(image_path):
    image = Image.open(image_path).convert('L')
    image = image.resize((28, 28), Image.LANCZOS)
    image = ImageOps.invert(image)
    transform = transforms.Compose([
        transforms.ToTensor()
    ])
    image = transform(image)
    image = image.unsqueeze(0)
    print(image)
    return image

따라서     image = ImageOps.invert(image) 를 통해 반전을 시켜주며 Resize로 사이즈를 맞춰준다.

그럼 다음과 같은 이미지가 나온다.

 

3. 실제 테스트

image_path = '/test_data_5.png'
input_image = preprocess_image(image_path).to(DEVICE)

with torch.no_grad():
    output = model(input_image)
    _, predicted = torch.max(output.data, 1)
    print('예측된 숫자:', predicted.item())

이를 예측한다.

(근데 생각보다 많이 부정확하다. 5개 중 3개만 맞췄다. 원인 파악을 해봐야겠다.)

 

논문 원문: https://www.ri.cmu.edu/pub_files/2014/7/Ji_LidarMapping_RSS2014_v8.pdf

 

추후 참고(할) 깃헙

1. https://github.com/laboshinl/loam_velodyne

2. https://github.com/stevenliu216/Lidar-Odometry-and-Mapping

 

읽으면서 참고한 블로그 : https://alida.tistory.com/44

 

[SLAM] Lidar Odometry And Mapping (LOAM) 논문 리뷰

본 포스트는 공부 목적으로 작성하였습니다.혹시 보시는 도중 잘못된 부분이나 개선할 부분이 있다면 댓글이나 메일주시면 확인 후 수정하도록 하겠습니다.해당 포스트는 앞서 pdf 파일로 정리

alida.tistory.com

 

 

Abstract

이 연구는 lidar(라이다) 센서를 이용하여 odometry와 mapping을 실시간으로 하는 방법에 대해 설명한다.

(라이다 센서 -> 사물까지의 거리를 타 센서들에 비해 정확하게 알 수 있음.)

우리의 연구는 'low-drift and low-computational complexity'를 모두 뛰어나다.

주요 알고리즘은 크게 두 가지로 나뉜다.

(1) 높은 빈도로 odometry를 계산하며

(2) 그보다는 낮은 빈도로 mapping한다.

 

1. Introduction

- 라이다는 high frequency range measurement가 가능하기에 mapping에 용이하며 오차에 대한 일관성이 있기에 유용함.

 

- 라이다의 the only motion은 rotating하는 레이저 빔이지만, 만일 라이다가 움직이면 어려워진다.

-> 이 문제를 해결하기 위해 GPS/INS를 이용하여 문제를 해결할 수 있음. 다른 방법으로는 odometry measurement를 이용하여 해결.

 

근데 odometry를 Integrates하면 조그마한 오차들이 많이 더해져 큰 오차가 발생할 수 있다. 이런 오차를 해소하기 위해 loop-closure(로봇이 이전에 방문했던 장소로 다시 돌아왔을 때 이를 인식하여, 현재의 위치와 맵 정보를 이전의 정보와 비교합니다. 이를 통해 누적된 오차를 조정하고 전체적인 위치 추정과 맵의 정확도를 향상시는 방법.)을 사용할 수 있음.

 

! 우리는 inpendent한 position estimation 없이 할거임. loop-closure도 안 쓸거임.

우리의 모델은 다른 기술 안 쓰고 low-drift 하며 low-computational complexity한 모델을 만듦.

(low-drift는 위치의 정확성, low-computational complexity는 real-time에서 빠른 연산을 위함인듯.)

 

두 알고리즘이 사용되는데

1. 높은 빈도로 performs odometry (but low fidelity)

2. 상대적으로 낮은 빈도, but fine matching and registration (of the point cloud).

 

두 알고리즘 모두 feature points를 추출하는데 sharp edges거나 planar surfaces이다.

odometry 알고리즘에선 추출된 feature points(특징점?)들을 corresponding (mapping이랑 같은 얘긴듯)을 빠른 속도로 한다.

 

이렇게 parallel한 알고리즘 구조가 real-time 속도로 위 내용들을 실행 가능하게 한다.

 

2. Related Works

Lidar는 매우 유용함.

lidar의 scan rate에 비해 외적 무빙이 작다면 스캔에 대한 왜곡은 무시해도 괜찮음.

-> 이 경우 two different scan에 대해 point끼리 mapping을 할 때 ICP method를 사용해도 됨.

그러나 scanning motion이 상대적으로 느리면 모션 왜곡이 무시 못 할 수 있음.

 

근데 우리가 할 것은 2-axis lidar로 다른 센서(IMU, GPS/INS 등) 도움 없이 하는 것임을 기억하자. (아닌가)

그러면, 모션 추정이나 왜곡 보정에이 또 하나의 문제가 된다.

우리의 방법은 edge/planar한 특징점을 뽑아 Cartesian space에 matching 시켜 높은 cloud density가 필요하지 않음.

 

우리 연구랑 가장 비슷한건 Bosse and Zlot의 것들(주석 3, 6, 22. 나중에 찾아보면 좋을 듯)

하지만 우리의 방법은 REAL-TIME에서 가능함 -> 이게 가장 큰 차별점인듯.

(결국 적은 연산량과 높은 정확성 사이에서 잘 타협을 본 method라는 듯.)

 

3. Notations And Task Description

(ego-motion : 환경 내에서 카메라의 3차원 이동)

 

아래는 이 paper에서 사용할 notation들.

좌표계 : 오른쪽 위 대문자 표기.

a sweep : 한 번의 scan coverage.

Pk : k번째 sweep 동안 받아진 point cloud.

 

좌표계는 크게 두 가지가 있는데 L와 W.

- L : Lidar 좌표계. Lidar 중심.

- W : L의 시작점 기준으로 우리의 세상(?) 기준 좌표계로 이해하면 될듯.

(좌표계를 잘 읽자. 뒤에서 읽다가 혼란 온다.)

 

 

결국 이 내용들을 하나의 Problem으로 정의한다면 다음과 같이 귀결된다.

 

4. System Overview

A. Lidar Hardware

Fig. 2.

우리가 사용할 모델. 하드웨어 스펙은 다음과 같다.

- The laser scanner has a field of view of 180도, 0.25 resolution, 40 lines/sec scan rate.

-The laser scanner is connected to a motor, 180도의 움직임 (-90도 ~ 90도).

 

A sweep : A rotation from -90 ~ 90도 (or in the inverse direction). 대략 1초

 

그럼 하나의 스윕동안 (모터가 1회 움직일 동안, laser scanner는 40줄을 훑는다. 두 개 구분 필요.)

 

B. Software System Overview

P hat : laser 스캔으로 부터 얻어진 점들.

각각의 스윕동안 P hat은 {L} 좌표계에 등록(registered)(?)됨.

k번째의 sweep에서 이렇게 registered된 point cloud를 Pk라 하며, 이런 Pk는 이제 두 개의 알고리즘을 타게 됨.

 

Lidar Odometry는 Point cloud를 받아서 두 연속적 sweep간의 motion을 계산한다.

이렇게 추정된 motion은 Pk에서의 왜곡을 수정하기 위해 사용되며, 이 알고리즘은 10Hz의 빈도로 발생.

추가로 이 결과는 lidar mapping에 의해서도 1Hz 주기로 처리됨.

 

이런 두 알고리즘에 의한 pose transforms가 10Hz 주기로 publish됨.

(어차피 5와 6에서 자세히 설명하니 대략적 흐름만 파악하고 넘어가자.)

 

5. Lidar Odometry

A. Feature Point Extraction

주요 내용 : Pk에서 feature points들을 뽑는 방법 설명.

 

(laser scanner의 구조가 이해가 가지 않아 확인해보니, 이런 원통형의 모양으로 돌아감. 이러면 상하 방향으로의 scan이 어렵기 때문에 이를 motor에 연결하여 움직이는 것인 아닐까? 라는 생각. 그리고 motor가 돌아가는 각도도 180도(-90도 ~ 90도)인 것도 사실 이런 모양이면 360도까지는 필요가 없기 때문 아닐까. 하나의 sweep으로 정의하는 것이 그럼 온전한 3D 이미지를 얻는 최소 단위기에 쪼개는 것이 아닐까.)

 

perpendicular한 방향의 resolution이 다른 것은 다 필요 없이 only laser scan의 rate이 40Hz이니 모터의 움직임(1초)동안 180도를 움직이는 동안, perpendicular하게는 180도 / 40 = 4.5도의 resolution. 

 

우리는 feature points를 sharp deges와 planar surface patches에서 뽑을 것.

 

S : set of consecutive points of i returned by the laser scanner in the same scan.

하나의 scan에서 point i를 기준으로 연속적인 점들 뽑은 것.

 

다음과 같이 smoothness of the local surface를 정의. (근데 엄밀하게 말하면 반대로 굽혀진 정도라 해야하지 않을까. 거기서 거긴가..)

하나의 스캔에서 c value로 sorting하면 큰 c를 갖는 점들이 edge points, 작은 값을 갖는 점들이 planar points라 명명.

 

우리는 하나의 scan을 4개의 identical한 subregion으로 나눔. 각 subregion에서 최대 2개의 edge points들과 4개의 planar한 point를 뽑음.

 

 

이러한 feature points를 뽑을 때 좀 이상한 case들을 제외해야함.

- 연속된 점들, 레이저 빔과 평행한 평면에 있는 점들. (a의 B)

- 어떤 물체에 가려진 점들. (b의 A)

 

정리하면

- c value의 sorting으로 골라진 edge points들과 planar points들의 수는 subregion의 최댓값을 넘지 못함.

- 이미 골라진 점들의 주위 점들을 뽑지 않음.

- 특이 점들(위의 예시)는 뽑지 않음.

 

Fig 5처럼 각각 노랑 : egde point, 빨강 : planar point로 시각화를 색깔로 표기.

 

B. Finding Feature Point Correspondence

odometry 알고리즘은 한 번의 스윕동안의 라이더의 motion을 추정.

 

tk : sweep k의 시작 '시각'.

각 스윕이 끝나면 sweep동안 얻어진 point cloud Pk또한 얻어짐. 이러한 Pk는 t(k+1)에 대해 reprojection 되는데 이렇게 얻어진 Pk_bar와 다음 스윕서 새롭게 얻어지는 Pk+1를 사용하여 lidar motion을 추정한다.

 

우리에게 Pk_bar와 Pk+1가 모두 주어졌을때 두 lidar clouds간 correspondences를 구하자.

 

 

 

 

 

 

 

 

 

C. Motion Estimation

 

D. Lidar odometry Algorithm

 

 

6. Lidar Mapping

dd

 

7. Experiments

 

 

 

8.Conclusion and Future work

What is Reinforcement Learning

강화학습이란 무엇인가 ?

강화 학습(Reinforcement learning)은 기계학습이 다루는 문제 중에서 다음과 같이 기술 되는 것을 다룬다. 어떤 환경을 탐색하는 에이전트가 현재의 상태를 인식하여 어떤 행동을 취한다. 그러면 그 에이전트는 환경으로부터 포상을 얻게 된다. 포상은 양수와 음수 둘 다 가능하다. 강화 학습의 알고리즘은 그 에이전트가 앞으로 누적될 포상을 최대화하는 일련의 행동으로 정의되는 정책을 찾는 방법이다.

위키피디아에서는 위와 같이 설명하고 있다.
 
강화학습은 Machine Learning의 범주 안에 있는 학습 방법 중의 하나.
아이가 환경과 상호 작용하며 걷는 방법을 알아가는 것처럼 배워가는 학습 방법.
(-> 강화학습 자체가 행동심리학에서 영감을 받았기에 실제 인간이 배우는 방법과 유사하여 아이디어 자체는 익숙한듯.)
 
강화 학습도 agent가 아무것도 모르는 상태에서 환경 속에서 경험을 통해 학습하는 것
(바둑으로 유명한 알파고 또한 강화학습 알고리즘에 기반)

Reinforcement learning is defined not by characterizing learningmethods, but by characterizing a learning problem.

Sutton 교수님의 책에서 강화학습은 학습하는 방식(method)로 정의 되는 것이 아닌, 문제로 정의 되어 짐.
 

출처 : https://dnddnjs.gitbook.io/rl/chapter-1-introduction-1/1.1-what-is-reinforcement-learning

Machine Learning은 크게 세 가지로 나뉜다.
1. Supervised Learning (지도학습) : 정답(label)을 알 수 있어 바로바로 피드백을 받을 수 있음.
2. Unsupervised Learning (비지도학습) : 정답이 없는 분류(classification)와 같은 문제를 푸는 것
3. Reinforcement Learning (강화학습) : 정답은 모르지만, 자신이 한 행동(action)에 대한 보상(reward)를 알 수 있어 그로부터 학습하는 것. 강화학습은 MDP(Markov Decision Process)로 표현되어지는 문제를 푸는 것.
 
강화학습의 가장 중요한 두가지 특징
1. Trial And Error : 해보지 않고 예측으로 움직이는 것이 아닌, 직접 해보며 조정해나가는 것.
2. Delayed Reward : 어떠한 행동에 대한 보상이 '즉각'적으로 이루어지는 것이 아닌 Delayed 될 수 있다. 환경이 반응할 때까지 여러 다른 행동들을 시간의 순서대로 했기에 어떤 행동이 좋은 행동이었는지 판단하기 어려운 점이 있음.
 

'ML & AI > RL' 카테고리의 다른 글

0. Rrologue  (0) 2024.10.06

Before to Start

해당 카테고리(RL)의 글들은 모두 Reinforcement Learning의 개인적 학습을 위한 자료이며 타 자료를 review하는 식으로 진행됩니다.

(최종 목적은 AD-tech에서 DSP의 입찰을 위해 공부를 진행하고자 한다.)

 

기본적인 자료는 이웅원님의 'Fundamental of Reinforcement Learning' (링크 : https://dnddnjs.gitbook.io/rl) 를 참고합니다.

 

Fundamental of Reinforcement Learning | Fundamental of Reinforcement Learning

2016년 초부터 모두의 연구소의 자율주행 드론 연구실 DCULab의 연구실장을 맡아서 드론을 연구을 해오고 있었습니다. 그러던 중에 "드론의 제어에 사용되는 PID 계수를 자동으로 맞춰주는 방법이

dnddnjs.gitbook.io

(또한 위 자료를 리뷰한 https://sumniya.tistory.com/1 또한 참고하였다.)

 

 

 

추후 공부를 위한 자료 

https://mclearninglab.tistory.com/49

 

강화학습 공부 자료 정리

강화학습 공부를 하기위해서 매번 찾아야하는 번거로움을 줄이기 위해 자료들을 모아놓기로 했다. Online Tutorial(Video) 기본적인 개념 뿐만 아니라 강화학습과 관련된 영상들을 정리하였다. 김성

mclearninglab.tistory.com

(https://arxiv.org/pdf/1811.12560 : 가볍게 읽어보기 좋을듯)

 

 

문의는 : dodongdog01@gmail.com

'ML & AI > RL' 카테고리의 다른 글

CHAPTER 1 : INTRODUCTION  (0) 2024.10.06

+ Recent posts