😎 κ³΅λΆ€ν•˜λŠ” μ§•μ§•μ•ŒνŒŒμΉ΄λŠ” μ²˜μŒμ΄μ§€?

[v0.33]μ˜μƒμ²˜λ¦¬_κ΄‘ν•™ 흐름(Optical Flow) λ³Έλ¬Έ

πŸ‘©‍πŸ’» IoT (Embedded)/Image Processing

[v0.33]μ˜μƒμ²˜λ¦¬_κ΄‘ν•™ 흐름(Optical Flow)

μ§•μ§•μ•ŒνŒŒμΉ΄ 2022. 1. 18. 00:34
728x90
λ°˜μ‘ν˜•

220118 μž‘μ„±

<λ³Έ λΈ”λ‘œκ·ΈλŠ” 귀퉁이 μ„œμž¬λ‹˜μ˜ λΈ”λ‘œκ·Έλ₯Ό μ°Έκ³ ν•΄μ„œ κ³΅λΆ€ν•˜λ©° μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€>

https://bkshin.tistory.com/entry/OpenCV-31-%EA%B4%91%ED%95%99-%ED%9D%90%EB%A6%84Optical-Flow?category=1148027 

 

OpenCV - 31. κ΄‘ν•™ 흐름(Optical Flow)

이번 ν¬μŠ€νŒ…μ—μ„œλŠ” 객체 좔적 방법인 κ΄‘ν•™ 흐름에 κ΄€ν•΄ λ°°μ›Œλ³΄κ² μŠ΅λ‹ˆλ‹€. 이번 ν¬μŠ€νŒ… μ—­μ‹œ '파이썬으둜 λ§Œλ“œλŠ” OpenCV ν”„λ‘œμ νŠΈ(μ΄μ„Έμš° μ €)'λ₯Ό μ •λ¦¬ν•œ κ²ƒμž„μ„ λ°νž™λ‹ˆλ‹€. μ½”λ“œ: github.com/BaekKyunShin/Op

bkshin.tistory.com

 

 

 

 

 

 

1. κ΄‘ν•™ 흐름(Optical Flow)

: κ΄‘ν•™ νλ¦„μ΄λž€ μ˜μƒ λ‚΄ 물체의 μ›€μ§μž„ νŒ¨ν„΄

: 이전 ν”„λ ˆμž„κ³Ό λ‹€μŒ ν”„λ ˆμž„ κ°„ 픽셀이 μ΄λ™ν•œ λ°©ν–₯κ³Ό 거리 뢄포

: κ΄‘ν•™ νλ¦„μœΌλ‘œ μ˜μƒ λ‚΄ 물체가 μ–΄λŠ λ°©ν–₯으둜 μ–Όλ§ˆλ§ŒνΌ μ›€μ§μ˜€λŠ”μ§€ νŒŒμ•… κ°€λŠ₯

: μΆ”κ°€ 연산을 ν•˜λ©΄ 물체의 μ›€μ§μž„μ„ 예츑 κ°€λŠ₯

https://en.wikipedia.org/wiki/Optical_flow

 

: κ΄‘ν•™ 흐름은 λ‹€μŒ 두 가지 사싀을 κ°€μ •

1. μ—°μ†λœ ν”„λ ˆμž„ μ‚¬μ΄μ—μ„œ μ›€μ§μ΄λŠ” 물체의 ν”½μ…€ 강도(intensity)λŠ” 변함이 μ—†λ‹€.
2. μ΄μ›ƒν•˜λŠ” 픽셀은 λΉ„μŠ·ν•œ μ›€μ§μž„μ„ κ°–λŠ”λ‹€.

 

: κ΄‘ν•™ 흐름을 κ³„μ‚°ν•˜λŠ” 방법은 두 가지

1. 일뢀 ν”½μ…€λ§Œ κ³„μ‚°ν•˜λŠ” ν¬μ†Œ(sparse) κ΄‘ν•™ 흐름

2. μ˜μƒ 전체 픽셀을 λͺ¨λ‘ κ³„μ‚°ν•˜λŠ” 밀집(dense) κ΄‘ν•™ 흐름

 

 

 

2. 루카슀-μΉ΄λ‚˜λ°(Lucas-Kanade) μ•Œκ³ λ¦¬μ¦˜

