OpenCV实战(29)——视频对象追踪

OpenCV实战(29)——视频对象追踪

    • 0. 前言
    • 1. 追踪视频中的对象
    • 2. 中值流追踪器算法原理
    • 3. 完整代码
    • 小结
    • 系列链接

0. 前言

我们已经学习了如何跟踪图像序列中点和像素的运动。但在多数应用中,通常要求追踪视频中的特定移动对象。首先确定感兴趣的对象,然后必须在视频序列中对其进行追踪。由于随着它在场景中的演变,视点和光照变化、非刚性运动、遮挡等,对象在视觉上会发生诸多变化,这为追踪视频中的对象带来了挑战。
本节中,我们将介绍一些在 OpenCV 库中实现的对象跟踪算法。我们将实现一个通用框架,以方便的对算法进行替换。我们可以将此实现与基于积分图像计算的直方图进行对象追踪的方法进行对比。

1. 追踪视频中的对象

对象追踪问题通常假设没有关于要追踪的对象的先验知识可用。因此,追踪是通过识别帧中的对象来启动的,并且追踪必须从对象识别开始。对象的初始识别是通过指定一个边界框来实现的,追踪器模块的目标是在后续帧中重新识别该对象。
因此,定义对象追踪框架的 OpenCVcv::Tracker 类有两个主要方法。第一个是用于定义初始目标边界框的 init 方法;第二个是 update 方法,它在给定新帧的情况下输出新的边界框。这两种方法都接受帧(即 cv::Mat 实例)和边界框(即 cv::Rect2D 实例)作为参数;在第一种方法中,边界框是输入参数,而对于第二种方法,边界框则是输出参数。

(1) 为了测试所提出的对象追踪算法,我们使用视频序列处理一节中介绍的视频处理框架。我们定义了一个帧处理子类,当接收到图像序列的每一帧时,VideoProcessor 类将调用该子类,该子类具有以下属性:

class VisualTracker : public FrameProcessor {cv::Ptr<cv::Tracker> tracker;cv::Rect2d box;bool reset;public:// 指定要使用的追踪器的构造函数VisualTracker(cv::Ptr<cv::Tracker> tracker) : reset(true), tracker(tracker) {}

(2) 每当通过指定新目标的边界框重新启动追踪器时,reset 属性就会设置为 true;它是用于存储新对象位置的 setBoundingBox 方法:

        // 初始化追踪器,设定边界框void setBoundingBox(const cv::Rect2d &bb) {box = bb;reset = true;}

(3) 用于处理每一帧的回调方法简单地调用追踪器的相应方法,然后在要显示的帧上绘制新的计算边界框:

        // 回调处理方法void process(cv::Mat &frame, cv::Mat &output) {if (reset) {reset = false;tracker->init(frame, box);} else {tracker->update(frame, box);}// 绘制边界框frame.copyTo(output);cv::rectangle(output, box, cv::Scalar(255, 255, 255), 2);}

(4) 为了演示如何使用 VideoProcessorFrameProcessor 实例追踪对象,我们使用在 OpenCV 中定义的中值流追踪器:

int main() {// 创建视频处理实例VideoProcessor processor;// 生成文件名std::vector<std::string> imgs;std::string prefix = "test1/test_";std::string ext = ".png";// 添加用于追踪的图像名称for (long i=3040; i<3076; i++) {std::string name(prefix);std::ostringstream ss; ss << std::setfill('0') << std::setw(3) << i; name += ss.str();name += ext;std::cout << name << std::endl;imgs.push_back(name);}// 创建特征追踪器实例cv::Ptr<cv::TrackerMedianFlow> ptr = cv::TrackerMedianFlow::create();VisualTracker tracker(ptr);// VisualTracker tracker(cv::TrackerKCF::createTracker());// 打开文件processor.setInput(imgs);// 设置帧处理器processor.setFrameProcessor(&tracker);processor.displayOutput("Tracked object");// 定义帧率processor.setDelay(50);// 指定原始对象位置cv::Rect bb(1150, 150, 330, 330);tracker.setBoundingBox(bb);// 开始追踪processor.run();
}

(5) 第一个边界框在测试图像序列中识别出一只狼先生,然后在后续帧中自动追踪:

测试图像帧
(6) 但随着视频的播放,跟踪器将不可避免地出错。这些小误差的累积会导致追踪器从真实目标位置缓慢偏移:

对象追踪

(7) 最终,追踪器将失去对对象的追踪。追踪器长时间追踪对象的能力是表征对象追踪器性能的最重要标准。

2. 中值流追踪器算法原理

上一小节中,我们学习了如何使用通用 cv::Tracker 类来跟踪图像序列中的对象。我们选择使用中值流追踪器算法来执行追踪,这是一种简单但有效的追踪纹理对象的方法,只要对象的运动不是太快,也没有被太严重被遮挡。
中值流追踪器基于特征点追踪。首先在要追踪的对象上定义一个点网格,我们也可以选择使用检测兴趣点中介绍的 FAST 运算符来检测对象上的兴趣点。但是,使用预定义位置的点具有许多优点:

