news 2026/8/30 17:43:13

大一单人挑战智能车竞赛:蚂蚁搬家赛题全流程技术备赛记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大一单人挑战智能车竞赛:蚂蚁搬家赛题全流程技术备赛记录

大一的时候,一句话描述了很多东西:没有队友、没有学长全程带路、没有充足的预算,甚至没真正调过摄像头。当我在社团备赛群里看到“蚂蚁搬家”这个赛题时,第一反应是名字挺有意思,第二反应是:这个东西真的能在大一一个人做出来吗?

后来我不仅做了,还把它做成了我大学阶段最完整的项目之一。这篇文章不是比赛复盘,也不是鸡汤,而是一份面向硬件的备赛记录,里面包含赛题理解、技术方案、图像处理思路、PID控制、状态机设计,以及一个人独自排查问题时踩过的坑。希望能给同样准备智能车竞赛、尤其是准备一个人起步的同学一点参考。

1. 背景理解:什么是“蚂蚁搬家”赛题

如果你还没拿到当年的赛题手册,第一件事一定是去官网和官方公众号把《竞赛规则》和对应组别的赛题手册下载下来。不同届别、不同组别的规则细节每年都会有调整,下面说的是我这届备赛时对“蚂蚁搬家”这个任务的一般理解,具体参数请一定以你们那一年的手册为准。

通俗地说,“蚂蚁搬家”考察的是智能车在一张固定场地上的任务完成能力:小车需要从起点出发,在场地中沿指定路径或者自由巡视,找到代表“蚂蚁”的标志物,然后把它们运送到指定的“巢穴”区域。听起来像是物流搬运,实际上它把智能车竞赛最经典的几个技术点全部串了起来:

  • 视觉感知:识别赛道边界、识别蚂蚁标志物的位置;
  • 运动控制:稳定地巡线、转向、调速,不能压线、不能冲出场地;
  • 机械执行:通过翻板、推杆、机械爪等机构完成蚂蚁的收集和投放;
  • 逻辑调度:什么时候找、什么时候搬、什么时候放,需要用状态机来管理。

这个赛题难就难在它不是单一技能考核,而是一个“感知—决策—执行”的闭环。对大一新生来说,真正的难点不是某一个模块,而是把所有模块串起来之后,整个系统能不能稳定地一遍跑完。

我当时选择“蚂蚁搬家”的原因也很简单:它比纯竞速组更有“项目感”,代码量适中,又能在硬件上有不少发挥空间。一个人备赛虽然累,但整个链路掌握下来之后,你会发现自己对嵌入式开发的理解会被拉高一大截。

2. 一个人也要做的整体技术方案

一个人备赛最容易犯的错误,就是拿到车模就开始乱接线、乱写代码,结果调了三天,发现连需求都没理清楚。我建议在动手之前,先把下面这张功能拆解图画在纸上,哪怕只是简简单单的几个方框。

整体系统可以拆成四层:

  • 感知层:摄像头负责采集场地图像,编码器负责测量车轮转速;
  • 决策层:主控芯片读取图像数据,跑图像处理和路径规划,然后给出目标速度和目标转角;
  • 执行层:电机驱动模块根据PWM控制直流电机转速,舵机根据PWM控制前轮转角;
  • 供电与通信层:电池提供能源,经过稳压模块给主控、摄像头、传感器供电,同时通过串口或无线模块把调试数据传到电脑上位机。

一个人的情况下,我建议按“最小可跑”原则分阶段推进:

阶段目标验收标准
第一阶段车能通电,电机能转,舵机能打角用手控制PWM占空比,车能前后动
第二阶段摄像头图像能传到上位机电脑能实时看到灰度图或二值图
第三阶段能稳定巡线简单环道上不掉线
第四阶段能识别蚂蚁标志物控制台打印出标志物坐标
第五阶段完成抓取和投放动作整个流程能在场地连续跑通

