넥스젠(NexGen)의 스타쿼리(* Query) 기능

스타쿼리는 와일드 카드 문자 중에서 모든 것을 의미하는 스타(*) 문자의 의미대로 위치기반 데이터 중 사용자가 검색하고자 하는 모든 것(*)을 하나의 기능만으로 쉽고 빠르며 정확하게 검색할 수 있는 기능입니다.

스타쿼리를 이용하여 행정구역명, 도로명, 지번주소, 도로명주소, 건물명을 기본적으로 검색할 수 있습니다. 또 여기에 사용자가 검색하고자 하는 특화된 데이터를 추가하여 검색할 수 있도록 커스터마이징이 가능합니다.

스타쿼리의 사용자 인터페이스(UI)는 아래처럼 매우 심플합니다.

검색 대상의 종류마다 상이한 UI가 아닌 공통된 하나의 UI만을 제공함으로써 사용자는 부차적인 기능을 학습할 필요없이 원하는 것을 바로 얻을 수 있습니다.

이 UI에 사용자가 검색하고자 하는 키워드를 입력하고 엔터 또는 검색 버튼을 클릭하기만 하면 연관된 결과가 검색창에 표시됩니다. 아래처럼요~!

검색 결과 중 하나를 선택하면 선택된 항목의 위치로 지도가 빠르게 이동합니다. 검색 결과는 물론 지도의 이동까지 너무 빠르죠~! 넥스젠은 사용자가 어떤 결과를 얻기 위해 기다리는 상황이 생기지 않습니다~!

아래는 스타쿼리에 대한 기능을 동영상으로 소개하고 있습니다.

위의 기능 소개에 대한 동영상을 보시면, 도로명 주소와 지번 주소에 대한 위치 검색은 지오코딩(Geocoding) 서비스를 활용합니다. 이 지오코딩 서비스 역시 NexGen의 한 기능이며, 완전한 표준주소가 아닌 불완전한 주소의 경우에도 주소정제(Address Cleaning)을 통해 위치 검색이 가능합니다.

Python과 OpenCV – 55 : Haar Cascades를 이용한 얼굴 검출

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

Haar 특징기반 다단계 분류자(Feature-based Cascade Classifiers)를 이용한 물체 검출은 Paul Viola와 Michael Jones이 2001년에 발표한 논문, “Rapid Object Detection using a Boosted Cascade of Simple Features”에서 제안된 효과적인 물체 검출 방법입니다. 이는 검출할 대상이 되는 물체가 있는 이미지와 없는 이미지(각각을 Positive Image와 Negative Image라고 함)을 최대한 많이 활용해서 다단계 함수를 훈련시키는 기계학습 방식입니다.

이 글에서는 검출할 대상을 얼굴로 시험해 봅니다. 처음에, 이 알고리즘은 분류자(Classifier)를 훈련시키기 위해 매우 많은 훈련용 이미지가 필요합니다. 이 이미지는 앞서 언급한 Positive와 Negative 이미지입니다. 다음 단계는 특징들(Features)를 추출해야 합니다. 이를 위해, 아래 그림에서의 Haar 특징이 사용됩니다. 이는 마치 컨볼루션 커널(Convolutional Kernel)과 같습니다. 각 특징은 하얀색 사각형에서의 픽셀값의 합을 검정색 사각형 영역의 픽셀값의 합에서 뺀 값입니다.

많은 특징을 계산하기 위해 이미지에 적용할 각 커널에 대한 모든 가능한 크기와 위치를 고려해야 합니다. 얼마나 많은 계산이 필요할까요? 24×24 크기의 이미지만 해도 160000개 이상의 특징 결과값이 존재합니다. 각 특징을 계산하기 위해서, 하얀색 영역과 검정색 영역의 픽셀의 합을 얻어야 합니다. 이를 위해, 논문에서는 인테그랄(Integral) 이미지를 언급합니다. 이 이미지는 픽셀의 합의 계산을 단순화 시켜 줍니다. 이는 알고리즘 속도를 매우 빠르게 개선시켜 줍니다.

