1. 基本原理
2. 稀疏光流 - KLT
稀疏光流 -- 流程:
3. 稠密光流 - HF
稠密光流跟踪是将当前帧的所有像素点与前一帧比较,有变化的标记出来。对比的点比较多,不是对比变化的那几个特征点。所以速度较慢。没有稀疏光流的速度快。但有的时候效果比稀疏光流要好。
4. 相关API
(1)opencv中的goodFeaturesToTrack函数可以计算Harris角点和shi-tomasi角点,但默认情况下计算的是shi-tomasi角点,函数原型如下:
void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners,
int maxCorners, double qualityLevel, double minDistance,
InputArray _mask, int blockSize,
bool useHarrisDetector, double harrisK )
参数解释:
image:8位或32位浮点型输入图像,单通道
corners:保存检测出的角点
maxCorners:角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点
qualityLevel:角点的品质因子
minDistance:对于初选出的角点而言,如果在其周围minDistance范围内存在其他更强角点,则将此角点删除
mask:指定感兴趣区,如不需在整幅图上寻找角点,则用此参数指定ROI
blockSize:计算协方差矩阵时的窗口大小
useHarrisDetector:指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点
harrisK:Harris角点检测需要的k值
(2)计算一个稀疏特征光流使用Lucas Kanade迭代法与金字塔。opencv中的函数为calcOpticalFlowPyrLK。
函数原型及参数解释如下:
void cv::calcOpticalFlowPyrLK(
InputArray prevImg, // 前一帧图像
InputArray nextImg, // 后一帧图像
InputArray prevPts, // 前一帧的稀疏光流点
InputOutputArray nextPts, // 后一帧光流点
OutputArray status, // 输出状态(无符号char),如果在当前图像中能够光流得到标定的特征点位置改
//变,则设置status的对应位置为1,否则设置为0
OutputArray err, // 表示错误
Size winSize = Size(21, 21), // 光流法对象窗口大小
int maxLevel = 3, // 金字塔层数,0表示只检测当前图像,不构建金字塔图像
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), // 窗口搜索时候停止条件
int flags = 0, // 操作标志
double minEigThreshold = 1e-4 // 最小特征值响应,低于最小值不做处理
)
(3)稠密光流函数 calcOpticalFlowFarneback
void cv::calcOpticalFlowFarneback (
InputArray prev, //输入前一帧图像(8位单通道);
InputArray next, //输入后一帧图像(与prev大小和类型相同);
InputOutputArray flow, //计算的流量图像具有与prev相同的大小并为CV_32FC2类型;
double pyr_scale, //指定图像比例(\ <1)为每个图像构建金字塔;
// pyr_scale = 0.5 ,意味着一个古典金字塔,其中每个下一层比前一层小两倍。
int levels, //金字塔层数包括初始图像;
//levels = 1意味着不会创建额外的图层,只会使用原始图像。
int winsize, //平均窗口大小;较大的值会增加算法对图像噪声的鲁棒性,并可以检测更快
// 速的运动,但会产生更模糊的运动场。
int iterations, // 每个金字塔等级上执行迭代算法的迭代次数。用于在每个像素中查找
// 多项式展开的像素邻域;
int poly_n, //较大的值意味着图像将近似于更光滑的表面,产生更稳健的算法和更模糊的
// 运动场,一般取poly_n = 5或7。
double poly_sigma, //用于平滑导数的高斯的标准偏差,用作多项式展开的基础;对于
//poly_n = 5,可以设置poly_sigma = 1.1,对于poly_n = 7,可以设置poly_sigma = 1.5;
int flags //操作标志,可取计算方法有:
// OPTFLOW_USE_INITIAL_FLOW 使用输入流作为初始流近似。
// OPTFLOW_FARNEBACK_GAUSSIAN 使用Gaussian winsize×winsiz过滤器代替光流估计的相同大小的盒子过滤器;通常情况下,这个选项可以比使用箱式过滤器提供更精确的流量,代价是速度更低;通常,应将高斯窗口的胜利设置为更大的值以实现相同的稳健性水平。
)
5. 代码演示
(1)稀疏光流跟踪
/*
稀疏光流跟踪
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat frame, gray;
Mat prev_frame, prev_gray;
vector<Point2f>features; //shi-tomasi角点检测 -- 特征数据
vector<Point2f>iniPoints; //存储需要跟踪的特征点(静止点会被删除)
vector<Point2f>fpts[2]; //定义两个vector数组,一个为fpts[0],一个为fpts[1],保存当前帧和前一帧的特征点
vector<uchar>status; //特征点跟踪成功标志位
vector<float>errors; //跟踪时候区域误差和
void detectFeatures(Mat &inFrame,Mat &inGray);
void drawFeature(Mat &inFrame);
void kltTrackFeature();
void drawTrackLines();
int main(int argc, char** argv)
{
VideoCapture capture;
capture.open("E:/技能学习/Opencv视频分析与对象跟踪/video_006.mp4");
if (!capture.isOpened())
{
cout << "could not find the video file..." << endl;
return -1;
}
namedWindow("input video", WINDOW_AUTOSIZE);
while (capture.read(frame))
{
//转为灰度图
cvtColor(frame, gray, COLOR_BGR2GRAY);
if (fpts[0].size() < 40)
{
//特征点检测
detectFeatures(frame, gray);
fpts[0].insert(fpts[0].end(), features.begin(), features.end());
iniPoints.insert(iniPoints.end(), features.begin(), features.end());
}
else
{
cout << "**************" << endl;
}
if (prev_gray.empty())
{
gray.copyTo(prev_gray);
}
//稀疏光流跟踪
kltTrackFeature();
//绘制出特征点
drawFeature(frame);
//更新前一帧数据
gray.copyTo(prev_gray);
frame.copyTo(prev_frame);
imshow("input video", frame);
char c = waitKey(500);
if (c == 27)
{
break;
}
}
capture.release();
waitKey(0);
destroyAllWindows();
return 0;
}
//角点检测
void detectFeatures(Mat &inFrame, Mat &inGray)
{
double maxCorners = 5000;
double qualitylevel = 0.01;
double minDistance = 10;
double blockSize = 3;
double k = 0.04;
goodFeaturesToTrack(inGray, features, maxCorners, qualitylevel, minDistance, Mat(), blockSize, false, k);
cout << "detect features:" << features.size() << endl;
}
//稀疏光流跟踪
void kltTrackFeature()
{
//光流法跟踪
calcOpticalFlowPyrLK(prev_gray, gray, fpts[0], fpts[1], status, errors);
int k = 0;
//特征点过滤
for (int i = 0; i < fpts[1].size(); i++)
{
// 距离与状态测量(上一帧的坐标 - 下一帧的坐标)
double dist = abs(fpts[0][i].x - fpts[1][i].x) + abs(fpts[0][i].y - fpts[1][i].y);
//如果状态量为1且每次移动到下一帧的距离大于2,就会画跟踪线
if (dist > 2 && status[i])
{
iniPoints[k] = iniPoints[i];
fpts[1][k++] = fpts[1][i];
}
}
//保存有用的特征点 并 绘制跟踪轨迹
iniPoints.resize(k);
fpts[1].resize(k);
drawTrackLines();
//更新数据
std::swap(fpts[1], fpts[0]); //将新一帧图像数据作为旧一帧图像数据,继续进入循环
}
void drawTrackLines()
{
for (size_t t = 0; t < fpts[1].size(); t++)
{
line(frame, iniPoints[t], fpts[1][t], Scalar(0, 255, 0), 2, 8, 0);
circle(frame, fpts[1][t], 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
//绘制出特征点
void drawFeature(Mat &inFrame)
{
for (size_t t = 0; t < fpts[0].size(); t++)
{
circle(inFrame, features[t], 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
(2)稠密光流跟踪
/*
稠密光流跟踪
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void drawOpticalFlowHF(Mat &flowdata, Mat &image, int step);
int main(int argc, char** argv)
{
VideoCapture capture;
capture.open("E:/技能学习/Opencv视频分析与对象跟踪/video_006.mp4");
if (!capture.isOpened())
{
cout << "could not find the video file..." << endl;
return -1;
}
Mat frame, gray;
Mat prev_frame, prev_gray;
Mat flowResult, flowdata;
capture.read(frame);
cvtColor(frame, prev_gray, COLOR_BGR2GRAY);
namedWindow("input", WINDOW_AUTOSIZE);
namedWindow("flow", WINDOW_AUTOSIZE);
//从第二帧数据开始
while (capture.read(frame))
{
cvtColor(frame, gray, COLOR_BGR2GRAY);
if (!prev_gray.empty())
{
//稠密光流
calcOpticalFlowFarneback(prev_gray, gray, flowdata, 0.5, 3, 15, 3, 5, 1.2, 0);
cvtColor(prev_gray, flowResult, COLOR_GRAY2BGR);
drawOpticalFlowHF(flowdata, flowResult, 1);
imshow("input", frame);
imshow("flow", flowResult);
}
char c = waitKey(500);
if (c == 27)
{
break;
}
}
capture.release();
waitKey(0);
destroyAllWindows();
return 0;
}
void drawOpticalFlowHF(Mat &flowdata, Mat &image, int step)
{
for (int row = 0; row < image.rows; row++)
{
for (int col = 0; col < image.cols; col++)
{
const Point2f fxy = flowdata.at<Point2f>(row, col);
if (fxy.x > 2 || fxy.y > 2)
{
line(image, Point(col, row), Point(cvRound(col + fxy.x), cvRound(row + fxy.y)), Scalar(0, 255, 0), 2, 8, 0);
circle(image, Point(col, row), 2, Scalar(0, 0, 255), -1);
}
}
}
}