【目标跟踪】红绿灯跟踪

文章目录

  • 一、前言
  • 二、结果
  • 三、跟踪
    • 3.1、检测输入
    • 3.2、预测与运动补偿
    • 3.3、第一次匹配
    • 3.4、第二次匹配
    • 3.5、第三次匹配
    • 3.6、航迹的起始与信息的发布
  • 四、后记

一、前言

  1. 红绿灯场景对当前无人驾驶来说是个灾难性的挑战。暂且不说复杂的十字路口,譬如简单的人行道红绿灯也算挑战。
  2. 这里简述下一般的处理方式:建图定位告知前方是个人行道路口,此时车子在经过红绿灯前应先停止,此时规划接受感知发送的信号,如果接收到红灯or黄灯,则车子停止。直到接受到绿灯则进行。
  3. 本篇会初略分享红绿灯感知包括但不限于检测+跟踪+分类。重点讲解如何稳定跟踪。

二、结果

先看结果:

视频B站链接:https://www.bilibili.com/video/BV1Vm411r7Fx/?spm_id_from=333.999.0.0

  1. 检测用的 yolo 系列模型,这部分已经很成熟了,主要内容是标注数据与训练。由于当前训练数据不够,可以看出除了正前方,左右两方的检测效果并不理想。
  2. 跟踪结合了 byteSort 与 BotSort,效果可以说相当稳定。抛开其他的不谈,对于我们重点观察的红绿灯(正前方红绿灯)可以说是稳稳的跟踪,也算遥遥领先。
  3. 分类用的较为简单的 卷积神经网络推理 DNN,效果算是可圈可点,也是由于数据量缺失,导致偶尔出现误检。分类类别分别为 unkonw、green、red、yellow。

在刚刚结束的首届深圳国际人工智能环卫机器人大赛,在人行道这个赛道就出现了红绿灯的考核,有不少企业的无人车就在这里栽了跟头。

放一张通宵比赛测试的图:(右一是博主)

图片名称 # 三、跟踪

因为是基于 ros 做的开发,同时红绿灯这个节点又包含了三个部分(检测+跟踪+分类)。

为了使代码美观、思路清晰,所以对三个部分封装。只需调用对应的一个接口就可以输出前后的消息,所以写代码前一定要把消息接口定义好。

所以跟踪代码只开放一个接口,这里就写做 update 吧。输入是检测的结果与图片,输出是跟踪后的框与id等,这里输出是用的引用的方式。

void update(std::vector<DetectResult>& objects, cv::Mat &img, std::vector<TrackResult>& tflTracks);

lightTracker->update(detectResults, image, trackResults);

三、跟踪

3.1、检测输入

第一部分肯定是对检测输入处理,无论是更换数据类型,还是保存相应的结构体 typedef struct,都是方便我们后续处理。

(1)我们这里分类保存检测的结果,分为高置信度检测的结果与低置信度的检测结果。

这是借鉴了我们 ByteTrack 匹配的方法。 ByteTrack 匹配采用了多次匹配的方法,首先将得分较高的目标框与历史轨迹相匹配,然后将得分较低的目标框与第一次没有匹配上的轨迹匹配,用于检测目标遮挡的情形。

当然这里还有自己处理的细节与亮点,我们比 ByteTrack 更多次的匹配验证与处理,效果更加完美。我们文章后面再一一道来。

if (objects.size() > 0)
{
    for (auto i = 0; i < objects.size(); i++)
    {
        float score = objects.at(i).detConf;
        STrack strack( objects.at(i), 10, true, 0.8); 
        if (score >= track_thresh)
        {
            detections.push_back(strack);
        }
        else
        {
            detections_low.push_back(strack);
        }
    }
}

(2)原来存在的跟踪航迹我们也做处理,区分上一次 update (匹配)过的航迹与上一帧未匹配(un_update)的航迹进行区分。

for (int i = 0; i < this->tracked_stracks.size(); i++)
{
    if (!this->tracked_stracks[i].is_activated)
        unconfirmed.push_back(&this->tracked_stracks[i]);
    else
        tracked_stracks.push_back(&this->tracked_stracks[i]);
}

这里主要是为了我们第一次做匹配做准备,区分重要 or 不重要的目标是我们可以从 ByteSort 学习到的思想,如何区分以及区分后如何处理,我们可以根据我们实际情况去操作。

3.2、预测与运动补偿

在我们匹配前,我们要对已有的所有航迹都进行一遍预测。大致的流程:输入检测结果——>预测航迹——>预测的航迹与检测结果匹配——>匹配上的预测结果更新,未匹配上的继续预测。

所以,我们在匹配之前一定要先 predict。预测之后我们对 我们目标框进行一个补偿。这个在之前博客提到过。这个借鉴了BOT-Sort思想。主要是处理相机发生剧烈抖动时。具体细节可以参考之前的博客。相应的思路与代码都有:相机运动补偿 。

