网站首页 > 文章精选 正文
OpenCV 扩展模块的单目标、多目标跟踪
扩展模块的目标跟踪算法有:
- KCF:TrackerKCF 使用目标周围区域的循环矩阵采集正负样本,利用脊回归训练目标检测器,并成功的利用循环矩阵在傅里叶空间可对角化的性质将矩阵的运算转化为向量的Hadamad积,即元素的点乘,大大降低了运算量,提高了运算速度,使算法满足实时性要求.
- MIL:TrackerMIL 以在线方式训练分类器将对象与背景分离;多实例学习避免鲁棒跟踪的漂移问题.
- OLB:TrackerBoosting 基于AdaBoost算法的在线实时对象跟踪.分类器在更新步骤中使用周围背景作为反例以避免漂移问题.
- MedianFlow:TrackerMedianFlow 跟踪器适用于非常平滑和可预测的运动,物体在整个序列中可见.
- TLD:TrackerTLD 将长期跟踪任务分解为跟踪,学习和检测.跟踪器在帧之间跟踪对象.探测器本地化所观察到的所有外观,并在必要时纠正跟踪器.学习估计检测器的错误并进行更新以避免再出现这些错误.追踪器能够处理快速运动,部分遮挡,物体缺失等情况.
由于用的最多的是 KCF ,这里只是把 KCF 的原理介绍。
KCF是一种鉴别式追踪方法,这类方法一般都是在追踪过程中训练一个目标检测器,使用目标检测器去检测下一帧预测位置是否是目标,然后再使用新检测结果去更新训练集进而更新目标检测器。而在训练目标检测器时一般选取目标区域为正样本,目标的周围区域为负样本,当然越靠近目标的区域为正样本的可能性越大。
一维脊回归:
1. 脊回归
2.循环矩阵
举例
循环矩阵傅氏空间对角化
傅氏对角化简化的脊回归
因为
(循环矩阵傅里叶对角化)对上式两边同时傅氏变换得:
这样就可以使用向量的点积运算取代矩阵运算,特别是求逆运算,大大提高了计算速度。
核空间的脊回归
快速检测
核矩阵的快速计算
1D到2D
多通道问题
总结:KCF相对于其他的 tracking-by-detection 方法速度得到了极大的提升,效果也相对较好,思想和实现十分简单。
总结下KCF的过程,左图是刚开始我们使用红色虚线框框定了目标,然后红色实线框就是使用的padding了,其他的框就是将padding循环移位之后对齐目标得到的样本,由这些样本就可以训练出一个分类器,当分类器设计好之后,来到了下一帧图像,也就是右图,这时候我们首先在预测区域也就是红色实线框区域采样,然后对该采样进行循环移位,对齐目标后就像图中显示的那个样子了,(这是为了理解,实际中不用对齐),就是周围那些框框,使用分类器对这些框框计算响应,显然这时候白色框响应最大,因为他和之前一帧红色框一样,那我们通过白色框的相对移位就能推测目标的位移了。然后继续,再训练再检测…
论文中还说到几点:
1)对特征图像进行cosine window加权,这主要是为了减轻由于边界移位导致图像不光滑。
2)padding的size是目标框的2.5倍,肯定要使用padding窗口,要不然移位一次目标就被分解重组合了…,效果就很差。
3)对于标签使用了高斯加权。
4)对α前后帧结果进行了线性插值,为了让他长记性,不至于模型剧烈变化。
优点:
- 使用目标周围区域的循环矩阵采集正负样本,利用脊回归训练目标检测器,并成功的利用循环矩阵在傅里叶空间可对角化的性质将矩阵的运算转化为向量的Hadamad积,即元素的点乘,大大降低了运算量,提高了运算速度,使算法满足实时性要求。
- 将线性空间的脊回归通过核函数映射到非线性空间,在非线性空间通过求解一个对偶问题和某些常见的约束,同样的可以使用循环矩阵傅里叶空间对角化简化计算。
- 给出了一种将多通道数据融入该算法的途径。
缺点:
- 依赖循环矩阵,对于多尺度的目标跟踪效果并不理想。当然可以通过设置多个size,在每个size上进行KCF运算,但这样的话很难确定应预先设置多少size,什么样的size,而且对size的遍历必将影响算法的速度。KCF最大的优势就是速度。
能不能通过少量特征点的匹配来调整窗口的size,当然这样的话,速度也是个问题。
这种情况下还能保证最大响应就对应着目标中心所在的框吗?如果不能偏差会不会越来越大?
- 初始化矩阵不能自适应改变,其实这个问题和上一个缺点类似,这里强调的是非刚体运动,比如跳水运动员,刚开始选定区域肯定是个瘦长的矩形框,但当运动员开始屈体的时候显然这个预选定框就很大误差了。21.png | center | 300x0
- 难处理高速运动的目标
- 难处理低帧率中目标,这个和3类似,都是说相邻帧间目标位移过大。
如果目标下一帧出现位置不在你的padding内,你怎么也不可能移位找到。
- 虽然算法中对模型系数α进行线性插值,但是对于目标一旦被遮挡若干帧之后,可能模型就再也回不去了,因为模型已经完全被遮挡物污染掉了。
单目标跟踪步骤:
1)实例化跟踪器 :Ptr < Tracker > tracker = TrackerKCF::create();
2)鼠标框选 ROI 目标:Rect2d roi = selectROI("input", frame);
3)初始化目标:tracker->init(frame, roi);
4)更新目标:tracker->update(frame, roi);
5)画出目标:rectangle(frame, roi, Scalar(255, 0, 0), 2, 8)。
例子代码:
#include<opencv2/opencv.hpp>
#include<opencv2/tracking.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void test(){
VideoCapture capture("Video.wmv");
if (!capture.isOpened())
{
cout << "could not load video data...\n" << endl;
}
namedWindow("output", CV_WINDOW_AUTOSIZE);
Mat frame;
//1.实例化一个跟踪器类
Ptr<Tracker> tracker = TrackerMedianFlow::create();
//Ptr<Tracker> tracker = TrackerKCF::create();
//Ptr<Tracker> tracker = TrackerTLD::create();
//Ptr<Tracker> tracker = TrackerMIL::create();
//Ptr<Tracker> tracker = TrackerBoosting::create();
//TrackerKCF TrackerMedianFlow 效果还行,但窗口会变大 TrackerTLD TrackerMIL TrackerBoosting 速度比较慢,效果较差
capture.read(frame); //读取视频第一帧
Rect2d roi = selectROI("output", frame);
//2.初始化目标
tracker->init(frame, roi);
while (capture.read(frame))
{
//3.更新目标
tracker->update(frame, roi);
rectangle(frame, roi, Scalar(255, 0, 0), 2, 8, 0);
imshow("output", frame);
char c = waitKey(50);
if (c == 27)
{
break;
}
}
capture.release();
}
int main(){
test();
waitKey(0);
return 0;
}
效果:单目标跟踪效果
给一个自己写的鼠标框选ROI区域的例子
例子代码:
#include<opencv2/opencv.hpp>
#include<opencv2/tracking.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat firstFrame;
Rect2d roi;
Point previousPoint, currentPoint;
void drawRectangle(int event, int x, int y, int flags, void *);
void test(){
VideoCapture capture("video_003.avi");
if (!capture.isOpened())
{
cout << "could not load video data...\n" << endl;
}
//获取视频的第一帧,并框选目标
Mat frame;
capture.read(firstFrame);
if (!firstFrame.empty())
{
namedWindow("output", CV_WINDOW_AUTOSIZE);
imshow("output", firstFrame);
setMouseCallback("output", drawRectangle, 0);
waitKey();
}
//1.实例化一个跟踪器类
Ptr<Tracker> tracker = TrackerMedianFlow::create(); //TrackerKCF TrackerMedianFlow 效果还行,但窗口会变大 TrackerTLD TrackerMIL TrackerBoosting 速度比较慢,效果较差
capture.read(frame);
//2.初始化目标
tracker->init(frame, roi);
namedWindow("output", CV_WINDOW_AUTOSIZE);
while (capture.read(frame))
{
//3.更新目标
tracker->update(frame, roi);
rectangle(frame, roi, Scalar(255, 0, 0), 2, 8, 0);
imshow("output", frame);
char c = waitKey(50);
if (c == 27)
{
break;
}
}
capture.release();
destroyWindow("output");
}
//框选目标函数
void drawRectangle(int event, int x, int y, int flags, void *){
if (event == EVENT_LBUTTONDOWN)
{
previousPoint = Point(x, y);
}
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON))
{
Mat temp;
firstFrame.copyTo(temp);
currentPoint = Point(x, y);
rectangle(temp, previousPoint, currentPoint, Scalar(0, 255, 0), 1, 8, 0);
imshow("output", temp);
}
else if (event == EVENT_LBUTTONUP)
{
roi.x = previousPoint.x;
roi.y = previousPoint.y;
roi.width = abs(previousPoint.x - currentPoint.x);
roi.height = abs(previousPoint.y - currentPoint.y);
}
else if (event == EVENT_RBUTTONUP)
{
destroyWindow("output");
}
}
int main(){
test();
waitKey(0);
return 0;
}
多目标跟踪 步骤:
1)实例化跟踪器 :MultiTracker trackers;
2)鼠标框选ROL目标:vector< Rect> rois; selectROIs("input", frame, rois, false);
3)传入的边界框数据类型是Rect2d,因为涉及到计算,所以需要double类型,而框选的ROI是Rect类型的的,需要转换一下;
4)添加目标:trackers.add(algorithms, frame, obj);
5)更新目标:trackers.update(frame, obj);
6)画出目标:rectangle(frame, obj[i] ,Scalar(255, 0, 0), 2, 8)。
例子代码:
#include<opencv2/opencv.hpp>
#include<opencv2/tracking.hpp>
#include<iostream>
using namespace std;
using namespace cv;
void test(){
VideoCapture capture("Video.wmv");
if (!capture.isOpened())
{
cout << "could not load video data...\n" << endl;
}
namedWindow("output", CV_WINDOW_AUTOSIZE);
//1.实例化一个多目标跟踪器的对象
//Ptr<MultiTracker> trackers = MultiTracker::create();
MultiTracker trackers;
Mat frame;
capture.read(frame); //读取第一帧视频
// 2.选择目标
vector<Rect> objects;
selectROIs("output", frame, objects,false);
//传入的边界框数据类型是Rect2d,因为涉及到计算,所以需要double类型,需要转换一下。
vector<Rect2d> obj;
vector<Ptr<Tracker>> algorithms;
for (auto i = 0; i < objects.size(); i++)
{
obj.push_back(objects[i]);
algorithms.push_back(TrackerMedianFlow::create()); //方法可以自己根据需要替换
}
// 3. 添加目标
trackers.add(algorithms, frame, obj);
while (capture.read(frame))
{
//imshow("input", frame);
//4.更新目标
trackers.update(frame,obj);
for (auto j = 0; j < obj.size(); j++)
{
rectangle(frame, obj[j], Scalar(0, 255, 0), 2, 8, 0);
}
imshow("output", frame);
char c = waitKey(50);
if (c == 27)
{
break;
}
}
}
int main(){
test();
waitKey(0);
return 0;
}
效果:多目标跟踪效果
猜你喜欢
- 2025-08-02 C++开发者都应该使用的十个C++11特性(上)
- 2025-08-02 如何实现自己的C++ unique_ptr?
- 2025-08-02 刚学会C++的小白用这个开源框架,做个 RPC 服务要多久?
- 2025-08-02 C++11+ 泛型编程(模板)
- 2025-08-02 abelkhan中的rpc框架
- 2025-08-02 C++设计模式:用代码演绎武侠世界的绝世神功
- 2025-08-02 ROS2开发实践:ROS核心(节点、话题、服务、DDS通信协议等)
- 2025-08-02 C++语言程序员编程必收藏的20个经典实战案例(附完整源码)
- 2025-08-02 C# 控制电脑睡眠,休眠,关机以及唤醒
- 2025-08-02 别再人云亦云,C++到底难在哪?
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)
- fmt.println (52)