: κ΄‘ν•™ 흐름은 μ΄μ›ƒν•˜λŠ” 픽셀이 λΉ„μŠ·ν•˜κ²Œ 움직인닀고 κ°€μ •

: 루카슀-μΉ΄λ‚˜λ° μ•Œκ³ λ¦¬μ¦˜μ€ 이 가정을 μ΄μš©ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜

: μ΄μ›ƒν•˜λŠ” 픽셀은 λΉ„μŠ·ν•œ μ›€μ§μž„μ„ κ°–λŠ”λ‹€κ³  μƒκ°ν•˜κ³  κ΄‘ν•™ 흐름을 νŒŒμ•…

: μž‘μ€ μœˆλ„(3 x 3 patch)λ₯Ό μ‚¬μš©ν•˜μ—¬ μ›€μ§μž„μ„ 계산 -> 물체 μ›€μ§μž„μ΄ 크면 문제 (μœˆλ„ 크기가 μž‘κΈ° λ•Œλ¬Έ)

: κ°œμ„ ν•˜κΈ° μœ„ν•΄ μ΄λ―Έμ§€ ν”ΌλΌλ―Έλ“œλ₯Ό μ‚¬μš©

: 이미지 ν”ΌλΌλ―Έλ“œ μœ„μͺ½μœΌλ‘œ 갈수둝(이미지가 μž‘μ•„μ§ˆμˆ˜λ‘) μž‘μ€ μ›€μ§μž„μ€ ν‹°κ°€ μ•ˆ λ‚˜κ³ 

큰 μ›€μ§μž„μ€ μž‘μ€ μ›€μ§μž„ κ°™μ•„ λ³΄μž„ (큰 μ›€μ§μž„λ„ 감지 κ°€λŠ₯)

 

nextPts, status, err = cv2.calcOpticalFlowPyrLK(prevImg, nextImg, prevPts, nextPts, status, err, wirnSize, maxLevel, criteria, flags, minEigThreshold)
- prevImg : 이전 ν”„λ ˆμž„ μ˜μƒ
- nextImg : λ‹€μŒ ν”„λ ˆμž„ μ˜μƒ
- prevPts : 이전 ν”„λ ˆμž„μ˜ μ½”λ„ˆ νŠΉμ§•μ , cv2.goodFeaturesToTrack()으둜 κ²€μΆœ
- nextPst : λ‹€μŒ ν”„λ ˆμž„μ—μ„œ μ΄λ™ν•œ μ½”λ„ˆ νŠΉμ§•μ 
- status : κ²°κ³Ό μƒνƒœ 벑터, nextPts와 같은 길이, λŒ€μ‘μ μ΄ 있으면 1, μ—†μœΌλ©΄ 0
- err : κ²°κ³Ό μ—λŸ¬ 벑터, λŒ€μ‘μ  κ°„μ˜ 였차
- winSize=(21,21) : 각 이미지 ν”ΌλΌλ―Έλ“œμ˜ 검색 μœˆλ„ 크기
- maxLevel=3 : 이미지 ν”ΌλΌλ―Έλ“œ 계측 수
- criteria=(COUNT+EPS, 30, 0.01) : 반볡 탐색 쀑지 μš”κ±΄

  • cv2.TERM_CRITERIA_EPS : 정확도가 epsilon보닀 μž‘μœΌλ©΄ 쀑지
  • cv2.TERM_CRITERIA_MAX_ITER : max_iter 횟수λ₯Ό μ±„μš°λ©΄ 쀑지
  • cv2.TERM_CRITERIA_COUNT : MAX_ITER와 동일
  • max_iter : μ΅œλŒ€ 반볡 횟수
  • epsilon : μ΅œμ†Œ 정확도

- flgs=0 : μ—°μ‚° λͺ¨λ“œ

  • 0 : prevPtsλ₯Ό nextPts의 초기 κ°’μœΌλ‘œ μ‚¬μš©
  • cv2.OPTFLOW_USE_INITAL_FLOW : nextPts의 값을 초기 κ°’μœΌλ‘œ μ‚¬μš©
  • cv2.OPTFLOW_LK_GET_MIN_EIGENVALS : 였차λ₯Ό μ΅œμ†Œ 고유 κ°’μœΌλ‘œ 계산

- minEigThreshold=1e-4 : λŒ€μ‘μ  계산에 μ‚¬μš©ν•  μ΅œμ†Œ μž„κ³„ 고유 κ°’

 

 

: μ˜μƒ λ‚΄ ν”½μ…€ 전체λ₯Ό ν•œλ²ˆμ— κ³„μ‚°ν•˜μ§€ μ•ŠμŒ

: cv2.goodFeaturesToTrack() ν•¨μˆ˜λ‘œ 얻은 νŠΉμ§•μ λ§Œ ν™œμš©ν•˜μ—¬ 계산

: 두 νŠΉμ§•μ μ΄ μ„œλ‘œ λŒ€μ‘ν•˜λ©΄ status λ³€μˆ˜κ°€ 1, 그렇지 μ•ŠμœΌλ©΄ 0

: maxLevel=0이면 이미지 ν”ΌλΌλ―Έλ“œλ₯Ό μ‚¬μš©ν•˜μ§€ X

# calcOpticalFlowPyrLK 좔적
import numpy as np, cv2

cap = cv2.VideoCapture('img/walking3.mp4')
fps = cap.get(cv2.CAP_PROP_FPS) # ν”„λ ˆμž„ 수 κ΅¬ν•˜κΈ°
delay = int(1000/fps)
# 좔적 경둜λ₯Ό 그리기 μœ„ν•œ 랜덀 색상
color = np.random.randint(0,255,(200,3))
lines = None  #좔적 선을 그릴 이미지 μ €μž₯ λ³€μˆ˜
prevImg = None  # 이전 ν”„λ ˆμž„ μ €μž₯ λ³€μˆ˜
# calcOpticalFlowPyrLK 쀑지 μš”κ±΄ μ„€μ •
termcriteria =  (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)

while cap.isOpened():
    ret,frame = cap.read()
    if not ret:
        break
    img_draw = frame.copy()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 졜초 ν”„λ ˆμž„ 경우
    if prevImg is None:
        prevImg = gray
        # 좔적선 그릴 이미지λ₯Ό ν”„λ ˆμž„ 크기에 맞게 생성
        lines = np.zeros_like(frame)
        # 좔적 μ‹œμž‘μ„ μœ„ν•œ μ½”λ„ˆ κ²€μΆœ  ---β‘ 
        prevPt = cv2.goodFeaturesToTrack(prevImg, 200, 0.01, 10)
    else:
        nextImg = gray
        # μ˜΅ν‹°μ»¬ ν”Œλ‘œμš°λ‘œ λ‹€μŒ ν”„λ ˆμž„μ˜ μ½”λ„ˆμ   μ°ΎκΈ° ---β‘‘
        nextPt, status, err = cv2.calcOpticalFlowPyrLK(prevImg, nextImg, \
                                        prevPt, None, criteria=termcriteria)
        # λŒ€μ‘μ μ΄ μžˆλŠ” μ½”λ„ˆ, 움직인 μ½”λ„ˆ 선별 ---β‘’
        prevMv = prevPt[status==1]
        nextMv = nextPt[status==1]
        for i,(p, n) in enumerate(zip(prevMv, nextMv)):
            px,py = p.ravel()
            nx,ny = n.ravel()
            # 이전 μ½”λ„ˆμ™€ μƒˆλ‘œμš΄ μ½”λ„ˆμ— 선그리기 ---β‘£
            cv2.line(lines, (px, py), (nx,ny), color[i].tolist(), 2)
            # μƒˆλ‘œμš΄ μ½”λ„ˆμ— 점 그리기
            cv2.circle(img_draw, (nx,ny), 2, color[i].tolist(), -1)
        # λˆ„μ λœ 좔적 선을 좜λ ₯ 이미지에 ν•©μ„± ---β‘€
        img_draw = cv2.add(img_draw, lines)
        # λ‹€μŒ ν”„λ ˆμž„μ„ μœ„ν•œ ν”„λ ˆμž„κ³Ό μ½”λ„ˆμ  이월
        prevImg = nextImg
        prevPt = nextMv.reshape(-1,1,2)

    cv2.imshow('OpticalFlow-LK', img_draw)
    key = cv2.waitKey(delay)
    if key == 27 : # Esc:μ’…λ£Œ
        break
    elif key == 8: # Backspace:좔적 이λ ₯ μ§€μš°κΈ°
        prevImg = None