  • 通过避免计算兴趣点来节省时间
  • 保证有足够数量的点可用于跟踪
  • 确保这些点很好地分布在整个对象上

中值流实现默认使用具有 10x10 点的网格:

初始网格
使用追踪视频中的特征点一节中介绍的 Lukas-Kanade 特征追踪算法,在下一帧中追踪网格的每个点:

追踪到的网格点
中值流算法估计追踪这些点时产生的误差。例如,可以通过计算在点的初始位置和追踪位置周围的窗口中的绝对像素差之和来估计误差,使用 cv::calcOpticalFlowPyrLK 函数可以方便地计算和返回误差。中值流算法可以使用的另一种误差度量是前向-后向误差,在当前帧和下一帧之间追踪这些点后,这些点在新位置被反向追踪以检查它们是否会返回到它们在初始图像中的原始位置,新获得的前后位置与初始位置之间的差异是追踪误差。
一旦计算了每个点的追踪误差,只需考虑 50% 的具有最小误差的点,然后将这组点用于计算下一个图像中边界框的新位置。这些点会投票选出一个位移值,并保留这些位移量的中值。对于尺度的变化,成对考虑这些点,估计初始帧中的两点与下一帧的距离的比值。同样,最终应用的是这些比值的中位数。
中值流追踪器是基于特征点追踪的视觉对象追踪器,另一类解决方案是基于模板匹配的方法,可以参考匹配局部模板方法一节,核相关滤波器 (Kernelized Correlation Filter, KCF) 算法是经典的基于模板匹配的算法,在 OpenCV 中可以使用 cv::TrackerKCF 类实现:

VisualTracker tracker(cv::TrackerKCF::createTracker());

本质上,该算法使用目标对象的边界框作为模板在另一图像中搜索新的对象位置。通常可以通过使用简单的相关性进行计算,但是 KCF 使用了一种基于傅立叶变换的方法,在图像滤波一节中,我们了解到,根据信号处理理论,在图像上关联模板等价于频域中的简单图像乘法。这可以极大的提高在另一图像中匹配窗口的识别速度,因此 KCF 是性能最好的追踪器之一。

3. 完整代码

头文件 (videoprocessor.h) 完整代码参考视频序列处理一节,头文件 (visualTracker.h) 完整代码如下所示:

#if !defined FTRACKER
#define FTRACKER#include <string>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/tracking/tracker.hpp>#include "videoprocessor.h"class VisualTracker : public FrameProcessor {cv::Ptr<cv::Tracker> tracker;cv::Rect2d box;bool reset;public:// 指定要使用的追踪器的构造函数VisualTracker(cv::Ptr<cv::Tracker> tracker) : reset(true), tracker(tracker) {}// 初始化追踪器,设定边界框void setBoundingBox(const cv::Rect2d &bb) {box = bb;reset = true;}// 回调处理方法void process(cv::Mat &frame, cv::Mat &output) {if (reset) {reset = false;tracker->init(frame, box);} else {tracker->update(frame, box);}// 绘制边界框frame.copyTo(output);cv::rectangle(output, box, cv::Scalar(255, 255, 255), 2);}
};#endif

