移动对象跟踪三要素:图像表示(跟踪的对象要在图像中出现)外光模型,移动模型。
稀疏光流跟踪,KTL
void calcOpticalFlowPyrLK( // 稀疏光流跟踪,KLT
InputArray prevImg, // 要跟踪的图像,8bit
InputArray nextImg, // 在目标图像跟跟踪 prevImg 上的 prevPts 特征点
InputArray prevPts, // prevImg 上的特征点(光流)的坐标位置;点坐标必须是单精度浮点数
InputOutputArray nextPts, // 如果在 nextImg 上跟踪到了 prevImg 上的 prevPts[i],则在 nextPts[i] 上保存该特征点现在的坐标,nextPts与prevPts尺寸相同
OutputArray status, // 输出状态向量(无符号char);如果相应位置的流特征被发现,向量的每个元素被设置为1,否则,被置为0.
OutputArray err, // 跟踪时候区域误差和
Size winSize = Size(21,21), // 在每个金字塔水平搜寻窗口的尺寸。
int maxLevel = 3, // 金字塔的高度,初始为3层
TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), // 在每个金字塔层,为某点寻找光流的迭代过程的终止条件
// flags CV_LKFLOW_PYR_A_READY , 在调用之前,第一帧的金字塔已经准备好
CV_LKFLOW_PYR_B_READY , 在调用之前,第二帧的金字塔已经准备好
CV_LKFLOW_INITIAL_GUESSES , 在调用之前,数组 B 包含特征的初始坐标 (Hunnish: 在本节中没有出现数组 B,不知是指的哪一个)
int flags = 0,
double minEigThreshold = 1e-4 // 大量实验得出的默认值,别乱改
);
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<opencv2/face.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv::face;
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
Mat frame, gray;//当前帧
Mat prev_gray;//前一帧
vector<Point2f> features;//shi-tomasi角点检测-特征数据
vector<Point2f>fpts[2];//保证当前帧和前一帧的特征点位置
vector<Point2f> iniPoints;
vector<uchar>status;//特征点跟踪成功标志位
vector<float>errors;//跟踪时候区域误差和
void detectFeatures(Mat &inFrame, Mat &ingray) // Shi-Tomas 角点检测
{
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 drawFeature(Mat&inFrame) {//绘制特征点
for (size_t t = 0;t< fpts[0].size(); t++) {
circle(inFrame, fpts[0][t], 2, Scalar(0, 0, 255), 2);
}
}
void drawTrackLines() // 在跟踪到的且移动了的特征点(光流)的开始跟踪的位置 到 当前跟踪到的位置之间绘制线段
{
for (size_t t = 0; t<fpts[1].size(); t++)
{
line(frame, iniPoints[t], fpts[1][t], Scalar(0, 255, 0), 1, 8, 0); // 绘制线段
circle(frame, fpts[1][t], 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
void KLTrackFeature() {//稀疏光流跟踪,KTL
calcOpticalFlowPyrLK(prev_gray, gray, fpts[0], fpts[1], status, errors);
int k = 0;//保存跟踪到的特征点数,最后将特征点的尺寸重新设置为k
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);
if (dist > 2 && status[i])//跟踪到的特征点,且距离移动了2以上的
{
iniPoints[k] = iniPoints[i];//将跟踪到的移动了的特征点在vector中连续起来,剔掉损失的和禁止不动的特征点(这些跟踪点在前面帧中)
fpts[1][k++] = fpts[1][i];//同上(只是这些跟踪点在当前帧中)
}
}
//保存特征点并绘制跟踪轨迹
iniPoints.resize(k);
fpts[1].resize(k);
drawTrackLines();
std::swap(fpts[1], fpts[0]);//交换,将此帧跟踪到特征点作为下一帧的待跟踪点
}
int main() {
VideoCapture capture;
capture.open("C:/Users/Administrator/Desktop/pic/3.avi");
while (capture.read(frame)) {
cvtColor(frame, gray, COLOR_BGR2GRAY);
if (fpts[0].size() < 40) {//跟踪40个特征点,如果跟踪的时候损失了一些特征点,重新检测,追加
detectFeatures(frame, gray);
fpts[0].insert(fpts[0].end(), features.begin(), features.end());//追加带跟踪的特征点
iniPoints.insert(iniPoints.end(), features.begin(), features.end());
}
else
{
cout << "tracjing........" << endl;
}
if (prev_gray.empty())
gray.copyTo(prev_gray);//保存当前已帧,第一帧过完就不保存了
KLTrackFeature();//稀疏光流跟踪,KLT
drawFeature(frame);//绘制特征点
//更新前一帧数据
gray.copyTo(prev_gray);//只需要灰度图
imshow("src", frame);
}
waitKey(0);
}
结果:
稠密光流跟踪
void calcOpticalFlowFarneback( // 稠密光流跟踪
InputArray prev, // 输入前一帧图像
InputArray next, // 输入后一帧图像
InputOutputArray flow, // 输出的光流
double pyr_scale, // 金字塔上下两层之间的尺度关系
int levels, // 金字塔层数
int winsize, // 均值窗口大小,越大越能denoise并且能够检测快速移动目标,但会引起模糊运动区域
int iterations, // 迭代次数
int poly_n, // 像素领域大小,一般为5,7等
double poly_sigma, // 高斯标注差,一般为1-1.5
int flags // 计算方法。主要包括OPTFLOW_USE_INITIAL_FLOW和OPTFLOW_FARNEBACK_GAUSSIAN
);
代码:
#include <opencv2/opencv.hpp>
#include <opencv2/xfeatures2d.hpp>
#include<opencv2/face.hpp>
#include<iostream>
#include<math.h>
#include <string>
#include<fstream>
using namespace cv::face;
using namespace cv;
using namespace std;
using namespace cv::xfeatures2d;
void drawOpticalFlowHF(const 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 > 1 || fxy.y > 1) // x 或 y 方向移动了1个像素以上就表示该像素移动了
{
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); // 较上一帧移动的像素点
}
}
}
}
int main()
{
VideoCapture capture;
capture.open("C:/Users/Administrator/Desktop/pic/3.avi");
if (!capture.isOpened()) cout << "video not open.." << endl;
Mat frame, gray;
Mat prev_frame, prev_gray;
Mat flowResult, flowdata;
capture.read(frame); // 先取出第一帧图像
cvtColor(frame, prev_gray, COLOR_BGR2GRAY);
// 从第二帧数据开始,与第一帧进行比较
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, 10); // 绘制跟踪
imshow("flow", flowResult);
imshow("src6-11", frame);
}
}
waitKey(0);
}
结果: