ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32与OpenMV的智能网球捡球小车:嵌入式视觉与无线控制实战

基于STM32与OpenMV的智能网球捡球小车:嵌入式视觉与无线控制实战 简介这是一套面向计算机科学、电子信息工程及自动化专业本科生的智能系统综合实践资源适用于课程设计、期末大作业或毕业设计选题解决网球运动场景中自动识别与拾取网球的实际问题。资源包共1254个文件涵盖562个C源码、245个头文件h、125个XML配置、27个Java文件APP逻辑及23个CRF/22个O等编译中间文件完整包含STM32固件、OpenMV图像处理算法、Android APP工程及WiFi通信协议栈压缩包大小为42.47MB。已有61人学习下载资源结构清晰含IAR/Keil工程uvprojx、axf、hex、语音识别模型baidu_speech_grammar.bsg、数学库arm_cortexM3l_math.a、测试图像test1.bmp及启动脚本gradlew.bat便于读者快速部署、调试并理解多模块协同机制。1. 项目缘起从“捡球”这件小事说起不知道你有没有过这样的经历在网球场上挥汗如雨一个漂亮的底线抽击之后球飞到了场地的另一端。几轮下来场地四周散落着十几个网球弯腰去捡不仅打断了练习的节奏对腰也是个不小的负担。传统的捡球器要么需要手动推着走要么就是大型的、昂贵的专业设备。我的毕设课题就是源于这个非常具体且真实的痛点——如何用相对低成本、易实现的技术做一个能自动识别并收集网球的智能小车。这不仅仅是一个简单的“遥控车加个筐”的项目。它涉及到机器视觉在动态环境下的目标识别与追踪、嵌入式系统对电机和机械结构的实时精准控制、无线通信在移动设备与固定主机间的稳定数据传输以及一个手机APP作为直观的人机交互界面。整个系统可以看作是一个微缩版的“仓储AGV”自动导引运输车或“服务机器人”其技术栈覆盖了嵌入式开发、图像处理、无线通信和移动应用开发等多个软硬件领域综合性极强。对于电子、自动化、计算机相关专业的同学来说这样一个项目作为毕业设计或课程设计含金量是相当高的。它避开了纯软件项目的“虚空造轮”也避免了纯硬件项目的“焊板子调参”的单一性迫使你必须进行跨领域的系统集成与调试这正是工程实践的核心。接下来我就把自己从零开始搭建这套“智能网球车捡球系统”的全过程、核心原理、踩过的坑以及一些实用的技巧毫无保留地分享出来。2. 系统架构总览一张图看懂所有模块如何协同工作在动手写一行代码、焊一个元件之前我们必须把系统的“骨架”——也就是架构——梳理清楚。这决定了后续开发是否顺畅模块间会不会“打架”。我的系统核心架构可以概括为“一体两翼视觉导航无线遥控”。一体指的是以STM32F407微控制器为核心的主控板。它就像整个系统的大脑负责协调所有外围模块接收来自手机APP的指令解析来自OpenMV的视觉数据生成控制两个直流电机的PWM信号以实现差速转向同时通过WiFi模块与手机保持通信。选择STM32F407主要是看中了其丰富的定时器资源用于多路PWM生成、足够的SRAM和Flash用于运行轻量级算法和存储程序、以及多个串口UART用于同时连接WiFi模块和OpenMV。两翼分别是感知侧翼和通信侧翼。感知侧翼OpenMV Cam H7。这是一个集成了MicroPython解释器的机器视觉模块核心是一颗STM32H7芯片和一颗摄像头。它的任务非常专一通过摄像头实时捕捉前方图像运行我预先写好的网球识别算法基于颜色阈值和圆形霍夫变换一旦识别到网球就计算出网球在图像中的坐标x, y以及大概的像素面积作为距离的粗略参考然后通过串口将这些数据打包发送给STM32主控。通信侧翼ESP8266 WiFi模块。我选用的是AT指令固件的ESP-01S模块。它充当了STM32接入无线网络的“网卡”。STM32通过串口向ESP8266发送AT指令使其连接到手机APP创建的热点或同一个路由器网络。连接建立后双方通过TCP协议进行Socket通信。手机APP发送的“前进”、“左转”、“自动寻球”等指令以及STM32回传的小车状态如电池电压、是否捡到球都通过这个通道传输。手机APP则是整个系统的指挥中心和状态显示器。我使用Android Studio开发界面简洁包含方向控制按钮、模式切换开关手动/自动、实时视频流显示区域接收来自OpenMV通过WiFi传输的MJPEG流、以及状态信息栏。在自动模式下APP只发送一个“开始自动寻球”的指令之后小车就依靠OpenMV的视觉反馈自主运行。整个数据流是这样的手机APP下达指令 - ESP8266接收并通过串口传给STM32 - STM32解析指令 - 若为手动模式直接控制电机若为自动模式STM32读取OpenMV的串口数据 - 根据网球位置偏左、偏右、居中和大小远、近计算出左右电机的速度差 - 驱动小车朝向网球移动直到机械机构将球收入囊中。注意这里存在一个关键设计抉择——视频流传输的路径。方案一OpenMV将图像处理结果坐标数据给STM32同时将原始视频流通过其自带的WiFi芯片直接发送给手机APP。方案二所有数据指令、视频流、状态都通过ESP8266这一个通道。我选择了方案一因为让OpenMV独立传输视频流可以极大减轻STM32和主通信通道的负担避免视频数据堵塞控制指令。OpenMV Cam H7本身支持在运行脚本的同时开启RTSP或MJPEG流服务器非常方便。3. 硬件选型与电路设计为什么是它们而不是别的硬件是系统的躯体选型不当会让软件开发者举步维艰。以下是我的核心部件选型理由和关键的电路连接细节。3.1 主控制器STM32F407VET6为什么不是更简单的STM32F103因为F103的串口和定时器资源可能捉襟见肘。我们需要至少两个串口UART分别与WiFi模块和OpenMV通信可能还需要一个用于调试打印。F407有多个UART绰绰有余。同时F407更高的主频168MHz能更从容地处理数据解析、电机控制算法和可能的传感器融合虽然本项目未用。为什么不是单片机因为STM32的生态HAL库、标准库和开发工具Keil、STM32CubeIDE能极大提升开发效率。3.2 视觉模块OpenMV Cam H7这是本项目成功的关键。尝试过用STM32直接驱动OV7670摄像头并运行图像算法但无论是资源消耗还是开发难度都呈指数级上升。OpenMV将复杂的图像传感器驱动、图像缓存、基础算法库都封装好了我只需要用PythonMicroPython写几十行代码专注于“识别黄色圆形物体”这个业务逻辑。其find_blobs函数颜色阈值结合find_circles函数霍夫圆变换能非常稳定地在网球场上识别出网球。H7版本性能更强能处理更高分辨率的图像。3.3 通信模块ESP8266-01S选择它纯粹是因为成本低、资料多、AT指令稳定。也有考虑过直接使用ESP32其功能更强大可以同时承担WiFi和图像处理但这会增加系统的复杂度和功耗且与OpenMV功能重叠。我们的架构是“各司其职”ESP8266就安心做好无线透传的工作。需要注意的是ESP-01S的GPIO很少我们只用到其UARTTX/RX和电源引脚将其设置为Station模式连接手机热点。3.4 电机与驱动直流减速电机 TB6612FNG驱动芯片小车底盘我直接采购了现成的两轮差速驱动机器人底盘自带编码器虽然本项目未用于闭环控制。电机驱动芯片的选择上抛弃了古老的L298N发热大、效率低选择了TB6612FNG。它体积小、效率高、内置保护电路。与STM32的连接非常简单两个PWM引脚控制速度利用STM32的定时器输出PWM两个GPIO引脚控制方向正转/反转。3.5 电源管理这是最容易忽略的坑整个系统涉及多块板子STM32核心板3.3V、OpenMV3.3V或5V需仔细看手册、ESP82663.3V、TB6612电机驱动电压建议7-12V。绝对不能用一个电源直接并联给所有模块供电尤其是电机启动和急停时会产生巨大的电压尖峰和电流波动会直接干扰甚至烧毁脆弱的数字电路。 我的方案是一块12V的锂电池组作为总电源。12V直接供给TB6612的VMOT引脚电机电源。通过一个DC-DC降压模块12V转5V将5V供给OpenMV如果它支持5V和另一个稳压模块。使用一个AMS1117-3.3或效率更高的DC-DC降压模块5V转3.3V产生纯净的3.3V同时供给STM32、ESP8266和TB6612的逻辑供电部分VCC。 这样形成了级联的电源隔离电机电路的噪声被限制在了前级保证了核心控制电路的稳定。关键电路连接示意图文字描述STM32 PA9 (TX) - ESP8266 RXSTM32 PA10 (RX) - ESP8266 TX 注意此处需要逻辑电平匹配STM32是3.3VESP8266也是3.3V直连即可。如果遇到通信不稳定可串联一个220欧姆电阻STM32 PA2 (TX) - OpenMV RXSTM32 PA3 (RX) - OpenMV TXSTM32 TIM1_CH1 (PA8) - TB6612 PWMASTM32 TIM1_CH2 (PA9) - TB6612 PWMBSTM32 PC0, PC1 - TB6612 AIN1, AIN2 (控制电机A方向)STM32 PC2, PC3 - TB6612 BIN1, BIN2 (控制电机B方向)所有模块的GND必须共地这是通信的基础。4. 下位机核心STM32的软件设计思路与避坑指南STM32的程序是整个系统的调度中心。我使用STM32CubeMX进行引脚初始化和生成HAL库工程框架这能节省大量配置外设的时间。4.1 多任务处理与状态机虽然系统没有上RTOS实时操作系统但我们需要模拟出多任务协作的效果。主循环while(1)中我采用一个简单的状态机State Machine来管理小车的工作模式typedef enum { MODE_MANUAL, // 手动遥控模式 MODE_AUTO_SEARCH, // 自动寻球模式 MODE_PICKING, // 正在捡球机械臂动作本项目简化版为直接驶过 MODE_RETURN // 返回起始点可选功能 } SystemMode_t;在自动寻球模式MODE_AUTO_SEARCH下程序的核心任务就是读取OpenMV数据定时例如每50ms检查串口是否有来自OpenMV的数据帧。数据帧需要自定义一个简单的协议例如$,x,y,area,*\r\n其中x,y是球心坐标area是像素面积。使用串口空闲中断Idle Interrupt加DMA的方式是最优雅的可以高效地接收不定长数据。解析与坐标转换OpenMV返回的(x, y)是图像像素坐标比如160x120分辨率下的值。我们需要将其转换为小车的转向指令。我采用了一种简单的比例控制设定图像中心线CENTER_X 80。计算偏差error x - CENTER_X。控制量turn_speed Kp * errorKp为比例系数。最终速度left_speed base_speed - turn_speed; right_speed base_speed turn_speed;这样当球在左边x80error为负左边轮子减速右边轮子加速小车左转。距离判断area值可以粗略反映距离。设定一个阈值AREA_THRESHOLD当area大于该阈值认为球已经很近可以触发“捡球”动作例如小车直行1秒利用前部的铲子或滚轮机构将球收入车筐然后切换状态。4.2 与ESP8266的AT指令通信这是软件部分最容易出bug的地方。ESP8266的AT指令必须严格按顺序发送并等待其返回“OK”或具体数据。我将其封装成一个状态机typedef enum { WIFI_STATE_INIT, WIFI_STATE_AT, WIFI_STATE_CWMODE, WIFI_STATE_CONNECT_AP, WIFI_STATE_GET_IP, WIFI_STATE_START_TCP, WIFI_STATE_READY } WifiState_t;在初始化阶段依次发送AT、ATCWMODE1Station模式、ATCWJAPSSID,password、ATCIFSR获取IP、ATCIPSTARTTCP,手机IP,端口号。每个指令后都需要解析串口返回的数据超时或错误都需要重试。务必注意的是AT指令的结尾必须是\r\n且每条指令的响应时间可能不同需要设置合理的超时时间。4.3 电机控制与PID进阶基础版本使用简单的比例控制转向已经可以工作。但如果希望小车运动更平滑、更精准可以引入PID控制器。这里以转向PID为例设定值Setpoint图像中心坐标CENTER_X。测量值Input识别到的网球x坐标。输出Output电机的速度差。PID计算output Kp*error Ki*integral Kd*derivative。 将output限制在一个合理范围然后叠加到基础速度上。Ki积分项可以消除静态误差让球始终对准中心Kd微分项可以预测变化趋势防止振荡。参数Kp, Ki, Kd需要在实际场地上反复调试这是一个“玄学”过程。踩坑实录电机干扰导致串口通信乱码。现象是每当电机启动或变速时OpenMV或ESP8266的串口数据就会偶尔出错。排查后发现虽然电源做了隔离但电机驱动板与STM32核心板之间的排线距离过长且未加屏蔽电机产生的大电流变化引发了电磁干扰。解决方案1. 缩短连接线并尽量使用双绞线。2. 在电机电源线两端并联一个0.1uF的瓷片电容和一个100uF的电解电容进行滤波。3. 在STM32的串口RX引脚对地加一个10pF-100pF的小电容滤除高频噪声。立竿见影。5. “眼睛”的算法OpenMV上的网球识别与追踪OpenMV的编程是在其自带的IDE中使用MicroPython。代码逻辑清晰主要分为初始化、主循环图像处理、数据发送三部分。5.1 初始化与颜色阈值设定import sensor, image, time, pyb from pyb import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) # 彩色图像 sensor.set_framesize(sensor.QVGA) # 320x240 平衡速度与精度 sensor.skip_frames(time 2000) # 等待感光元件稳定 clock time.clock() uart UART(3, 115200) # 使用UART3与STM32通信 # 定义网球的颜色阈值 (在RGB色彩空间下) # 这是一个关键步骤需要在实际灯光环境下用IDE的“阈值编辑器”工具选取 yellow_threshold (45, 75, 10, 50, 20, 70) # (L_min, L_max, A_min, A_max, B_min, B_max) in LAB color spaceLAB颜色空间比RGB对光照变化更鲁棒。阈值的选取需要将摄像头对准网球在OpenMV IDE中打开“工具 - 机器视觉 - 阈值编辑器”拖动滑块直到网球区域为白色背景为黑色。这个过程可能需要针对室内、室外、晴天、阴天进行微调。5.2 主循环寻找色块与圆形while(True): clock.tick() img sensor.snapshot() # 抓取一帧图像 # 方法1先找色块速度快但可能把其他黄色物体误认为球 blobs img.find_blobs([yellow_threshold], pixels_threshold100, area_threshold100, mergeTrue) if blobs: largest_blob max(blobs, keylambda b: b.pixels()) # 找最大的色块 img.draw_rectangle(largest_blob.rect()) # 画框 img.draw_cross(largest_blob.cx(), largest_blob.cy()) # 画中心十字 # 方法2在色块区域进行圆形检测提高准确率 # 可以只对色块所在的矩形区域进行霍夫圆变换节省计算量 roi largest_blob.rect() circles img.find_circles(threshold2000, x_margin10, y_margin10, r_margin10, r_min10, r_max100, roiroi) if circles: # 找到了圆形认为这是网球 c circles[0] img.draw_circle(c.x(), c.y(), c.r(), color(0, 255, 0)) # 画绿色圆 # 准备发送的数据x坐标 y坐标 圆的面积近似距离 data_to_send $,%d,%d,%d* % (c.x(), c.y(), c.r()*c.r()*3.14) uart.write(data_to_send) else: # 是黄色但不是圆可能误判发送特定数据 uart.write($,0,0,0*) else: # 没找到黄色发送零数据 uart.write($,0,0,0*)5.3 优化与抗干扰策略多特征融合单纯的颜色阈值在复杂地面如有枯叶下容易误判。结合圆形检测可以极大提升准确率。网球的形状是相对标准的圆形。区域兴趣ROI不需要对整个图像进行圆形检测那样太慢。只在找到的黄色色块区域进行检测大大减少了计算量。滤波与跟踪可以加入简单的滤波器比如对连续几帧的球心坐标进行平均或者使用卡尔曼滤波预测球的位置能让小车的运动更平滑避免因单帧识别抖动导致的“抽搐”。曝光调整在强光或暗光下自动曝光可能失效。可以尝试使用sensor.set_auto_exposure(False)和sensor.set_auto_gain(False)来手动设置曝光和增益或者使用sensor.set_auto_whitebal(False)关闭白平衡让颜色更稳定。6. 手机APP开发Android上的控制与监视界面APP的功能相对直接但要做好稳定性和用户体验。我使用Java开发核心是三个部分UI布局、网络通信TCP Client、视频流显示。6.1 界面布局一个简单的布局包含方向控制区一个类似游戏手柄的十字按钮或者四个独立的方向按钮前、后、左、右。按钮按下时发送对应的指令字符如F、B、L、R释放时发送停止指令S。模式切换一个Switch或RadioButton用于切换“手动模式”和“自动模式”。视频显示区一个TextureView或SurfaceView用于渲染从OpenMV传来的MJPEG流。状态显示几个TextView用于显示连接状态、接收到的STM32回传数据如电池电量。6.2 网络通信在Android中网络操作必须在子线程中进行。我使用Socket和BufferedReader/BufferedWriter。// 连接线程 private class ConnectThread extends Thread { private String serverIp; private int serverPort; public ConnectThread(String ip, int port) { this.serverIp ip; this.serverPort port; } Override public void run() { try { socket new Socket(serverIp, serverPort); outputStream socket.getOutputStream(); inputStream socket.getInputStream(); // 通知主线程连接成功更新UI // 启动一个线程持续读取STM32发来的状态信息 new ReceiveThread().start(); } catch (IOException e) { // 连接失败通知主线程 } } } // 发送指令方法 public void sendCommand(final String cmd) { if (outputStream ! null) { new Thread(new Runnable() { Override public void run() { try { outputStream.write(cmd.getBytes()); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }6.3 MJPEG视频流显示OpenMV可以通过/mjpeg地址提供MJPEG流。在Android端我们可以使用MjpegInputStream这个开源类来解析这种特殊的流它本质是一系列连续的JPEG图片。然后在一个独立的线程中不断读取流并将解码后的Bitmap更新到TextureView上。// 简化示例 MjpegInputStream mjpegIs MjpegInputStream.read(socket.getInputStream()); Bitmap bmp; while ((bmp mjpegIs.readMjpegFrame()) ! null) { // 将bmp设置到ImageView或TextureView runOnUiThread(() - imageView.setImageBitmap(bmp)); }6.4 注意事项权限别忘了在AndroidManifest.xml中添加网络权限uses-permission android:nameandroid.permission.INTERNET /。IP地址配置APP需要知道ESP8266连接后获取到的IP地址。一个简单的方法是让手机和ESP8266连接同一个路由器在APP里固定服务器的IP路由器分配给ESP8266的IP。或者可以让手机开启热点ESP8266连接手机热点此时手机的IP通常是192.168.43.1常见AP模式地址。心跳包为了保持TCP长连接防止因网络不稳定或NAT超时导致断开APP可以定时如每5秒向小车发送一个心跳包如字符HSTM32收到后原样回复。如果一段时间收不到回复则触发重连机制。7. 系统联调与实战测试从实验室到网球场的挑战当所有模块单独测试通过后最激动人心也最令人头疼的联调就开始了。这个过程会暴露无数设计时未曾考虑的问题。7.1 分阶段集成STM32 电机驱动先让小车能听话地前后左右移动。测试PWM输出是否正常电机转向是否正确。STM32 ESP8266让STM32能通过AT指令连接手机热点并建立TCP连接。用手机上的网络调试助手如TCP Client发送字符控制小车运动。这一步验证了通信链路。STM32 OpenMV断开WiFi将OpenMV的数据打印到串口助手同时让STM32解析这些数据并直接控制电机转向。用一个黄色的球在摄像头前移动观察小车是否能跟随。OpenMV独立视频流配置OpenMV使其在运行识别脚本的同时开启MJPEG服务器。在手机浏览器输入OpenMV的IP地址看能否看到视频流。全系统整合将所有模块连接。手机APP连接OpenMV的视频流并通过TCP向STM32发送指令。测试手动模式。最后切换到自动模式观察整套系统是否工作。7.2 真实场景测试与调参实验室的桌面和真实的网球场是天壤之别。带到球场后我遇到了以下问题光照影响早晨、中午、傍晚的光照色温完全不同导致OpenMV的颜色阈值失效。解决方案采用更鲁棒的LAB颜色空间并在代码中加入简单的自动白平衡或动态阈值调整逻辑。比如可以识别场地绿色作为背景动态计算黄色相对于绿色的阈值范围。地面干扰球场边界线、广告牌、观众的衣服都可能含有黄色。解决方案强化形状判断。结合圆形检测和色块的大小比例网球在图像中的大小是有限的。还可以加入多帧验证只有连续3-5帧都识别为网球才认为是真目标。小车运动导致的图像模糊小车在移动时摄像头拍摄的图像会模糊影响识别。解决方案1. 在OpenMV代码中尝试提高快门速度减少曝光时间但这需要更好的光照条件。2. 让小车采用“走-停-看”的策略移动一小段距离停下来识别一帧图像根据结果决定下一步移动。虽然效率降低但识别率大幅提升。通信距离与延迟在户外WiFi信号可能不稳定。解决方案1. 使用外置天线的ESP8266模块。2. 在通信协议中加入序号和应答机制。APP发送的每条指令带一个序号STM32收到后需要回复一个包含相同序号的确认包。如果APP在一定时间内没收到确认则重发指令。3. 视频流如果卡顿可以尝试降低OpenMV的图像分辨率如从QVGA降到QQVGA和帧率。7.3 机械结构与捡球策略我的简易版本是在小车前部安装了一个用塑料板弯成的“铲子”小车径直驶向网球利用铲子的弧度将球兜起来滚入后部的网兜。更复杂的方案可以加入小型舵机控制的机械臂。但无论哪种都需要在STM32的程序中设计一个简单的“捡球序列”当OpenMV检测到球面积足够大距离很近时小车不是直接撞上去而是先微调姿态对准然后缓慢直行一小段距离确保球被铲起再后退或转弯继续寻找下一个目标。8. 项目总结与扩展思考回顾整个项目从电路设计、PCB焊接如果自己画板的话、嵌入式C编程、Python图像算法到Android开发几乎把大学里学的核心课程都串了起来。最大的收获不是做出了一个能捡球的车而是掌握了解决一个复杂系统工程问题的方法论如何分解需求、如何选型、如何设计接口、如何分模块调试、如何解决棘手的干扰问题。这个项目还有巨大的扩展空间路径规划与多球任务当前是“看到一个追一个”。可以升级为让小车记住已搜索过的区域规划高效的“Z”字形路径覆盖整个球场并记录已捡球的位置和数量。SLAM与建图引入一个激光雷达如RPLIDAR A1和IMU在STM32上跑一个简化的SLAM算法如Cartographer让小车能构建球场地图并实现自主导航而不仅仅依赖视觉。云端管理与多机协作通过4G模块将小车状态上传到云端服务器用户可以通过网页远程查看球场情况和控制小车。甚至可以设想多台小车协同工作由云端服务器分配任务。能源优化加入电池电量监测实现低电量自动回充需要定位和路径规划支持。对于想尝试的同学我的建议是从最简单的版本开始。先实现手机遥控小车移动再加上视觉识别让小车跟随一个色块最后再整合成完整的自动捡球系统。每完成一步都能给你带来正反馈避免一开始就陷入复杂的多线程调试中。硬件上尽量使用现成的模块和开发板把精力集中在软件逻辑和算法集成上。这个项目最迷人的地方就在于你看着自己写的代码让一堆冰冷的硬件“活”了过来去完成一个具体的任务——这种成就感是无与伦比的。本文还有配套的精品资源点击获取
返回列表