【目标跟踪】红绿灯跟踪

文章目录

  • 一、前言
  • 二、结果
  • 三、跟踪
    • 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]);elsetracked_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);elsedupa.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 状态的目标

四、后记

欢迎相互学习交流

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

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

相关文章

马上蓝桥杯了,干货总结动态规划专题,祝你考场爆杀(拔高篇)最佳课题选择 书本整理 打鼹鼠 吃吃吃 非零字段划分

目录 最佳课题选择 思路&#xff1a; 书本整理 思路&#xff1a; 打鼹鼠 思路&#xff1a; 吃吃吃 思路&#xff1a; 非零字段划分 最佳课题选择 思路&#xff1a; 根本还是论文的分配&#xff0c;每个课题分配多少个论文是不确定的&#xff0c;这个也是很影响转…

天梯算法Day3整理

浮点数解析 炸鱼题掠过 冲突值 题面 解析 方法一 —— 并查集 按照边值排序&#xff0c;然后按边值从大到小遍历&#xff0c;通过并查集判断能否将所有点无冲突地归于两个集合。在判断时&#xff0c;若有两个点不得不产生冲突&#xff0c;则输出这两个点之间的边值并结束。…

PLC通讯时如何判断选用MODBUS方式还是现场总线方式?

在工业自动化领域&#xff0c;PLC扮演着至关重要的角色。然而&#xff0c;许多人在初次接触PLC通讯时&#xff0c;常因其复杂性而感到困扰。事实上&#xff0c;PLC的通讯并不如人们想象中的那么神秘&#xff0c;它主要只有两种类型&#xff1a;一种是需要编写代码的通讯方式&am…

Verilog语法之assign语句学习

assign语法主要是对组合逻辑的变量进行赋值的&#xff0c;就是把一个变量赋值给另一个变量&#xff0c;被复制的变量必须是wire类型的参数。 从仿真结果可以看出&#xff0c;data_in变量的值赋值给了data_out,assign语法就是赋值没有任何延迟&#xff0c;data_in是什么值&#…

服务器被挖矿了怎么办,实战清退

当我们发现服务器资源大量被占用的时候&#xff0c;疑似中招了怎么办 第一时间重启服务是不行的&#xff0c;这些挖矿木马一定是会伴随着你的重启而自动重启&#xff0c;一定时间内重新霸占你的服务器资源 第一步检查高占用进程 top -c ps -ef 要注意这里%CPU&#xff0c;如果…

[Python人工智能] 四十五.命名实体识别 (6)利用keras构建CNN-BiLSTM-ATT-CRF实体识别模型(注意力问题探讨)

从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解融合Bert的实体识别研究,使用bert4keras和kears包来构建Bert+BiLSTM-CRF模型。这篇文章将详细结合如何利用keras和tensorflow构建基于注意力机制的CNN-BiLSTM-ATT-CRF模型,并实现中文实体识别…

【MySQL】16.事务管理(重点) -- 2

1. 事务隔离级别 如何理解隔离性1 MySQL服务可能会同时被多个客户端进程(线程)访问&#xff0c;访问的方式以事务方式进行一个事务可能由多条SQL构成&#xff0c;也就意味着&#xff0c;任何一个事务&#xff0c;都有执行前&#xff0c;执行中&#xff0c;执行后的阶段。而所…

Linux 动静态库的制作,使用和加载

Linux 动静态库的制作,使用和加载 一.前置说明1.mylib.h2.mylib.c3.mymath.h mymath.c4.如何制作库 二.动静态库的制作1.静态库的制作1.制作2.使用一下静态库,验证是否成功打包 2.动态库的制作1.编译.c源文件文件生成.o目标文件2.打包生成动态库3.编写makefile文件,自动化制作动…

【SpringCloud】Ribbon负载均衡

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

警惕.360勒索病毒:如何预防.360勒索病毒攻击

