智能车镜头组入门(四)元素识别
元素识别是摄像头部分中难度最大的一部分,也是我花时间最长的一部分,前前后后画了很长时间,最后还是勉勉强强完成了。
基础的元素识别主要有两个:十字,圆环,和斑马线。十字要求直行,圆环需要进圆环。我们的车在完全不写的情况下,十字可以猛冲过去,只是会非常抖。圆环不能进。
如果十字不能走直线的话,是直接判失败的,因为你抄了近路。圆环不能进入的话,是罚时30s的,我们这一年华南赛区一等奖是要在10s之内的,区2也要15s左右。所以如果你吃了这个罚时,基本上与区二无缘了。
前面我的推文提到过,我们在搜线搜完之后会额外出现两个数组,一个是左右边线的丢线数组,另一个是赛道宽度的数组。我们先对左右边线丢线数组进行处理:
loss_pointer_L = -1;loss_pointer_R = -1;if (loss_array[IMG_H - 1][0] == 1) {loss_array_L[++loss_pointer_L] = IMG_H - 1;}if (loss_array[IMG_H - 1][1] == 1) {loss_array_R[++loss_pointer_R] = IMG_H - 1;}for (int j = IMG_H - 1; j > IMG_H - max_length; j--) {if (loss_array[j - 1][0] == 1 && loss_array[j][0] == 0) { // headloss_array_L[++loss_pointer_L] = j;}if (loss_array[j - 1][0] == 0 && loss_array[j][0] == 1) { // endloss_array_L[++loss_pointer_L] = j - 1;loss_counter_L[loss_pointer_L / 2] = loss_array_L[loss_pointer_L - 1] - loss_array_L[loss_pointer_L];}if (loss_array[j - 1][1] == 1 && loss_array[j][1] == 0) { // headloss_array_R[++loss_pointer_R] = j;}if (loss_array[j - 1][1] == 0 && loss_array[j][1] == 1) { // endloss_array_R[++loss_pointer_R] = j - 1;loss_counter_R[loss_pointer_R / 2] = loss_array_R[loss_pointer_R - 1] - loss_array_R[loss_pointer_R];}}
这其中,loss_array[IMG_H][2]中存的是丢线的数据,比如左边线的第100行丢线了,则loss_array[100][0] = 1。
这串代码的意图是把这个loss_array分成四个一维数组,分别为loss_array_L,loss_array_R,loss_counter_L,loss_counter_R。loss_array_L[]这个数组偶数存放的是左边线丢线的开始,奇数存放的是 左边线丢线的结束,loss_counter_L[]这个数组存放的是左边线丢线的个数。便于之后辨别元素。例如loss_array_L[0] = 75, loss_array_L[1] = 55,那么说明左边线的55行-75行存在丢线的情况,那么 loss_counter_L[0]的值则为20。
所需要的另一个数组则为宽度的计算
我们首先需要计算在大直道的情况下,如果把车至于赛道中央,顶端和最低端赛道的像素数量
for (i = IMG_H - 1; i >= 0; i--) {compared_array[i] = (HEAD_LENGTH + (END_LENGTH - HEAD_LENGTH) * (float)i / IMG_H) * ELEMENT_RATE;if (boundary_length[i] > compared_array[i]) {elem_array[i] = 1;}else {elem_array[i] = 0;}}
这样我们可以计算出大直道上理论上的赛道宽度,如果大于这个宽度*ELEMENT_RATE,则有进元素的嫌疑。
之后我们还是像刚刚处理丢线的方式,来把丢线的长度入栈,
for (int j = ELEM_SEARCH_BEGIN; j > IMG_H - max_length; j--) {if (elem_array[j - 1] == 1 && elem_array[j] == 0) { // 开始elem[++elem_pointer] = j;line_L[elem_pointer] = abs(boundary[j][LEFT] - boundary[j - 1][LEFT]);line_R[elem_pointer] = abs(boundary[j][RIGHT] - boundary[j - 1][RIGHT]);}if (elem_array[j - 1] == 0 && elem_array[j] == 1) { // 结束elem[++elem_pointer] = j - 1;//结尾入栈elem_counter[elem_pointer / 2] = elem[elem_pointer - 1] - elem[elem_pointer];line_L[elem_pointer] = abs(boundary[j - 1][LEFT] - boundary[j][LEFT]);line_R[elem_pointer] = abs(boundary[j - 1][RIGHT] - boundary[j][RIGHT]);}}if (elem_array[1] == 1) { //如果图象底部缺线 则将底部入栈elem[++elem_pointer] = 1;elem_counter[elem_pointer / 2] = elem[elem_pointer - 1] - elem[elem_pointer];line_L[elem_pointer] = abs(boundary[1][LEFT] - boundary[0][LEFT]);line_R[elem_pointer] = abs(boundary[1][RIGHT] - boundary[0][RIGHT]);}
这样,我们就完成了数据的预处理。
我们来具体分析下十字和圆环的特征。
图片是选用逐飞对ccd组进行讲解的图,对上面的标号对镜头组其实不适用,可以忽略
我们可以看出,临近十字有一条很长的直线,圆环的一侧是一条很长的直线,我们可以用这个来辅助判断圆环和十字的特征
然后圆环的入口和出口会有一个很大的丢线区域,十字中也会有一个很大的丢线区域
所以,我们需要一个函数来判断是否为直线
unsigned char is_straight(unsigned char begin, unsigned char end, line_type line);//直线判断函数
我们的函数接受三个数据,读取boundary数组的line行,也就是确定左边线还是右边线。其他的有begin end 返回值是1或者0。
现在,我们来开始元素判断
if (mode == NORMAL && max_length > 95) {if (elem_pointer > 0) {i = 0;while (i <= elem_pointer && i < 10) {int lc = 0, rc = 0, llc = 0, rlc = 0, lcbegin = 0, rcbegin = 0;//l counter(long), l loss counterif(elem[i] - elem[i + 1] < 20) return;for(int a = 0; a < loss_pointer_L; a += 2){llc += loss_counter_L[a / 2];if(loss_counter_L[a / 2] > 25){lc++;lcbegin = loss_array_L[a];}}for(int b = 0; b < loss_pointer_R; b += 2){rlc += loss_counter_R[b / 2];if(loss_counter_R[b / 2] > 25){rc++;rcbegin = loss_array_R[b];}}
这部分是基础的判断,把丢线再次分为了长丢线和短丢线,这是一个很方便的分辨圆环和十字的方式,即圆环为一边的长丢线,十字为两边都有长丢线。
先来进行圆环的判断,前面有提到,圆环的一个很显著的特征是一侧边线为直线,但是实际你的图象不可能特别完美,比如可能会出现这样的情况:
显然 左边线的处理会比较困难,另外,可能由于光照之类的因素,左边线并不是非常的完整的直线,所以需要将左边线简单的做一下分割,分段判断是否为直线,方式和前文提到的数据预处理相差不大
if(loss_array_R[0] == IMG_H - 1){//在底部丢线的情况下 排除底部for(int j = 1; j <= loss_pointer_R; j += 2){straight_judge_array[++straight_pointer] = loss_array_R[j];straight_judge_array[++straight_pointer] = loss_array_R[j + 1];if(loss_array_R[j + 1] < 20){straight_judge_array[straight_pointer] = 20;break;}}
}
else
{straight_judge_array[++straight_pointer] = IMG_H - 1;straight_judge_array[++straight_pointer] = loss_array_R[0];for(int j = 1; j <= loss_pointer_R; j += 2){straight_judge_array[++straight_pointer] = loss_array_R[j];straight_judge_array[++straight_pointer] = loss_array_R[j + 1];if(loss_array_R[j + 1] < 20){straight_judge_array[++straight_pointer] = 20;break;}}
}
然后,进行数据的筛除,把那些特别小的段进行剔除,之后可以进行直线判断
for(int j = 1; j <= straight_pointer; j++){if(straight_pointer <= 2) break;if(straight_judge_array[j + 1] - straight_judge_array[j] > 5) flag = 0;if(abs(boundary[straight_judge_array[j + 1]][RIGHT] - boundary[straight_judge_array[j]][RIGHT])> 5) flag = 0;}
for(int j = 0; j <= straight_pointer; j += 2){if(!is_straight(straight_judge_array[j], straight_judge_array[j + 1], RIGHT)){flag = 0;break;}
}
这样判断后,圆环基本上可以实现识别到圆环了
if(flag == 1){if(system_time - last_elem_finish_flag < 200) return;if(circle_num_L == 0){return;}else{mode = FAR_CIRCLE_L;}
}
一种比较鸡贼的避免误判的方式 就是把根据赛道把圆环数量写死,也就是代码中的circle_num_L,需要注意每一次发车的时候都需要重置这个值
当然还有一些比较特殊的情况,需要另外加判断去屏蔽,这些需要大家跑车的时候多去试试。
圆环是误判最麻烦的一部分,因为圆环分成了好几个状态,每个状态都需要不同的补线方式,我们采用的出圆环的方式是陀螺仪的积分,这样就要求小车必须转一圈才会出圆环,所以一旦误判圆环就会造成直接的错误。
接下去是十字识别
if(lc && rc){if(abs(lcbegin - rcbegin) > 20) return;l_down = elem[i]; r_down = elem[i];l_up = elem[i + 1]; r_up = elem[i + 1];for(int k = 10; k >= 0; k = k - 2){if(k > loss_pointer_L) continue;if(elem[i] - 3 < loss_array_L[k] && loss_counter_L[k / 2] > 5 && loss_array_L[k] != IMG_H - 1){l_down = loss_array_L[k] + 3;l_up = loss_array_L[k + 1] - 7;break;}}for(int k = 10; k >= 0; k = k - 2){if(k > loss_pointer_R) continue;if(elem[i] - 3 < loss_array_R[k] && loss_counter_R[k / 2] > 5 && loss_array_R[k] != IMG_H - 1){int a;r_down = loss_array_R[k] + 3;for(int b = 7; b < 13; b++){if(boundary[loss_array_R[k + 1] - b][RIGHT] < IMG_W - 5){r_up = loss_array_R[k + 1] - b;break;}}break;}}if(is_straight( l_down, l_down + 15, LEFT) && is_straight( r_down, r_down + 15, RIGHT)){mode = FAR_CROSS;return;}}
十字识别相对来说简单些,两边都有长丢线:两边丢线到底端都是直线,而且直线误判也没关系,状态机会很快的恢复到正常模式,也不用单独调整权重。所以我们并没有在直线判断上下很多功夫
相关文章:

智能车镜头组入门(四)元素识别
元素识别是摄像头部分中难度最大的一部分,也是我花时间最长的一部分,前前后后画了很长时间,最后还是勉勉强强完成了。 基础的元素识别主要有两个:十字,圆环,和斑马线。十字要求直行,圆环需要进…...

Java键盘输入语句
编程输入语句 1.介绍:在编程中,需要接受用户输入的数据,就可以使用键盘输入语句来获取。 2.步骤: 1)导入该类的所在包,java.util.* 2)创建该类对象(声明变量) 3)调用里面的功能 3…...

【读书笔记-《30天自制操作系统》-22】Day23
本篇内容比较简单,集中于显示问题。首先编写了应用程序使用的api_malloc,然后实现了在窗口中画点与画线的API与应用程序。有了窗口显示,还要实现关闭窗口的功能,于是在键盘输入API的基础上实现了按下按键关闭窗口。最后发现用上文…...

C++学习笔记(33)
三十五、栈 示例: #include <iostream> using namespace std; typedef int ElemType; // 自定义链栈的数据元素为整数。 struct SNode // 链栈的结点。 { ElemType data; // 存放结点的数据元素。 struct SNode* next; // 指向下一个结点的指针。 }; // 初始化…...

智谱清影 -CogVideoX-2b-部署与使用,带你揭秘生成6s视频的极致体验!
文章目录 1 效果展示2 CogVideoX 前世今生3 CogVideoX 部署实践流程3.1 创建丹摩实例3.2 配置环境和依赖3.3 模型与配置文件3.4 运行4 遇到问题 1 效果展示 A street artist, clad in a worn-out denim jacket and a colorful bandana, stands before a vast concrete wall in …...

探索Java中的设计模式:原则与实例
探索Java中的设计模式:原则与实例 大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天,我们将深入探讨Java中的设计模式,包括一些关键的设计原则和具体的实例。设计…...

【Java】关键字-static【主线学习笔记】
文章目录 前言关键字:static静态变量静态方法设置为静态的场景 下一篇 前言 Java是一门功能强大且广泛应用的编程语言,具有跨平台性和高效的执行速度,广受开发者喜爱。在接下来的学习过程中,我将记录学习过程中的基础语法、框架和…...

数字自然资源领域的实现路径
在数字化浪潮的推动下,自然资源的管理与利用正经历着前所未有的变革。本文将从测绘地理信息与遥感专业的角度,深度分析数字自然资源领域的实现路径。 1. 基础数据的数字化 数字自然资源的构建,首先需要实现基础数据的数字化。这包括地形地貌…...

GitLab邮箱发送邮件:如何实现自动化发信?
gitlab邮箱发送邮件设置教程?Gitlab邮箱配置和使用? GitLab不仅提供了代码版本控制、持续集成/持续部署等功能,还支持通过其内置的邮件功能实现自动化邮件发送。AokSend将深入探讨如何在GitLab中配置和使用邮箱发送邮件功能。 GitLab邮箱发…...

sqli-labs靶场搭建
下载了一个phpstudy进行搭靶场搭建 然后打开phpstudy安装好php,mysql等环境 正式sqli-labs靶场搭建 第一步:下载源码:https://codeload.github.com/Audi-1/sqli-labs/zip/master 解压后放进网站根目录,进到 sqli-labs的文件夹下࿰…...

Leetcode Hot 100刷题记录 -Day14(矩阵置0)
矩阵置0 问题描述: 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0。 示例 1: 输入:matrix [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0,1],[0,0,0],[1,0,1]]示例 2:…...

每日刷题(算法)
我们N个真是太厉害了 思路: 我们先给数组排序,如果最小的元素不为1,那么肯定是吹牛的,我们拿一个变量记录前缀和,如果当前元素大于它前面所有元素的和1,那么sum1是不能到达的值。 代码: #def…...

大牛直播SDK核心音视频模块探究
技术背景 视沃科技旗下”大牛直播SDK”,始于2015年,致力于传统行业极致体验的音视频直播技术解决方案,产品涵盖跨平台的实时RTMP推流、RTMP/RTSP直播播放(支持RTSP|RTMP H.265,Enhanced RTMP H.265)、GB28181设备接入、推送端播放…...

gin配置swagger文档
一、基本准备工作 1、安装依赖包 go get -u github.com/swaggo/swag/cmd/swag go get -u github.com/swaggo/gin-swagger go get -u github.com/swaggo/files2、在根目录上配置swagger的路由文件 //2.初始化路由router : initialize.Routers()// 配置swaggerdocs.SwaggerInfo…...

基于ssm的快餐店点餐系统设计与实现
需要项目源码请联系我,目前有各类成品 毕设 javaweb ssh ssm springboot等等项目框架,源码丰富。 专业团队,咨询就送开题报告,活动限时免费,有需要的朋友可以来留言咨询。 一、摘要 进入二十一世纪以来,计…...

集合框架底层使用了什么数据结构
1.是什么 集合框架(Collection Framework)是Java标准库的一部分,它提供了一系列接口和实现类,用于处理不同类型的集合。这些集合可以用于存储和操作对象,如列表、集合、映射等。集合框架的底层数据结构是多种多样的&am…...

Activiti7《第二式:破剑式》——工作流中的以柔克刚
冲冲冲!开干 这篇文章将分为九个篇章,带你逐步掌握工作流的核心知识。这篇文章将带你深入探讨工作流中的 “破剑式”,揭示如何通过 柔与刚 的结合来破解工作流的复杂性。本篇包含了 Activiti7 环境的进一步优化和表结构的深入分析࿰…...

docker快速搭建kafka
1、拉取镜像 kafka和 zk镜像 docker pull wurstmeister/zookeeper docker pull wurstmeister/kafka:1.1.02、运行zk容器 docker run -itd --restart always --name zookeeper -p 2181:2181 wurstmeister/zookeeper3、运行kafka容器 192.168.31.112 这个地址为zk地址 docker…...

基于 onsemi NCV78343 NCV78964的汽车矩阵式大灯方案
一、方案描述 大联大世平集团针对汽车矩阵大灯,推出 基于 onsemi NCV78343 & NCV78964的汽车矩阵式大灯方案。 开发板搭载的主要器件有 onsemi 的 Matrix Controller NCV78343、LED Driver NCV78964、Motor Driver NCV70517、以及 NXP 的 MCU S32K344。 二、开…...

OpenAl o1论文:Let’s Verify Step by Step 快速解读
OpenAl又火了,这次是o1又带给大家惊艳。网上的博主已经有了真真假假的各种评测,我这篇来一点硬核的,解读OpenAl o1背后的论文:Let’s Verify Step by Step 大模型在给定的上下文资料正确的情况下也有可能出现幻觉,这篇…...

Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.
Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.Youmayincreasethelimitbyauthenticatingandupgrading:https://www.docker.com/increase−rate−limit.See ′ dockerrun−−help 在拉取docker进行的时候遇到这个问题,如何解决呢?本文提供的解决方…...

[2025]医院健康陪诊系统(源码+定制+服务)
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...

Golang | Leetcode Golang题解之第405题数字转换为十六进制数
题目: 题解: func toHex(num int) string {if num 0 {return "0"}sb : &strings.Builder{}for i : 7; i > 0; i-- {val : num >> (4 * i) & 0xfif val > 0 || sb.Len() > 0 {var digit byteif val < 10 {digit 0…...

VB中如何使用正则表达式(Regular Expressions)
在Visual Basic (VB) 中使用正则表达式,你需要依赖于.NET Framework的System.Text.RegularExpressions命名空间。这个命名空间提供了一系列的类,其中最常用的是Regex类,它允许你在VB.NET中执行复杂的文本搜索和替换操作。 以下是如何在VB.NE…...

Docker FROM 指定基础镜像
所谓定制镜像,其一定是以一个镜像为基础,在其上进行定制。 比如一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必…...

19:I2C一:程序模拟I2C通信时序
I2C 1、什么是I2C2、I2C的通信时序2.1:起始信号2.2:停止信号2.3:主机向从机发送一个字节数据2.4:主机向从机读取一个字节数据2.5:主机接收应答2.6:主机发送应答 3、程序模拟I2C的通信时序3.1:指…...

最佳实践 · MySQL 分区表实战指南
引言 在数据量急剧增长的今天,传统的数据库管理方式可能无法有效处理海量数据的存储和查询需求。MySQL 提供了分区表功能,这不仅能够帮助优化性能,还能简化数据管理过程。分区表允许将数据表拆分成多个逻辑上的分区,每个分区可以…...

详细介绍 Redis 列表的应用场景
Redis 的列表(List)数据类型有以下一些主要应用场景: 一、消息队列 生产者-消费者模式 生产者可以使用LPUSH命令将消息从列表的左端(头部)插入到 Redis 列表中。消费者则使用BRPOP或RPOP命令从列表的右端(…...

游戏如何检测加速外挂
在游戏面临的众多外挂风险中,除了常见的内存修改挂、注入挂等作弊手段,黑灰产还常用「加速」手段实现作弊。 游戏安全风险分布占比图 「加速」顾名思义是指改变游戏内的速度。游戏在运行中需要以帧为单位播放画面,而计算每帧动画播放所需时间…...

【STM32 HAL库】OLED显示模块
【STM32 HAL库】OLED显示模块 前言理论OLED基本参数OLED基本驱动原理OLED坐标轴 应用CubeMx配置底层函数代码高层封装函数printf显示函数 前言 本文为笔者学习 OLED 的总结,基于keysking的视频内容,如有错误,欢迎指正 理论 OLED基本参数 …...