【目标跟踪】红绿灯跟踪

文章目录

  • 一、前言
  • 二、结果
  • 三、跟踪
    • 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://www.hqwc.cn/news/577345.html

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

相关文章

括号生成(回溯+剪枝)

22. 括号生成 - 力扣&#xff08;LeetCode&#xff09; 题目描述 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 样例输入 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))&q…

Java毕业设计 基于SSM新闻管理系统

Java毕业设计 基于SSM新闻管理系统 SSM jsp 新闻管理系统 功能介绍 用户&#xff1a;首页 图片轮播 查询 登录 注册 新闻正文 评论 广告 社会新闻 天下新闻 娱乐新闻 个人中心 个人收藏 管理员&#xff1a;登录 用户管理 新闻管理 新闻类型管理 角色&#xff1a;用户 管理员…

鸿蒙OS开发实例:【瀑布流式图片浏览】

介绍 瀑布流式展示图片文字&#xff0c;在当前产品设计中已非常常见&#xff0c;本篇将介绍关于WaterFlow的图片浏览场景&#xff0c;顺便集成Video控件&#xff0c;以提高实践的趣味性 准备 请参照[官方指导]&#xff0c;创建一个Demo工程&#xff0c;选择Stage模型熟读Har…

在视频号上开小店,这些细节内容你知道吗?过来人经验分享!

大家好&#xff0c;我是电商小布。 现在有越来越多的小伙伴&#xff0c;看到了视频号小店的内部的发展机会&#xff0c;纷纷想要加入这个市场。 但是不了解这个项目&#xff0c;在开店运营的时候都是无处下手的。 这其中的一些细节内容一定要提前的了解清楚。 接下来&#…

vue3使用UEditorPlus 、后端配置、上传图片等处理

前端安装 vue3安装vue-ueditor-wrap // vue-ueditor-wrap v3 仅支持 Vue 3 npm i vue-ueditor-wrap3.x -S // or yarn add vue-ueditor-wrap3.x 下载 UEditorPlus 仓库地址 把dist文件复制到vue3项目中的public下&#xff0c;重命名为UEditorPlus UEditorPlus文档 在main.…

从电荷角度理解开关电容中的电荷守恒

目录 一些铺垫电容的电荷量的解释电荷流入流出对节点电压的影响 从电荷角度理解开关电容加法器中的电荷守恒以开关电容积分器为例说明什么样的节点是电荷守恒 一些铺垫 电容的电荷量的解释 对于一个1F的电容&#xff0c;当它的压差为1V时&#xff0c;它所携带的电荷量是QCU1库…

霸榜京东数据库图书热卖榜!《图数据库:理论与实践》热销中

《图数据库&#xff1a;理论与实践》自2月上市以来&#xff0c;受到了数据库行业的广泛关注与热烈支持&#xff0c;问世两周便销量破千本&#xff01;近期还荣登京东 “数据库图书榜”热卖榜第二名&#xff0c;广获好评&#xff01; 在此&#xff0c;真挚的感谢各位读者的认可…

掌握html这一篇就够了

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、HTML是什么&#xff1f;二、基础标签6个标题标签6级标题其他基础标签 8种文本标签6种表格标签4种表格标签基础标签修饰 4种媒体标签a标签img:图片audio&…

持续交付/持续部署流程主要系统构成(CD)

目录 一、概述 二、持续交付/持续部署主要构成 2.1 镜像容器管理系统 2.1.1 镜像分类 2.1.1.1 磁盘镜像 2.1.1.2 镜像容器 2.1.1.2.1 镜像容器分层管理示意图 2.1.2 镜像容器管理系统软件 2.2 配置管理系统 2.2.1 配置管理系统的功能 2.2.1.1 管理操作系统层、中间件…

云原生(七)、Kubernetes初学 + 裸机搭建k8s集群

Kubernetes简介 Kubernetes&#xff08;通常简称为K8s&#xff09;是一个开源的容器编排平台&#xff0c;最初由Google设计和开发&#xff0c;现在由Cloud Native Computing Foundation&#xff08;CNCF&#xff09;维护。它旨在简化容器化应用程序的部署、扩展和管理。 Kube…

一周学会Django5 Python Web开发-Django5模型定义

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计41条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

黑马点评(Redis实战)项目笔记【10万字!超详细!收藏!】

Redis基础篇 续篇Redis基础篇 知识点参考博客 前言&#xff1a;本文首次完成于语雀文档&#xff0c;发布至个人CSDN分享&#xff0c;全篇内容4万余字&#xff0c;若对您有帮助希望点个赞与收藏&#xff01; 由于缩进格式不兼容等问题&#xff0c;若影响您的观看体验&#xff0c…