[OpenCV] 06-1. Meanshift and Camshift
🐍Python/OpenCV

[OpenCV] 06-1. Meanshift and Camshift

728x90
반응형

< Meanshift and Camshift >

이번 장에서는

  • 비디오에서 물체를 찾고 트래킹하기 위해 사용되는 Meanshift 와 Camshift에 대해서 배워 볼 것이다.

Meanshift

Meanshift의 알고리즘은 간단하다. 수 많은 포인트 셋을 가지고 있다고 생각해보자(히스토그램의 배경투사 같은 픽셀 분포일 수 있다.) 작은 창(아마 원)을 받고, 이 창을 픽셀 밀집도가 최대인 지역(또는 포인트의 수가 최대인 곳)으로 이동시켜야한다. 아래 그림을 보고 이해해보자.

사진

초기 창은 파란색 원으로 모이는 “C1”이다. 이 창의 중심은 파란색 직사각형으로 표시된 “C1_o”이다. 하지만 만약 창 안의 포인트들의 중심을 찾으면 “C1_r”(작은 파랑 동그라미, 창의 실제 중심이다.)을 얻을 것이다. 확실히 이는 매칭이 안된다. 그러니 새로운 창을 이전 중심과 일치하도록 창을 이동시킨다(C1_r을 중심으로 하도록 원을 이동) 아마도 이것 또한 일치하지 않을 것이다. 따라서 다시 이동하고 반복해서 창 중심(점이 밀집한 곳)에 일치하도록(최소한의 C1_o ~ C1_r 오차로) 창을 이동시킨다. 결국 최대 픽셀 분포를 가진 창을 얻게 된다. 이는 초록색 원인 “C2”로 나타난다. 이미지에서 볼 수 있듯이, 포인트의 수가 최대이다. 이 모든 과정은 아래 Gif로 나타나있다.



따라서 보통 히스토그램이 투사된 이미지와 이 이미지에서의 초기 타겟 지역을 지정하여 전달해야한다. 물체가 움직일 때, 이 움직임은 히스토그램이 투사된 이미지에 반영된다. 그 결과로, meanshift 알고리즘은 최대 밀집도를 가진 새로운 지역으로 창을 움직이게 한다.

Meanshift in OpenCV

OpenCV에서 meanshift를 사용하기 위해서는, 먼저 타겟을 지정해야한다. 히스토그램을 찾아 각 프레임에 있는 타겟을 배경수아하여 meanshift 계산을 한다. 또한 창의 초기 위치도 정해줘야 한다. 히스토그램에 대해, 여기서는 Hue(색상)만 고려된다. 또한, 낮은 밝기로 인한 false value를 피하기 위해서는, cv2.inRange()로 이를 제거해야한다.

[Reference code]i(http://blog.naver.com/PostView.nhn?blogId=samsjang&logNo=220658244422&categoryNo=66&parentCategoryNo=0&viewDate=&currentPage=2&postListTopCurrentPage=&from=postList&userTopListOpen=true&userTopListCount=10&userTopListManageOpen=false&userTopListCurrentPage=2)

import cv2
import numpy as np

col, width, row, height = -1,-1,-1,-1
frame = None
frame2 = None
inputmode = False
rectangle = False
trackWindow = None
roi_hist = None

def onMouse(event,x,y,flags,param):
    global col,width,row,height, frame,frame2
    global inputmode, rectangle, roi_hist, trackWindow

    if inputmode:
        # 좌 클릭시 
        if event == cv2.EVENT_LBUTTONDOWN:
            rectangle = True
            col, row = x,y
        # 좌 클릭하는 도중 움직일 때
        elif event == cv2.EVENT_MOUSEMOVE:
            if rectangle:
                frame = frame2.copy()
                cv2.rectangle(frame,(col,row),(x,y),(0,255,0),2)
                cv2.imshow('frame',frame)
        # 좌 클릭 뗐을때        
        elif event == cv2.EVENT_LBUTTONUP:
            inputmode = False
            rectangle = False
            cv2.rectangle(frame,(col,row),(x,y),(0,255,0),2)
            height, width = abs(row-y), abs(col-x)
            trackWindow = (col,row,width,height)
            roi = frame[row:row+height,col:col+width]
            # HSV 색공간으로 변환
            roi = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)
            # HSV 색공간으로 변경한 히스토그램 계산
            roi_hist = cv2.calcHist([roi],[0],None,[180],[0,180])
            # 계산된 히스토그램 노말라이즈
            cv2.normalize(roi_hist,roi_hist,0,255,cv2.NORM_MINMAX)
    return