cv2.destroyAllWindows()
cap.release()

https://bkshin.tistory.com/entry/OpenCV-31-%EA%B4%91%ED%95%99-%ED%9D%90%EB%A6%84Optical-Flow?category=1148027

- cv2.goodFeatureToTrack() ν•¨μˆ˜λ‘œ 이전 ν”„λ ˆμž„μ˜ νŠΉμ§•μ μ„ κ²€μΆœ

- cv2.calcOpticalFlowPyrLK() ν•¨μˆ˜λ‘œ κ΄‘ν•™ 흐름을 계산해 λ‹€μŒ ν”„λ ˆμž„μ˜ νŠΉμ§•μ  찾음

- 이전 ν”„λ ˆμž„κ³Ό λ‹€μŒ ν”„λ ˆμž„ νŠΉμ§•μ  쀑 잘 λŒ€μ‘λ˜λŠ” νŠΉμ§•μ λ§Œ μ„ λ³„ν•˜μ—¬ μ„ κ³Ό 점으둜 ν‘œμ‹œ

- 원본 이미지에 좔적선을 ν•©μ„±ν•˜λŠ” λ°©μ‹μœΌλ‘œ ν‘œν˜„

- λ‚˜λŠ” 될 λ“―ν•˜λ‹€κ°€ 였λ₯˜ λ‚œλ‹Ή... κΉŒλΉ™ 보고 μ‹Άμ—ˆλŠ”λ°

 

 

 

 

 

 

3. κ΅°λ‚˜λ₯΄ νŒŒλ„ˆλ°±(Gunner Farneback) μ•Œκ³ λ¦¬μ¦˜

: 밀집 λ°©μ‹μœΌλ‘œ κ΄‘ν•™ 흐름을 κ³„μ‚°ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜

: 밀집 방식은 μ˜μƒ μ „μ²΄μ˜ 픽셀을 ν™œμš©ν•΄ κ΄‘ν•™ 흐름을 계산

 

flow = cv2.calcOpticalFlowFarneback(prev, next, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags)
- prev, next : 이전, 이후 ν”„λ ˆμž„
- flow : κ΄‘ν•™ 흐름 계산 κ²°κ³Ό, 각 픽셀이 μ΄λ™ν•œ 거리 (μž…λ ₯κ³Ό λ™μΌν•œ 크기)
- pyr_scale : 이미지 ν”ΌλΌλ―Έλ“œ μŠ€μΌ€μΌ
- levels : 이미지 ν”ΌλΌλ―Έλ“œ 개수
- winsize : 평균 μœˆλ„ 크기
- iterations : 각 ν”ΌλΌλ―Έλ“œμ—μ„œ λ°˜λ³΅ν•  횟수
- poly_n : 닀항식 근사λ₯Ό μœ„ν•œ 이웃 크기, 5 λ˜λŠ” 7
- poly_sigma : 닀항식 κ·Όμ‚¬μ—μ„œ μ‚¬μš©ν•  κ°€μš°μ‹œμ•ˆ μ‹œκ·Έλ§ˆ (poly_n=5일 λ•ŒλŠ” 1.1, poly_n=7일 λ•ŒλŠ” 1.5)
- flags : μ—°μ‚° λͺ¨λ“œ

  • cv2.OPTFLOW_USE_INITAL_FLOW : flow 값을 초기 κ°’μœΌλ‘œ μ‚¬μš©
  • cv2.OPTFLOW_FARNEBACK_GAUSSIAN : λ°•μŠ€ ν•„ν„° λŒ€μ‹  κ°€μš°μ‹œμ•ˆ ν•„ν„° μ‚¬μš©

: 밀집 κ΄‘ν•™ 흐름은 ν¬μ†Œ κ΄‘ν•™ 흐름과 λ‹€λ₯΄κ²Œ μ˜μƒ 전체 픽셀을 ν™œμš©ν•΄ 계산

