大家好,又见面了,我是你们的朋友全栈君。
一.基于特征点的目标跟踪的一般方法
基于特征点的跟踪算法大致可以分为两个步骤:
1)探测当前帧的特征点;
2)通过当前帧和下一帧灰度比较,估计当前帧特征点在下一帧的位置;
3)过滤位置不变的特征点,余下的点就是目标了。
二.光流法
1.首先是假设条件:
(1)亮度恒定,就是同一点随着时间的变化,其亮度不会发生改变。这是基本光流法的假定(所有光流法变种都必须满足),用于得到光流法基本方程;
(2)小运动,这个也必须满足,就是时间的变化不会引起位置的剧烈变化,这样灰度才能对位置求偏导(换句话说,小运动情况下我们才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数),这也是光流法不可或缺的假定;
(3)空间一致,一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。这是Lucas-Kanade光流法特有的假定,因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。我们假定特征点邻域内做相似运动,就可以连立n多个方程求取x,y方向的速度(n为特征点邻域总点数,包括该特征点)。
2.方程求解
多个方程求两个未知变量,又是线性方程,很容易就想到用最小二乘法,事实上opencv也是这么做的。其中,最小误差平方和为最优化指标。
按照理论基础与数学方法的区别把它们分成四种:基于梯度(微分)的方法、基于匹配的方法、基于能量(频率)的方法、基于相位的方法和神经动力学方法。
1) 基于梯度的方法
基于梯度的方法又称为微分法,它是利用时变图像灰度(或其滤波形式)的时空微分(即时空梯度函数)来计算像素的速度矢量。
由于计算简单和较好的结果,该方法得到了广泛应用和研究。典型的代表是Horn-Schunck算法与Lucas-Kanade(LK)算法。
2) 基于匹配的方法
基于匹配的光流计算方法包括基于特征和区域的两种。
基于特征的方法不断地对目标主要特征进行定位和跟踪,对目标大的运动和亮度变化具有鲁棒性。存在的问题是光流通常很稀疏,而且特征提取和精确匹配也十分困难。
基于区域的方法先对类似的区域进行定位,然后通过相似区域的位移计算光流。这种方法在视频编码中得到了广泛的应用。然而,它计算的光流仍不稠密。另外,这两种方法估计亚像素精度的光流也有困难,计算量很大。
3)基于能量的方法
4)基于相位的方法
5)神经动力学方法
3.稠密光流与稀疏光流
除了根据原理的不同来区分光流法外,还可以根据所形成的光流场中二维矢量的疏密程度将光流法分为稠密光流与稀疏光流两种。
稠密光流
稠密光流是一种针对图像或指定的某一片区域进行逐点匹配的图像配准方法,它计算图像
上所有的点的偏移量,从而形成一个稠密的光流场。通过这个稠密的光流场,可以进行像素级别的图像配准。
Horn-Schunck算法以及基于区域匹配的大多数光流法都属于稠密光流的范畴。
由于光流矢量稠密,所以其配准后的效果也明显优于稀疏光流配准的效果。但是其副作用也是明显的,由于要计算每个点的偏移量,其计算量也明显较大,时效性较差。
稀疏光流
与稠密光流相反,稀疏光流并不对图像的每个像素点进行逐点计算。它通常需要指定一组点进行跟踪,这组点最好具有某种明显的特性,例如Harris角点等,那么跟踪就会相对稳定和可靠。稀疏跟踪的计算开销比稠密跟踪小得多。
上文提到的基于特征的匹配方法是典型的属于稀疏光流的算法。
代码:
#include <opencv2/video/video.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <cstdio>
#include "stdafx.h"
#include <math.h>
using namespace std;
using namespace cv;
//opencv稀疏光流法-------物体运动跟踪:https://blog.csdn.net/qq_40238526/article/details/90183193
Mat frame, gray;//当前帧图片
Mat prev_frame, prev_gray;//前一帧图片
vector<Point2f> features;//保存特征点
vector<Point2f> inPoints;//初始化特征点
vector<Point2f> fpts[2];//保存当前帧和前一帧的特征点位置 //定义一个一维数组,数组元素的类型是Point2f(2维点, 含xy,浮点类型)
vector<uchar> status;//特征点跟踪标志位
vector<float> err;//误差和
double g_dPixelsPerMetric;
double pixel[550];
double deformation[550];
int d = 0;
int const maxCorners = 10;
void delectFeature(Mat &inFrame, Mat &ingray);//角点检测
void drawFeature(Mat &inFrame);//画点
void track();//运动
void drawLine();//画运动轨迹
int main()
{
//VideoCapture capture(0);
// 摄像头读取文件开关
VideoCapture capture("D:/VSc++code/Vs2012code/光流法/光流法/视频2_2倍速.mp4");//调用本地视频
if (capture.isOpened())
{
while (capture.read(frame)) {
d++;
cvtColor(frame, gray, COLOR_BGR2GRAY);
//cout <<"帧数:"<< d <<"的特征点平均位移为"<<endl;
if (fpts[0].size() < 1) { // 特征点损失,增加特征点 本来是40!!
delectFeature(frame, gray);
fpts[0].insert(fpts[0].end(), features.begin(), features.end());//在前一帧特征点向量的末尾加上检测到当前frame中的所有features
inPoints.insert(inPoints.end(), features.begin(), features.end());//在初始特征点向量的末尾加上检测到当前frame中的所有features
}
else {
putText(frame, "follow", Point(100, 100), 1, 2, Scalar(255, 0, 0), 2, 8);
}
if (prev_gray.empty()) {
gray.copyTo(prev_gray);
}
track();// 光流跟踪
//保存当前帧为前一帧
gray.copyTo(prev_gray);
frame.copyTo(prev_frame);
imshow("1", frame);//imshow("input video", frame);??
waitKey(27);
}
}
waitKey();
capture.release();
return 0;
}
void delectFeature(Mat &inFrame, Mat &ingray) {//角点检测
//double maxCorners = 500;//默认500
double qualityLevel = 0.01;
double minDistance = 10;// 小于这个就属于同一个特征点,默认10
double blockSize = 3.0;
double k = 0.04;
goodFeaturesToTrack(ingray, features, maxCorners, qualityLevel, minDistance, Mat(), blockSize, false, k);//输入图像地址,输出角点vector,限定最大角点数,质量水平系数,最小距离,mask,参与角点运算的区域大小一般为3,true则使用Harris角点检测false则使用Shi Tomasi算法,使用Harris算法时使用最好使用默认值0.04
putText(frame, "get point", Point(100, 100), 1, 2, Scalar(255, 0, 0), 2, 8);//图像,文字,文本框左下角,字体,尺寸,颜色,线宽,线型默认8
}
void drawFeature(Mat &inFrame) {
for (size_t t = 0; t < fpts[0].size(); t++) {//size_t类似于int
circle(inFrame, fpts[0][t], 2, Scalar(0, 255, 0), 2, 8);//绿点
}
}
/*
1.calcOpticalFlowPyrLK函数作用是对输入的特征点fpts[0],根据下一帧的图像对这些特征点判定是不是光流,
检测结束后,status的每个下标会保存答案,再进行判断即可。
2.initPoints集合用于存放初始化特征数据,每次的calcOpticalFlowPyrLK后都会重新更新一次,用status判断判断有没有新的特征是可以追踪的,或者用status判断哪些旧的特征可以不要了,
3.fpts[1]集合用于存放当前帧的数据,
*/
void track() {
calcOpticalFlowPyrLK(prev_gray, gray, fpts[0], fpts[1], status, err);//金字塔 Lucas-Kanade 光流法。前一帧灰度图,当前帧灰度图,前一帧特征点,当前帧特征点;输出状态向量如果找到相应特征的流,则向量的每个元素设置为1,否则设置为0;error
//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);//asb()求绝对值
// if (dist > 2 && status[i]) {//&&表示逻辑与的意思,即当运算符两边的表达式的结果都为true时,整个运算结果才为true
// //删除损失的特征点
// inPoints[k] = inPoints[i];
// fpts[1][k++] = fpts[1][i];
// }
// //1.将用KLT算法找到的特征点集fpts[1]进行筛选,将没用的点去除,
// //没用的点包括距离太小没有变化和status的状态
// //2.将有用的点放进fpts[1]中
//}
//inPoints.resize(k);
//fpts[1].resize(k);
drawLine();
swap(fpts[1], fpts[0]);//更新帧的特征点
}
void drawLine() {
int flag = 0;
double sum = 0;
for (size_t t = 0; t < fpts[1].size(); t++) {
line(frame, inPoints[t], fpts[1][t], Scalar(0, 0, 255), 1, 8, 0);//源图像指针,线段起点,终点,线颜色BGR,线粗细,线条类型默认8,小数点位数--红线
circle(frame, fpts[1][t], 2, Scalar(0, 255, 0), 2, 8, 0);//源图像指针,圆心坐标,半径,圆颜色BGR,线条粗细,线条类型默认8,小数点位数--绿点
pixel[flag] = sqrt(pow(inPoints[t].x - fpts[1][t].x , 2) + pow(inPoints[t].y - fpts[1][t].y, 2));
g_dPixelsPerMetric = (double)142.5/25; //25mm.视频3里面占据是127.5个像素;视频3_2倍速.mp4占据378.6个像素;视频2_2倍速.mp4占据142.5个像素
deformation[flag] = pixel[flag]/(double)g_dPixelsPerMetric;
flag ++;
sum = sum + deformation[flag];
if(flag % maxCorners == 0){
//cout << "软组织形变为"<< sum/100 << "mm(每100个特征点的平均值)"<<endl;
cout << sum/ (maxCorners-2) <<endl;//每次打印把maxCorners(500)个特征点位移的全部打印出来
sum = 0;
flag = 0;
}
}
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/163539.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...