영상 분할이란 영상 안의 화소를 의미 있는 영역(segment)으로 분할하는 것을 의미한다. 즉 영상의 모든 화소에 어떤 레이블을 붙이는 것이다. 예를 들면, 영상의 화소들을 자동차, 도로, 건물 등으로 분할하는 것이 영상 분할이다. 완벽한 영상 분할 처리는 아직도 어려운 공학적인 문제이다.
이진화는 가장 간단한 영상 분할 방법이다. 어떤 임계값을 정하고 이 값을 기준으로 그레이스케일 영상을 이진 영상으로 만든다. 많이 사용되는 방법들에는 최대 엔트로피 방법(maximum entropy method), 오투의 방법(Otsu's method), k-means 클러스터링 등이 있다.
K-means 알고리즘은 영상을 K개의 클러스터로 나누는 반복적인 알고리즘이다.
거리와 화소는 클러스터 중심 간의 제곱값이나 절대 차이값으로 계산된다. 화소 간의 거리는 일반적으로 화소의 색상, 밝기, 질감, 위치 또는 이러한 요소의 가중치 합을 기반으로한다. K값은 수동으로 또는 무작위로 결정된다. K-means 알고리즘은 수렴하지만 최적의 솔루션을 반환하지 않을 수 있다.
에지 검출은 영상 처리에서 가장 잘 발달된 분야이다. 영역 경계와 에지는 밀접한 관련이 있다. 영역 경계에서 화소의 발기가 급격하게 변경되기 때문이다. 따라서 에지 검출 기술은 영상 분할의 기초 자료로 사용되어 왔다. 문제는 에지 검출로 식별된 에지는 종종 연결이 끊어진다는 점이다. 영상에서 물체를 분할하려면 영역 경계가 닫혀 있어야 한다. 따라서 끊어진 에지들을 연결 시키는 방법이 필요하다.
이진화는 가장 간단한 영상 분할 방법이다.
img = cv2.imread("../Downloads/lena.png", 0)
threshold_value = 128
max_value = 255
threshold_type = cv2.THRESH_BINARY
def MyThreshold(pos):
ret, dst = cv2.threshold(img, pos, max_value, threshold_type)
cv2.imshow("result", dst)
cv2.namedWindow("result", cv2.WINDOW_AUTOSIZE)
cv2.createTrackbar("threshold", "result", threshold_value, max_value, MyThreshold)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
앞서 이진화할 때, 하나의 임계값을 전체 영상에 사용했다. 그러나 영상 안에서도 조명 조건이 달라지는 경우에 하나의 임계값을 사용하는 것이 좋지 않을 수 있다. 적응적 이진화에서는 영상의 각 영역에 따라서 서로 다른 임계값을 사용한다.
img = cv2.imread("../Downloads/book.jpg", 0)
img = cv2.resize(img, (360, 270))
dst = cv2.medianBlur(img, 5)
ret1, th1 = cv2.threshold(dst, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(dst, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
th3 = cv2.adaptiveThreshold(dst, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
cv2.imshow("Original", img)
cv2.imshow("Global Thresholding", th1)
cv2.imshow("Adaptive Mean", th2)
cv2.imshow("Adaptive Gaussian", th3)
cv2.waitKey(0)
cv2.destroyAllWindows()
Otsu는 히스토그램을 분석해서 임계값을 자동으로 결정할 수 있다고 주장한다. 간단히 말하자면 Otsu의 이진화 방법은 영상의 히스토그램으로부터 임계값을 자동으로 계산한는 방법이다. 하지만 히스토그램이 쌍봉이 아닌 경우에는 정확하지 않다.
ret2, th4 = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
cv2.imshow("Otsu", th4)
배경 제거는 많은 영상 처리 응용 프로그램의 주요 전처리 단계이다. 예를들어 카메라를 이요하여서 방에 들어오거나 방에서 나가는 사람의 숫자를 세는 프로그램이나 차량에 대한 정보를 추출하는 교통 카메라 등과 같은 경우에는 반드시 배경 제거가 필요하다. 배경을 제거하기 위해서는 움직이지 않는 배경과 움직이는 전경들을 구별할 수 있어야 한다.
pMOG2 = cv2.createBackgroundSubtractorMOG2()
cap = cv2.VideoCapture("../Downloads/hand_move.mp4")
if cap.isOpened() == False :
print('ERROR FILE NOT FOUND! OR WRONG CODEC USED!')
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
result = pMOG2.apply(frame)
cv2.imshow("Frame", frame)
cv2.imshow("MOG2", result)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()
우리는 카메라를 통하여 동전의 개수를 세려고 한다. 이진화를 통하여 동전과 배경이 분리되었다고 하였을 떄 동전과 동전은 모두 검은색으로만 되어 있어서 동전이 몇 개인지 알 수가 없다. 따라서 영상에서 서로 연결된 성분이 몇개인지를 분석하여야 한다. 이것이 바로 연결 성분 레이블링(connected component labeling)이다.
import random
img = cv2.imread("../Downloads/coins_on_white.jpg", 0)
ret, img_edge = cv2.threshold(img, 232, 255, cv2.THRESH_BINARY_INV)
n, labels, stats, centroids = cv2.connectedComponentsWithStats(img_edge)
colors = []
colors.append((0,0,0))
for i in range(1,n):
colors.append((random.randint(0,255),random.randint(0,255),random.randint(0,255)))
img_color = np.zeros((800,800,3), dtype=np.uint8)
for y in range(img_color.shape[1]):
for x in range(img_color.shape[0]):
label = labels[y,x]
img_color[y,x] = colors[label]
cv2.imshow("Labeled map", img_color)
cv2.waitKey(0)
cv2.destroyAllWindows()