Python과 OpenCV – 41 : Optical Flow

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

이 글에서는 광학 흐름(Optical Flow)을 이해하고 Lucas-Kanade 방법을 사용하여 이를 예측해 보는 예제를 살펴봅니다. OpenCv에서는 Lucas-Kanade 방법은 cv2.calcOpticalFlowPyrLK 함수를 사용합니다.

먼저 Optical Flow는 카메라 또는 물체의 이동에 의해 생기는 연속된 2개의 이미지 간의 어떤 이동에 대한 패턴입니다. 이 이동 패턴은 첫번재 프레임에서 두번째 프레임 간의 어떤 지점의 이동을 보여주는 2차원 변위 벡터(Displacement Vector)입니다. 아래 이미지를 보면..

5개의 연속된 프레임에서 빨간 공이 이동하고 있는 그림입니다. 하얀색 화살표가 바로 변위 벡터입니다. Optical Flow는 다양한 분야에서 활용되는데, 다음과 같은 응용 분야가 있습니다.

  • 물체의 이동 분석
  • 비디오 압축
  • 비디오 화질 개선
  • 등등

Optical Flow는 몇가지 가정이 있으며, 그중 2가지는 다음과 같습니다.

  1. 객체의 픽셀 강도는 연속된 프레임 간에 변환지 않는다.
  2. 한 픽셀의 이동과 그 인근 픽셀의 이동은 유사하다.

이러한 Optical Flow를 분석하는 방법으로 Lucas-Kanade 매서드가 있는데, OpenCV에서 cv2.calcOpticalFlowPyrLK()라는 하나의 함수를 통해 가능합니다. 비디오에서 어떤 포인트를 추적해 보는 하나의 예제를 살펴보겠습니다. 어떤 포인트에 대한 결정은 cv2.goodFeaturesToTrack() 함수를 사용하는데, 이 함수는 Shi-Tomasi 방식에 의한 특징점으로써 코너(Corenr)를 찾아냅니다. 첫번째 프레임에서 코너를 찾아내어 추적할 포인트로 결정합니다. 그리고 연속된 2개의 프레임들 간에 Lucas-Kanade Optical Flow를 사용하여 반복적으로 추적합니다. cv2.calcOpticalFlowPyrLK() 함수에 이전 프레임, 추적할 이전 포인트, 다음 프레임 등을 인자로 전달함으로써 이루어집니다. 이 함수는 이전 프레임에서 추적할 포인트가 연속된 다음 프레임에서 추적될 경우 상태값 1을 반환하고 발견되지 못할 경우 0을 반환합니다. 또한 추적할 포인트가 이동한 새로운 위치값도 함께 반환합니다. 다음 예제를 통해 더 깊이 이해할 수 있습니다.

import numpy as np
import cv2

cap = cv2.VideoCapture('./data/vtest.avi')

# params for ShiTomasi corner detection
feature_params = dict( maxCorners = 100,
                       qualityLevel = 0.01,
                       minDistance = 30,
                       blockSize = 14)

# Parameters for lucas kanade optical flow
lk_params = dict( winSize  = (15,15),
                  maxLevel = 0,
                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))

# Create some random colors
color = np.random.randint(0,255,(100,3))

# Take first frame and find corners in it
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)

# Create a mask image for drawing purposes
mask = np.zeros_like(old_frame)

while(1):
    ret,frame = cap.read()

    if not ret:
        break

    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # calculate optical flow
    p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)

    # Select good points
    good_new = p1[st==1]
    good_old = p0[st==1]

    # draw the tracks
    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

    # Now update the previous frame and previous points
    old_gray = frame_gray.copy()
    p0 = good_new.reshape(-1,1,2)

cv2.destroyAllWindows()

위의 예제는 다음 추적된 키포인트가 얼마나 정확한지 검사하지 않습니다. 그러므로 어떤 특징점 포인트가 이미지에서 사라질 수 있는데, 이 경우라고 할지라도 Optical Flow는 이를 개선할 수 있습니다. 실제 더 견고한 추적을 위해, 특징점인 코너는 특정한 구간(예를들어 5개의 프레임 마다)에서 검출되어야 합니다. 아래는 실행 결과입니다.

실제 실행 결과를 보면, 동영상 속에서 이동되는 객체(사람)가 이동된 경로로 선이 그려지는 것을 볼 수 있습니다.

다음은 앞서 살펴본, Optical Flow를 이동 경로로 보여주는 것이 아닌 밀도로 보여주는 것을 살펴봅니다. 이는 Gunner Farneback 알고리즘에 기반하고 있습니다. 아래의 예제 코드는 이 알고리즘을 사용하여 Optical Flow의 밀도(Dense)를 보여줍니다.

import cv2
import numpy as np
cap = cv2.VideoCapture('./data/vtest.avi')

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

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

    flow = cv2.calcOpticalFlowFarneback(prvs,next, 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)
    prvs = next

cap.release()
cv2.destroyAllWindows()

실행 결과 중 이미지의 한 컷은 다음과 같습니다. (위 예제의 코드에 따라 s키를 눌러 저장된 이미지임)

“Python과 OpenCV – 41 : Optical Flow”에 대한 3개의 댓글

  1. 안녕하세요. 우선 좋은 자료 만들어주셔서 감사드립니다. 제가 optical flow 관련해서 프로젝트를 진행중인데, 혹시 가능하시면 메일을 통해 구체적으로 말씀나눌 수 있으면 좋을 것 같아 댓글 남깁니다.

    1. 안녕하세요, 이 글은 OpenCV를 이용한 프로젝트 수행 중 OpenCV 기능 파악을 위한 별도의 학습을 위해 작성된 것으로 말씀하신 optical flow에 대해서는 깊이 있게 알지 못합니다.

      1. optical flow에 대한 구체적인 내용보다는 이를 어떻게 처리할지 자문을 구하고 싶은데 가능할까요?

포돌이에 답글 남기기 응답 취소

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다