def meanShift():
    global frame,frame2,inputmode,trackWindow,roi_hist

    try:
        # 저장된 영상 불러옴
        cap = cv2.VideoCapture('./videos/walking.avi')
        cap.set(3,1280)
        cap.set(4,720)
    except Exception as e:
        print(e)
        return

    ret, frame = cap.read()

    cv2.namedWindow('frame')
    cv2.setMouseCallback('frame',onMouse,param=(frame,frame2))

    # meanShift 함수의 3번째 인자. 10회 반복 혹은 C1_o ~ C1_r의 차이가 1pt 날 때까지 작동
    termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10,1)

    while True:
        ret, frame = cap.read()
        if not ret:
            break
        if trackWindow is not None:
            hsv = cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
            dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
            ret, trackWindow = cv2.meanShift(dst,trackWindow,termination)

            x,y,w,h = trackWindow
            # 추적된 물체 녹색 사각형으로 표시
            cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)

        cv2.imshow('frame',frame)

        k = cv2.waitKey(60) & 0xFF
        if k == 27:
            break
        # i를 눌러서 영상을 멈춰서 roi 설정    
        if k == ord('i'):
            print("Meanshift를 위한 지역을 선택하고 키를 입력해라")
            inputmode = True
            frame2 = frame.copy()

            while inputmode:
                cv2.imshow('frame',frame)
                cv2.waitKey(0)

    cap.release()
    cv2.destroyAllWindows()

meanShift()

결과는 아래와 같다.


사람과 사람간에는 구분을 하지 못하는 모습을 보인다. 또 창의 크기가 계속 일정하게 유지되면서 추적하는 것을 볼 수 있다. 객체의 크기를 가늠하지 못하는 것이다.

Camshift

아까 말했듯이 meanshift는 물체의 크기 변화에 상관없이 균일한 크기의 창을 유지하고 있다. 이는 좋지 않은 현상이다. 창의 크기와 타겟의 회전도 감안하여야한다. 이를 해결할 수 있는 것이 CAMshift(Continuously Adaptive Meanshift)이다.

이는 먼저 meanshift를 적용시킨다. meanshift가 수렴할 때, 창의 크기를 s=2×M00256로 업데이트한다. 또한 객체와 가장 잘 맞는 타원의 방향도 계산한다. 다시 새로운 크기의 창과 이전 창의 위치를 이용해서 meanshift를 적용한다. 요구되는 정확도까지 프로세스는 계속해서 반복된다.


Camshift in OpenCV

거의 대부분의 구성이 Meanshift와 유사하지만 회전된 사각형과 박스 파라미터(다음 반복에서 창을 찾기 위해 입력된다)가 반환된다. 아래 코드를 보자!

def camShift():
    global frame,frame2,inputmode,trackWindow,roi_hist

    try:
        # 저장된 영상 불러옴
        cap = cv2.VideoCapture(0)
        cap.set(3,480)
        cap.set(4,320)
    except Exception as e:
        print(e)
        return

    ret, frame = cap.read()

    cv2.namedWindow('frame')
    cv2.setMouseCallback('frame',onMouse,param=(frame,frame2))

    # meanShift 함수의 3번째 인자. 10회 반복 혹은 C1_o ~ C1_r의 차이가 1pt 날 때까지 작동
    termination = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10,1)

    while True:
        ret, frame = cap.read()
        if not ret:
            break
        if trackWindow is not None:
            hsv = cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)
            dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180],1)
            ret, trackWindow = cv2.CamShift(dst,trackWindow,termination)

            ### 이 부분 수정
            pts = cv2.boxPoints(ret)
            pts = np.int0(pts)
            cv2.polylines(frame,[pts],True,(0,255,0),2)

        cv2.imshow('frame',frame)

        k = cv2.waitKey(60) & 0xFF
        if k == 27:
            break
        # i를 눌러서 영상을 멈춰서 roi 설정    
        if k == ord('i'):
            print("Meanshift를 위한 지역을 선택하고 키를 입력해라")
            inputmode = True
            frame2 = frame.copy()

            while inputmode:
                cv2.imshow('frame',frame)
                cv2.waitKey(0)

    cap.release()
    cv2.destroyAllWindows()

camShift()

Camshift가 조금 더 유연하게 크기 / 회전에 대해서 반응하는 것을 볼 수 있다.


728x90
반응형