2017년 6월 27일 화요일

텐서플로우 기반 CIFAR 훈련모델 활용 이미지 인식

이 글은 텐서플로우 기반 CIFAR 훈련모델을 활용한 이미지 인식 방법을 간단히 소개한다. CIFAR 데이터 셋에 대한 자세한 내용은 여기를 참고한다.


1. 텐서플로우 소스 및 예제 다운로드
텐서플로우는 다양한 딥러닝 예제를 제공하고 있다. 우리는 CIFAR 데이터 셋 훈련을 위해 제공하는 예제를 다운로드해서 사용한다. 콘솔에서 git 명령을 이용해, 텐서플로우 소스와 예제를 다운로드 한다.

git clone https://github.com/tensorflow
git clone https://github.com/tensorflow/models

작업폴더 아래에 다음과 같이 cifar10 tutorial 예제 소스 코드를 확인할 수 있다.

cifar10_input.py 는 CIFAR 이미지를 읽고 전처리한다.
cifar10_train.py 는 loss(), train() 함수를 정의하며, 모델을 훈련한다.
cifar10_eval.py는 훈련된 모델을 이용해 예측 테스트한다.

2. 학습 모델 실행 및 확인
cifar10 예제는 CNN 다층 신경망 구조 모델로 학습을 한다. 신경망 구조를 살펴보기 전에, 우선 Google에서 제공한 cifar10 파이썬 예제를 다음과 같이 실행한다.

cd tensorflow\models\tutorials\image\cifar10
python cifar10.py
python cifar10_train.py

각 파이썬 모듈을 실행하면, CIFAR10 데이터 셋을 다운로드 받고 다음과 같이 학습된다.


학습된 결과를 텐서보드를 통해 확인해 본다. cifar10_train.py소스를 확인해 보면, 훈련된 로그는 '/tmp/cifar10_train'폴더에 있다. 다음과 같이 텐서보드를 실행한다.
tensorboard --logdir=/tmp/cifar10_train


텐서보드 localhost:6006 을 오픈하면, 다음과 같이 훈련 모델 그래프 구조를 확인할 수 있다.

이미지 메뉴를 선택하면, 각 step에서 입력된 이미지를 확인할 수 있다.

Scalars, Histogram, Embedding 메뉴에서 각 신경망 모델 연산의 출력 결과를 확인할 수 있다.

3. 학습 모델 평가 
학습된 모델을 평가하려면, 다음 명령을 입력한다.
python cifar10_eval.py

소스코드는 CIFAR10 데이터 셋 중 10,000를 선택해, 예측 정확도를 비교하게 되어 있다.

4. 학습 모델 신경망 구조 분석
CIFAR-10 모델은 컨볼루션 및 비선형이 포함된 다중 레이어 구조로 되어 있다. 이 레이어는 입력 데이터의 이미지 특징 벡터를 계산한다. 이 결과를 Fully connected layer 가 입력받아 softmax 로 결과를 분류한다. 이 모델은 Alex Krizhevsky 가 만든 모델을 활용해 수정된 것이다. 이 모델은 최대 86% 정확도를 달성할 수 있다.

1. 모델 입력
CIFAR-10 이미지 바이너리를 inputs() 함수로 읽는다. 이미지는 24x24 픽셀로 정규화된다. 학습도를 높이기 위해, 무작위로 이미지를 왜곡해 데이터셋 수를 키운다. 예를 들어, 이미지 뒤집기, 밝기 왜곡, 대비 왜곡 등을 한다.

2. 모델 예측
모델 예측은 inference() 함수에서 정의된다. 이 함수는 다음 레이어를 정의하고 있다.
input - conv1 - pool1 - norm1 - conv2 - norm2 - pool2 - local3 - local4 - softmax_linear

구조는 다음과 같다.
input[224x224x3] - conv1[224x224x64] - pool1[112x112x64] - norm1 - conv2[112x112x64] - norm2 - pool2[56x56x64] - local3[DIMx384 + 384] - local4[384x192 + 192] - softmax_linear[10]

각 레이어에 대한 개념은 다음 영상을 참고한다.

아래는 텐서보드에서 가시화된 그래프 구조이다.
3. 모델 훈련
N-way 부류를 위해, softmax 회귀로 알려진 다항 로지스틱 회귀를 수행한다. 정규화된 예측값은 1-hot encoding된 라벨 간의 차이를 cross-entropy로 계산해, 차이를 줄이는 방향으로 weight를 조정한다.

5. 텐서플로우 소스코드 분석
좀 더 정확한 이해를 위해, 소스코드의 주요부분을 분석해 본다.

1. cifar10_train.py
FLAGS = tf.app.flags.FLAGS
tf.app.flags.DEFINE_string('train_dir', '/tmp/cifar10_train',
                       """Directory where to write event logs """
                       """and checkpoint.""")   # 텐서보드 용 로그, 체크포인트 저장 폴더 지정
tf.app.flags.DEFINE_integer('max_steps', 2000,
                            """Number of batches to run.""") # 1000000

def train():
  with tf.Graph().as_default():
  with tf.device('/cpu:0'):
    images, labels = cifar10.distorted_inputs()  # 이미지 왜곡해 라벨과 함께 리턴

  logits = cifar10.inference( images)  # 예측 모델 그래프 생성
  loss = cifar10.loss(logits, labels)     # loss 계산 그래프 추가
  train_op = cifar10.train(loss, global_step)  # 훈련 모델 그래프 추가

  # 체크포인트 파일 저장, 로그 후킹을 위해,
  # InteractiveSession() 대신 MonitoredTrainingSession()을 사용
  with tf.train.MonitoredTraningSession(
    checkpoint_dir=FLAGS.train_dir,
    hooks=[tf.train.StopAtStepHook(last_step=FLAGS.max_steps),  # 최대 훈련 횟수
               tf.train.NanTensorHook(loss), _LoggerHook()],           # 로그 후킹
    config=tf.ConfigProto(
            log_device_placement=FLAGS.log_device_placement)) as mon_sess:
    while not mon_sess.should_stop():   # stop() 이 아닐때 까지
      mon_sess.run(train_op)               # 모델 훈련

