Optical Flow - OpenCV(C++)
Updated:
Optical FLow 해보기
Visual Studio 2017을 사용하였습니다.
- goodFeaturesToTrack, calcOpticalFlowPyrLK, calcOpticalFlowFarneback
Optical Flow 이론에 대한 내용보다는 코딩 관련에 집중하여 포스팅하겠습니다.
1. Optical Flow?
정의
- 물체나 카메라의 움직임에 의해 물체가 움직일 때, 2개의 프레임 사이에서 객체의 외관상 움직임의 패턴
- Vector로 표현(2D), 첫 frame에서 두 번째 frame으로 이동
- 적용
- 움직임을 통한 구조 분석
- 비디오 압축
- Video stabilization (영상이 흔들렸거나 blur일 때, 깨끗한 영상으로 처리하는 기술)
PyrLK(Pyramid Lucas Kanade) Optical Flow 방식
- 비디오 이미지에서 추적할 포인트 결정 ( cv::goodFeaturesToTrack )
- 비디오 이미지에서 첫 frame을 추출하고 shi-Tomasi 코너 검출 수행
- 이들 점에 대해 Lucas-Kanade 실행 및 반복
Dense Optical Flow 방식
- 이전 프레임과 현재 프레임에 대해 Dense Optical Flow 수행 (cv::calcOpticalFlowFarneback)
- 결과에 대해 magnitude와 angle을 구하고 HSV 이미지 생성 후 BGR 이미지로 변환
2. goodFeaturesToTrack(코너 검출하기)
함수 원형
goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance, InputArray mask = noArray(), int blockSize = 3, bool useHarrisDetector = false, double k = 0.04 )
- InputArray image : Input 8-bit or floating-point 32-bit, single-channel image
- OutputArray corners : Output vector of detected corners
- int maxCorners : Maximum number of corners to return
- double qualityLevel : Parameter characterizing the minimal accepted quality of image corners
- double minDistance : Minimum possible Euclidean distance between the returned corners
- InputArray mask : Optional region of interest
- int blockSize : Size of an average block for computing a derivative covariation matrix over each pixel neighborhood
- bool useHarrisDetector : Parameter indicating whether to use a Harris detector (see #cornerHarris) or #cornerMinEigenVal
- double k : Free parameter of the Harris detector
3. calcOpticalFlowPyrLK(Lucas-Kanade Optical Flow)
함수 원형
calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg, InputArray prevPts, InputOutputArray nextPts, OutputArray status, OutputArray err, Size winSize = Size(21,21), int maxLevel = 3, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), int flags = 0, double minEigThreshold = 1e-4 )
- InputArray prevImg : first 8-bit input image or pyramid constructed by buildOpticalFlowPyramid
- InputArray nextImg : second input image or pyramid of the same size and the same type as prevImg
- InputArray prevPts : vector of 2D points for which the flow needs to be found; point coordinates must be single-precision floating-point numbers
- InputOutputArray nextPts : output vector of 2D points (with single-precision floating-point coordinates) containing the calculated new positions of input features in the second image
- OutputArray status : output status vector (of unsigned chars)
- OutputArray err : output vector of errors
- Size winSize : size of the search window at each pyramid level
- int maxLevel : 0-based maximal pyramid level number
4. calcOpticalFlowFarneback(Dense Optical Flow)
함수 원형
calcOpticalFlowFarneback( InputArray prev, InputArray next, InputOutputArray flow, double pyr_scale, int levels, int winsize, int iterations, int poly_n, double poly_sigma, int flags)
- InputArray prev : first 8-bit single-channel input image
- InputArray next : second input image of the same size and the same type as prev
- InputOutputArray flow : computed flow image that has the same size as prev and type CV_32FC2
- double pyr_scale : pyr_scale parameter, specifying the image scale (<1) to build pyramids for each image; pyr_scale=0.5 means a classical pyramid, where each next layer is twice smaller than the previous one.
- int levels : number of pyramid layers including the initial image
- int winsize : averaging window size
- int iterations : number of iterations the algorithm does at each pyramid level
- int poly_n : size of the pixel neighborhood used to find polynomial expansion in each pixel
- double poly_sigma : standard deviation of the Gaussian that is used to smooth derivatives used as a basis for the polynomial expansion
5. 코드
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
// using namespace cv;
using namespace std;
int main()
{
cout << "\n\n프로그램 종료는 esc key를 누르세요.\n";
cout << "\n ===================================================================== \n";
cout << "웹캠 사용은 1, 동영상 재생은 2를 누르세요.\n\n\n";
int num;
cin >> num;
if (num == 1) // 웹캠 선택
{
cv::VideoCapture cap(0); // camera 연결
if (!cap.isOpened())
{
cerr << "\n카메라 열 수 없음.\n" << endl;
return -1;
}
cv::Mat old_frame, old_gray;
cap.read(old_frame); // 초기 frame 설정
cv::cvtColor(old_frame, old_gray, cv::COLOR_BGR2GRAY);
vector<cv::Point2f> p0, p1;
cv::goodFeaturesToTrack(old_gray, p0, 100, 0.3, 7, cv::Mat(), 7, false, 0.04); // 이미지에서 코너 추출하기
/*goodFeaturesToTrack( InputArray image, OutputArray corners,
int maxCorners, double qualityLevel, double minDistance,
InputArray mask = noArray(), int blockSize = 3,
bool useHarrisDetector = false, double k = 0.04 );*/
// Create a mask image for drawing purposes
cv::Mat mask = cv::Mat::zeros(old_frame.size(), old_frame.type());
cv::Mat D_frame, D_prvs;
cap.read(D_frame);
cv::cvtColor(D_frame, D_prvs, cv::COLOR_BGR2GRAY);
while (true) // 종료 시까지 영상 출력
{
cv::Mat frame;
cap.read(frame);
if (frame.empty())
{
cerr << "캡쳐 실패." << endl;
break;
}
imshow("Live", frame);
/*==============================Lucas - Kanade Optical Flow 해보기===========================================*/
cv::Mat LK_frame, LK_frame_gray;
cv::copyTo(frame, LK_frame, cv::Mat());
cv::cvtColor(LK_frame, LK_frame_gray, cv::COLOR_BGR2GRAY);
vector<uchar> status;
vector<float> err;
cv::TermCriteria criteria = cv::TermCriteria((cv::TermCriteria::COUNT) + (cv::TermCriteria::EPS), 10, 0303);
//TermCriteria(int _type, int _maxCount, double _epsilon);
cv::calcOpticalFlowPyrLK(old_gray, LK_frame_gray, p0, p1, status, err, cv::Size(21, 21), 3, criteria);
/*calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
InputArray prevPts, InputOutputArray nextPts,
OutputArray status, OutputArray err,
Size winSize = Size(21,21), int maxLevel = 3,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
int flags = 0, double minEigThreshold = 1e-4 );*/
vector<cv::Point2f> good_new;
for (uint i = 0; i < p0.size(); i++)
{
// Select good points
if (status[i] == 1)
{
good_new.push_back(p1[i]); // 다음 p0로 사용하기 위해 좋은거만 선택
// draw the tracks
line(mask, p1[i], p0[i], (0, 0, 255), 3);
circle(LK_frame, p1[i], 5, (0, 255, 255), 2);
}
}
cv::Mat LK_result_image;
cv::add(LK_frame, mask, LK_result_image);
imshow("Lucas-Kanade Result", LK_result_image);
// Now update the previous frame and previous points
old_gray = LK_frame_gray.clone();
//p0 = good_new;
p0 = p1;
/*=================================Dense Optical Flow 해보기=============================================*/
cv::Mat D_frame2, D_next;
cv::copyTo(frame, D_frame2, cv::Mat());
cv::cvtColor(D_frame2, D_next, cv::COLOR_BGR2GRAY);
cv::Mat flow(D_prvs.size(), CV_32FC2);
cv::calcOpticalFlowFarneback(D_prvs, D_next, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
/*calcOpticalFlowFarneback( InputArray prev, InputArray next, InputOutputArray flow,
double pyr_scale, int levels, int winsize,
int iterations, int poly_n, double poly_sigma,
int flags );*/
//visualization
cv::Mat flow_parts[2];
cv::split(flow, flow_parts);
cv::Mat magnitude, angle, magn_norm;
cv::cartToPolar(flow_parts[0], flow_parts[1], magnitude, angle, true);
/*cartToPolar(InputArray x, InputArray y,
OutputArray magnitude, OutputArray angle,
bool angleInDegrees = false);*/
cv::normalize(magnitude, magn_norm, 0.0f, 10.f, cv::NORM_MINMAX);
angle *= ((1.f / 360.f) * (180.f / 255.f));
//build hsv image
cv::Mat _hsv[3], hsv, hsv8, D_result_image; // HSV는 색상, 채도, 명도로 image 표현
_hsv[0] = angle;
_hsv[1] = cv::Mat::ones(angle.size(), CV_32F);
_hsv[2] = magn_norm;
merge(_hsv, 3, hsv); // 3채널을 1채널로 합침
hsv.convertTo(hsv8, CV_8U, 255.0); // DataType 변경
cvtColor(hsv8, D_result_image, cv::COLOR_HSV2BGR);
imshow("Dense Result", D_result_image);
D_prvs = D_next;
int key = cv::waitKey(10);
if (key == 27) // esc key 누르면 종료
break;
}
}
else if (num == 2) // 동영상 출력
{
cv::VideoCapture cap("BlackBOX.mp4");
if (!cap.isOpened())
{
cerr << "파일을 열 수 없습니다." << endl;
return -1;
}
cv::Mat old_frame, old_gray;
cap.read(old_frame); // 초기 frame 설정
cv::cvtColor(old_frame, old_gray, cv::COLOR_BGR2GRAY);
vector<cv::Point2f> p0, p1;
cv::goodFeaturesToTrack(old_gray, p0, 100, 0.3, 7, cv::Mat(), 7, false, 0.04); // 이미지에서 코너 추출하기
/*goodFeaturesToTrack( InputArray image, OutputArray corners,
int maxCorners, double qualityLevel, double minDistance,
InputArray mask = noArray(), int blockSize = 3,
bool useHarrisDetector = false, double k = 0.04 );*/
// Create a mask image for drawing purposes
cv::Mat mask = cv::Mat::zeros(old_frame.size(), old_frame.type());
cv::Mat D_frame, D_prvs;
cap.read(D_frame);
cv::cvtColor(D_frame, D_prvs, cv::COLOR_BGR2GRAY);
while (true) // 종료 시까지 영상 출력
{
cv::Mat frame;
cap.read(frame);
if (frame.empty())
{
cerr << "캡쳐 실패." << endl;
break;
}
imshow("Live", frame);
/*==============================Lucas - Kanade Optical Flow 해보기===========================================*/
cv::Mat LK_frame, LK_frame_gray;
cv::copyTo(frame, LK_frame, cv::Mat());
cv::cvtColor(LK_frame, LK_frame_gray, cv::COLOR_BGR2GRAY);
vector<uchar> status;
vector<float> err;
cv::TermCriteria criteria = cv::TermCriteria((cv::TermCriteria::COUNT) + (cv::TermCriteria::EPS), 10, 0303);
//TermCriteria(int _type, int _maxCount, double _epsilon);
cv::calcOpticalFlowPyrLK(old_gray, LK_frame_gray, p0, p1, status, err, cv::Size(21, 21), 2, criteria);
/*calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
InputArray prevPts, InputOutputArray nextPts,
OutputArray status, OutputArray err,
Size winSize = Size(21,21), int maxLevel = 3,
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
int flags = 0, double minEigThreshold = 1e-4 );*/
vector<cv::Point2f> good_new;
for (uint i = 0; i < p0.size(); i++)
{
// Select good points
if (status[i] == 1)
{
good_new.push_back(p1[i]); // 다음 p0로 사용하기 위해 좋은거만 선택
// draw the tracks
line(mask, p1[i], p0[i], (0, 0, 255), 3);
circle(LK_frame, p1[i], 5, (0, 255, 255), 2);
}
}
cv::Mat LK_result_image;
cv::add(LK_frame, mask, LK_result_image);
// imshow("mask", mask);
imshow("Lucas-Kanade Result", LK_result_image);
// Now update the previous frame and previous points
old_gray = LK_frame_gray.clone();
p0 = good_new;
/*=================================Dense Optical Flow 해보기=============================================*/
cv::Mat D_frame2, D_next;
cv::copyTo(frame, D_frame2, cv::Mat());
cv::cvtColor(D_frame2, D_next, cv::COLOR_BGR2GRAY);
cv::Mat flow(D_prvs.size(), CV_32FC2);
cv::calcOpticalFlowFarneback(D_prvs, D_next, flow, 0.5, 3, 15, 3, 5, 1.2, 0);
/*calcOpticalFlowFarneback( InputArray prev, InputArray next, InputOutputArray flow,
double pyr_scale, int levels, int winsize,
int iterations, int poly_n, double poly_sigma,
int flags );*/
//visualization
cv::Mat flow_parts[2];
cv::split(flow, flow_parts);
cv::Mat magnitude, angle, magn_norm;
cv::cartToPolar(flow_parts[0], flow_parts[1], magnitude, angle, true);
/*cartToPolar(InputArray x, InputArray y,
OutputArray magnitude, OutputArray angle,
bool angleInDegrees = false);*/
cv::normalize(magnitude, magn_norm, 0.0f, 10.f, cv::NORM_MINMAX);
angle *= ((1.f / 360.f) * (180.f / 255.f));
//build hsv image
cv::Mat _hsv[3], hsv, hsv8, D_result_image; // HSV는 색상, 채도, 명도로 image 표현
_hsv[0] = angle;
_hsv[1] = cv::Mat::ones(angle.size(), CV_32F);
_hsv[2] = magn_norm;
merge(_hsv, 3, hsv); // 3채널을 1채널로 합침
hsv.convertTo(hsv8, CV_8U, 255.0); // DataType 변경
cvtColor(hsv8, D_result_image, cv::COLOR_HSV2BGR);
imshow("Dense Result", D_result_image);
D_prvs = D_next;
int key = cv::waitKey(10);
if (key == 27) // esc key 누르면 종료
break;
}
}
return 0;
}
Leave a comment