FingerEyes-Xr에서 카카오맵 표시

구글이나, 네이버 및 카카오맵 등의 저작권을 가지고 있는 회사에서 제공하는 지도 API가 아닌 URL로 직접 가져와 활용하는 것은 저작권에 위배되는 일이긴 하지만, 몇몇 기관에서는 지도 회사와 계약을 맺어 URL을 통해 지도를 가져와 활용할 수 있는 경우가 있다. 이에 대한 요구 사항으로 웹 GIS 엔진인 FignerEyes-Xr에서 카카오맵의 지도를 URL로 가져와 표시하는 예제를 정리한다.

먼저 필요한 DOM 요소를 다음과 같이 정의한다.




    
    

    


    

#mapDiv가 지도가 표시되는 DOM 요소이고, 이 DOM의 스타일은 다음과 같다.


그리고 스크립트는 다음과 같다.


실행해보면 다음과 같이 지도가 표시된다.

로드맵도 연계를 해줘야 하는데.. 이처럼 잘만들어진 지도를 가져다 붙여 사용자가 원하는 기능을 빠르게 제공하는 것도 참 좋은 것 같다.

Python과 OpenCV – 49 : SVM(Support Vector Machines)

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_ml/py_svm/py_svm_basics/py_svm_basics.html 입니다.

선형으로 분리될 수 있는 데이터

빨간색과 파란색에 대한 두 종류의 데이터에 대한 아래의 그림을 봅시다. kNN에서는, 테스트 데이터에 대해서, 훈련을 위한 전체 데이터와의 거리를 측정하여 최소 거리를 가지는 것 하나를 구했습니다. 이는 훈련 데이터 전체를 저장하고 모든 거리를 구해야 하므로 메모리를 많이 활용하고, 계산 시간도 많이 소요됩니다. 그러나 아래의 이미지처럼 주어진 데이터에 대해도 이처럼 많은 자원이 필요할까요?

생각을 바꿔서, 두 영역으로 데이터를 분리하는 어떤 선, 가 있습니다. 새로운 시험 데이터 가 있다면, 이를 에 대입해서, 이면 파란색 그룹에 속하고, 그렇지 않다면 빨간색 그룹에 속한다고 할 수 있습니다. 이러한 선을 결정 경계(Decsion Boundary)라고 부를 수 있습니다. 이는 매우 단순하며 메모리 효율성이 높습니다. 직선(또는 더 높은 차원에서는 평면)으로 두 영역으로 나눌 수 있는 이러한 데이터를 선형으로 나눠질 수 있다라고 합니다.

위의 이미지에서, 이러한 선은 매우 많습니다. 이 중 어떤 선을 선택해야 할까요? 매우 직관적으로, 모든 데이터로부터 가능한 멀리 떨어져 통과하는 선이라고 할 수 있습니다. 왜냐면, 입력 데이터에 잡음이 있을 수 있기 때문입니다. 이런 선은 분류 정확도에 영향을 줘서는 안됩니다. 가정 멀리 떨어진 선을 사용하는 것은 잡음에 대해 더 강하다는 의미입니다. 그래서 SVM(Support Vector Machines)이란 이러한 작선(또는 평면)을 얻는 것입니다. 아래의 그림에서 굷은 선이 바로 그 것입니다.

결정 경계(Decision Boundary)을 찾기 위해서는 훈련 데이터(Training Data)가 필요합니다. 그렇다면 전체 훈련 데이터가 필요할까요? 단지 상대 그룹과 가까운 것들만으로도 충분합니다. 위의 그림에서는 하나의 파란색 원과 두개의 빨간색 사각형이면 충분합니다. 바로 이 데이터를 지지 벡터(Support Vectors)라고 하며, 이 데이터를 통과하는 선들을 지지 평면(Support Planes)이라고 합니다. 이 것은 결정 경계를 구하는데 충분한 기반이 됩니다.

