영상 분할(image segmentation)

Seungpil Choi·2022년 8월 17일
0

영상 분할(image segmentation)

영상 분할이란?

영상 분할이란 영상 안의 화소를 의미 있는 영역(segment)으로 분할하는 것을 의미한다. 즉 영상의 모든 화소에 어떤 레이블을 붙이는 것이다. 예를 들면, 영상의 화소들을 자동차, 도로, 건물 등으로 분할하는 것이 영상 분할이다. 완벽한 영상 분할 처리는 아직도 어려운 공학적인 문제이다.

이진화(thresholding)

이진화는 가장 간단한 영상 분할 방법이다. 어떤 임계값을 정하고 이 값을 기준으로 그레이스케일 영상을 이진 영상으로 만든다. 많이 사용되는 방법들에는 최대 엔트로피 방법(maximum entropy method), 오투의 방법(Otsu's method), k-means 클러스터링 등이 있다.

클러스터링 방법(Clustering methods)

K-means 알고리즘은 영상을 K개의 클러스터로 나누는 반복적인 알고리즘이다.

  1. 임의로 K개 클러스터의 중심을 선택한다.
  2. 각 화소를 화소와 클러스터 중심 사이의 거리가 최소가 되는 클러스터에 할당한다.
  3. 클러스터의 모든 화소를 평균하여 클러스터 중심을 다시 계산한다.
  4. 클러스터링이 수렴할 때까지 2단계와 3단계를 반복한다.

거리와 화소는 클러스터 중심 간의 제곱값이나 절대 차이값으로 계산된다. 화소 간의 거리는 일반적으로 화소의 색상, 밝기, 질감, 위치 또는 이러한 요소의 가중치 합을 기반으로한다. 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()

적응적 이진화

앞서 이진화할 때, 하나의 임계값을 전체 영상에 사용했다. 그러나 영상 안에서도 조명 조건이 달라지는 경우에 하나의 임계값을 사용하는 것이 좋지 않을 수 있다. 적응적 이진화에서는 영상의 각 영역에 따라서 서로 다른 임계값을 사용한다.

  • adaptive_threshold() 매개변수
    • src : 입력 영상
    • maxValue : 화소값이 임계값을 넘으면 부여되는 값
    • adaptiveMethod : 적응적 이진화의 방법 선택
      • ADAPTIVE_THRESH_MEAN_C : 임계값은 인접 지역의 평균이 된다.
      • ADAPTIVE_THRESH_GAUSSIAL_C : 임계값은 가중치가 가우시안인 윈도우를 이웃 화소에 씌워서 계산한 가중치의 합이다.
    • thresholdType : 이진화 타입
    • blockSize : 영역으로 나눌 크기 (nxn), 3이상의 홀수
    • C : 계산된 임계값 결과에서 가감할 상수(음수 가능)
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는 히스토그램을 분석해서 임계값을 자동으로 결정할 수 있다고 주장한다. 간단히 말하자면 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)이다.

  • connectedComponentsWithStat() 매개변수
    • image : 입력영상
    • labels : 레이블 영상
    • connectivity : 출력 영상의 레이블타입 CV_32S 또는 CV_16U
    • Itype : 각 레이블에 대한 통계 자료
    • 반환값 : 레이블의 개수
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()

0개의 댓글