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

news2024/9/8 23:49:08

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.coloradmin.cn/o/856632.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FFmpeg安装和使用

sudo apt install ffmpeg sudo apt-get install libavfilter-devcmakelist模板 CMakeLists.txt cmake_minimum_required(VERSION 3.16) project(ffmpeg_demo)# 设置ffmpeg依赖库及头文件所在目录&#xff0c;并存进指定变量 set(ffmpeg_libs_DIR /usr/lib/x86_64-linux-gnu) …

SpringBoot自动装配及run方法原理探究

自动装配 1、pom.xml spring-boot-dependencies&#xff1a;核心依赖在父工程中&#xff01;我们在写或者引入一些SpringBoot依赖的时候&#xff0c;不需要指定版本&#xff0c;就因为有这些版本仓库 1.1 其中它主要是依赖一个父工程&#xff0c;作用是管理项目的资源过滤及…

冠达管理:“高温超导”不是“室温超导”,5天4板百利电气再次澄清

短短半个月&#xff0c;“室温超导”在惊喜、质疑间回转&#xff0c;但资本市场对“超导概念股”的炒作还在进行&#xff0c;8月7日室温超导概念持续疯涨。同花顺显现&#xff0c;到8月7日收盘&#xff0c;18只超导概念股中&#xff0c;有16只股票飘红。 广东研山私募证券投资&…

如何将GPS坐标点如何网格化?

目录 题主问题&#xff1a; 解答&#xff1a; 高效判断点是否在正六边形蜂窝内的方法 代码实现&#xff1a;ArcGIS中实现指定面积蜂窝&#xff08;正六边形&#xff09;方法 碰巧自己前段时间处理过类似的数据&#xff0c;讲一下自己的解决思路。 题主问题&#xff1a; 解…

【小练习】交互式网格自定义增删改(进行中)

学习SQL和PLISQL数据类型的区别和应用场景 Oracle plsql 基础篇1 数据类型以及流程控制_bb_tarek的博客-CSDN博客https://blog.csdn.net/bb_tarek/article/details/17555713?ops_request_misc&request_id&biz_id102&utm_termplsql%E5%9F%BA%E6%9C%AC%E6%95%B0%E6…

9.异常

文章目录 9.1 Java 异常类层次结构图9.2 Throwable 类常用方法9.3 try-catch-finally9.4使用 try-with-resources 来代替try-catch-finally 9.1 Java 异常类层次结构图 在 Java 中&#xff0c;所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个…

CentOS安装Postgresql

PG基本安装步骤 安装postgresql&#xff1a; sudo yum install postgresql-server初始化数据库&#xff1a;安装完毕后&#xff0c;需要初始化数据库并创建初始用户&#xff1a; sudo postgresql-setup initdb启动和停止服务&#xff1a; sudo systemctl start postgresql sudo…

06微服务间的通信方式

一句话导读 微服务设计的一个挑战就是服务间的通信问题&#xff0c;服务间通信理论上可以归结为进程间通信&#xff0c;进程可以是同一个机器上的&#xff0c;也可以是不同机器的。服务可以使用同步请求响应机制通信&#xff0c;也可以使用异步的基于消息中间件间的通信机制。同…

【TS第三讲】完善TS开发环境

文章目录 &#x1f31f; 写在前面&#x1f31f; ts-node&#x1f31f; nodemon&#x1f31f; nodemon文件类型&#x1f31f; nodemon文件范围&#x1f31f; 写在最后 &#x1f31f; 写在前面 &#x1f525;探索TypeScript世界&#xff0c;驭Vue3Ts潮流&#xff0c;开启前端之旅…

【Ubuntu】简化反向代理和个性化标签页体验

本文将介绍如何使用Docker部署Nginx Proxy Manager和OneNav&#xff0c;两个功能强大且易用的工具。Nginx Proxy Manager用于简化和管理Nginx反向代理服务器的配置&#xff0c;而OneNav则提供个性化的新标签页体验和导航功能。通过本文的指导&#xff0c;您将学习如何安装和配置…

【打印整数二进制的奇数位和偶数位】