导言&#xff1a; 在网络安全领域&#xff0c;勒索病毒是一种非常危险的恶意软件&#xff0c;它以其独特的加密方式和高昂的赎金要求&#xff0c;给个人和企业带来了严重的损失。.360勒索病毒便是其中之一&#xff0c;它属于BeijingCrypt勒索病毒家族&#xff0c;具有高度的隐…

NO12 蓝桥杯单片机之DS1302的使用

1 DS1302是什么 DS1302由两块存储器组成&#xff0c;一个是日历时钟寄存器还有一个是31位的静态RAM存储器。 而在蓝桥杯中常考的就是日历时钟寄存器&#xff0c;故这里只介绍日历时钟寄存器。简单来说&#xff0c;其就是一个“电子表”&#xff0c;他会自动的实时记录时间&am…

Suno - AI自动作曲

文章目录 关于 Suno创作歌词结构曲风 关于 Suno Suno 是一款自动编曲工具。 官网 &#xff1a;https://www.suno.ai Suno is building a future where anyone can make great music. Whether you’re a shower singer or a charting artist, we break barriers between you …

关于Devc++调试的问题以及解决STL变量无法查看

目前Devc的调试主要有以下几点&#xff1a; 1.调试不能直接查看stl变量&#xff0c;会卡死不动 2.目前单步进入只能用鼠标键按 3.若想按下一步进入函数体内&#xff0c;要在函数体内打上断点才行 4.调试到return 0 ;上一句就停了&#xff0c;不会结束程序 5.目前F2跳至断点…

matplotlib 绘图

matplotlib 绘图 方便设置legend图例的位置 ax1.legend(loc‘upper center’, bbox_to_anchor(0.3, -0.1)) ax2.legend(loc‘upper center’, bbox_to_anchor(0.6, -0.1)) import numpy as np import matplotlib.pyplot as plt from scipy.stats import norm from scipy.inter…

SpringBoot Redis的使用

官方文档&#xff1a; 官方文档&#xff1a;Spring Data Redis :: Spring Data Redis 和jedis一样&#xff0c;SpringBoot Redis 也可以让我在Java代码中使用redis&#xff0c;同样也是通过引入maven依赖的形式。 加速访问github: 使用steam可以免费加速访问github Spring…

HarmonyOS实战开发-目标管理、如何实现一个自定义弹窗。

介绍 本篇Codelab将介绍如何使用State、Prop、Link、Watch、Provide、Consume管理页面级变量的状态&#xff0c;实现对页面数据的增加、删除、修改。要求完成以下功能&#xff1a; 实现一个自定义弹窗&#xff0c;完成添加子目标的功能。实现一个可编辑列表&#xff0c;可点击…

Android14之深入理解sp模板类(二百零二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Android R 广播注册与发送流程分析

静态广播注册时序图 动态广播注册时序图 发送广播时序图 前言 广播接收器可以分为动态和静态&#xff0c;静态广播接收器就是在 AndroidManifest.xml 中注册的&#xff0c;而动态的广播接收器是在代码中通过 Context#registerReceiver() 注册的。 这里先从静态广播的流程开始…

2020年天津市二级分类土地利用数据(矢量)

天津市&#xff0c;位于华北平原海河五大支流汇流处&#xff0c;东临渤海&#xff0c;北依燕山。地势以平原和洼地为主&#xff0c;北部有低山丘陵&#xff0c;海拔由北向南逐渐下降&#xff0c;地貌总轮廓为西北高而东南低。天津有山地、丘陵和平原三种地形&#xff0c;平原约…

深夜变电站三维可视化:电力之心的全新解读

在寂静的深夜&#xff0c;城市的灯火依旧璀璨夺目&#xff0c;而在这背后&#xff0c;有一个不为人知的守护者正在默默工作——那就是变电站。如今&#xff0c;随着科技的飞速发展&#xff0c;我们有了更直观、更生动的方式来了解这个神秘的电力枢纽——三维可视化技术。 深夜变…