Python과 OpenCV – 32 : Shi-Tomasi Corner Detection

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

Shi-Tomasi Corner Detection는 Harris Corner Detector를 기본으로 개선을 통해 좀더 정확한 Corner를 추출해 내는 알고리즘으로 cv2.goodFeaturesToTrack 함수로 구현되어 있습니다. 이에 대한 예제는 아래와 같습니다.

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

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

corners = cv2.goodFeaturesToTrack(gray,30,0.01,10)
corners = np.int0(corners)

for i in corners:
    x,y = i.ravel()
    cv2.circle(img,(x,y),3,255,-1)

plt.imshow(img),plt.show()

cv2.goodFeaturesToTrack 함수는 4개의 인자를 받는데, 첫번째는 특징점인 Corner를 추출할 입력 이미지로 1채널인 Grayscale 이미지를 받습니다. 두번째는 추출할 특징점의 개수이고 세번째는 특징점으로써의 최소 품질인데 품질의 범위는 0-1 사이값입니다. 이 최소 품질보다 낮으면 특징점으로써 버려집니다. 그리고 마지막 인자는 최소 거리로써 얻어진 특징점에서 이 최소 거리 이내의 특징점은 버려집니다. 아래는 위 예제의 결과입니다.

Python과 OpenCV – 31 : Harris Corner Detection

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

이미지의 특징점인 귀퉁이(Corner) 지점을 추출하는 알고리즘 중 하나인 Harris Corner Detection에 대한 예제를 살펴 봅니다.

import cv2
import numpy as np

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

gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)

img[dst>0.01*dst.max()]=[0,0,255]

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

결과는 아래와 같습니다. 입력 이미지에 대해 Corner로 인식되는 부분에 빨간색 점 표시가 되어 있습니다.

cv2.cornerHarris() 함수의 인자를 살펴보면, 첫번째는 입력 이미지로써 단일 채널 이미지(Grayscale)여야 하며 데이터 타입은 float32입니다. 두번째는 Corner 검출을 위한 알고리즘 수행 중 검사할 이웃 픽셀의 크기입니다. 세번째는 내부적으로 적용할 Sobel 필터링에 대한 인자(Apeture Parameter)입니다. 끝으로 네번째는 방적식의 Harris Detector의 자유변수입니다. cv2.corenrHarris() 함수의 결과로 입력 이미지와 동일한 크기의 2차원 배열이 생성되는데, Corner 지점은 이 배열의 값들 중 최대값의 0.01 배 이상인 지점만을 표시하기 위해 11번 코드가 사용됩니다.

Corner를 cv2.corenrHarris 함수를 통해 추출한 뒤, 이 추출 결과를 이용해 다시 좀더 정밀한 귀퉁이를 추출할 수 있습니다. 이를 SubPixel Accuraacy를 가지는 Corner이라고 합니다. 예제는 다음과 같습니다.

import cv2
import numpy as np

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

# find Harris corners
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
dst = cv2.dilate(dst,None)
ret, dst = cv2.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)

# find centroids
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)

# define the criteria to stop and refine the corners
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)

# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]] = [0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]

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

결과는 다음과 같습니다.

픽셀이 작아 눈에 보기 힘들 수 있지만, 빨간색의 픽셀이 앞서 언급한 cv2.cornerHarris 함수를 통해 추출한 Corner이고 초록색의 픽셀이 cv2.cornerHarris 함수를 통해 얻은 결과를 활용해 cv2.cornerSubPix 함수를 적용해 좀더 정확한 Corenr의 위치를 추출한 결과입니다.

Python과 OpenCV – 30 : 특징(Features)의 이해

이 글의 출처는 https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_features_meaning/py_features_meaning.html 입니다.

이 글에서는 피쳐가 무엇인지, 왜 중요한지를 알아봅니다. 그리고 이미지에서 귀퉁이(Corner)이 왜 중요한지도 알아봅니다.

우리들의 대부분은 직소 퍼즐을 가지고 놀아보았을 것 입니다. 이 퍼즐 게임은 이미지의 많은 조각을 가지고 하는데, 실제로는 큰 실제 이미지에 이 조각들을 정확히 맞춥니다. 질문은, 어떻게 플레이 하느냐 입니다. 이 게임에 대한 놀이 방식을 컴퓨터가 할 수 있도록 할 수 있지 않을까요? 만약 컴퓨터가 직소 퍼즐을 할 줄 안다면, 컴퓨터에게 실 세계의 자연 경관 이미지를 제공하고 이들 이미지를 하나의 큰 이미지로 맞추도록 할수 있지 않을까요? 만약 컴퓨터가 여러개의 조각 이미지를 하나로 맞출 수 있다면, 건물이나 어떤 구조물에 대한 많은 이미지를 주고는, 컴퓨터에게 3차원 모델로 만들어 보라고 할 수 있지 않을까요?

