[OpenCV] 06-2. Optical Flow
🐍Python/OpenCV

[OpenCV] 06-2. Optical Flow

728x90
반응형

< Optical Flow >

이번 장에서는,

  • 광학 흐름의 개념에 대해서 이해하고 Lucas-Kanade 방법을 사용한 추정을 해보고
  • cv2.calcOpticalFlowPyrLK() 함수를 사용해서 비디오에서의 특성 점을 추적해볼 것이다.

Optical Flow

광학 흐름은 물체나 카메라의 움직임에 의해 발생하는 두 개의 연속되는 프레임사이에서 객체의 외관상 움직임의 패턴이다. 각 벡터는 첫 번째 프레임에서 점이 두 번째 프레임으로 점으로의 움직임을 보여주는 이동 벡터인 2D 벡터 필드이다. 아래 이미지를 보자!

사진

위 사진은 공의 움직임을 5개 연속적인 프레임으로 보여준다. 화살표는 이동 벡터를 나타낸다. 광학 흐름은 다음과 같은 분야에서 적용된다 :

  • 움직임을 통한 구조 분석
  • 비디오 압축
  • 비디오 안정화 : 깨끗한 영상으로 만드는 것

광학 흐름은 여러 가정위에서 작동한다.

  1. 객체의 픽셀 채도(intensity)는 연속된 프레임 사이에서 바뀌면 안된다.
  2. 인접부 픽셀들은 유사한 움직임을 갖는다.

첫 프레임에서의 픽셀 I(x,y,t)을 고려해보자. (새 차원, 시간이 추가되었는지 확인하자. 애초에 이미지로만 작업했으므로 시간이 필요하진 않다.) dt시간이 지나면 그 다음 프레임에서 (dx,dy)만큼 이동한다. 그리고 픽셀들은 동일하고 채도는 변하지 않기에 다음과 같이 말 할 수 있다.

I(x,y,t)=I(x+dx,y+dy,t+dt)

그리고 다음의 식을 얻기 위해, 오른쪽 식의 테일러 시리즈 근사치를 취하고, 공통항을 제거하고 dt로 나눈다.

fxu+fyv+ft=0

  • fx=fx ; fy=fy
  • u=dxdt ; v=dydt

위의 식을 Optical Flow 식이라고 부른다. 그 안에서, fx fy를 찾을 수 있고, 이는 이미지의 기울기(gradients)이다. 비슷하게 ft 시간 경과에 따른 gradient이다. 하지만 (u,v)는 알 수 없다. 이 하나의 식을 두 개의 미지수로 풀 수 없다. 그래서 여러 방법들이 제공되는데, 그 중에 하나가 Lucas-Kanade이다.

Lucas-Kanade method

주변부 픽셀은 유사한 움직임을 갖는다는 가정을 봤었다. Lucas-Kanade 방법은 점을 주변으로 3x3 패치를 취한다. 그래서 총 9개의 점이 동일한 움직임을 갖게 된다. 그리고 9개 점에 대한 (fx,fy,ft)를 찾을 수 있다. 그래서 이제 과단성있는 두 개의 미지수로 9개의 방정식을 풀어야는 것이 문제가 된다. 최소자승법으로 더 나은 해결책을 얻을 수 있다. 아래의 식을 보자

[uv]=ifxi2ifxifyiifxifyiifyi21ifxiftiifyifti

(Harris corner detector로 매트릭스의 역함수의 유사도를 체크한다. 이는 코너가 추적하기에 더 나은 포인트라는 것을 말한다)

사용자의 관점에서 보면 아이디어는 간단하다. 추적할 포인트들을 주고, 이 점들의 광학 흐름 벡터를 얻는 것이다. 하지만 다시 문제는 등장한다. 현재까지는, 작은 움직임을 다뤄왔었다. 그래서 이는 커다란 움직임이 있으면 실패했었다. 그래서 피라미드로 간다. 피라미드의 상위로 가게 되면, 작은 움직임은 제거되고 큰 움직임이 작은 움직임이 된다. 그래서 거기에 Lucas-Kanade를 적용해서, 스케일에 따른 광학 흐름을 얻는 것이다.

Lucas-Kanade Optical Flow in OpenCV

OpenCV는 이 모든걸 아우르는 cv2.calcOpticalFlowPyrLK() 함수를 제공한다. 여기서, 비디오 내의 점을 추적하기 위한 간단한 기능을 생성한다. 이 점을 결정하기 위해서, cv2.goodFeaturesToTrack()을 사용한다. 첫 프레임을 받아서, Shi-Tomasi 코너 점을 탐지하고, Lucas-Kanade 광학 흐름을 사용하여 반복적으로 점을 추적한다. cv2.calcOpticalFlowPyrLK() 함수를 위해, 이전의 프레임과 이전의 점, 다음 프레임을 보낸다. 다음 포인트를 찾으면 값이 1인 번호와 함께 반환하고, 그렇지 않으면 0이다. 이 다음 포인트를 다음 단계의 이전 포인트로 반복해서 보낸다. 아래 코드를 보자.

