C# OpenCV 강좌 : 제 60강 - 광학 흐름 - Farneback

   

광학 흐름 - Farneback (Optical Flow Farneback)


1 카메라와 피사체의 상대 운동에 의하여 발생하는 피사체의 운동에 대한 패턴을 검출합니다.

Farneback 방법은 Gunnar Farneback의 알고리즘을 사용하여 밀도가 높은 광학 흐름을 계산합니다.


이전 프레임(Previous)현재 프레임(Current)은 영상이나 이미지를 사용하면 됩니다.


영상 사용하기 : 3강 바로가기
이미지 사용하기 : 4강 바로가기


Main Code


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenCvSharp;

namespace test
{
    class OpenCV : IDisposable
    {
        IplImage gray;    
        IplImage optical;

        public IplImage GrayScale(IplImage src)
        {
            gray = new IplImage(src.Size, BitDepth.U8, 1);
            Cv.CvtColor(src, gray, ColorConversion.BgrToGray);
            return gray;
        }
            
        public IplImage OpticalFlowFarneback(IplImage previous, IplImage current)
        {
            IplImage prev = this.GrayScale(previous);
            IplImage curr = this.GrayScale(current);
            optical = current;

            int rows = optical.Height;
            int cols = optical.Width;

            IplImage flow = new IplImage(optical.Size, BitDepth.F32, 2);
            double pyrScale = 0.5;
            int level = 3;
            int winSize = 15;
            int iterations = 3;
            int polyN = 5;
            double polySigma = 1.1; 

            Cv.CalcOpticalFlowFarneback(prev, curr, flow, pyrScale, level, winSize, iterations, polyN, polySigma, LKFlowFlag.PyrAReady);

            for (int i = 0; i < cols; i += winSize)
            {
                for (int j = 0; j < rows; j += winSize)
                {
                    int dx = (int)flow[j, i][0];
                    int dy = (int)flow[j, i][1];

                    if(dx != 0 || dy != 0)
                    {
                        Cv.DrawLine(optical, Cv.Point(i, j), Cv.Point(i + dx, j + dy), CvColor.Blue, 1, LineType.AntiAlias, 0);
                        Cv.DrawCircle(optical, new CvPoint(i + dx, j + dy), 3, CvColor.Blue, -1);
                    }
                }
            }
            return optical;
        }
            
        public void Dispose()
        {
            if (gray!= null) Cv.ReleaseImage(gray);   
            if (optical != null) Cv.ReleaseImage(optical);  
        }
    }
}


Class Code


public IplImage OpticalFlowFarneback(IplImage previous, IplImage current)
{
    ...
}

이전 프레임 previous와 현재 프레임 current를 매개변수로 사용하여 검출을 진행합니다.


IplImage prev = this.GrayScale(previous);
IplImage curr = this.GrayScale(current);
optical = current;

광학 흐름 함수는 그레이스케일을 적용하여 검출을 진행합니다.

계산이미지로 사용할 prevcurr 변수에 그레이스케일을 적용합니다.

이후, 결과로 사용할 optical 필드에 현재 프레임을 사용합니다.


  • Tip : 그레이스케일을 사용하여 검출하므로 급격한 밝기 변화노이즈에는 정확한 검출을 얻어낼 수 없습니다.


int rows = optical.Height;
int cols = optical.Width;

을 설정합니다.


IplImage flow = new IplImage(optical.Size, BitDepth.F32, 2);
double pyrScale = 0.5;
int level = 3;
int winSize = 15;
int iterations = 3;
int polyN = 5;
double polySigma = 1.1; 

광학 흐름 검출 함수에 사용할 인수들을 설정합니다.

flow는 광학 흐름에 대한 정보를 저장합니다. 비트 깊이는 F32, 채널은 2를 사용합니다.

pyrScale는 프레임의 피라미드를 만들기 위한 이미지 크기를 설정합니다. 값은 0~1의 값을 사용합니다. 0.5고전적인 피라미드의 크기입니다.

level은 피라미드 이미지의 레벨값을 설정합니다. 1로 사용할 경우, 원본 이미지로 사용합니다.

winSize는 윈도우 창의 크기를 의미합니다. 값이 클수록 노이즈의 영향과 처리속도가 짧아지지만, 검출 결과가 흐릿해집니다.

iterations는 각 피라미드에서 알고리즘이 반복 수행할 횟수입니다.

polyN은 각 픽셀에서 다항식 확장을 찾는 데 사용되는 인접 픽셀 영역 크기입니다. 값이 클수록 매끄러워지지만, 검출 결과가 흐릿해집니다. 값은 5~7의 값을 가장 많이 사용합니다.

polySigma는 부드럽게 하기 위한 가우시안의 표준 편차입니다. polyN의 값이 5일 경우, 1.1의 값을 주로 사용하며, polyN의 값이 7일 경우, 1.5의 값을 주로 사용합니다.


Cv.CalcOpticalFlowFarneback(prev, curr, flow, pyrScale, level, winSize, iterations, polyN, polySigma, LKFlowFlag.PyrAReady);

Cv.CalcOpticalFlowFarneback()을 사용하여 광학 흐름을 구합니다.

Cv.CalcOpticalFlowFarneback(이전 프레임, 현재 프레임, 광학 흐름 저장 변수, 피라미드 스케일, 레벨, 윈도우 창 크기, 반복 횟수, 인접 픽셀 영역 크기, 가우시안 표준 편차, 플래그)를 의미합니다.


for (int i = 0; i < cols; i += winSize)
{
    for (int j = 0; j < rows; j += winSize)
    {
        ...
    }
}

이중 for문을 사용하여 윈도우 창 크기의 간격 만큼 반복합니다.


int dx = (int)flow[j, i][0];
int dy = (int)flow[j, i][1];

flow에 저장되어있는 광학 흐름에 대한 값을 받아옵니다.

(j ,i)지점에서 index 0index 1 값을 dx, dy에 저장합니다.

index 0x좌표에 대한 정보가 담겨있으며, index 1y좌표에 대한 정보가 담겨있습니다.


if(dx != 0 || dy != 0)
{
    Cv.DrawLine(optical, Cv.Point(i, j), Cv.Point(i + dx, j + dy), CvColor.Blue, 1, LineType.AntiAlias, 0);
    Cv.DrawCircle(optical, new CvPoint(i + dx, j + dy), 3, CvColor.Blue, -1);
}

if문을 이용하여 광학 흐름이 발생되지 않았을 때는 표시하지 않습니다.

Cv.DrawLine()Cv.DrawCircle()을 사용하여 광학 흐름을 optical 필드에 표시합니다.


Result


2



도움이 되셨다면 광고 클릭 부탁드립니다.

⤧  Next post Python 강좌 : 제 28강 - PIP 설치 ⤧  Previous post C# OpenCV 강좌 : 제 59강 - 광학 흐름 - PyrLK