主函数文件 (oTracker.cpp) 完整代码如下所示:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>#include "visualTracker.h"int main() {// 创建视频处理实例VideoProcessor processor;// 生成文件名std::vector<std::string> imgs;std::string prefix = "test1/test_";std::string ext = ".png";// 添加用于追踪的图像名称for (long i=3040; i<3076; i++) {std::string name(prefix);std::ostringstream ss; ss << std::setfill('0') << std::setw(3) << i; name += ss.str();name += ext;std::cout << name << std::endl;imgs.push_back(name);}// 创建特征追踪器实例cv::Ptr<cv::TrackerMedianFlow> ptr = cv::TrackerMedianFlow::create();VisualTracker tracker(ptr);// VisualTracker tracker(cv::TrackerKCF::createTracker());// 打开文件processor.setInput(imgs);// 设置帧处理器processor.setFrameProcessor(&tracker);processor.displayOutput("Tracked object");// 定义帧率processor.setDelay(50);// 指定原始对象位置cv::Rect bb(1150, 150, 330, 330);tracker.setBoundingBox(bb);// 开始追踪processor.run();cv::waitKey();// 中值追踪器cv::Mat image1 = cv::imread("test1/test_3050.png", cv::ImreadModes::IMREAD_GRAYSCALE);// 定义网格点std::vector<cv::Point2f> grid;for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {cv::Point2f p(bb.x+i*bb.width/10.,bb.y+j*bb.height/10);grid.push_back(p);}}// 追踪下一帧cv::Mat image2 = cv::imread("test1/test_3051.png", cv::ImreadModes::IMREAD_GRAYSCALE);std::vector<cv::Point2f> newPoints;std::vector<uchar> status; // 追踪特征点的状态std::vector<float> err;    // 追踪误差// 追踪点cv::calcOpticalFlowPyrLK(image1, image2,    // 2 张连续图像 grid,                           // 第一幅图像中的输入点位置 newPoints,                      // 第二图像中的输出点位置 status,                         // 追踪状态err);                           // 追踪误差// 绘制点for (cv::Point2f p : grid) {cv::circle(image1, p, 1, cv::Scalar(255, 255, 255), -1);}cv::imshow("Initial points", image1);for (cv::Point2f p : newPoints) {cv::circle(image2, p, 1, cv::Scalar(255, 255, 255), -1);}cv::imshow("Tracked points", image2);cv::waitKey();return 0;
}

小结

视频对象追踪是在视频中随着时间的推移定位移动对象的过程,在智能安防等领域有着重要用途,本节介绍一些在 OpenCV 库中实现的对象跟踪算法,并实现了一个通用的对象追踪框架。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计
OpenCV实战(25)——3D场景重建
OpenCV实战(26)——视频序列处理
OpenCV实战(27)——追踪视频中的特征点
OpenCV实战(28)——光流估计

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/60035.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023河南萌新联赛第(五)场:郑州轻工业大学 --亚托莉 -我挚爱的时光-

题目描述 亚托莉&#xff0c;-我挚爱的时光- 亚托莉自身机器可能有出了一点小故障&#xff0c;希望你能帮助她解决这个问题&#xff5e; 亚托莉内部的操作系统的是 Linux 操作系统&#xff0c;不同于 Windows 操作系统。在大多数情况下&#xff0c; Linux 操作系统一般是通过…

【第一阶段】kotlin的函数

函数头 fun main() {getMethod("zhangsan",22) }//kotlin语言默认是public,kotlin更规范&#xff0c;先有输入&#xff08; getMethod(name:String,age:Int)&#xff09;再有输出(Int[返回值]) private fun getMethod(name:String,age:Int): Int{println("我叫…