: 좔적할 νŠΉμ§•μ μ„ λ”°λ‘œ 전달할 ν•„μš”κ°€ μ—†μŒ

: 전체 픽셀을 ν™œμš©ν•΄ κ³„μ‚°ν•˜λ―€λ‘œ 속도가 느림

# calcOPticalFlowFarneback 좔적
import cv2, numpy as np

# ν”Œλ‘œμš° κ²°κ³Ό 그리기 ---β‘ 
def drawFlow(img,flow,step=16):
  h,w = img.shape[:2]
  # 16ν”½μ…€ κ°„κ²©μ˜ κ·Έλ¦¬λ“œ 인덱슀 κ΅¬ν•˜κΈ° ---β‘‘
  idx_y,idx_x = np.mgrid[step/2:h:step,step/2:w:step].astype(np.int)
  indices =  np.stack( (idx_x,idx_y), axis =-1).reshape(-1,2)
  
  for x,y in indices:   # 인덱슀 순회
    # 각 κ·Έλ¦¬λ“œ 인덱슀 μœ„μΉ˜μ— 점 그리기 ---β‘’
    cv2.circle(img, (x,y), 1, (0,255,0), -1)
    # 각 κ·Έλ¦¬λ“œ μΈλ±μŠ€μ— ν•΄λ‹Ήν•˜λŠ” ν”Œλ‘œμš° κ²°κ³Ό κ°’ (이동 거리)  ---β‘£
    dx,dy = flow[y, x].astype(np.int)
    # 각 κ·Έλ¦¬λ“œ 인덱슀 μœ„μΉ˜μ—μ„œ μ΄λ™ν•œ 거리 만큼 μ„  그리기 ---β‘€
    cv2.line(img, (x,y), (x+dx, y+dy), (0,255, 0),2, cv2.LINE_AA )


prev = None # 이전 ν”„λ ˆμž„ μ €μž₯ λ³€μˆ˜

cap = cv2.VideoCapture('img/walking.mp4')
fps = cap.get(cv2.CAP_PROP_FPS) # ν”„λ ˆμž„ 수 κ΅¬ν•˜κΈ°
delay = int(1000/fps)

while cap.isOpened():
  ret,frame = cap.read()
  if not ret: break
  gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
  # 졜초 ν”„λ ˆμž„ 경우 
  if prev is None: 
    prev = gray # 첫 이전 ν”„λ ˆμž„ --- β‘₯
  else:
    # 이전, 이후 ν”„λ ˆμž„μœΌλ‘œ μ˜΅ν‹°μ»¬ ν”Œλ‘œμš° 계산 ---⑦
    flow = cv2.calcOpticalFlowFarneback(prev,gray,None,\
                0.5,3,15,3,5,1.1,cv2.OPTFLOW_FARNEBACK_GAUSSIAN) 
    # 계산 κ²°κ³Ό 그리기, μ„ μ–Έν•œ ν•¨μˆ˜ 호좜 ---⑧
    drawFlow(frame,flow)
    # λ‹€μŒ ν”„λ ˆμž„μ„ μœ„ν•΄ 이월 ---⑨
    prev = gray
  
  cv2.imshow('OpticalFlow-Farneback', frame)
  if cv2.waitKey(delay) == 27:
      break
cap.release()
cv2.destroyAllWindows()

calcOPticalFlowFarneback 좔적 λ™μ˜μƒ

- drawFlow() ν•¨μˆ˜λ‘œ 16ν”½μ…€ κ°„κ²©μœΌλ‘œ 격자 λͺ¨μ–‘μ˜ 점을 찍음

- 각 μ μ—μ„œ ν•΄λ‹Ήν•˜λŠ” 픽셀이 μ΄λ™ν•œ 만큼 μ„ μœΌλ‘œ ν‘œμ‹œ

- ν¬μ†Œ κ΄‘ν•™ 흐름과 λ‹€λ₯΄κ²Œ 밀집 κ΄‘ν•™ 흐름은 μ˜μƒ μ „μ²΄μ—μ„œ μΌμ–΄λ‚˜λŠ” μ›€μ§μž„μ„ 감지

- μ™• λŠλ¦¬λ‹€!!!

 

 

 

 

 

κ΅Ώ

728x90
λ°˜μ‘ν˜•
Comments