def main(argv=None):
  cifar10.maybe_download_and_extract()     # CIFAR10 데이터 셋 다운로드 
  train()                                               # 모델 훈련

2. cifar10.py
def maybe_download_and_extract():  # Alex 사이트 CIFAR-10 다운로드 및 압축 해제 함수
    filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath, _progress)  # 다운로드
    tarfile.open(filepath, 'r:gz').extractall(dest_directory)                        # 압축해제

def distorted_inputs():   # CIFAR 이미지 왜곡을 통한 데이터 수 확대
  images, labels = cifar10_input.distorted_inputs(data_dir=data_dir,
                                                  batch_size=FLAGS.batch_size)  # 이미지 왜곡

def inference(images):   # 예측 모델 그래프 생성
  # conv1 정의
  with tf.variable_scope('conv1') as scope:
    kernel = _variable_with_weight_decay('weights', shape=[5,5,3,64], stddev=5e-2, wd=0)
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(pre_activation, name=scope.name)  # ReLU 활성함수 정의

  # pool1 정의. max pooling.
  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                         padding='SAME', name='pool1')
  # norm1 정의. local_response_normalization() 
  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='norm1')

  # conv2 정의
  with tf.variable_scope('conv2') as scope:
    kernel =_variable_with_weight_decay('weights', shape=[5,5,64,64], stddev=5e-2, wd=0)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(pre_activation, name=scope.name)  # ReLU 활성함수 정의

  # norm2 정의
  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, name='norm2')

  # pool2 정의
  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')

  # local3 정의 
  with tf.variable_scope('local3') as scope:
    reshape = tf.reshape(pool2, [FLAGS.batch_size, -1])
    dim = reshape.get_shape()[1].value
    weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)

  # local4 정의
  with tf.variable_scope('local4') as scope:
    weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)

  # WX + b 정의
  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=0.0)
    biases = _variable_on_cpu('biases', [NUM_CLASSES], tf.constant_initializer(0.0))
    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
  return softmax_linear

def loss(logits, labels):
  # cross entropy loss 평균 계산
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
      labels=labels, logits=logits, name='cross_entropy_per_example')
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  tf.add_to_collection('losses', cross_entropy_mean)
  return tf.add_n(tf.get_collection('losses'), name='total_loss')

tf.app.flags.DEFINE_integer('batch_size', 128)  # 배치 데이터 크기

def train(total_loss, global_step):
  num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size
  decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY)

  # Exp()함수에 따른 Learning rate 관련 decay 값 정의
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE, global_step, decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR, staircase=True)

  # Generate moving averages of all losses and associated summaries.
  loss_averages_op = _add_loss_summaries(total_loss)

  # gradients 계산 연산자 정의
  with tf.control_dependencies([loss_averages_op]):
    opt = tf.train.GradientDescentOptimizer(lr)
    grads = opt.compute_gradients(total_loss)

  # gradients 연산자 정의
  apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

  # histograms 추가
  for var in tf.trainable_variables():
    tf.summary.histogram(var.op.name, var)

  # 모든 훈련 변수들에 대한 이동 평균 추적
  variable_averages = tf.train.ExponentialMovingAverage(
      MOVING_AVERAGE_DECAY, global_step)
  variables_averages_op = variable_averages.apply(tf.trainable_variables())

  with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
    train_op = tf.no_op(name='train')
 return train_op

3. cifar10_input.py
IMAGE_SIZE = 24        # 픽셀 크기를 24로 정의
NUM_CLASSES = 10    # 라벨 종류

def read_cifar10(filename_queue):              # CIFAR10 읽기 함수
def distorted_inputs(data_dir, batch_size):    # CIFAR10 데이터 이미지 왜곡 함수

4. cifar10_eval.py
def evaluate():
  with tf.Graph().as_default() as g:
    images, labels = cifar10.inputs(eval_data=eval_data)  # 검증용 CIFAR10 데이터셋 읽기

    logits = cifar10.inference(images)               # 예측 모델 그래프 정의
    top_k_op = tf.nn.in_top_k(logits, labels, 1)    # 예측

    # 체크포인트 파일에서 복구를 위한 moving average 그래프 및 saver 생성
    variable_averages = tf.train.ExponentialMovingAverage(
        cifar10.MOVING_AVERAGE_DECAY)
    variables_to_restore = variable_averages.variables_to_restore()
    saver = tf.train.Saver(variables_to_restore)

    while True:
      eval_once(saver, summary_writer, top_k_op, summary_op)  # saver, top_k_op 평가

def eval_once(saver, summary_writer, top_k_op, summary_op):
  with tf.Session() as sess:
    ckpt = tf.train.get_checkpoint_state(FLAGS.checkpoint_dir)
    if ckpt and ckpt.model_checkpoint_path:  # 체크포인트 파일로 부터 학습 모델 복구
      saver.restore(sess, ckpt.model_checkpoint_path)

      total_sample_count = num_iter * FLAGS.batch_size
      step = 0
      while step < num_iter and not coord.should_stop():
        predictions = sess.run([top_k_op])       # 복구된 학습 모델 예측
        true_count += np.sum(predictions)
        step += 1


레퍼런스

텐서플로우 기반 딥러닝 훈련 모델 파일 저장, 로딩 및 재활용

텐서플로우를 사용해 딥러닝 훈련 모델 파일 저장 및 복구를 통한 재활용 방법을 간단히 기술한다. 딥러닝은 학습할 때 많은 시간이 소요된다. 학습된 모델을 저장해, 필요할 때 로딩하여 재활용하면, 이런 학습 시간을 생략할 수 있다.