打印整数二进制的奇数位和偶数位 1.题目 获取一个整数二进制序列中所有的偶数位和奇数位&#xff0c;分别打印出二进制序列 2.题目分析 打印一个整数的二进制位中的偶数位和奇数位&#xff0c;可以对整数进行移位操作&#xff0c;再将移位的二进制位与1进行&操作。 按位&a…

HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制概述

ArkUI通过自定义组件的build()函数和builder装饰器中的声明式UI描述语句构建相应的UI。 在声明式描述语句中开发者除了使用系统组件外&#xff0c;还可以使用渲染控制语句来辅助UI的构建&#xff0c;这些渲染控制语句包括控制组件是否显示的条件渲染语句&#xff0c;基于数组数…

Rocky Linux更换为国内源

Rocky Linux提供的可供切换的源列表&#xff1a;Mirrors - Mirror Manager 其中以 COUNTRY 列为 CN 的是国内源。 选择其中一个Rocky Linux 源使用帮助 — USTC Mirror Help 文档 操作前请做好备份 对于 Rocky Linux 8&#xff0c;使用以下命令替换默认的配置 sed -e s|^mirr…

Java用方法实现登录名和密码的校验

Java用方法实现登录名和密码的校验 需求分析代码实现小结Time 需求分析 系统正确的登录名和密码是:学习/123&#xff0c;请在控制台开发一个登录界面&#xff0c;接收用户输入的登录名和密码&#xff0c;判断用户是否登录成功&#xff0c;登录成功后展示:“欢迎进入系统!”&…

一文5000字详解Python中PO模式的设计与实现

在使用 Python 进行编码的时候&#xff0c;会使用自身自带的编码设计格式&#xff0c;比如说最常见的单例模式等。本文将为大家介绍PageObject自动化设计模式(PO模式)的设计与实现&#xff0c;感兴趣的可以了解一下 在使用 Python 进行编码的时候&#xff0c;会使用自身自带的…

Nginx(3)

目录 1.Nginx虚拟主机1.1基于IP虚拟主机1.2基于端口虚拟主机1.3基于域名实现的虚拟主机 2.日志详解 1.Nginx虚拟主机 虚拟主机&#xff0c;Nginx配置中的多个server{}区域对应不同的业务(站点) 虚拟主机方式基于域名的虚拟主机不同的域名访问不同的站点基于IP的虚拟主机不同的…

纯跟踪(Pure Pursuit)路径跟踪算法研究(1)

纯跟踪(Pure Pursuit)路径跟踪算法研究&#xff08;1&#xff09; 下午主要读了几篇论文 《自动泊车路径纯跟踪算法应用研究》 《基于纯追踪算法和樽海鞘优化算法的无人驾驶路径跟踪算法研究》 《基于自适应PP和MPC的智能车辆路径跟踪控制》 首先在公式推导方面还不是很清晰 最…

缓解针对LLM应用程序的存储提示注入攻击

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 LLM提供提示文本&#xff0c;并根据其已训练和访问的所有数据进行响应。为了用有用的上下文补充提示&#xff0c;一些 AI 应用程序捕获来自用户的输入&#xff0c;并在将最终提示发送到 LLM 之前将用户看不…

7.5 详解批量规范化 对某个维度取平均值代码解读

一.举例计算均值、方差 假设我们有以下一组数据&#xff1a;[10, 15, 20, 25, 30] 首先&#xff0c;我们计算均值&#xff0c;即将所有数据相加后除以数据的数量&#xff1a; 均值 (10 15 20 25 30) / 5 100 / 5 20 1.1标准差 接下来&#xff0c;我们计算标准差&…

_declspec(naked) 初试(裸函数)

最近在写驱动时候初次使用裸函数&#xff0c;做一些记录 _declspec(naked)修饰可以生成一个“裸”函数&#xff0c; 使用后C编译器将生成不含函数框架的纯汇编代码&#xff0c;裸函数中什么都没有&#xff0c;所以也不能使用局部变量&#xff0c;只能全部用内嵌汇编实现。 可…