3.3、第一次匹配

在我们 3.1 中我们筛选了出来了高置信度检测目标与上一次匹配过的跟踪目标。我们第一次匹配优先选择他们进行匹配。

匹配方式选用匈牙利匹配。匈牙利细节可以参考我之前的博客匈牙利匹配

计算目标与目标的方式选用了 L2 范式匹配而舍弃了传统的 iou 匹配。这主要是因为(1)红绿灯相对于人来说是小目标(2)红绿灯有时会伸缩种类不一致。

float Tracker::l2(std::vector<float> &tlwh1, std::vector<float> &tlwh2)
{
    float dx, dy, dw, dh;
    dx = cv::abs(tlwh1[0] - tlwh2[0]);
    dy = cv::abs(tlwh1[1] - tlwh2[1]);
    dw = cv::abs(tlwh1[2] - tlwh2[2]);
    dh = cv::abs(tlwh1[3] - tlwh2[3]);

    dx = cv::min(1.0, dx / 100.0);
    dy = cv::min(1.0, dy / 100.0);
    dw /= cv::max(tlwh1[2] + 1, tlwh2[2] + 1);
    dh /= cv::max(tlwh1[3] + 1, tlwh2[3] + 1);
    return 0.3 * dx + 0.3 * dy + 0.2 * dw + 0.2 * dh;
}

测试效果好于 iou 匹配。

3.4、第二次匹配

第一次匹配我们筛选了高置信度的检测目标与已有航迹(上一帧匹配过的)进行匹配。与高置信度匹配确保那些置信度较高的目标能够被稳定地跟踪。

这次我们选取低置信度的目标与第一次匹配没有匹配上的已有航迹匹配。distance_score 计算采用我们熟悉的 iou 匹配。

与低置信度检测框匹配主要是为了解决我们常见的遮挡与漏检的问题。

遮挡时,大概率会出现较低置信度的检测,我们不丢弃而是选择与重要的目标进行再匹配。

且低置信度目标可能包含了目标的运动趋势,利用这些消息可以提升我们跟踪的准确性与鲁棒性。

std::vector<std::vector<float>> Tracker::ious(std::vector<std::vector<float>> &atlbrs,  std::vector<std::vector<float>> &btlbrs)
{
    std::vector<std::vector<float>> ious;
    if (atlbrs.size() * btlbrs.size() == 0)
        return ious;

    ious.resize(atlbrs.size());
    for (int i = 0; i < ious.size(); i++)
    {
        ious[i].resize(btlbrs.size());
    }

    for (int k = 0; k < btlbrs.size(); k++)
    {
        std::vector<float> ious_tmp;
        float box_area = (btlbrs[k][2] - btlbrs[k][0] + 1) * (btlbrs[k][3] - btlbrs[k][1] + 1);
        for (int n = 0; n < atlbrs.size(); n++)
        {
            float iw = cv::min(atlbrs[n][2], btlbrs[k][2]) - cv::max(atlbrs[n][0], btlbrs[k][0]) + 1;
            if (iw > 0)
            {
                float ih = cv::min(atlbrs[n][3], btlbrs[k][3]) - cv::max(atlbrs[n][1], btlbrs[k][1]) + 1;
                if(ih > 0)
                {
                    float ua = (atlbrs[n][2] - atlbrs[n][0] + 1)*(atlbrs[n][3] - atlbrs[n][1] + 1) + box_area - iw * ih;
                    ious[n][k] = iw * ih / ua;
                }
                else
                {
                    ious[n][k] = 0.0;
                }
            }
            else
            {
                ious[n][k] = 0.0;
            }
        }
    }
    return ious;
}

3.5、第三次匹配

前两次匹配大致都可以理解,那为什么还要第三次匹配呢?这是为了处理我们的[拖油瓶],哈哈哈哈!我们自己原有的航迹中有些航迹是一直可以匹配上,有些航迹可能连续好几帧都没有匹配上。按照我们之前的逻辑,我们也不能轻易的丢掉没有匹配的航迹

第三次匹配主要是处理我们那些潜在的航迹,给我们潜在的航迹一个重生的机会。这部分虽然目标不多,但也要进行处理,能救一个是一个。

所以第三次匹配的目标是,第一次匹配未匹配上的高置信度的检测目标 与 潜在的已有的航迹(上一帧未匹配上,但是还未 remove 掉的)。我这里保守设置了 10 帧才完全丢失 remove 掉航迹。

匹配计算方式同样采取了 iou 计算匹配。

3.6、航迹的起始与信息的发布

航迹的起始。