这样划分的原因是:每个阶段都只引入一个新的不稳定因素。智能车是最典型的多变量耦合系统,如果你同时调电机PID和摄像头图像,出问题的时候你根本不知道是哪个环节引起的。

3. 环境准备与硬件选型

如果你的实验室有传承下来的硬件,优先用现成的。一个人备赛,最不该浪费时间的事就是纠结“哪块板子更高级”。我们当年用的是一套基于STC8系列主控的方案,配的是入门级灰度摄像头和普通直流减速电机。这套组合的好处是资料多、便宜、坏了不心疼。

常规环境清单如下:

  • 车模:根据赛题手册选择指定车模规格,我们用的是普通三轮或四轮小车底盘;
  • 主控:STC8系列或者其他你熟悉且有库的单片机,常见的有STC8G、STC8H、TC264等;
  • 摄像头:入门级灰度摄像头,分辨率160x120或者更小都够用,关键是要能拿到实时图像;
  • 编码器:安装在电机后面,用来测速,做闭环控制;
  • 舵机:负责转向,型号需要能承受转向力矩;
  • 电机驱动模块:可以控制电机正反转和调速;
  • 电池与稳压:7.2V或11.1V锂电池组,加稳压模块给主控和传感器供电;
  • 调试工具:USB转TTL下载器、逻辑分析仪、串口屏或者直接单片机串口发数到电脑。

这些硬件的具体型号请以你们实验室库存和比赛规则为准,我不写死,避免误导。但有一点要提醒:如果条件允许,尽量给摄像头和电机驱动单独供电,或者至少做好电容滤波。电机启动瞬间电流很大,经常会把单片机直接拉复位。我们第一次上电调车,车一动屏幕就黑,排查了半天才发现是电源设计出了问题。

开发环境一般是Keil或IAR,加上官方的下载调试器。建议一开始就把工程目录整理清楚,我用的结构大概是这样:

project/ ├── doc/ # 赛题手册、数据手册、笔记 ├── hardware/ # 原理图、接线图 ├── firmware/ │ ├── app/ # 主逻辑,例如巡线、识别、状态机 │ ├── driver/ # 单片机外设驱动,摄像头、编码器、PWM │ ├── algorithm/ # PID、图像处理 │ └── main.c └── tools/ # 上位机、图像分析脚本

一个人写项目,代码目录清晰是后期能坚持调下去的重要前提。

4. 图像处理与蚂蚁识别

4.1 摄像头图像二值化与去噪

“蚂蚁搬家”的第一关,是把摄像头采集到的灰度图变成好处理的二值图。摄像头看到的东西可以理解成一张每个像素都有亮度值的矩阵,比如160(宽)x120(高)的灰度图,每个像素是0到255的亮度值。

简单做法是固定阈值二值化:设定一个阈值THRESHOLD,亮度大于阈值的像素置为白色,小于阈值的置为黑色。这样赛道边界、蚂蚁标志物都会以黑白块的形式呈现。核心思路如下:

import cv2 import numpy as np def binarize(image, threshold=130): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY_INV) # 用小核做开运算去噪,再做闭运算补洞 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return binary

固定阈值在比赛场地光线稳定的情况下很好用,但如果比赛中场地不同区域反光不一样,固定阈值就会出问题。这时候更稳妥的做法是自适应阈值,也就是统计整张图像的亮度分布,或者分区域计算阈值。

代码里我用THRESHOLD_INV表示反二值化,目的是把暗色标志物变成白色前景,因为场地通常是浅色,“蚂蚁”标志或赛道边界往往是深色。如果你做的是反色场景,记得把参数反过来调。

4.2 用轮廓特征识别蚂蚁标志

二值化之后,我们需要在图像中找出“什么是蚂蚁”。如果赛题用的是圆形或者近似圆形的标志物,最保守的识别方案是“面积 + 圆形度 + 半径”。

圆形度的计算方法是:

圆形度 = 4 * π * 面积 / 周长^2