1. softmax 기반 MNIST 모델 학습 및 저장하기
학습 모델을 파일로 저장하고 재활용하기 위해, 필기체 이미지 MNIST 데이터를 다음과 같이 softmax 활성 함수를 이용한 신경망을 만든 후 학습한다. softmax 기반 신경망에 대한 자세한 내용은 여기를 참고한다.
softmax 기반 학습 모델

MNIST 데이터셋 예

학습된 모델은 tf.saver를 이용해 minist_softmax.ckpt 파일로 저장한다. 참고로 텐서플로우 사용방법은 여기를 참고한다.
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data_2/", one_hot=True)
print("Download Done!")

x = tf.placeholder(tf.float32, [None, 784])

# paras
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

y = tf.nn.softmax(tf.matmul(x, W) + b)
y_ = tf.placeholder(tf.float32, [None, 10])

# loss func
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

# init
init = tf.initialize_all_variables()

sess = tf.Session()
sess.run(init)

# train 
for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

correct_prediction = tf.equal(tf.arg_max(y, 1), tf.arg_max(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

print("Accuarcy on Test-dataset: ", sess.run(accuracy, feed_dict={x:        mnist.test.images, y_: mnist.test.labels}))

# save model 
saver = tf.train.Saver()
save_path = saver.save(sess, "./minist_softmax.ckpt")

import os
print (os.getcwd())
print("Model saved in file: ", save_path)
저장된 파일은 사용자 작업 폴더에 다음과 같이 저장되어 있다.

확장자가 .meta 파일은 학습 모델 그래프의 메타정보를 저장한 파일이다. 해당 파일을 열어보면, 다음과 같이 그래프 메타 정보가 저장되어 있는 것을 확인할 수 있다.

checkpoint파일은 단순하게 체크포인트 경로 등을 저장해 놓은 파일이다. 실제 그래프 데이터는 .chpt.data-00000-of-00001 에 저장되어 있다.

2. MNIST 훈련 모델 기반 필기체 숫자 이미지 예측 소스코드
앞에서 훈련된 softmax 기반 MNIST 모델 파일(minist_softmax.ckpt)을 saver로 읽어, 필기체 숫자 이미지 예측 프로그램을 개발한다. 다음 순서로 진행한다.

1. 만약 PIL 라이브러리를 설치 하지 않았다면, 콘솔에서 pip install pillow 명령으로 설치한다. 참고로 PIL은 Python Imaging Library이다.
2. 작업폴더안에 MNIST_test_image.jpg 정사각형 숫자 이미지를 준비한다. 이 예에서는 다음과 같이 3이라고 쓴 필기체 이미지를 이용하였다.
3. 아래 코드를 실행한다. 
from PIL import Image
from numpy import *
import tensorflow as tf
import sys

im=Image.open("./MNIST_test_image.jpg")
img = array(im.resize((28, 28), Image.ANTIALIAS).convert("L"))
data = img.reshape([1, 784])

x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

y = tf.nn.softmax(tf.matmul(x, W) + b)

saver = tf.train.Saver()
init_op = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init_op)
    save_path = "./minist_softmax.ckpt"
    saver.restore(sess, save_path)
    predictions = sess.run(y, feed_dict={x: data})
    print(predictions[0]);
훈련된 모델을 복구해 처리하면, 다음과 같이 출력된다. 이미지 x가 주어졌을 때, y 벡터 요소 중 제일 큰 값을 가지고 있는 것이 예측된 라벨이다. 벡터요소는 0에서 9까지 10개 원소를 가진다. 아래 경우, 네번째 원소의 값이 1이므로, 3이라고 쓴 필기체 이미지가 정확히 라벨 3로 인식된 것을 확인할 수 있다.

또 다른 테스트를 위해 다음 이미지를 준비해 본다. 
그리고, 앞의 코드를 실행하면 다음과 같이 8번 라벨로 잘 예측되는 것을 확인할 수 있다.

3. 학습 모델 그래프 메타 파일 사용
앞의 예제에서는 학습 모델을 저장한 체크포인트 파일을 restore()하기 전에, 학습 모델에 사용된 그래프를 다시 코딩해 사용하였다. 체크포인트 파일이 저장될 때 함께 저장되는 meta 파일을 이용하면, 학습 모델에 사용된 그래프 정의도 import할 수 있다. 사용하는 방법은 다음과 같다.
new_saver = tf.train.import_meta_graph("./minist_softmax.ckpt.meta")new_saver.restore(sess, "./minist_softmax.ckpt")

4. 마무리
훈련된 학습 모델을 파일로 보관하고 있으면, 다시 학습하느라 오랜 훈련 시간을 보낼 필요 없이, 저장된 파일을 restore()해서 사용하면 된다.

다음은 훈련 모델 재활용과 관련된 또 다른 레퍼런스이다. 비슷한 방식으로 작업되어 있다.
다음은 이를 이용해 아이폰 앱, C++ 플랫폼 기반 서비스를 개발하는 예이다. 좀 더 자세한 내용은 텐서플로우 각 플랫폼 별 SDK example 을 참고한다.



2017년 6월 22일 목요일

유니티 기반 3차원 포인트 클라우드 뷰어

이 글은 Unity(유니티) 게임엔진에서 3차원 포인트 클라우드(point cloud)를 확인할수 있는 Point Cloud Free Viewer를 간단히 소개한다.

Point Cloud Free Viewer는 Unity 기반 앱으로 unity3d assetstore에서 다운로드 및 설치할 수 있다.

1. 설치하기 전에 우선 Unity(4.3.2 이상) 프로젝트를 하나 만든다.
2. Viewer 링크를 클릭하고, 다음 '유니티에서 열기' 버튼을 클릭해, Unity에 설치한다. 


3. Viewer를 설치하면, 다음과 같이 asset이 설치된다. 

4. 다음과 같은 순서로 빈 객체를 만들고, 관련 파라메터를 설정한다. 
Add an empty Game Object in the Scene.
Add the script "PointCloudManager.cs" to this object.
Write the path of your PointCloud file without the extension (e.g. "/PointCloud/xyzrgb_manuscript")
Assign the material 'VertexColor' to 'Mat Vertex'
Choose other parameters such as scale, invert YZ and force reload.

5. Play 한다. 그럼 다음과 같이 지정된 xyzrgb_manuscript.off 파일의 점군을 읽어, 메쉬로 자동 구성한 후, 이를 가시화해 준다. 



2017년 6월 17일 토요일

딥러닝 Hello World - MNIST, CIFAR-10, COCO, VOC 학습 데이터 구조와 이미지넷

이 글은 딥러닝계의 Hello World 인 MNIST, CIFAR, COCO, VOC 데이터베이스 구조와 덥러닝 기술을 크게 발전시킨 세계 최대 이미지 학습 데이터 사이트 이미지넷을 소개한다.

MNIST 데이터베이스 구조
이 글은 yann.lecun.com/exdb/mnist (Yann LeCun. NYU, Director of AI Research at Facebook, Silver Professor of Computer Science at the Courant Institute of Mathematical Sciences) 를 참고하였다. 이외에 많이 사용되고 있는 테스트 데이터셋은 다음과 같다.
  • MNIST
  • PTB(Penn Tree Bank)
  • CIFAR-10, CIFAR-100
MNIST는 NYU의 Yann LeCun 사이트에서 다운로드 할 수 있다.
  train-images-idx3-ubyte.gz:  training set images (9912422 bytes) 
  train-labels-idx1-ubyte.gz:  training set labels (28881 bytes) 
  t10k-images-idx3-ubyte.gz:   test set images (1648877 bytes) 
  t10k-labels-idx1-ubyte.gz:   test set labels (4542 bytes)

이 사이트에서는 딥러닝 신경망 학습에 필요한 필기체 숫자 이미지 60,000 훈련 집합, 10,000개 테스트 집합을 제공한다. 이를 이용해, 패턴 인식 연구에 필요한 데이터 수집 노력을 줄일 수 있다.

이 이미지는 원래 흑백이었지만, 20 x 20 픽셀 크기에 맞춰 정규화된 그레이 이미지로 처리되었다. 이를 위해, 안티 앤리어싱 기술을 적용하여, 이미지 펙셀 중심을 계산하여, 28x28 크기로 변환하였다.

MNIST 데이터는 SD-3와 SD-1이 있으며, SD3가 노이즈가 별로 없고 쉽게 인식할 수 있다.
참고로, SD-1은 500명의 사람이 쓴 58,527 이미지가 포함되어 있다.

MNIST 파일 형식은 MSB(Most Significatn Bit. 가장 큰 숫자를 왼쪽에 기록하는 자리 표기법) 방식으로 저장된다. 각 파일의 형식은 다음과 같다.

TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000801(2049) magic number (MSB first) 
0004     32 bit integer  60000            number of items 
0008     unsigned byte   ??               label 
0009     unsigned byte   ??               label 
........ 
xxxx     unsigned byte   ??               label

라벨값은 0에서 9까지이다.

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000803(2051) magic number 
0004     32 bit integer  60000            number of images 
0008     32 bit integer  28               number of rows 
0012     32 bit integer  28               number of columns 
0016     unsigned byte   ??               pixel 
0017     unsigned byte   ??               pixel 
........ 
xxxx     unsigned byte   ??               pixel
픽셀들은 row-wise로 구성되어 있다. 픽셀 값은 0에서 255이다. 0은 배경(흰색), 255는 전경색(검정색)을 의미한다.

TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000801(2049) magic number (MSB first) 
0004     32 bit integer  10000            number of items 
0008     unsigned byte   ??               label 
0009     unsigned byte   ??               label 
........ 
xxxx     unsigned byte   ??               label
라벨값은 0에서 9까지이다.

TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
[offset] [type]          [value]          [description] 
0000     32 bit integer  0x00000803(2051) magic number 
0004     32 bit integer  10000            number of images 
0008     32 bit integer  28               number of rows 
0012     32 bit integer  28               number of columns 
0016     unsigned byte   ??               pixel 
0017     unsigned byte   ??               pixel 
........ 
xxxx     unsigned byte   ??               pixel
픽셀들은 row-wise로 구성되어 있다. 픽셀 값은 0에서 255이다. 0은 배경(흰색), 255는 전경색(검정색)을 의미한다.

IDX 파일 포맷은 간략한 벡터 형식으로 되어 있다.
magic number
size in dimension 0
size in dimension 1
size in dimension 2
.....
size in dimension N
data

매직 넘버는 정수형(MSB)이다. 첫번째 2바이트는 항상 0이고, 세번째 바이트 코드는 다음을 의미한다.
0x08: unsigned byte 
0x09: signed byte
0x0B: short (2 bytes)
0x0C: int (4 bytes)
0x0D: float (4 bytes)
0x0E: double (8 bytes)



CIFAR 데이터베이스 구조
CIFAR-10과 CIFAR-100은 80 백만개의 소형 이미지 데이터셋이다. 이 데이터는 Alex Krizhevsky, Vinod Nair, Geoffrey Hinton이 모았다. 이 글은 Krizhevsky 홈페이지를 참고하였다. 이 이미지를 이용해, 다양한 머신러닝 기법이 테스트되고 있다(예. 텐서플로우 기반 CNN 이미지 훈련)

CIFAR-10은 60000 32x32 컬러 이미지로 10개 클래스로 구성된다. 50000개의 훈련 이미지와 10000개의 테스트 이미지가 있다.

데이터셋은 5개 훈련 배치 셋과 한개 테스트 배치 셋으로 구분되며, 각 셋은 10000 개 이미지이다. 테스트 배치 셋은 1000 개 선택된 임의 이미지가 포함되어 있다. 훈련 배치 셋은 각 클래스별로 5000개 이미지로 구성된다.

이미지넷 폴더 구조는 다음과 같다.
|-- annotation.txt
|-- 0001.jpg
|-- 0002.jpg
|... 
|-- n.jpg

라벨 정보가 포함된 annotation 파일 구조는 다음과 같다.
0001.jpg <label ID>
0002.jpg <label ID>
...
n.jpg <label ID>

다음은 다운로드 링크이다.
VersionSizemd5sum
CIFAR-10 python version163 MBc58f30108f718f92721af3b95e74349a
CIFAR-10 Matlab version175 MB70270af85842c9e89bb428ec9976c926
CIFAR-10 binary version (suitable for C programs)162 MBc32a1d4

COCO 학습 데이터셋 구조
COCO는 Common Objects in Context 의 약자로, 대규모 객체 인식, 세그먼테이션, 데이터셋 캡션을 위해 고려된 학습 데이터셋이다. COCO는 330,000 개 이미지와 200,000개 라벨링된 객체 정보를 제공하고, 80개 객체 카테고리를 제공한다. 이런 이유로, 객체 탐지 및 세그먼테이션 딥러닝 모델 학습 기본 데이터셋으로 널리 활용되고 있다.
 
참고로, 이 데이터셋은 Google Brain, Microsoft, Caltech, Chicago, Cornell Tech, FAIR, Georgia Tech, CMU 등의 연구진 간 협업으로 개발된 것이다.


코코는 5개 annotation 유형을 사용한다. 이는 객체 검출, 키포인트 검출, stuff segmentation, panoptic segmentation, 이미지 캡셔닝에 사용된다.


코코는 JSON파일 포맷이며, 경계 박스 등 정보를 포함한다.
  • info: dataset information
  • license: image license
  • categories: image category list
  • images: image information
  • annotations: 각 image의 개별 객체 annotation 정보 
다음 그림은 각 요소의 예시이다. 
Annotation은 이미지 별 개별 객체정보를 담는다.
  • segmentation: segmentation masks 객체 정의를 위한 다각형 정점 x, y 좌표
  • area: bounding box area (pixel)
  • iscrowd: 만약 단일 객체 세그먼트이면 이 값은 0이다. 한 이미지에 여러 객체들이 있다면, 이 값은 1로 설정된다. 이 경우, RLE(run length encoding) 이 사용된다. 이를 통해, 세그먼트 다각형 정보를 압축해 처리 속도를 개선하고 용량을 줄일 수 있다.
  • imageid: image ID.  ID는 image section에서 정의된다.
  • bbox: 경계 박스 정의. top left x top left y, width, height. 
  • category: category 정의.
  • id: annotation ID

이렇게 정의된 데이터 파일은 다음 폴더 구조로 정의된다.
|-- val
      |-- 0001.jpg
      |-- 0002.jpg
      ... 
      |-- n.jpg
|-- annotations 
      |-- instances_val.json


데이터셋은 다음 링크에서 다운로드 받을 수 있다.
Pascal VOC 학습 데이터셋 구조
Pascal(Pattern Analysis, Statistical Modeling and Computational Learning) VOC(Visual Object Classes)는 객체 탐지를 위한 표준 이미지 데이터셋을 제공한다. 이 학습 데이터셋은 파스칼 VOC 챌린지 대회에서 개발된 것으로 객체 인식 기술을 겨루는 국제 대회에서 주어진 것이다.이 대회는 2005년부터 2012년까지 진행되었고, 총 20가치 객체에 대해 우승자를 가리는 대회였다. 이 대회를 통해 비전 분야에 여러 here와 혁신적인 기술이 탄생했다.

코코와 다른 점은 VOC는 XML을 사용하고, 개별 이미지별로 라벨 정보 파일이 존재하며, 경계 박스가 최상단 좌표, 최하단 좌표란 점이다.
데이터 형식

VOC는 폴더별로 이미지가 저장된다. 크기는 이미지 폭, 높이, 깊이(컬러의 경우 3)를 정의한다. 객체는 name, pose, truncated, difficult, bndbox를 포함하고 있다. 이 중에 truncated는 이미지에 포함된 일부 짤려진 객체인 경우 1로 설정되며, 그렇지 않으면 0이다. difficult는 인식이 어려운 객체인 경우 1, 아니면 0을 설정한다.

VOC 데이터 폴더 구조는 다음과 같다.
|-- VOCdevkit
    |-- VOC
        |-- Annotations
              |-- 0001.xml
              |-- 0002.xml 
              ... 
              |-- n.xml 
        |-- ImageSets
              |-- Layout
                  |-- test.txt
              |-- Main
                  |-- 0001_test.txt
                  |-- 0002_test.txt 
                  ... 
                  |-- n_test.txt 
              |-- Segmentation
                  |-- test.txt 
        |-- Images
              |-- 0001.jpg
              |-- 0002.jpg 
              ... 
              |-- n.jpg 
        |-- SegmentationClass
              |-- 0001.png
              |-- 0002.png
              ... 
              |-- n.png
        |-- SegmentationObject
              |-- 0001.png
              |-- 0002.png
              ... 
              |-- n.png


데이터셋은 다음에서 다운로드 가능하다.
참고로, 아래 스크립트는 라벨링에 많이 사용하는 BBox-Label-Tool 에서 생성된 데이터 파일들을 VOC 포맷으로 변환해 주는 스크립트이다. 필요하다면, 목적에 맞게 수정해 사용하길 바란다.
# Programming by KTW# email. laputa99999@gmail.com# Note. You are responsible for any problems caused by use this program.import os
import glob
from os import listdir, getcwd
from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP

# Modify the below parametercategoryCount = 2 
names = ['none', 'con_eq', 'worker', 'none']
width = 500height = 500
# Programroot = ''def createVOCfolders(root):
    try:
        os.mkdir(root)
        os.mkdir(root + 'Annotations')
        os.mkdir(root + 'ImageSets')
        os.mkdir(root + 'JPEGImages')
    except:
        return    return
def saveVOC(folder, filename, imageName, category, object, w, h, BBoxList):
    try:
        file = open(folder + filename, "w")

        file.write('<annotation>\n')
        file.write('<folder>' + folder + '</folder>\n')
        file.write('<owner><name>ktw</name></owner>\n')
        file.write('<size><width>' + str(w) + '</width>' + '<height>' + str(h) + '</height>' + '<depth>3</depth></size>\n')
        file.write('<segmented>1</segmented>\n')
        for BBox in BBoxList:  # type: object            file.write('<object>\n')
            file.write('<name>' + object + '</name>\n')
            file.write('<pose>Unspecified</pose>\n')
            file.write('<truncated>0</truncated>\n')
            file.write('<difficult>0</difficult>\n')
            file.write('<bndbox>\n')
            file.write('<xmin>' + str(BBox[0]) + '</xmin>\n')
            file.write('<ymin>' + str(BBox[1]) + '</ymin>\n')
            file.write('<xmax>' + str(BBox[2]) + '</xmax>\n')
            file.write('<ymax>' + str(BBox[3]) + '</ymax>\n')
            file.write('</bndbox>\n')
            file.write('</object>\n')
        file.write('</annotation>\n')
    except:
        return    return
def convertBBoxToVOC(root, labelDir, category, w, h):
    labelList = glob.glob(os.path.join(labelDir, '*.*'))
    for filename in labelList:
        BBoxList = []
        try:
            file = open(filename, "r")
            count = file.readline()
            count = count.rstrip('\n')
            num = int(count)
            for line in range(num):
                BBox = file.readline()
                BB = BBox.split()
                points = []
                for pt in BB:
                    points.append(int(pt))
                BBoxList.append(points)
        except:
            continue
        object = names[category]

        name = os.path.basename(filename)
        name, extension = os.path.splitext(name)
        vocfile = name + '.xml'        saveVOC(root + 'Annotations/', vocfile, name, category, object, w, h, BBoxList)
    return
wd = getcwd()
root = wd + '/VOC_data/'createVOCfolders(root)

for index in range(categoryCount):
    index = index + 1    imageDir = getcwd() + '/'    imageDir = imageDir + os.path.join(r'BBox/Images/', '%03d' % (index))
    imageList = glob.glob(os.path.join(imageDir, '*.*'))

    if len(imageList) == 0:
        print('No images found in the specified dir!')

    labelDir = getcwd() + '/'    labelDir = labelDir + os.path.join(r'BBox/Labels/', '%03d' % (index))

    convertBBoxToVOC(root, labelDir, index, width, height)

이미지 넷
이미지넷은 Fei-Fei Li 교수가 2007년 제안한 학습용 이미지 데이터베이스 구축 아이디어에서 시작된 프로젝트이다. 비전 기술 개발 시 필요한 세계 최대 학습용 이미지 데이터베이스가 무료로 제공된다.

이미지넷은 무료로 학습용 데이터베이스를 제공하고 있다. 이미지넷 초창기 Fei-Fei Li 와 주변 동료들은 약 15,000,000 이미지에 대한 라벨링 작업을 큰 R&D펀드 없이 진행하였으며, 이후 도움을 준 세계 각국의 연구자들도 그녀의 아이디어에 영감을 받아, 개인의 시간과 자원을 공헌하였다.

이미지넷은 매년 비전 기술의 사물 인식 정확도를 기준으로 평가하는 대회를 열고 있다. 대회에서 우수한 알고리즘을 평가된 기술은 큰 주목을 받고, 오픈소스로 공개되어, 많은 사람들에게 도움을 주고 있다. 2012년 캐나다 토론토 대학의 알렉스 크리제스브키는 GPU 기반 CNN 딥러닝 모델(ImageNet Classification with Deep Convolutional Neural Networks, 2012)을 이용해, 80% 이상 정확도를 보여주어, 사람들을 놀라게 했다. 그는 이 기술을 오픈소스로 공개하였으며, 마이크로소프트는 이 기술을 기반으로 정확도를 96%까지 끌어올렸다(관련 기사).

이미지넷은 비전 분야에서 오픈소스를 기반으로 공유하고 발전하는 문화를 만들게 된 큰 계기가 되었다. 이와 관련된 자세한 내용은 다음 Fei-Fei Li 교수의 TED 강연을 통해 확인할 수 있다.
ImageNet (Fei-Fei Li 교수. TED)

최근 스탠포드대학에서 구글로 자리를 옮긴 Fei-Fei Li 교수에 대한 좀 더 자세한 내용은 다음 링크에서 살펴볼 수 있다.
마무리
이 글에서 머신러닝 훈련에 필요한 데이터를 제공하는 레퍼런스를 간략히 살펴보았다. 이외에, 이분야에서 공헌한 연구자 중 한명인 Fei-Fei Li 교수의 ImageNet에 대해서도 간단히 소개해 보았다. 이외에 데이터셋이 필요한 경우, Kaggle 등을 방문해 학습에 필요한 데이터를 얻을 수 있다.

레퍼런스

2017년 6월 7일 수요일

홀로렌즈 개발 환경 설정 및 앱 개발 방법

MR(mixed reality) 분야에서 이슈가 되고 있는 홀로렌즈 개발 환경 설정 방법과 앱 개발 방법을 간단히 알아본다. 아울러, 홀로렌즈를 사용해 본 느낌, 장단점을 나눔한다. MR은 VR(virtual reality), AR(augmented reality)를 합쳐놓은 혼합 환경을 말한다. 아래는 MR에 대한 영상이다.
MR 소개 영상(마이크로소프트 홀로렌즈. Microsoft Hololens)

MR분야에서 홀로렌즈보다 구글 글래스가 먼저 이슈화가 되었지만, 여러가지 문제점들이 있어, 현재는 홀로렌즈가 관심을 받고 있다(본인은 곧 구글이나 HTC같은 업체에서 더 싸고 좋은 MR기기를 출시하리라 기대한다ㅎ). 

1. 홀로렌즈 스펙
홀로렌즈는 웬만한 PC스펙을 자랑한다.
  • 윈도우 10 내장, 인텔 32비트 CPU, 2GB 메모리, 64GB 저장공간, 200만 화소 카메라, 홀로그래픽스 프로세싱 유닛(holographic processing unit), 마이크 4개, 외부 스피커, 광센서, IEEE802.11ac 무선랜 지원, 배터리 2~3일 연속 사용, 무게 579g
  • 무료 홀로렌즈 개발자 도구, 유니버셜 윈도우 앱 지원

2. 홀로렌즈 개발 설정
홀로렌즈로 개발을 하기 위해서는 개발자 모드로 장치를 설정해야 한다.
설정 한 후에는 홀로렌즈 고유 네트워크 IP를 통해, 홀로렌즈에서 보이는 화면을 크롬과 같은 인터넷 탐색기로 확인할 수도 있고, 홀로렌즈에 필요한 앱도 설치 혹은 제거할 수 있다. 아래는 홀로렌즈 개발 설정 영상이다.
영상을 보고 하나씩 따라 하면 된다. 
HoloLens setup 순서

홀로렌즈는 제스춰 및 음성 인식을 통해, 클릭, 윈도우 메인 메뉴 호출, 앱 실행, 앱 화면 크기 조정 등을 수행할 수 있다. 

홀로렌즈 자체가 윈도우 10에서 실행되는 컴퓨터이므로, WiFi가 연결되어 있다면, 앱을 다운로드 받고, 홀로렌즈에서 실행하거나, 인터넷을 검색하고, 유튜브를 감상하는 것들이 자유롭다.

3. 개발환경 설치 순서
홀로렌즈 앱 개발은 Visual Studio 2015 Update 3혹은 Visual Studio 2017 버전이 필요하다. 홀로렌즈는 윈도우 10 환경에서 운영되며, 앱도 윈도우 앱 마켓에서 다운로드 받는 식으로 설치된다. 다만, 개발자가 개발한 앱은 손쉽게 홀로렌즈에 원격 컴퓨터로 설치할 수 있다. 개발 환경 설치 순서는 Install the tools 문서에 잘 나와 있다. 주요 설치 순서는 다음과 같다.

2. Visual Studio 2015 Update 3, Visual Studio 2017 (Community, Professional, Enterprise)이상 설치
3. HoloLens Emulator 설치(옵션. 윈도우10 프로페셔널 이상 가능)
4. 최신버전 Unity 설치
5. Vuforia 설치

HoloLens Emulator는 직접 홀로렌즈 장치에 개발한 앱을 올릴때는 필요 없다. Emulator로 테스트해 보려면, 윈도우10 프로 버전 이상만 설치 가능하다. Emulator가 윈도우 10프로 이상에서 설치되는 가상머신인 Hyper-V를 사용하기 때문이다.

앞의 2단계를 제대로 설정하였다면, 크롬에서 홀로렌즈 화면을 미러링해 볼 수 있다. 이외에 다음과 같은 미러링 방법이 있으니 참고하길 바란다.
본인은 Device Portal for HoloLens 문서에서 설명한 대로, 홀로렌즈를 설정해 보았다. 다음과 같이, 윈도우 익스플로어의 Windows Device Portal 에서 Mixed Reality Capture의 Live preview화면이 보이면 성공한 것이다. 
Mixed Reality Capture 화면(왼쪽 위에 윈도우 10 메뉴가 홀로렌즈를 통해 보인다)

왼쪽 메뉴는 Apps 설치/삭제 관리, 3D View, 성능 및 프로세스, 파일 탐색기, 시뮬레이션, 네트워크, 가상 입력 등의 기능을 제공한다.
Apps 메뉴 화면

4. 유니버셜 앱 개발 예
홀로렌즈는 윈도우 10 운영체제에서 실행된다. 그러므로, Universal Windows Platform (UWP)위에서 실행되는 유니버셜 앱 유형으로 개발해야 한다. 개발 방법은 다음 링크에 잘 설명되어 있다. 그대로 따라해 보면 개발이 그리 어렵지 않다. 
위 문서에 따른 개발 순서는 크게 다음과 같이 진행된다.

1. 홀로렌즈 전원을 켠다.
2. Visual Studio 2017을 실행한 후, 다음과 같이 유니버셜 앱 프로젝트를 하나 생성한다. 

3. 앱 메인 윈도우에 적당히 텍스트 박스와 버튼 등을 디자인해 넣는다.

4. 프로젝트 빌드 시 원격 컴퓨터 설정하고, 앱 빌드 후 홀로렌즈에 앱을 배포한다.
5. 홀로렌즈 화면에 유니버셜 앱이 다음과 같이 실행된다.

6. 윈도우 익스플로어의 Windows Device Portal 에서 Mixed Reality Capture의 Live preview화면을 보면, 홀로렌즈 화면을 PC, 노트북에서 확인할 수 있다. 

5. Unity 개발 예
Visual Studio에는 Unity 프로젝트 템플릿이 포함되어 있어, Unity 앱을 개발하기 용이하다. Unity 또한 Hololens 앱 타입으로 Visual Studio 프로젝트 파일과 리소스를 생성할 수 있도록 되어 있어, 개발이 편리하다. Unity App 개발 방법은 다음 링크에 잘 설명되어 있다.
앞의 Holograms 100 프로젝트를 빌드한 후 홀로렌즈에 앱을 설치하고, 3차원 큐브가 홀로렌즈를 통해 보인다면 성공한 것이다. 다음은 Holograms 100 에 설명한 예제를 바탕으로 텍스트 객체를 추가해 만든 홀로렌즈 Unity App이다.

1. Unity 앱 장면에 큐브와 텍스트 객체를 추가한다.

2. Unity 프로젝트를 다음과 같이 설정하고 빌드해, Visual studio solution 파일을 생성한다.

3. 생성된 Unity 프로젝트의 Visual studio solution 파일을 Visual Studio 2017에서 로딩한다.

4. 유니티 앱을 빌드하고, 홀로렌즈에 배포한다. 그럼, 다음과 같이, 홀로렌즈 화면에 유니티 앱이 자동 실행된다. 주변 공간을 자동으로 인식해, 고정된 좌표에서 3차원 객체가 떠 있는 것을 확인할 수 있다.

시간이 있다면, 아래의 Galaxy Explorer 소스 코드를 GitHub에서 다운로드 후 빌드해 본다.

6. BIM 모델 뷰어 앱 개발 예
3차원 건축모델(BIM. Building Information Modeling)을 홀로렌즈 Unity 앱으로 띄워보자. 간단한 작업 순서는 다음과 같다. 상세한 개발 과정은 Holograms 100 문서를 참고한다.

1. BIM 모델러(예. Revit 등)에서 샘플 건물 모델을 로드하다.

2. 건물모델을 FBX 등 메쉬파일로 Export 한다.
3. Export한 메쉬파일을 Unity 프로젝트 창으로 Drag&Drop하여, Asset으로 추가한다.

4. Asset으로 추가된 메쉬를 장면에 추가하고, 메쉬의 위치, 방향 등을 설정한다.
5. 유니티 앱을 홀로랜즈 앱 유형으로 빌드한다. Visual Studio 솔류션 파일이 생성된다.
6. 생성된 Visual Studio 솔류션 파일을 빌드하여, 홀로렌즈에 배포한다.

7. 홀로렌즈에서 다음과 같이 자동으로 앱이 실행된다.


참고로, 아래는 스케치업에서 다운로드 받은 건축물, 도시 모델을 Unity로 import하고, 앱으로 만든 경우이다(참고 - Exporting from Sketchup to Unity3D tutorial). 스케치업에서는 텍스쳐 적용이 쉽고, OBJ, DAE 메쉬파일 저장 시 텍스터 파일이 함께 저장된다. 

스케치업 House 모델
유니티 House DAE 메쉬 모델 Import
스케치업 도시 모델
유니티 도시 DAE 메쉬 모델 Import
홀로렌즈에서 띄워 본 건축 및 도시 모델

7. 홀로렌즈 장단점
홀로렌즈를 활용해 간단히 개발해 본 후 느낀 장단점을 적어본다.

장점은 다음과 같다.
1. 공간 자동 인식/생성: 센서에서 스캔된 3차원 점군에서 3차원 공간 메쉬 실시간 구성함
2. 안정적인 가상 객체의 공간 상 절대 좌표 변환: 헤드셋 움직임에도 객체가 떨리지 않음
3. 꽤 괜찬은 혼합 현실 몰입도: 실내에서 선명하게 보이는 혼합 현실
4. 높은 윈도우 10 기반 앱 호환성: 모든 UWP 앱 설치 및 실행 가능
5. 빠른 3차원 객체 렌더링 속도
6. 객체 음원 중심적인 사운드: 인식된 공간 왼쪽에 장작불이 타고 있다면, 본인이 회전할 때, 달라진 음원의 위치를 계산해, 사운드를 출력해 줌
7. Visual Studio 기반 편리한 개발 환경

단점은 다음과 같다.
1. 좁은 스크린 화면: 40인치 TV 정도 크기의 스크린으로 느껴짐. 좀 답답함.
2. 아직은 무겁고 불편한 헤드셋 마운트: 오래 착용하면, 머리가 아픔
3. 아직은 조작이 불편한 제스춰 및 보이스 인식: 입력에는 아직 키보드가 최고
4. 원활한 사용을 위해서는 WiFi 네트워크가 필요함
5. 3차원 어지럼증
6. 밝은 곳에서는 렌더링 객체가 선명히 보이지 않음: 야외에서는 활용이 어려울 듯 함
7. 3차원 표현을 위해 무거운 게임 엔진을 사용해야 함: 홀로렌즈 효과를 얻기 위해 기존 앱은 게임엔진으로 포팅해야 할 지도 모름
8. 앱 개발을 위해, 3D 프로그래밍과 공간 맵핑에 대한 이해가 필요함
9. 대중화되기에는 비싼 가격: 오큘러스 리프트의 대략 3배 이상되는 부담스러운 가격

참고로, 오큘러스 리프트를 사용해 보았을 때도 2, 4, 7과 같은 단점은 있었다.
홀로렌즈의 단점에서 불구하고, 마이크로소프트가 기존 가상현실 기술들과 차원이 다른 가상현실의 발전 방향과 미래를 보여 줬다는 면에서 훌륭한 기술이라 생각한다.

앞으로 건설, 건축 분야에서 다양한 응용이 기대된다.



8. 홀로렌즈 개발 시 이슈들
시작이 반인 MR장치라, 개발자 그룹에서 원성이 자자한 이슈들이 있다.ㅎ 경험상, 이런 지뢰들은 반듯이 겪는다고 생각하고, 미리 피해가야 정신 건강에 이롭다.

레퍼런스
아래는 개발 시 참고할 만한 레퍼런스이다.