그러나 이렇게 계산된 모든 특징들 중 대부분이 적합하지 않습니다. 예를들어, 아래의 이미지를 봅시다. 첫번째 선택된 특징은 눈 영역이 코나 뺨 영역보다 자주 어둡다라는 점에 주목합니다. 두번째 선택된 특징은 눈이 미간보다 어둡다는 것에 주목합니다. 그러나 뺨이나 다른 곳에 적용된 동일한 이미지는 부적절합니다. 그래서 160000개 이상의 특징 중 최고의 특징을 어떻게 선택해야 할 것인가가 문제입니다. 이 문제를 Adaboost가 해결해 냈습니다.

이를 위해서, 모든 훈련 이미지들에 대해 각각의 모든 특징을 적용합니다. 각 특징에 대해, 이미지 상에 얼굴이 있는지 없는지(즉 positive 또는 negative 인지)를 분류될 최고의 임계값을 찾습니다. 그러나 명백하게, 여기에는 오류와 잘못된 분류가 존재합니다. 최소한의 에러율로 특징을 선택하는데, 이는 얼굴 이미지인지, 얼굴이 아닌 이미지인지를 아주 잘 분류한다는 것을 의미합니다. (이런 처리는 말처럼 간단하지 않습니다. 각 이미지는 시작부터 동일한 가중치가 주어집니다. 각각의 분류 후에, 잘못 분류된 이미지의 가중치는 증가합니다. 그러면 다시 같은 절차가 수행됩니다. 새로운 에러율이 얻어지거나 원하는 개수의 특징점이 발견됩니다.)

최종 분류자는 이러한 약한 분류자 들의 가중치 합입니다. 약하다고 하는 이유는 분류자 하나만으로 이미지를 분류하지 못하고 분류자들이 모여야만 제대로 이미지를 분류할 수 있기 때문입니다. 논문에서는 200개의 특징만으로도 90%의 정확도를 제공한다고 합니다. 최종적으로 약 6000개의 특징점이 도출됩니다. (160000개 이상의 특징점이 6000개의 특징점으로 줄었다는 것은 엄청난 이득입니다.)

이제 이미지를 취합니다. 각 24×24 크기의 윈도우를 잡습니다. 6000개의 특징을 이미지에 적용합니다. 얼굴이 있는지 없는지 검사합니다. 그런데, 이 방식은 시간 소모도 많고 비효율적입니다. 논문의 저자는 이를 위해 좋은 해결책을 제시합니다.

이미지에서, 이미지 영역의 대부분은 얼굴이 아닌 영역입니다. 이 생각은 윈도우가 얼굴 영역이 아닌지를 검사하는 간단한 방법이 됩니다. 만약 얼굴 영역이 아니라면, 이를 단번에 버립니다. 다시 수행하지 않습니다. 대신 얼굴이 있는 곳의 영역에 초점을 맞춥니다.

이를 위해 논문의 저자들은 다단계 분류자(Cascade of Classifiers)의 개념을 소개합니다. 윈도우에 대한 6000개의 모든 특징을 적용하는 대신, 분류자의 다른 단계로 특징을 묶고 하나씩 하나씩 적용하는 것입니다. (보통 처음 몇몇 단계는 매우 적은 수의 특징을 갖습니다.) 만약 윈도우가 첫번째 단계에서 실패하면 버립니다. 나머지 특징들은 더 이상 고려하지 않습니다. 만약 통과되면 특징의 두번째 단계를 정용하고 이를 계속 반복합니다. 모든 단계를 통과한 윈도우가 얼굴 영역이 됩니다!!

이제 이 Haar Cascades 알고리즘을 구현한 OpenCV의 함수를 이용해 얼굴을 검출해 보는 코드를 살펴 보겠습니다.