자, 질문과 상상을 계속해 봅시다. 그러나 이런 모든 질문들은 모두 매우 기초적인 질문에 기반하고 있습니다. 어떻게 직소 퍼즐을 우리 사람이 하는거냐는 질문말입니다. 어떻게 우리가 흩어진 많은 이미지 조각을 하나의 큰 이미지로 정렬해 맞추는가? 어떻게 우리가 자연에 존재하는 많은 이미지를 하나의 이미지로 짜맞추는가?

그 대답은, 우리가 어떤 패턴 또는 어떤 특징을 찾고 있다는 것인데.. 그 특징(Feature)라 함은 유일하고, 쉽게 찾을 수 있고, 쉽게 다른 것과 비교할 수 있는 것입니다. 만약 어떤 특징(Feature)에 대해 정의 내릴 수 있다면, 말로는 표현하기 어렵겠지만 그게 무엇인지는 발견할 수 있습니다. 만약 누군가 당신에게 여러개의 이미지들 간에 비교할 수 있는 좋은 특징 하나를 알려달라고 한다면, 그 특징 하나를 말해 줄 수 있습니다. 이것이 어린 아이조차도 이 직소 게임을 단순하게 가지고 놀 수 있는 이유입니다. 우리는 이미지에서 이러한 특징들을 검색하고, 발견하며, 다른 이미지에서 동일한 특징을 발견합니다. 바로 이것입니다. (직소 퍼즐에서 우리가 다른 이미지들의 연속성을 더 주의깊게 살피는 것입니다). 이러한 모든 능력들을 우리는 선천적으로 타고 난거죠.

그래서 우리의 한가지 기본 질문은 몇가지로 확장지만 좀더 명확해 집니다. 이런 특징은 무엇인가? (대답은 컴퓨터도 이해할 수 있어야 합니다.)

자, 어떻게 인간이 이러한 특징들을 발견하는지 이야기 하는것은 어렵습니다. 이는 이미 우리 뇌에 프로그래밍되어 있습니다. 그러나 만약 우리가 어떤 그림을 유심히 살펴보고 다양한 패턴을 살펴본다면, 흥미로운 어떤 것을 찾을 것입니다. 예를 들어 아래의 그림을 봅시다.

이미지는 매우 단순합니다. 이미지의 상단에는 6개의 작은 이미지 조각이 있습니다. 당신이 대답해야할 질문은 원본 이미지에서 이들 조각 이미지의 정확한 위치가 어디냐는 것입니다. 얼마나 많은 정확한 결과를 당신은 찾을 수 있나요?

A와 B는 평편한 표면이고, 이것은 많은 지역에 분포되어 있습니다. 이런 패턴의 정확한 위치를 발견하는 것은 어렵습니다.

C와 D는 좀더 단순합니다. 이들은 건물의 모서리(Edge)입니다. 대략적인 위치는 찾을 수 있지만 정확한 위치는 여전이 말하기 어렵습니다. 이유는 패턴이 모서리를 따라 반복되기 때문입니다. 그래서 모서리는 평편한 영역보다는 더 좋은 특징이기는 하지만 충분히 좋은 특징은 못됩니다.

최종적으로 E와 F는 건물의 귀퉁이(Corner)입니다. 이들은 쉽게 발견할 수 있고 정확히 그 위치를 확인할 수 있음으로 좋은 특징이 될 수 있습니다. 그래서 이제는 더 나은 이해를 위해 (이미지에 범용적으로 사용할 수 있도록) 좀더 단순화를 해봅시다.

위의 이미지에서, 파랑색 조각은 그 위치를 추적하고 발견하기 애매합니다. 검정색 조각은 모서리인데, 수직으로 이동해 보면 이미지가 변하지만 수평으로 이동하면 항상 같아 보입니다. 그리고 빨간색 조각은 모서리(Corner)입니다. 이 빨간색 조각을 이미지 위에서 어디로든 이동해 보면 이미지가 항상 변합니다. 즉 이는 유일합니다. 그래서 기본적으로 모서리는 이미지에서 좋은 특징으로 고려됩니다.

이제, “특징(Feature)은 무엇인가?”라는 질문에 대해 답했습니다. 그러나 다음 질문이 생겼습니다. “이 특징들은 어떻게 찾는가?”라는 질문 말입니다. 또는 “모서리는 어떻게 찾는가?”라는 질문입니다. 이는 직관적인 방법으로 답변되는데, 예를 들어 특징을 갖는 영역의 모든 주변에서 이 특징 영역을 움직일때 최대의 변위를 가지는 곳입니다. 이는 Feature Detection 이라는 이미지 특징을 찾는 또 다른 글에서 살펴 보겠습니다.