1、对于高置信度,但是未匹配上的目标,我们会把次目标放入我们的航迹管理中。
2、对前三次匹配上的目标,但是原本航迹并不是conform (上一次未 update)的目标。会重新加入我们的航迹管理中,且状态变为 Tracked。
3、去除可能相同的航迹,Tracked 的航迹可能与 Lost 的航迹有冲突,利用 iou 与航迹的生命周期进行剔除(存活了多少帧)。

void Tracker::remove_duplicate_stracks( std::vector<STrack> &resa,  std::vector<STrack> &resb,  std::vector<STrack> &stracksa,  std::vector<STrack> &stracksb)
{
    std::vector< std::vector<float> > pdist = iou_distance(stracksa, stracksb);
    std::vector<std::pair<int, int> > pairs;
    for (int i = 0; i < pdist.size(); i++)
    {
        for (int j = 0; j < pdist[i].size(); j++)
        {
            if (pdist[i][j] < 0.15)
            {
                pairs.push_back(std::pair<int, int>(i, j));
            }
        }
    }

    std::vector<int> dupa, dupb;
    for (int i = 0; i < pairs.size(); i++)
    {
        int timep = stracksa[pairs[i].first].frame_id - stracksa[pairs[i].first].start_frame;
        int timeq = stracksb[pairs[i].second].frame_id - stracksb[pairs[i].second].start_frame;
        if (timep > timeq)
            dupb.push_back(pairs[i].second);
        else
            dupa.push_back(pairs[i].first);
    }

    for (int i = 0; i < stracksa.size(); i++)
    {
        std::vector<int>::iterator iter = find(dupa.begin(), dupa.end(), i);
        if (iter == dupa.end())
        {
            resa.push_back(stracksa[i]);
        }
    }

    for (int i = 0; i < stracksb.size(); i++)
    {
        std::vector<int>::iterator iter = find(dupb.begin(), dupb.end(), i);
        if (iter == dupb.end())
        {
            resb.push_back(stracksb[i]);
        }
    }
}

信息的发布。

1、第一次、第二次、第三次匹配,匹配上的目标
2、匹配上的目标,但是航迹不是 Tracked 状态的目标

四、后记

欢迎相互学习交流

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go语言学习Day6:数组与切片

名人说&#xff1a;莫愁千里路&#xff0c;自有到来风。 ——钱珝 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1. 数组① 什么是数组② 数组的声明③ 初始化数组的几种方式④ 遍历数组元素⑤ 数组为值类型⑥ 数…

云平台教程 | 手把手教你绘制时序分析

爱基百客云平台小工具——时序分析使用教程目录 1 爱基百客云平台之时序分析 2 参数设置 3 任务查看 4 结果 01 爱基百客云平台小工具使用 首先&#xff0c;打开爱基百客官网&#xff1a;http://www.igenebook.com&#xff1b;点击菜单栏最右侧“云平台”按钮。 弹出云平…

Qt实现Kermit协议

1 概述 Kermit文件运输协议提供了一条从大型计算机下载文件到微机的途径。它已被用于进行公用数据传输。 其特性如下: Kermit文件运输协议是一个半双工的通信协议。它支持7位ASCII字符。数据以可多达96字节长度的可变长度的分组形式传输。对每个被传送分组需要一个确认。Kerm…

红米手机Redmi 不会自动弹出USB调试选项,如何处理?(红米小米均适用)

参考&#xff1a; 红米手机Redmi 不会自动弹出USB调试选项&#xff0c;如何处理&#xff1f;&#xff08;红米小米均适用&#xff09; - 知乎 以红米9A为例&#xff1b; 【设置】菜单进入后&#xff0c;找到【我的设备】&#xff0c; 选择【全部参数】&#xff0c; 对准miui版…

什么是framebuffer,怎么应用(二)————如何打印BMP图片、字幕函数、字符串

如何切换到终端模式 在昨天写的文章中&#xff0c;没有写到如何切换到终端模式&#xff0c;在编译完函数之后&#xff0c;我们需要从桌面切换到终端模式&#xff1a; ALTCTRLF3切换到终端模式后&#xff0c;登录账号名与密码&#xff0c;其余操作均有桌面终端一样。 如何切换…

机器学习概论—增强学习

机器学习概论—增强学习 强化学习(Reinforcement Learning, RL)或者说是增强学习,是机器学习的一个领域,旨在使智能体通过与环境的交互学习如何做出决策,它是关于在特定情况下采取适当的行动来最大化奖励。它被各种软件和机器用来寻找在特定情况下应采取的最佳行为或路径…

Unity类银河恶魔城学习记录11-7 p109 Aplly item modifiers源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ItemData_Equipment.cs using System.Collections; using System.Collecti…

第三篇:3.2 广告可见性 - IAB与MRC及《增强现实广告效果测量指南1.0》