import numpy as np 
import cv2

cap = cv2.VideoCapture('./videos/walking.avi')

# ShiTomasi 코너 탐지를 위한 파라미터
feature_params = dict(maxCorners = 100,
                     qualityLevel = 0.3,
                     minDistance = 7,
                     blockSize = 7)

# Lucas-Kanade 광학 흐름을 위한 파라미터
lk_params = dict(winSize = (15,15),
                maxLevel = 2,
                criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# 랜덤 색 생성하기
color = np.random.randint(0,255,(100,3))

# 첫 프레임을 가지고 코너를 찾자
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame,cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)

# 그리기 위해서 마스크 이미지를 생성하기
mask = np.zeros_like(old_frame)

while(1):
    res, frame = cap.read()
    frame_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)

    # 광학 흐름 계산하기
    p1,st,err = cv2.calcOpticalFlowPyrLK(old_gray,frame_gray,p0,None,**lk_params)

    # 좋은 포인트를 고른다
    good_new = p1[st==1]
    good_old = p0[st==1]

    # 추적하는 것 그리기
    for i,(new,old) in enumerate(zip(good_new,good_old)):
        a,b = new.ravel()
        c,d = old.ravel()
        mask = cv2.line(mask,(a,b),(c,d),color[i].tolist(),2)
        frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
    img = cv2.add(frame,mask)

    cv2.imshow('frame',img)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break

    # 이제 이전 프레임과 이전 포인트를 업데이트한다.
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)

cv2.destroyAllWindows()
cap.release()

이 코드는 다음 키포인트가 얼마나 정확한지 확인 못한다. 따라서 어떤 특징 포인트가 이미지내에서 사라지게 되면, 광학 흐름은 그것에 가깝에 보일 수 있는 다음 포인트를 찾을 가능성이 있다. 따라서 강건한 추적을 위해서, 특정 간격으로 코너 포인트를 감지해야 한다. OpenCV 샘플은 5프레임마다 특징점을 찾는 샘플을 제공한다. 또한 광학 흐름 포인트를 역 확인해서(backward-check) 좋은 점만 선택하도록 했다.

결과는 아래와 같다.



Dense Optical Flow in OpenCV

Lucas-Kanade 방법은 희소 특성 집합에 대한 광학 흐름을 계산한다. (위의 예에서 Shi-Tomasi 알고리즘을 사용하여 검출된 코너) OpenCV는 밀집한(dense) 광학 흐름을 찾기 위한 또 다른 알고리즘을 제공한다. 그것은 프레임의 모든 포인트에 대한 광학 흐름을 계산한다.

아래 샘플은 위의 알고리즘을 사용하여 밀집한 광학 흐름을 찾는 밥법을 보여준다. 영상속의 움직임을 크기와 방향, 2개의 채널로 나타내는데,(u,v) 움직임의 방향은 방향에 해당하는 Hue(색상)값으로 이미지를 나타내고 크기는 Value plane으로 나타낸다.

import numpy as np 
import cv2

cap = cv2.VideoCapture('./videos/walking.avi')

ret, frame1 = cap.read()
prev = cv2.cvtColor(frame1,cv2.COLOR_BGR2GRAY)
hsv = np.zeros_like(frame1)
hsv[...,1] = 255

while(1):
    ret, frame2 = cap.read()
    nexts = cv2.cvtColor(frame2,cv2.COLOR_BGR2GRAY)

    flow = cv2.calcOpticalFlowFarneback(prev,nexts,None,0.5,3,15,3,5,1.2,0)

    mag, ang = cv2.cartToPolar(flow[...,0],flow[...,1])
    hsv[...,0] = ang*180/np.pi/2
    hsv[...,2] = cv2.normalize(mag,None,0,255,cv2.NORM_MINMAX)
    rgb = cv2.cvtColor(hsv,cv2.COLOR_HSV2BGR)

    cv2.imshow('frame2',rgb)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break
    elif k == ord('s'):
        cv2.imwrite('opticalfb.png',frame2)
        cv2.imwrite('opticalhsv.png',rgb)
    prev = nexts

cap.release()
cv2.destroyAllWindows()

아래 결과를 보자.


위와 같이 사람의 형상이 번진것 처럼 보이며 추적한다.


728x90
반응형