이제, 우리는 이미지에서 특징을 발견했다고 가정하겠습니다. 일단 발견하면, 일단 다른 이미지에서 이 특징과 등일한 위치를 찾아야 합니다. 어떻게 할까요? 특징 주위의 영역들을 가져와야 하는데, 이 주의 영역들에 대해서, “윗 부분은 파란 하늘이고, 아래 부분은 건물 영역이고, 건물에는 유리 등이 있다”라고 설명되고 다른 이미지에서 이와 동일한 영역을 찾습니다. 기본적으로 우리들은 특징을 이렇게 말로 설명하는데, 이와 유사한 방식으로 컴퓨터 역시 특징 주위의 영역을 설명하고 다른 이미지에서 이 특징과 동일한 위치를 찾을 수 있습니다. 이러한 컴퓨터 관점에서 특징에 대한 설명을 Feature Description이라고 합니다. 일단 특징을 찾았고, 이 특징을 기술할 수 있다면, 다른 모든 이미지에서 동일한 특징을 찾을 수 있고 이 특징에 대한 조각 이미지를 정렬할 수 있으며 붙여 하나의 큰 이미지를 만드는 등 우리가 원하는 것들을 할 수 있습니다.

Python과 OpenCV – 29 : GrabCut 알고리즘을 이용한 전경(Foreground) 추출

이 글의 원문은 https://github.com/abidrahmank/OpenCV2-Python-Tutorials/blob/master/source/py_tutorials/py_imgproc/py_grabcut/py_grabcut.rst 입니다.

GrabCut 알고리즘은 이미지에서 배경이 아닌 전경에 해당하는 이미지를 추출해 내는 방법입니다. 이미지에서 한번에 전경을 추출해 내는 것이 아닌 사용자와의 상호 작용을 통해 단계적으로 전경을 추출합니다. 이 상호작용은 크게 2가지 단계로 진행되는데, 첫번째는 이미지에서 전경이 포함되는 영역을 사각형으로 대략적으로 지정합니다. 단, 이때 지정한 사각형 영역 안에는 전경이 모두 포함되어 있어야 합니다. 그리고 두번째는 첫번째에서 얻어진 전경 이미지의 내용중 포함되어진 배경 부분은 어디인지, 누락된 전경 부분은 어디인지를 마킹하면 이를 이용해 다시 전경 이미지가 새롭게 추출됩니다.

위의 GrabCut 알고리즘에 대한 OpenCV의 구현은 cv2.grabCut 입니다. 예제를 통해 이 함수를 살펴보겠습니다. 첫번째 예제는 앞서 언급한 GrabCut의 상호작용 중 전경이 포함된 사각 영역을 지정하는 것만을 이용해 전경을 추출하는 경우입니다.

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

img = cv2.imread('./data/messi5.jpg')
mask = np.zeros(img.shape[:2],np.uint8)

bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

rect = (50,50,450,290)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)

mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]

plt.imshow(img),plt.colorbar(),plt.show()

결과는 아래와 같습니다.

코드 중 전경이 포함된 사각영역은 11번 코드에서 rect 변수에 지정하고 있습니다. 이 rect 변수를 이용해 12번 코드에서 전경을 추출하는 cv2.grabCut 함수가 실행됩니다. 이 함수의 인자를 살펴보면 첫번째는 입력 이미지, 두번째는 6번 코드에서 생성한 모든 요소가 0인 마스크 이미지, 세번째는 사각형역, 네번째와 다섯번째는 이 알고리즘의 수행 과정중 활용할 메모리, 여섯번째는 해당 알고리즘의 반복 수행 횟수, 일곱번재는 사각형 영역 지정을 통한 GrabCut일 경우 cv2.GC_INIT_WITH_RECT를 지정합니다. 이 인자 중 6번째인 mask에는 전경에 해당하는 화소 위치에 값이 설정되는데, 0 또는 2 값은 배경이고 1 또는 3인 전경이라는 의미입니다. 이를 이용해 원본 이미지에 마스킹 처리를 해서 전경만을 표시하는 것이 14-17번 코드입니다.

다음은 사각형 영역의 지정 이후에 추출된 전경에 대해 다시 누락된 전경과 포함된 배경 부분을 마킹하여 보다 완전한 전경 이미지를 추출하는 전체적인 GrabCut 알고리즘에 대한 예제입니다.

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

img = cv2.imread('./data/messi5.jpg')
mask = np.zeros(img.shape[:2],np.uint8)

bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)

# Step 1
rect = (50,50,450,290)
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,1,cv2.GC_INIT_WITH_RECT)

# Step 2
newmask = cv2.imread('./data/newmask2.png',0)
mask[newmask == 0] = 0
mask[newmask == 255] = 1
cv2.grabCut(img,mask,None,bgdModel,fgdModel,1,cv2.GC_INIT_WITH_MASK)

mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()

결과는 다음과 같습니다.

코드 중 Step1으로 처리된 부분은 앞서 설명한 사각영역 지정을 통한 전경의 추출이고 Step2가 마킹을 통한 전경 이미지 추출입니다. 마킹 방식은 newmask2.png에 마킹 정보를 나타내고 있는데 이 이미지는 아래와 같습니다.

즉, 하얀색 픽셀값(255)은 전경을, 검정색 픽셀값(0)은 배경임을 나타내는 마킹 정보를 포함하고 있는 이미지입니다.