圆的圆形度接近1,四边形、细长形状的圆形度会明显偏低。我们可以利用这个特征过滤掉大量干扰区域。下面这段Python代码可以在电脑上用一张实拍图片快速验证识别思路,验证完再移植到单片机上。

import cv2 import numpy as np def find_ants(image, min_area=80, max_area=8000, min_radius=5): binary = binarize(image) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ants = [] for cnt in contours: area = cv2.contourArea(cnt) if area < min_area or area > max_area: continue perimeter = cv2.arcLength(cnt, True) if perimeter < 1e-6: continue circularity = 4 * np.pi * area / (perimeter * perimeter) (x, y), radius = cv2.minEnclosingCircle(cnt) if circularity > 0.6 and radius > min_radius: ants.append((int(x), int(y), int(radius))) return ants

这段代码放到电脑上,配合比赛场地照片,可以直接看出哪个阈值、哪个圆形度参数最合适。强烈建议准备一小段场地实拍视频,跑一遍这个脚本,把参数定下来,再动手移植到嵌入式环境。单片机上实现时,轮廓提取会麻烦一些,但完全可以用“面积统计 + 边界点计算圆形度”的简化方法,不一定要用完整的OpenCV。

4.3 单片机端巡线偏移量计算

识别到蚂蚁之后,小车怎么沿着赛道走,是另一个核心问题。最经典的方案是“图像中线法”:在摄像头画面底部到中部,选取若干行,每一行找到赛道左边界和右边界,取中点,对所有行的中点求平均,就得到了赛道中心线的横向偏移。

这个偏移量可以直接喂给舵机PD控制器,用来打角。单片机端用C语言实现大致是这样的思路:

#define IMAGE_W 160 #define IMAGE_H 120 #define THRESHOLD 130 uint8_t image[IMAGE_H][IMAGE_W]; // 二值图,1表示白色前景 // 返回值:负值偏左,正值偏右,-1表示丢线 int compute_line_offset(void) { int sum = 0; int count = 0; for (int row = IMAGE_H - 1; row >= IMAGE_H / 2; row--) { int left = -1, right = -1; for (int col = 0; col < IMAGE_W; col++) { if (image[row][col] == 1) { if (left < 0) left = col; right = col; } } if (left >= 0 && right >= 0) { sum += (left + right) / 2; count++; } } if (count == 0) { return -1; // 这一帧丢线了 } return sum / count - IMAGE_W / 2; }

丢线处理是巡线里最容易翻车的地方。如果某一行没有找到边界,我建议先用上一行的有效中线做补偿,如果连续多行都找不到,就进入“丢线保护”状态,让车减速并朝最后一次有效偏移方向打角。

5. 控制层:PID与运动控制

5.1 速度闭环为什么要用PID

电车在下地跑的时候,如果只给固定的PWM占空比,车轮转速会受电池电压和地面摩擦影响,忽快忽慢。编码器的主要作用就是测量实际转速,然后用PID把实际转速拉回到目标值。

PID的核心思想是:根据“目标值—当前值”的误差,按比例、积分、微分三项叠加计算输出。比例项决定响应快慢,积分项消除稳态误差,微分项抑制超调。智能车这种系统,速度可以用PI,转向可以用PD。

下面是一个精简但完整的PID结构体实现,可以直接作为工程里的基础模块使用:

typedef struct { float target; float kp; float ki; float kd; float integral; float last_error; float out_limit; float integral_limit; } PidCtrl; void pid_reset(PidCtrl *pid) { pid->integral = 0.0f; pid->last_error = 0.0f; } float pid_update(PidCtrl *pid, float current, float dt) { float error = pid->target - current; pid->integral += error * dt; if (pid->integral > pid->integral_limit) pid->integral = pid->integral_limit; if (pid->integral < -pid->integral_limit) pid->integral = -pid->integral_limit; float d_error = (dt > 0.0f) ? (error - pid->last_error) / dt : 0.0f; pid->last_error = error; float out = pid->kp * error + pid->ki * pid->integral + pid->kd * d_error; if (out > pid->out_limit) out = pid->out_limit; if (out < -pid->out_limit) out = -pid->out_limit; return out; }