翻译计划 第一篇 概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇 广告效果测量定义和其他矩阵之- 3.1 广告印象&#xff08;AD Impression&#xff09;第三篇 广告效果测量定义和其他矩阵之- 3.2 可见性 &#xff08;Viewability&#xf…

计算机视觉的应用26-关于Fast-R-CNN模型的应用场景,Fast-R-CNN模型结构介绍

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用26-关于Fast-R-CNN模型的应用场景&#xff0c;Fast-R-CNN模型结构介绍。Fast R-CNN是一种深度学习模型&#xff0c;主要用于目标检测任务&#xff0c;尤其适用于图像中物体的识别与定位。该模型在基…

【Qt】常用控件(输入类)

目录 一、Line Edit二、Text Edit三、ComBo四、DateTimeEdit五、Slider 一、Line Edit QLineEdit 用来表示单行输入框&#xff0c;可以输入一段文本&#xff0c;但是不能换行。 属性说明test输入框中的文本inputMask输入内容格式约束maxLength最大长度frame是否添加边框echoM…

政安晨:专栏目录【TensorFlow与Keras机器学习实战】

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本篇是作者政安晨的专栏《TensorFlow与Keras机器…

【职场攻略】撰写求职信的艺术:如何用AI技术让你脱颖而出

AI与求职的完美结合&#xff1a;会话式AI产品如何助力你的求职之路&#xff1f; 在当今竞争激烈的求职市场中&#xff0c;一封精心准备的求职信可以为你打开通往理想工作的大门。一封好的求职信不仅能展示你的专业技能和工作经验&#xff0c;还能体现你对职位的热情和对公司文化…

labelme AI 模型运用

一、lebelme 1、界面介绍 点击上图位置&#xff0c;选择对应的模型。这里我每个模型都测试了一下&#xff0c;EfficientSam这个模型最好用&#xff0c;准确率和速度都ok。 2、使用方法 目标框标注方法&#xff1a;点左上角【编辑】-> 【Create Ai-Mask】就可以标志了&…

【智能家居项目】RT-Thread版本——DHT11获取温湿度 | MQTT上传到服务器 | 服务器控制外设

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 这篇文章中&#xff0c;本喵将使用RT-Thread Studio来实现这个智能家居的项目&#xff0c;最终…

AXS2003B 爱协生 2.4W单通道 AB类音频功率放大器 兼容LM4890 低成本

AXS2003B 是一颗单通道AB类音频功率放大器。在5V电源供电&#xff0c;THDN10%&#xff0c;4欧姆负载上可以输出2.4W 的功率。 AXS2003B优异的噪声和THD指标可以提供高品质的音频信号放大。极少的外围元件就能提供芯片稳定工作&#xff0c;大大减少了PCB面积并降低成本。 AXS20…

SSL证书一年多少钱?有便宜的吗?

SSL安全证书的价格因其类型、品牌、验证级别、附加功能&#xff08;如多域名支持、通配符功能等&#xff09;以及购买时长&#xff08;通常以年为单位&#xff09;的不同而有所差异。以下是大致的价格范围&#xff1a; 永久免费SSL证书_永久免费https证书_永久免费ssl证书申请…

广告买量的数据驱动策略:从归因到精准投放

在广告买量场景下&#xff0c;数据驱动一定是有意义的。对中小型企业和产品而言&#xff0c;起量和精准是重点&#xff0c;毕竟他们更关注ROI&#xff08;短期利润&#xff09;&#xff0c;这也是效果广告专注中小型企业的原因。而大企业的核心是把流量合理导入自身构建的生态中…

哲学家带你深♂入了解文件操作

目录 一、文件指针 二、文件的打开与关闭 三、顺序读写函数的介绍 四、文件的随机读写 1、fseek 2、ftell 3、rewind 总结 前言 c语言中的文件操作虽然不怎么常用但也是非常重要的知识&#xff0c;今天由本哲学家带大家深♂入了解c语言文件操作。 一、文件指针 每个被使用的文…

Day24:回溯法 LeedCode 77.组合

回溯法解决的问题都可以抽象为树形结构 for循环就是遍历集合区间&#xff0c;可以理解一个节点有多少个孩子&#xff0c;这个for循环就执行多少次。 从图中看出for循环可以理解是横向遍历&#xff0c;backtracking&#xff08;递归&#xff09;就是纵向遍历&#xff0c;这样就把…

virtualbox 日常运维

前言 虽然平常以macOS和Linux作为主打工作环境&#xff0c;但还是有很多需要用到windows的时候&#xff0c;如camtasia和券商QMT软件。 在二手ThinkPad P53上安装了几个windows虚机&#xff0c;作为测试环境。Mac笔记本远程桌面连接嫌麻烦&#xff0c;还是命令行舒服。MacOS自…