가장 멋진 지지 평면을 찾기 위한 수학적인 모델을 생각해 봅시다. 예를 들어서, 파란색 데이터는 로 나타낼 수 있는 반면, 빨간색 데이터는 로 나타낼 수 있습니다. 는 가중치 벡터(Weight Vector)()이며, 는 픽쳐 벡터(Feature Vector)()입니다. 는 편향(Bias)입니다. 가중치 벡터는 분리 경계의 방향을 결정하며, 편향은 위치를 결정합니다. 결정 경계는 이러한 평면 사이의 중앙을 지나는 것으로 정의될 수 있고, 로 표현할 수 있습니다. 지지 벡터에서 결정 경계 사이의 최소한의 거리는 로 주어집니다. 이 거리의 2배가 위 그림에서의 빈공간(Margin)이며, 이 빈공간이 최대화되어야 합니다. 예를들어, 아래와 같은 제약조건을 가지는 새로운 함수 를 최소화할 필요가 있습니다.

위의 식에서 인 각 군(class)의 라벨입니다.

비선형으로 분리할 수 있는 데이터

직선으로 분리할 수 없는 어떤 데이터를 생각해 봅시다. 예를 들어서, ‘X’가 -3과 +3이고 ‘O’가 -1과 +1인 1차원 데이터 말입니다. 이 데이터는 선형으로 분리될 수 없음이 명백합니다. 그렇지만 이러한 문제를 해결할 수 있는 방법이 존재합니다. 이 데이터를 함수를 통해 ‘X’는 9가 되고, ‘O’는 1이 되어 선형으로 분리되어 집니다.

반면에 1차원 데이터를 2차원 데이터로 변환할 수 있습니다. 함수를 이용해 ‘X’는 (-3,9)와 (3,9)가 되고 ‘O’는 (-1,1)과 (1,1)이 되며, 이는 선형으로 분리됩니다. 요약하면, 낮은 차원 공간에서의 비선형으로 분리되어지는 데이터는 더 높은 차원 공간에서는 선형으로 분리될 수 있는 더 많은 가능성이 있습니다.

일반적으로 D>d일때, d차원 공간에서의 포인트들이 D-차원 공간으로 맵핑될때 선형으로 분리될 가능성이 높아집니다. 여기에 낮은 차원으로 입력된 (피쳐;feature) 공간에서의 계산으로 높은 차원 (커널;kernel) 공간에서 내적(dot product)를 계산하는데 도움이 되는 방법이 존재합니다. 다음의 예로 설명해 보겠습니다.

2차원 공간에서 2개의 포인트(, )를 생각해 봅시다. 를 2차원에서 3차원 공간으로 맵핑하는 함수라고 할때 아래와 같습니다.

커널 함수 를 아래와 같이 정의하면, 이 함수는 두 포인트 사이의 내적(dot product)입니다.

이는, 3차원 공간에서의 내적은 2차원 공간에서의 내적의 제곱이라는 의미입니다. 이를 더 높은 차원의 공간에서 적용할 수 있습니다. 그래서 더 낮은 차원에서의 데이터를 그대로 더 높은 차원으로 계산할 수 있습니다. 일단 이렇게 맵핑되면, 더 높은 차원의 공간에서의 데이터를 얻을 수 있습니다.

지금까지의 모든 개념에 덧붙여, 여기에는 잘못된 분류에 대한 문제가 존재합니다. 그래서 단순히 최대 빈공간(Margin)을 갖는 결정 경계(Decision Boundary)를 찾는 것만으로는 충분하지 않습니다. 잘못된 분류에 대한 문제도 함께 고려해야 합니다. 때때로, 더 나은 분류를 제공하는 더 작은 빈공간을 갖는 결정공간이 존재할 수 있습니다. 어째거나, 최대한의 빈공간을 가지면서 더 적은 분류 오류를 제공하는 결정 경계를 찾아야만합니다. 최소화된 기준은 다음과 같습니다.

아래 그림이 이러한 개념을 나타냅니다. 훈련 데이터의 각 샘플에 대해서, 새로운 파라메터 가 정의됩니다. 이 파라메터는 훈련 데이터와 수정된 결정 영역 사이의 거리입니다. 여기에는 잘못된 분류가 없는데, 지지 평면에 샘플 데이터가 옳바르게 떨어지고, 거리는 0입니다.

이제 새로운 최적화된 문제는 다음과 같습니다.