OpenCV는 트레이너와 검출자 모드를 제공합니다. 차량이나 비행기와 같은 물체 검출을 위한 자신만의 훈련이 필요하다면 OpneCV를 이용해 훈련시킬 수 있습니다. (참조 : https://docs.opencv.org/2.4/doc/user_guide/ug_traincascade.html)

이 글에서는 검출에 대해서만 설명합니다. OpenCV에서는 이미 얼굴, 눈 등에 대한 미리 훈련된 데이터를 XML 파일 형식으로 제공합니다. 바로 이 XML 분류자 파일을 로드 하는 것이 시작입니다. 그리고 분류할 이미지를 Grayscale로 로드하는 것입니다.

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

face_cascade = cv2.CascadeClassifier('./data/haar/haarcascade_frontface.xml')
eye_cascade = cv2.CascadeClassifier('./data/haar/haarcascade_eye.xml')

img = cv2.imread('./data/haar/img.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

다음은 입력 이미지에서 얼굴을 검출하는 것입니다. 만약 얼굴을 발견하면, 발견한 얼굴에 대한 위치를 Rect(x,y,w,h) 형태로 얻을 수 있습니다. 이 위치를 얻었다면, 얼굴에 대한 ROI를 만들고, 이 안에서 눈을 검출합니다. (눈은 얼굴 안에 있으니까요!)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)

for (x,y,w,h) in faces:
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(roi_gray)
    for (ex,ey,ew,eh) in eyes:
        cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

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

아이언맨의 얼굴과 캡틴아메리카의 얼굴은 검출되지 못했는데, 이는 훈련 데이터가 정면에서 본 얼굴에 대한 이미지들로 만들어졌기 때문입니다.

Python과 OpenCV – 54 : 이미지 복원(Image Inpainting)

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

이 글에서는 오래된 사진의 작은 흡집 등을 제거하는 방법인 Inpainting에 대해 살펴보고, 이를 OpenCV의 예제를 통해 살펴 보겠습니다.

아래의 이미지의 왼쪽처럼 오래된 훼손된 사진에는 작은 흠집이 있는데, 이 흠집을 제거하고 원래의 사진으로 복구할 수 없을까요? 단순하게 이 흠짐을 사진에서 지우는 것만으로는 충분하지 않습니다. 이런 경우 Image Inpainting이라는 기술을 사용할 수 있습니다. 기본 아이디어는 단순합니다. 흠집에 위치한 픽셀을 그 이웃 픽셀을 가져와 교체하는 것입니다.

이런 목적을 위해 설계된 여러가지 알고리즘이 있으며, OpenCV에서는 2가지를 제공합니다. 이 2가지 모두 cv2.inpaint() 함수를 통해 활용할 수 있습니다.

첫번째 알고리즘은 2004년 Alexandru Telea의 논문 “An Image Inpainting Technique Based on the Fast Marching Method”에 기반합니다. 이 알고리즘은 Fast Marching Method에 기반합니다. 복원(Inpainting)하고자 하는 영역에 대해 생각해 봅시다. 이 알고리즘은 먼저 이 영역의 경계로부터 시작하며 영역 내부를 영역의 외곽선부터 시작해 점진적으로 채웁니다. 복원될 곳의 픽셀 주위의 이웃(인근)에 작은 범위의 픽셀을 구합니다. 구해진 픽셀들의 정규화된 가중치 합으로 계산되어진 값으로 복원될 픽셀이 교체됩니다. 가중치 값의 선택은 매우 중요한 문제입니다. 복원 지점에 가깝게 놓여져 있을수록 더 많은 가중치값이 주어집니다. 일단 하나의 픽셀이 복원되면, Fast Matching Method를 사용해 가장 가까운 다음 픽셀로 이동합니다. 이 알고리즘은 cv2.INPAINT_TELEA 플래그를 사용해 활성화됩니다.

두번째 알고리즘은 2001년의 논문인 “Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting”에 기반합니다. 이 알고리즘은 부분 편차 방정식과 유체역학에 기반합니다. 기반 이론은 휴리스틱 방식이며 cv2.INPAINT_NS 플래그를 활성화하여 이 알고리즘을 사용합니다.

이제 2가지의 알고리즘을 OpenCV의 함수를 통해 살펴보겠습니다. 먼저 복원하고자 하는 영역에 해당하는 픽셀에 0이 아닌 값을 담고 있는 마스크 이미지가 필요한데, 이 이미지는 입력 이미지와 크기가 동일해야 합니다. 먼저 cv2.INPAINT_TELEA 프래그에 대한 예제입니다.

import numpy as np
import cv2

img = cv2.imread('./data/inpainting_input.png')
mask = cv2.imread('./data/inpainting_mask.png',0)

dst = cv2.inpaint(img,mask,3,cv2.INPAINT_TELEA)

cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

cv2.INPAINT_NS에 대한 알고리즘은 7번 코드의 함수의 인자를 변경하기만 하면 됩니다. 아래의 이미지는 복원되어질 이미지와 마스크 이미지 그리고 2가지 알고리즘에 수행 결과를 모두 표시한 것입니다.

4개의 이미지 중 좌하단의 이미지는 cv2.INPAINT_TELEA에 대한 결과이며, 우하단의 이미지는 cv2.INPAINT_NS의 결과 입니다.

Python과 OpenCV – 53 : 이미지에서 잡음 제거(Image Denoising)

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

이 글에서는 이미지 안의 잡음을 제거하기 위해 Non-local Means 잡음제거 알고리즘에 대해 살펴봅니다. 이를 위한 OpenCV에서 제공하는 cv2.fastNlMeansDenoising(), cv2.fastNlMeansDenoisingColored() 등과 같은 함수들의 차이점에 대해서도 살펴봅니다.

이전 글에서, 가우시안 블러링, 메디안 블러링 등과 같은 이미지를 부드럽게 만드는 기법에 대해 살펴 보았으며, 이런 연산이 어느 정도의 잡음을 제거할 수 있었음을 보였습니다. 이들 방식은, 픽셀 주위의 다른 픽셀들 몇개를 취해 값들의 평균값등으로 원래의 값을 변경합니다. 요약하면, 이런 방식의 잡음 제거는 자신의 이웃에 한해서만 이루어집니다.

잡음에는 어떤 성질이 있습니다. 잡음은 일반적으로 제로 평균(Zero Mean)을 가진 무작위로 생겨납니다. 잡음 픽셀, 을 살펴봅시다. 는 픽셀의 참 값이고 은 그 픽셀의 잡음입니다. 다른 이미지들에서 동일한 픽셀들을(이라 합시다) 많이 취해서, 이들의 평균을 계산합니다. 이상적으로는 잡음의 평균은 0이므로 이여야 합니다.

간단한 설정을 통해 이것을 확인할 수 있습니다. 고정 카메라를 어떤 위치에 몇초간 놓습니다. 그러면 동일한 장면에 대한 많은 이미지 또는 프레임을 얻을 수 있겠죠. 그러고는 찍힌 모든 프레임의 평균을 구하는 코드를 작성합니다. 첫번째 프레임과 최종 결과를 비교해 봅시다. 잡음이 감소된 것을 볼 수 있을 것입니다. 불행히도 이 간단한 방법은 카메라와 움직이는 장면에 대해서는 적용하기 어렵습니다.

아이디어는 간단한데, 비슷한 이미지에 대한 잡음의 평균값들이 필요합니다. 이미지에서 작은 윈도우(5×5 크기로 합시다)를 고려해 봅시다. 하나의 이미지 안에는 동일한 조각이 다른 위치 여러 곳에 존재할 가능성이 큽니다. 때때로 조각 근처에 나타날 수 있습니다. 이러한 유사한 조각들을 활용해서 이들의 평균을 구한다면 어떨까요? 아래의 그림을 봅시다.

이미지에서 파란색 조각들은 유사해 보입니다. 파란색 조각도 유사해 보이죠. 이러한 픽셀을 취해, 이 픽셀 주위에 작은 윈도우를 만들어, 이미지에서 유사한 윈도우 영역을 찾아서, 모든 윈도우들의 평균을 내고, 그 평균 결과로 픽셀을 교체합니다. 이 방법이 NonLocal Means Denosing(잡음제거)이라고 합니다. 앞서 봤던 블러링(Burring) 기법과 비교하면 시간은 더 소요되지만, 결과는 매우 좋습니다.

칼라 이미지에 대해서, 이미지를 CIELAB 칼라공간(Colorspace)로 변경하고 L과 AB 요소에 대해 별도로 잡음을 제거 합니다.

이런 방법에 대해서 OpenCV에서는 4가지 방식을 제공합니다.

  1. cv2.fastNlMeansDenoising() – 그레이 이미지 하나에 대해서만 작동함
  2. cv2.fastNlMeansDenoisingColored() – 칼라 이미지 하나에 대해서 작동함
  3. cv2.fastNlMeansDenoisingMulti() – 짧은 시간 동안 찍힌 여러 개의 이미지에 대해서 작동하며 그레이 이미지여야 함
  4. cv2.fastNlMeansDenoisingColoredMulti() – 짧은 시간 동안 찍한 여러 개의 이미지에 대해서 작동하며 칼라 이미지에서 작동함

위 함수들의 공통 인자는 다음과 같습니다.

  • h : 필터 강도를 결정하는 인자. 더 높은 h 값이 잡음을 더 잘 제거하지만 잡음이 아닌 픽셀도 제거함(10이면 적당함)
  • hForColorComponents : h와 동일하지만, 칼라 이미지에 대해서만 사용됨(보통 h와 같음)
  • templateWindowSize : 홀수값이여야 함(7을 권장함)
  • searchWindowSize : 홀수값이여야 함(21을 권장함)

이 글에서는 2번과 3번에 대한 예제를 살펴 봅니다.

1. cv2.fastNlMeansDenoisingColored()

앞서 언급했듯이 칼라 이미지로부터 잡음을 제거 하는데 사용됩니다. 코드는 아래와 같습니다.

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

img = cv2.imread('./data/die.png')

dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)

plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()

아래는 결과인데, 입력 이미지는 인 가우시안 잡음을 가지고 있습니다.

2. cv2.fastNlMeansDenoisingMulti()

이제 비디오에 대해서도 동일한 방법을 적용해 봅시다. 첫번째 인자는 잡음이 들어간 프레임 리스트입니다. 두번재 인자 imgToDenoiseIndex는 잡음을 제거할 프레임의 인덱스인데, 이를 위해 입력 리스트에 프레임 인덱스를 전달합니다. 세번째 인자는 temporalWindowSize으로 잡음 제거를 위해 사용할 인근 프레임의 개수입니다. 이 값은 홀수여야 합니다. 예제는 다음과 같습니다.

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

cap = cv2.VideoCapture('./data/TownCentreXVID.mp4')

# create a list of first 5 frames
img = [cap.read()[1] for i in range(5)]

# convert all to grayscale
gray = [cv2.cvtColor(i, cv2.COLOR_BGR2GRAY) for i in img]

# convert all to float64
gray = [np.float64(i) for i in gray]

# create a noise of variance 25
noise = np.random.randn(*gray[1].shape)*10

# Add this noise to images
noisy = [i+noise for i in gray]

# Convert back to uint8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]

# Denoise 3rd frame considering all the 5 frames
dst = cv2.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)

plt.subplot(131),plt.imshow(gray[2],'gray')
plt.subplot(132),plt.imshow(noisy[2],'gray')
plt.subplot(133),plt.imshow(dst,'gray')
plt.show()

결과는 다음과 같습니다.

계산에 소요되는 시간이 상당한데, 첫번째 이미지는 원본 프레임이고 두번째는 잡음을 넣은 것이며 마지막은 두번째 이미지에 대한 잡음을 제거한 것입니다.