본문 바로가기
OpenCV/OpenCV-Chapter

CH10 OpenCV-Python Optical flow

by kiimy 2021. 12. 25.
728x90

옵티컬플로우(Optical flow)란?- 광학 흐름

- 연속하는 두 프레임(영상)에서 카메라 or 객체의 움직임에 의해 나타나는

  객체의 이동 정보 패턴

== Structure from Motion, Video Compression, Video Stabilization(손떨림 방지), etc

두 가지 사실을 가정
1. 연속된 프레임 사이에서 움직이는 물체의 픽셀 강도(intensity)는 변함이 없다.
2. 이웃하는 픽셀은 비슷한 움직임을 갖는다.

옵티컬플로우(Optical flow) 계산 함수

나중에 정리해보도록 하자...

* 루카스-카나데 알고리즘(Lucas-Kanade algorithm)

= 주로 Sparse(희소한) points에 대한 이동 벡터 계산

==> 특정 픽셀에서 optical flow 벡터 계산

이웃하는 픽셀은 비슷한 움직임을 갖는다고 생각하고 광학 흐름을 파악
루카스-카나데 알고리즘은 작은 윈도우(3 x 3 patch)를 사용하여 움직임을 계산
그래서 물체 움직임이 크면 문제가 생긴다. 이 문제를 개선하기 위해 이미지 피라미드를 사용
이미지 피라미드 위쪽으로 갈수록(이미지가 작아질수록) 작은 움직임은 티가 안 나고 큰 움직임은
작은 움직임 같이 보이기 때문에 큰 움직임도 감지할 수 있다.
src1 = cv2.imread('ch10\\images\\frame1.jpg')
src2 = cv2.imread('ch10\\images\\frame2.jpg')

if src1 is None or src2 is None:
    print('Image load failed!')
    sys.exit()

gray1 = cv2.cvtColor(src1, cv2.COLOR_BGR2GRAY)

# cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance)
# grayscale image만 받음(corner 검출을 위함)
pt1 = cv2.goodFeaturesToTrack(gray1, 50, 0.1, 10)
'''
corners, shape=(N, 1, 2)
maxCorners:  maxCorners <=0 이면 무제한
minDistance : 두 corner가 너무 근접해서 나타나면 하나는 버리겠다.
10 pixel 금방에 있는 것들은 그 중에서 코너해리스가 높은 것만 선택 나머진 버림
'''

pt2, status, err = cv2.calcOpticalFlowPyrLK(src1, src2, pt1, None)
'''
cv2.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status=None,
                        err=None, winSize=None, maxLevel=None, criteria=None, flags=None, 
                        minEigThreshold=None) -> nextPts, status, err

prevPts: 이전 프레임에서 추적할 점들. shape=(N, 1, 2), dtype=np.float32
nextPts: 출력으로 받음(pt2)
status: 점들의 매칭상태. shape=(N,1)
		i번째 원소가 1이면 prevPts의 i번째 점이 nextPts의 i번째 점으로 이동
'''

dst = cv2.addWeighted(src1, 0.5, src2, 0.5, 0)

for i in range(pt2.shape[0]): # corner 개수
    # 잘 된것만 표현해주기 위함
    if status[i,0] == 0: # status.shape= (34,1)
        continue

    # 도형그릴땐 int값만 받음=> 안하면 error
    # error: only size-1 arrays can be converted to Python scalars
    '''
    pt1, pt2 = [[[1,2], [2,4], ...]]
    각 좌표성분이 들어가 있는데
    *각 좌표 접근방법*
    ==> pt1[1, 0] = 첫번째 좌표 
    '''
    cv2.circle(dst, (pt1[i,0]).astype(int), 4, (0,255,255), 2, cv2.LINE_AA)
    cv2.circle(dst, (pt2[i,0]).astype(int), 4, (0,0,255), 2, cv2.LINE_AA)
    cv2.arrowedLine(dst, (pt1[i,0]).astype(int), (pt2[i,0]).astype(int), (0,255,0), 2)

cv2.imshow('dst', dst)
cv2.waitKey()

영상에서도 추적(좀 문제가 있음)


* 파네백 알고리즘(Farneback's algorithm)

= Dense(밀집) points에 대한 이동 벡터 계산

==> 모든 픽셀에서 optical flow 벡터 계산

def draw_flow(img, flow, step=16):
    h, w = img.shape[:2]
    y, x = np.mgrid[step/2:h:step, step/2:w:step].reshape(2, -1).astype(int)
    fx, fy = flow[y, x].T
    lines = np.vstack([x, y, x+fx, y+fy]).T.reshape(-1, 2, 2)
    lines = np.int32(lines + 0.5)
    vis = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    cv2.polylines(vis, lines, 0, (0, 255, 255), 2, lineType=cv2.LINE_AA)

    for (x1, y1), (_x2, _y2) in lines:
        cv2.circle(vis, (x1, y1), 1, (0, 128, 255), -1, lineType=cv2.LINE_AA)

    return vis

cap = cv2.VideoCapture("ch10\\videos\\vtest.avi")

if not cap.isOpened():
    print('Camera open failed!')
    sys.exit()

ret, frame1 = cap.read()

if not ret:
    print('frame read failed!')
    sys.exit()

gray1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)

# np.zeros_like = 똑같은 크기와 타입이지만 0으로 초기화한 값
hsv = np.zeros_like(frame1)
# hsv에서 s에 해당하는건 255
hsv[..., 1] = 255

while True:
    ret, frame2 = cap.read()

    if not ret:
        print('frame read failed!')
        sys.exit()

    gray2 = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
    flow = cv2.calcOpticalFlowFarneback(gray1, gray2, None, 0.5, 3, 13, 3, 5, 1.1, 0)
    '''
    # 파라미터 값을 잘 줘야함(사용하기가 어려움...)
    cv2.calcOpticalFlowFarneback(prev, next, flow, pyr_scale, levels, winsize,
                                iterations, poly_n, poly_sigma, flags) -> flow

    flow: 계산된 optical_flow, shape=(h, w, 2)
    '''
    # 모션벡터의 x 성분 = flow[..., 0] h, w, x
    # 모션벡터의 y 성분 = flow[..., 1] h, w, y
    # mag, ang 두 좌표 벡터의 크기와 각도(방향정보)
    mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])

    # hsv[..., 0] == h
    # hsv[..., 2] == v
    hsv[..., 0] = ang*180/np.pi/2
    hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)

    bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

    cv2.imshow('frame', frame2)
    cv2.imshow('flow', bgr)
    cv2.imshow('frame2', draw_flow(gray2, flow))
    if cv2.waitKey(20) == 27:
        break

    # 이렇게 안하면 특정 위치에서 값을 계속 가지고있는데..왜?
    gray1 = gray2

색을 통한 이동방향 / draw_flow..?

728x90

댓글