위의 식에서 인자 C는 어떻게 정해야 할까요? 이에 대한 대답은 시험 데이터가 어떻게 분포되었느냐에 따라 결정된다입니다. 비록 일반화된 방법은 없지만 다음과 같은 규칙을 따르면 좋습니다.

  • 큰 C 값은 분류에 대한 작은 오차를 제공하지만 빈공간(Margin)이 더 작아지게 된다. 최적화의 목표가 인자의 최소화에 있으므로, 약간의 분류 오차가 허용된다.
  • 작은 C 값은 더 큰 빈공간을 제공하지만 분류 시 더 많은 오차가 존재한다. 큰 빈공간을 가지는 평면을 찾는 것에 더 중요한 목표이므로 최소화는 고려되지 않는 경우이다.

Python과 OpenCV – 48 : kNN을 이용한 글자 인식(OCR)

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_ml/py_knn/py_knn_opencv/py_knn_opencv.html 입니다.

이 글은 손으로 그린 글자를 판독하는 기능에 대한 것입니다. 이를 위해 몇가지 훈련 데이터(train_data)와 시험 데이터(test_data)가 필요합니다. 아래와 같은 2000×1000 픽셀 크기의 digits.png 파일을 사용합니다.

이 이미지에는 손으로 작성한 5000개의 0~9까지의 문자가 담겨 있습니다. 문자 하나당 500개씩 기록되어 있으며, 가로와 세로로 각각 100개씩, 5개씩 표기되어 있습니다. 이미지에서 문자 하나가 차지하는 크기는 20×20 픽셀입니다. 가장 먼저 이 이미지에서 5000개의 문자 단위로 잘라내야 합니다. 그리고 이 20×20 픽셀 크기 문자 이미지를 400 크기의 단일 행으로 만듭니다. 이 데이터가 모든 픽셀에 대한 화소값을 가지는 피쳐셋(Feature Set)입니다. 우리가 생성할 수 있는 가장 단순한 피쳐셋입니다. 이 데이터에서 각 문자의 250개에 해당하는 부분은 train_data로 사용하고 나머지 250개는 test_data로 사용합니다.

이제 코드를 작성해 보면..

import numpy as np
import cv2

img = cv2.imread('./data/digits.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# Now we split the image to 5000 cells, each 20x20 size
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]

# Make it into a Numpy array. It size will be (50,100,20,20)
x = np.array(cells)

# Now we prepare train_data and test_data.
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)

# Create labels for train and test data
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()

# Initiate kNN, train the data, then test it with test data for k=1
knn = cv2.ml.KNearest_create()
knn.train(train, cv2.ml.ROW_SAMPLE, train_labels)
ret, result, neighbors, dist = knn.findNearest(test, k=5)

# Now we check the accuracy of classification
# For that, compare the result with test_labels and check which are wrong
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print(accuracy)

코드를 설명하면, 8번은 digits.png 이미지를 가로로 100개, 세로로 50로 잘라 조각내어 cells 변수에 저장하는데, 각각의 조각 이미지에는 문자 하나가 담겨 있습니다. 11번 코드는 다시 이 cells를 NumPy의 배열로 만들어 x 변수에 저장합니다. 14번 코드는 배열 x 중 절반을 학습 데이터로 사용하고 나머지 절반을 테스트 데이터로 사용하고자 각각 train과 test 변수에 담습니다. train 변수에 저장된 문자에 대해 0~9까지의 값으로 라벨링해줘야 하는데, 18-19번 코드가 그에 해당합니다. 바로 이 train 데이터와 train_labels 데이터가 학습 데이터라고 할 수 있습니다. 이렇게 학습된 데이터를 토대로 test 변수에 저장된 문자들이 0~9까지 중 무엇에 해당하는지 kNN 알고리즘으로 파악하는 것이 23~25번 코드입니다. 최종적으로 테스트 데이터가 정확히 인식되었는지 확인하는 코드가 29~32번 코드입니다. 출력값은 91.76인데, 즉 성공률이 91.76%라는 의미입니다.