使用的时候只需要初始化一次目标值和三个系数,然后在控制中断里不断调用pid_update

PidCtrl speed_pid = { .target = 1.0f, // 单位按你的编码器换算 .kp = 0.5f, .ki = 0.02f, .kd = 0.0f, .last_error = 0.0f, .integral = 0.0f, .out_limit = 100.0f, .integral_limit = 30.0f }; float duty = pid_update(&speed_pid, current_speed, dt);

5.2 转向PD控制

舵机控制最重要的不是速度,而是平稳。如果直接用偏移量线性映射到舵机角度,车往往会左右剧烈摇晃,因为误差一变,转向角度就突变。

所以舵机一般用PD控制,公式是:

steer = kp * offset + kd * (offset - last_offset) / dt

offset就是前面图像处理算出的赛道中线偏移量。这里kd尽可能不要省,它相当于阻尼,能有效抑制转向抖动。实际调参时,我会先让kd=0,只加kp直到车能过弯但开始抖动,然后逐渐加kd消除抖动,直到车在直道稳定、弯道不冲出赛道。

调PID参数不要一次给满。一个人的精神状态消耗不起反复炸车,建议用上位机把速度曲线、转向输出、误差值记录下来,对着曲线调参,而不是靠肉眼猜测。

6. 端到端任务状态机设计

“蚂蚁搬家”最大的特点是流程多,所以主程序不能写成从头到尾一条大直线。我用了状态机来管理整个任务流程,这是一个人备赛时最能节省精力的设计。

状态机可以简单理解成一个多分支的switch-case,每个case代表一个阶段,阶段之间通过标志位跳转。我的状态划分大致是这样:

enum TaskState { STATE_INIT, STATE_TRACK, STATE_FIND_ANT, STATE_CAUTION, STATE_CATCH, STATE_CARRY, STATE_DROP, STATE_FINISH }; enum TaskState state = STATE_INIT; void task_loop(void) { switch (state) { case STATE_INIT: if (start_flag) { state = STATE_TRACK; } break; case STATE_TRACK: track_line(); if (find_ant_flag) { state = STATE_CAUTION; } break; case STATE_CAUTION: // 降速,继续用摄像头寻找蚂蚁精确位置 set_speed(0.2f); if (ant_centered_flag) { state = STATE_CATCH; } break; case STATE_CATCH: catch_action(); // 电动推杆或翻板执行收集 if (catch_done_flag) { state = STATE_CARRY; } break; case STATE_CARRY: track_line(); if (reach_nest_flag) { state = STATE_DROP; } break; case STATE_DROP: drop_action(); if (drop_done_flag) { state = STATE_FINISH; } break; case STATE_FINISH: stop_car(); break; } }

这套状态机的核心思路是“每个时刻只干一件事”。你在STATE_TRACK阶段不需要同时判断“要不要下电机关机械爪”,你只要专注巡线;进入STATE_CAUTION后再切换成低速识别模式。这样代码逻辑简单,也方便单独测试每个状态。

状态转移条件最好不要依赖单一帧的判定结果。比如find_ant_flag,如果摄像头某一帧误判一下,状态就会跳错。我习惯用连续计数,比如连续三帧都检测到蚂蚁,才认为真的找到了;连续五帧都确认到了巢穴位置,才执行投放。这个“滤波”思路在嵌入式视觉里非常常用。

7. 调试排错:一个人踩过的坑

以下是我在一个人备赛过程中真实遇到过的几类问题,按出现频率排序。一个人调试时最怕问题出来了不知道从哪下手,所以我把现象、原因和排查思路整理成了一张表:

问题现象常见原因排查思路
电机一启动,单片机就重启电源瞬间压降过大,主控供电不足独立供电、加大电容、检查稳压模块电流余量
图像在电脑上看全是雪花点摄像头信号线松动或供电不足用示波器看信号波形,更换线材,单独给摄像头供电
二值化后赛道边缘忽宽忽窄固定阈值不适合多变光线改自适应阈值,减少画面反光区域
直道正常,弯道冲出跑道舵机Kp太小或车速太快先降速,再增大方向Kp,最后补Kd
巡线时左右剧烈摆动舵机Kd为0,误差噪声被放大给偏移量做滤波,增加Kd限制角速度
蚂蚁标志识别漏检圆形度阈值过严、面积范围不对用场地实拍图离线调试参数
机械爪每次都差一点抓不进执行机构位置有误差,控制逻辑在固定点触发改成连续检测标志物相对位置,到位后延时执行
跑着跑着突然停车程序卡死、看门狗没喂,或者电池欠压打开看门狗,串口打印当前状态,检查电池电压

我记得最揪心的一次,是车在场上跑一圈后突然停下,指示灯也不亮。排查了一整晚,最后发现是一根负责电机驱动板逻辑供电的杜邦线在车震动中松了,导致驱动板进入保护状态。从那以后我的原则是:所有接插件必须用扎带或热熔胶固定,重要信号线直接焊接或者压接。一个人备赛没有队友帮你看线,接线可靠性就是唯一的兜底。

另外一个建议是善用串口打印。主控每一次状态切换,我都把当前状态、偏移量、速度、识别结果通过串口发到上位机。跑车之前先在场地把车架起来空跑一遍,看串口数据是否正常。等确认上层逻辑没问题,再放下去实跑,这样能省下大量反复弯腰捡车的时间。

8. 一个人也要有的工程习惯

很多人觉得智能车比赛比的是谁调参调得多,实际上比的是谁的系统更稳定、谁的问题定位更快。一个人备赛,工程习惯尤其重要。我总结了几条非常实在的经验:

第一,写调试笔记。不要相信自己的短期记忆。我调PID时经常改一个参数就试跑一圈,跑完如果不记,下午就完全忘了哪个参数是“当前最优”。我后来用了一个最简单的表格,里面记录日期、状态、参数、现象、结论。这张表后来成了我比赛前调优的最重要依据。

第二,做模块化封装。摄像头驱动、编码器读取、PID计算、状态机逻辑,全部拆成不同文件。不要全部堆在main.c里。哪怕你写的代码只是给自己用,也要让一个礼拜后的自己能看懂。一个人备赛最痛苦的场景之一,就是代码写到一半,连自己都找不出上次的逻辑。

第三,版本管理很重要。哪怕不用Git,也要经常备份工程。最稳妥的做法是在每次试跑前复制一份完整的工程文件夹,命名为20240518_track_ok_backup。就算后面调崩了,也能回到上一个稳定版本。用Git也行,但对大一新人来说,先养成“改代码前备份”的习惯更重要。

第四,不要硬扛。一个人备赛容易陷入死磕状态,一个Bug调三天还没结果。建议给自己设一个时间上限,比如一个疑难问题超过三小时没进展,就去翻官方论坛、看B站公开调试视频、查CSDN上的同类问题。把别人的经验拿来验证,比自己熬夜盲试效率高太多。智能车竞赛本身有很丰富的社区资料,官方公众号“智能车竞赛”也会发规则解读和技术分享,这些公开资料就是一个人最好的“隐形队友”。

第五,注意安全。锂电池充电要有人在场,烙铁焊接注意通风,跑车时手不要伸进电机齿轮传动区域。一个人搞项目,出任何安全问题都没有队友在旁边帮忙,所以安全底线必须守住。

9. 关于大一、一个人,和“蚂蚁搬家”

如果现在有人问我,大一一个人选“蚂蚁搬家”这个赛题值不值得,我会说值得,但前提是你能接受这个过程非常“孤独且琐碎”。

所谓孤独,是指从焊板子到查资料,从调图像到改机械结构,所有事情都要自己决定、自己承担结果。所谓琐碎,是指你真正花在“写代码”上的时间可能只有三分之一,剩下时间都在查线、换电池、改阈值、看波形、量电压。但正是因为一个人,你必须逼着自己把整个系统的每一个环节都弄懂,而不是像有队友时那样各管一段。

我大一备赛那段时间,最常用到的反而不是复杂的算法,而是扎实的基础:知道怎么看电路图,知道PWM占空比和电机转速的大致关系,知道摄像头图像每帧的像素数据怎么读进内存,知道怎么用串口把变量发出来看。这些都是肉眼可见的成长。等到大二再回头看,你会发现当初那些让你焦虑到整晚睡不着的Bug,绝大多数都是简单的接线问题、阈值问题和参数问题。

如果你也准备一个人挑战智能车,我的建议很简单:选一个你真正感兴趣的赛题,把项目拆成最小可跑的步骤,先把车跑起来,再一步步加功能。别怕代码写得丑,别怕第一次用烙铁烫到手,也别怕把车模撞坏。只要还能从场地里把车捡回来,你就在往前走。

这篇文章既是备赛记录,也是写给当年那个独自坐在实验室里的自己。如果你的情况和我类似,希望它能帮你少走一点弯路,也让你知道,一个人并不等于什么都做不成。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 17:43:07

无视觉版智能车:先稳运动控制,再谈视觉识别

“21届走马观碑车模运行视频&#xff08;未加入视觉&#xff09;”这个标题&#xff0c;很多人看到后的第一反应可能是&#xff1a;既然项目叫“走马观碑”&#xff0c;视觉识别才是核心&#xff0c;没加视觉是不是意味着只是个半成品&#xff1f;我的看法恰恰相反。在智能车和…

作者头像 李华
网站建设 2026/8/30 17:40:01

示波器截图软件SWcopy(V1.3.12)

&#xfeff;&#xfeff;使用示波器的测量完成后,要保留测量结果,最好的办法是截图.一般截图有几种方式:1.使用U盘保存,但是一来一回,浪费了很多时间,而截图多了,后期又难以区分管理 2.使用手机拍照,截图快,但是后期也是要拷到电脑上,再加上电脑不识别手机相册,真是抓狂 3.最好…

作者头像 李华
网站建设 2026/8/30 17:39:56

138、动力学基础:拉格朗日与牛顿欧拉方程

138、动力学基础:拉格朗日与牛顿欧拉方程 兄弟们,今天这篇咱们不聊策略网络,也不聊扩散模型,聊点“硬”的——机器人动力学。为啥突然写这个?因为前两天我在调试一个机械臂的力控接口,用的是某国产协作臂,官方SDK里给的力矩前馈模型,在低速空载的时候跑得挺顺,结果一…

作者头像 李华
网站建设 2026/8/30 17:36:32

AI语音钓鱼攻击iPhone失窃黑产:Apple ID双重认证与防范

这次我们要看的是一个被安全团队披露的恶意工具包 AnonyMousKIT。它面向的场景很具体&#xff1a;当一台 iPhone 被偷或被盗之后&#xff0c;攻击者不再硬猜锁屏密码&#xff0c;而是用 AI 语音钓鱼主动联系机主&#xff0c;冒充苹果官方客服&#xff0c;套取 Apple ID 密码&am…

作者头像 李华
网站建设 2026/8/30 17:36:23

多模态线稿上色框架OmniColor:统一文本、参考图与调色板条件

线稿上色在漫画工业、插画辅助、老照片修复和游戏原画设计里是刚需&#xff0c;但传统工具和单模态模型用起来都挺别扭&#xff1a;画师手绘几百张线稿、再逐张指定颜色方案&#xff0c;工作量巨大&#xff1b;文本提示容易“翻车”&#xff0c;参考图又往往抓不住构图结构。EC…

作者头像 李华