Python做一个绘图系统3:从文本文件导入数据并绘图

文章目录 导入数据文件对话框修改绘图逻辑源代码 Python绘图系统系列&#xff1a;将matplotlib嵌入到tkinter 简单的绘图系统 导入数据 单纯从作图的角度来说&#xff0c;更多情况是已经有了一组数据&#xff0c;然后需要将其绘制。这组数据可能是txt格式的&#xff0c;也可能…

一分钟学会JS获取当前年近五年的年份

先看效果图 上代码&#xff1a; 1、HTML <div><el-date-pickerv-model"queryYearXmgk.startYear"format"yyyy"value-format"yyyy"type"year"placeholder"开始"clearable:picker-options"pickerStartAuditYe…

Node RESTful API说明

1、什么是 REST REST即表述性状态传递&#xff1b; 表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。需要注意的是&#xff0c;REST是设计风格而不是标准。 2、HTTP 方法 以下为 REST 基本架构的四个方法&#xff1a; GET - …

原创 | 一文读懂多模态强化学习

作者&#xff1a;陈之炎 本文约3500字&#xff0c;建议阅读8分钟 本文介绍了多模态强化学习。 多模态强化学习是将多个感知模态和强化学习相结合的方法&#xff0c;能够使智能系统从多个感知源中获取信息&#xff0c;并利用这些信息做出更好的决策。这种方法对于处理现实世界中…

关于使用pycharm遇到只能使用unittest方式运行,无法直接选择Run

相信大家可能都遇到过这个问题&#xff0c;使用pycharm直接运行脚本的时候&#xff0c;只能选择unittest的方式&#xff0c;能愁死个人 经过几次各种尝试无果之后&#xff0c;博主就放弃死磕了&#xff0c;原谅博主是个菜鸟 后来遇到这样的问题&#xff0c;往往也就直接使用cm…

翻出了我当时学习的笔记来了html

php&#xff1a;高级语言 web应用程序 万维网 浏览器中查看 apache&#xff1a;服务器 mysql&#xff1a;数据库 html 标签 css&#xff1a;层叠样式表 javascript&#xff1a;客户端脚本 js jquery mysql数据库基础 php语法基础 面向对象&#xff08;物件&#xff09; smar…

MySQL:内置函数、复合查询和内外连接

内置函数 select 函数; 日期函数 字符串函数 数学函数 其它函数 复合查询&#xff08;多表查询&#xff09; 实际开发中往往数据来自不同的表&#xff0c;所以需要多表查询。本节我们用一个简单的公司管理系统&#xff0c;有三张 表EMP,DEPT,SALGRADE来演示如何进行多表查询…

基于CentOS 7构建LVS-DR集群

DIPVIPRIPClient192.169.41.139 LVS 192.168.41.134192.169.41.10RS1192.168.41.135RS2192.168.41.138 要求&#xff1a; node4为客户端&#xff0c;node2为LVS&#xff0c;node3和node4为RS。 1.配置DNS解析&#xff08;我这里使用本地解析&#xff09; 192.168.41.134 www.y…

ElasticSearch:环境搭建步骤

1、拉取镜像 docker pull elasticsearch:7.4.0 2、创建容器 docker run -id --name elasticsearch -d --restartalways -p 9200:9200 -p 9300:9300 -v /usr/share/elasticsearch/plugins:/usr/share/elasticsearch/plugins -e "discovery.typesingle-node" elasti…

UE5、CesiumForUnreal接入WMTS格式地图瓦片,如ArcGIS、Mapbox、天地图

文章目录 1.实现目标2.实现过程2.1 WMTS与TMS2.2 cesium-native改造2.3 CesiumForUnreal插件改造2.4 WMTS瓦片加载测试2.5 EPSG:3857与43263.参考资料1.实现目标 通过改造cesium-native和CesiumForUnreal插件,参考tms的栅格瓦片地图加载逻辑,实现在UE5中通过CesiumForUnreal…