인식 정확도를 개선하기 위해서는 인식이 실패한 데이터를 학습시켜 train 변수에 추가하고 다음에 이 변수를 재활용하는 것입니다. 이를 위해 파일에 저장하고 다음에 저장된 파일로부터 불러오는 함수가 필요합니다. 학습 데이터를 저장하는 예는 다음과 같습니다.

np.savez('knn_data.npz',train=train, train_labels=train_labels)

데이터 파일을 불러오는 예는 다음과 같습니다.

with np.load('knn_data.npz') as data:
    print data.files
    train = data['train']
    train_labels = data['train_labels']

Python과 OpenCV – 47 : kNN(k-Nearest Neighbour)의 이해

이 글의 원문은 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_ml/py_knn/py_knn_understanding/py_knn_understanding.html#knn-understanding 입니다.

kNN은 감독학습(Supervised Learning)을 통한 가장 단순한 분류 알고리즘 중에 하나입니다. 알고리즘에 대한 아이디어의 시작점은 공간 상의 시험 데이터와 가장 가까운 것들을 묶는다는 것입니다. 아래의 이미지를 통해 볼 수 있죠.

위의 이미지에는 파란색 사각형과 빨간색 삼각형으로 구성되어 있습니다. 즉 2개의 그룹이 존재합니다. 각 그룹을 클래스(Class)라고 부릅니다. 이제 새로운 요소(초록색 원)이 나타났습니다. 이 새로운 요소는 파란색 사각형으로 분류될까요, 아니면 빨간색 삼각형으로 분류 될까요? 이를 kNN 알고리즘을 활용해 보자는 것입니다.

한가지 방법은 이 초록색 원과 가장 가까운 이웃을 찾는 것입니다. 위의 그림에서는 빨간색 삼각형과 가장 가깝다는 것을 확인할 수 있습니다. 그래서 이 초록색 원은 빨간색 삼각형으로 분류됩니다. 이 방법은 오직 가장 가까운 이웃에 의한 분류이므로 단순히 ‘가장 가까운 이웃(Nearest Neighbour)’ 이라고 부릅니다.

그러나 여기에는 문제가 있습니다. 빨간색 삼각형이 가장 가까울 수 있습니다. 그러나 이 초록색 원 주위에는 아주 많은 파란색 사각형이 존재한다면 어떨까요? 그러면 파란색 사각형은 빨간색 삼각형보다 더 많은 영향력을 초록색 원에 주고 있다고 할 수 있습니다. 그래서 가장 가까운 것만으로는 충분하지 않습니다. 대신 어떤 k-Nearest 군이라는 개념을 검사해야 합니다. 그림에서, 3 Nearest 군, 즉 k=3이라고 합시다. 초록색 원과 가장 가까운 3(k값)개를 취합니다.그러면 2개의 빨간색과 1개이 파란색이 있고 초록색은 하나 더 많은 빨간색으로 분류됩니다. k=7이라면? 5개이 파란색과 2개의 빨간색이 존재하고 파란색으로 분류됩니다. k값을 변경하는 것이 전부입니다. 재미있는 것은 만약 k=4일때입니다. 이 경우 2개의 빨간색과 2개의 파란색이 존재합니다. 애매해지죠. 그래서 k값은 홀수로 잡는 것이 좋습니다. 이러한 분류 방법을 k-Nearest Neighbor이라고 합니다.

지금까지 언급한 kNN에서는 k개의 이웃 모두를 동일하게 다루고 있습니다. 이게 맞는 것일까요? 예를 들어, k=4인 경우에 분류가 애매해 진다고 했습니다. 그러나 좀더 살펴보면, 2개의 빨간색은 다른 2개의 파란색보다 좀더 가깝습니다. 그렇다면 빨간색에 더 많은 가중치를 줘야 합니다. 이를 수학적으로 어떻게 설명할까요? 가까운 요소들에 대해 그 거리에 따라 계산된 가중치를 줘야 합니다. 가까운 요소에는 더 높은 가중치를, 상대적으로 멀리 있는 요소에는 낮은 가중치를 말이죠. 결국 가장 높은 가중치의 합을 가지는 쪽으로 분류될 수 있는데, 이를 Modified kNN이라고 합니다.

여기에 몇가지 중요한 것을 발견할 수 있습니다.

  • 공간 상에 분포되는 빨간색과 파란색 요소 전체에 대한 정보가 필요합니다. 왜냐하면 새로운 요소와 이미 존재하는 각 요소 사이의 거리를 구해야 하기 때문입니다. 그래서 매우 많은 요소가 존재할 경우 더 많은 메모리와 더 많은 계산 시간이 필요할 것입니다.
  • kNN에는 어떤 형태의 훈련과 준비가 필요치 않습니다.

OpneCV에서 kNN을 살펴봅시다.

이 글에서는 먼저 앞서 살펴본 그림처럼 2개의 군으로 구성된 단순한 예를 활용하겠습니다. 다음 글에서 좀더 복잡한 예를 처리할 것이구요.

빨간색 군은 Class-0(0이라 표기)이라고 하고, 파란색 군은 Class-1(1이라고 표기)이라고 합시다. 25개의 요소를 새성하고 각 요소에 대해 Class-0과 Class-1 중 하나로 정합니다. 이를 위해 Numpy에서 난수 생성자가 유용합니다.

시각화는 Matplotlib가 유용합니다. 빨간색 군은 빨간색 삼각형으로, 파란색 군은 파란색 사각형으로 표시합니다.

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Feature set containing (x,y) values of 25 known/training data
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)

# Labels each one either Red or Blue with numbers 0 and 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)

# Take Red families and plot them
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')

# Take Blue families and plot them
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')

plt.show()

실행 결과는 다음과 같습니다. 난수를 사용했으므로 실행 결과는 매번 다릅니다.

다음으로 trainData와 responses 변수를 전달하여 kNN 알고리즘을 초기화 합니다(검색 트리가 구성됩니다).

이제 새로운 요소를 가져와 이 새로운 요소가 빨간색 또는 파란색 중 어디로 분류될지 OpenCV의 kNN을 이용합니다. kNN으로 가기 전에 먼저 새로운 요소, 즉 테스트 데이터에 대한 정보를 알아야 합니다. 이 테스트 데이터는 실수형 타입의 배열로써 크기는 입니다. 이제 새로운 요소와 가장 가까운 요소를 찾습니다. 가장 가까운 요소를 몇개까지 찾을 것인지 지정할 수 있습니다. 이러한 검색의 결과는 다음과 같습니다.

  • kNN 이론의 기반하여 새로운 요소에 Class-0과 Class-1과 같은 이름이 반환됩니다. 만약 매우 단순한 Nearest Neighbour 알고리즘을 원한다면 k=1로 지정합니다.
  • 새로운 요소와 각각의 가까운 요소 사이의 거리값이 반환됩니다.

지금까지의 설명에 대한 코드는 다음과 같습니다. 이 추가 코드는 앞의 코드 중 18번째 줄에 추가됩니다.

newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')

knn = cv2.ml.KNearest_create()
knn.train(trainData, cv2.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)

print("result: ", results)
print("neighbours: ", neighbours)
print("distance: ", dist)

실행 결과는 다음과 같습니다.

콘솔에 출력된 결과는 다음과 같구요.

result: [[0.]]
neighbours: [[0. 1. 0.]]
distance: [[ 32. 202. 261.]]

결과는 빨간색(Class-0)으로 분류되었다는 것과 새로운 요소(초록색 원)에서 가장 가까운 3개의 요소가 있으며, 검색된 3개의 요소는 빨간색 요소(Class-0)이 2개이고 파란색 요소(Class-1)이 1개이며 각각의 거리 값입니다.

만약 새로운 요소의 개수가 많다면 배열로 전달할 수 있으며, 각각의 분류 결과는 배열로써 얻어집니다. 아래는 10개의 새로운 요소에 대한 분류에 대해 앞의 코드를 대체하는 코드입니다.

newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
plt.scatter(newcomers[:,0],newcomers[:,1],80,'g','o')

knn = cv2.ml.KNearest_create()
knn.train(trainData, cv2.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomers, 3)

print("result: ", results)
print("neighbours: ", neighbours)
print("distance: ", dist)