1. 项目概述:当机器人小车遇上机器学习与手机App
如果你对嵌入式开发、机器人控制或者机器学习感兴趣,但又觉得这些领域各自为战、门槛太高,那么这个将机器人小车(RSLK)、机器学习(ML)和安卓App三者结合的项目,绝对是一个能让你打通任督二脉的绝佳实践。我最近就完整地走了一遍这个流程,从让小车在桌面上瞎跑,到教会它识别交通标志并自动做出反应,最后用手机App远程监控和指挥,整个过程就像在玩一个超级硬核的“乐高”,但收获的却是实打实的全栈式系统开发能力。
简单来说,这个项目的核心是构建一个智能、互联的机器人系统。RSLK(机器人系统学习套件)作为物理执行层,负责感知环境(通过传感器)和执行动作(控制电机);机器学习模型作为其“大脑”,负责处理传感器数据(如图像),做出智能决策(如识别出“停止”标志);而安卓App则作为人机交互与远程控制层,为我们提供了一个直观的界面来监控小车状态、发送指令甚至更新其“大脑”。这三者的结合,将一个简单的遥控玩具,升级成了一个具备边缘AI能力的可交互智能体。无论你是学生想做一个惊艳的毕业设计,还是工程师想验证某个算法在真实物理世界的表现,这个项目都能提供一个从理论到实践的完整闭环。
2. 核心思路与系统架构设计
2.1 为什么是RSLK+ML+Android?
单独玩转RSLK、训练一个机器学习模型或者开发一个安卓App,都有大量的教程。但这个项目的精髓在于“集成”,它强迫你去思考系统级的问题。RSLK通常基于如TI的MSP432或SimpleLink微控制器,计算资源有限,无法运行复杂的模型。这就引出了第一个关键设计抉择:机器学习推理任务放在哪里执行?
常见的方案有三种:
- 云端推理:小车将摄像头图像通过Wi-Fi发送到云端服务器,服务器运行模型并返回结果,小车再执行。优点是模型可以非常复杂,缺点是完全依赖网络,延迟高,不适合实时控制。
- 边缘设备推理:在小车上加装一个计算能力更强的边缘设备,如树莓派(Raspberry Pi)或英伟达Jetson Nano。由这个设备运行模型,直接控制小车。平衡了计算力和实时性,是当前的主流方案。
- 微控制器端推理:使用TensorFlow Lite for Microcontrollers等框架,将极度轻量化的模型直接部署到RSLK的主控MCU上。优点是响应极快、功耗低、完全离线,缺点是模型必须非常小,精度受限。
对于学习和原型开发,方案2(边缘设备+RSLK)最具可行性和教育意义。它让你同时接触到嵌入式系统交互、边缘AI部署和移动端开发。在本项目中,我选择使用树莓派4B作为边缘计算大脑,通过GPIO与RSLK的主控板通信,同时运行一个轻量级的图像分类模型。
2.2 整体系统工作流程
整个系统的数据流和控制流可以这样理解:
- 感知:安装在RSLK上的树莓派摄像头(或USB摄像头)持续采集图像。
- 推理:树莓派上运行的Python程序,调用预训练好的TensorFlow Lite模型,对当前图像进行推理,识别其中是否包含预设的交通标志(如“停止”、“左转”、“右转”)。
- 决策与执行:根据识别结果,Python程序通过GPIO向RSLK主控板发送相应的控制指令(如“识别到停止标志,停车3秒”)。RSLK主控板解析指令,驱动电机执行动作。
- 交互与监控:同时,树莓派上运行一个Socket服务器或MQTT客户端。安卓App作为客户端,连接到树莓派,可以实时接收小车摄像头画面(或推理结果),并发送手动控制指令(如前进、后退)或更新任务的指令。
这个架构清晰地划分了功能模块,也定义了我们需要完成的三大核心开发任务:机器学习模型训练与部署、树莓派与RSLK的集成控制程序、以及安卓App的开发。
3. 机器学习模型训练与部署实战
3.1 模型选择与数据集准备
在资源受限的边缘设备上,模型的选择至关重要。像ResNet、VGG这类大型网络并不合适。我选择了MobileNetV2的轻量化版本,并结合TensorFlow Lite进行部署。MobileNetV2专为移动和嵌入式设备设计,在精度和速度之间取得了很好的平衡。
首先需要准备数据集。我们的目标是识别几种基本的交通标志。可以从公开数据集如“德国交通标志识别基准数据集(GTSRB)”中抽取需要的几类,或者更简单点,自己制作。我用手机拍摄了打印出来的“停止”、“限速”、“左转”、“右转”等标志,每种标志在不同光线、角度下拍摄了约200张图片,总共1000张左右。
注意:自己制作数据集时,背景要尽量简单且一致(比如都是白墙或桌面),这能极大降低模型学习的难度,提高初期实验的成功率。后期可以逐步加入复杂背景以增强模型鲁棒性。
使用labelImg这类工具对图片进行标注,生成PASCAL VOC格式的XML文件。然后将数据集按8:1:1的比例划分为训练集、验证集和测试集。
3.2 模型训练与转换
我使用TensorFlow 2.x的Keras API进行训练。关键步骤包括:
- 数据预处理:将图像缩放到224x224像素(MobileNetV2的标准输入尺寸),并进行归一化。
train_datagen = ImageDataGenerator( rescale=1./255, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, validation_split=0.1 ) - 构建模型:加载MobileNetV2的预训练权重(基于ImageNet),去掉顶部分类层,加入我们自己的全连接层用于交通标志分类。
base_model = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3), include_top=False, weights='imagenet') base_model.trainable = False # 先冻结基础模型,进行微调 global_average_layer = tf.keras.layers.GlobalAveragePooling2D() prediction_layer = tf.keras.layers.Dense(num_classes, activation='softmax') model = tf.keras.Sequential([ base_model, global_average_layer, prediction_layer ]) - 训练与微调:先训练新添加的顶层,然后解冻基础模型的部分顶层进行联合微调,以获得更好的特征提取能力。
- 转换为TFLite格式:训练完成后,将模型转换为TensorFlow Lite格式,并进行动态范围量化,以进一步减小模型体积、提升推理速度。
converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert() with open('traffic_sign_model.tflite', 'wb') as f: f.write(tflite_model)
3.3 在树莓派上部署与优化
将生成的.tflite模型文件拷贝到树莓派。在树莓派上安装TensorFlow Lite运行时库。编写推理脚本,核心是利用tf.lite.Interpreter加载模型并进行推理。
import tflite_runtime.interpreter as tflite import numpy as np from PIL import Image # 加载模型 interpreter = tflite.Interpreter(model_path="traffic_sign_model.tflite") interpreter.allocate_tensors() # 获取输入输出详情 input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() # 预处理图像 image = Image.open('current_frame.jpg').resize((224, 224)) input_data = np.expand_dims(np.array(image, dtype=np.float32) / 255.0, axis=0) # 执行推理 interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() output_data = interpreter.get_tensor(output_details[0]['index']) prediction = np.argmax(output_data[0])实操心得:树莓派上默认的Python环境可能缺少一些优化。建议使用针对ARM架构预编译的TensorFlow Lite轮子,或者从源码编译以获得最佳性能。此外,推理循环中要避免频繁的内存分配,可以预先分配好输入输出张量所需的内存空间。
4. 树莓派与RSLK的集成控制
4.1 硬件连接与通信协议
RSLK的主控板(例如基于MSP432)通常通过GPIO或UART串口接收指令。树莓派拥有丰富的GPIO引脚,可以模拟UART或直接使用GPIO进行电平控制。更稳定和通用的方法是使用串口通信(UART)。
你需要用杜邦线连接树莓派的UART引脚(TX, RX, GND)到RSLK主控板的对应串口接收引脚。确保两边的波特率(Baud Rate)设置一致,例如115200。
通信协议需要自行定义。一个简单有效的文本协议如下:
FWD,100:以100的速度前进REV,80:以80的速度后退STOP:停止TURN_LEFT,90:左转90度TURN_RIGHT,90:右转90度SIGN_STOP:识别到停止标志(由树莓派发出,RSLK执行停车动作)
在树莓派端,使用Python的pyserial库发送指令:
import serial ser = serial.Serial('/dev/ttyS0', 115200, timeout=1) # 根据实际串口设备名调整 ser.write(b'FWD,100\n')在RSLK的嵌入式代码中(通常是C语言),你需要编写串口中断服务程序或轮询解析这些指令,并调用相应的电机控制函数。
4.2 核心控制逻辑实现
树莓派上的主控程序需要整合摄像头采集、模型推理和串口控制。程序结构可以是一个循环:
import cv2 import serial from inference_module import predict_image # 导入前面写好的推理函数 ser = serial.Serial('/dev/ttyS0', 115200) cap = cv2.VideoCapture(0) # 打开摄像头 last_sign = None while True: ret, frame = cap.read() if not ret: break # 1. 预处理帧并进行推理 sign_class, confidence = predict_image(frame) # 2. 决策逻辑 if confidence > 0.8: # 置信度阈值 if sign_class == 'STOP' and last_sign != 'STOP': ser.write(b'SIGN_STOP\n') time.sleep(3) # 停车3秒 last_sign = 'STOP' elif sign_class == 'TURN_LEFT': ser.write(b'TURN_LEFT,90\n') last_sign = 'TURN_LEFT' # ... 处理其他标志 else: # 未识别到有效标志,可进入手动模式或巡线模式 pass # 3. 此处可添加将帧或结果发送给安卓App的代码这个循环实现了最基本的“感知-决策-执行”闭环。last_sign变量用于防止对同一个标志重复触发动作。
5. 安卓App开发与通信
5.1 App功能设计与界面
安卓App的核心功能有两个:视频流监控和指令发送。界面可以设计得很简洁:一个大的TextureView或SurfaceView用于显示树莓派传来的实时视频流,下方是一排控制按钮(前进、后退、左转、右转、停止),以及一个显示当前识别结果的状态栏。
更高级的功能可以包括:
- 拍照并上传到树莓派,用于动态更新数据集。
- 切换小车模式:自动识别模式 vs 手动遥控模式。
- 调整模型置信度阈值。
5.2 通信方案选择:Socket vs MQTT
实现树莓派与安卓App通信有两种主流方式:
TCP Socket + 视频流服务器:树莓派运行一个多线程Socket服务器。一个线程处理视频流(使用OpenCV捕获帧,并通过MJPG-streamer或自定义协议将JPEG帧流式传输),另一个线程处理来自App的控制指令。安卓端使用
Socket类建立连接,用BufferedReader/BufferedWriter收发指令,用HttpURLConnection或专门的流解析库接收视频流。这种方式控制力强,但需要自己处理粘包、协议解析等问题。MQTT协议:这是一个轻量级的发布/订阅消息协议。树莓派和安卓App都作为MQTT客户端,连接到一个共同的MQTT代理(Broker,可以运行在树莓派本地的Mosquitto,或者使用公共云服务)。树莓派发布(Publish)主题如
rpi/video_frame(携带图片数据)和rpi/sign_detected(携带识别结果)。安卓App订阅(Subscribe)这些主题来接收数据。同时,安卓App发布app/control主题来发送控制指令,树莓派订阅该主题。MQTT方案解耦更好,更适合多设备、不稳定网络的环境,实现起来也更简单优雅。
我强烈推荐使用MQTT方案。在树莓派上安装paho-mqtt库,在安卓项目中使用org.eclipse.paho.client.mqttv3依赖。
树莓派MQTT客户端示例(发布视频帧和结果):
import paho.mqtt.client as mqtt import base64 client = mqtt.Client() client.connect("localhost", 1883, 60) # 连接本地代理 # 在推理循环中 _, jpeg_buffer = cv2.imencode('.jpg', resized_frame) jpg_as_text = base64.b64encode(jpeg_buffer).decode('utf-8') client.publish("rpi/video_frame", jpg_as_text) if sign_class: client.publish("rpi/sign_detected", f"{sign_class}:{confidence}")安卓App端则需要连接同一个MQTT代理,订阅rpi/video_frame主题,将收到的Base64字符串解码为Bitmap并显示在ImageView上。同时,订阅rpi/sign_detected更新状态栏。当用户按下按钮时,向app/control主题发布对应的指令字符串。
6. 系统集成与调试中的核心挑战
6.1 实时性与资源竞争
系统集成后,你可能会发现视频流卡顿、控制指令延迟高。这是因为树莓派上的单个Python进程同时在做图像采集、模型推理、串口通信和MQTT发布这几件计算密集型或I/O密集型任务,造成了资源竞争。
解决方案:
- 多进程/多线程:将任务拆分。例如,主进程负责图像采集和推理;单独一个进程运行MJPG-streamer提供视频流;一个线程专门处理串口通信;另一个线程处理MQTT。使用Python的
multiprocessing模块或threading模块,并注意线程安全。 - 优化推理频率:不必对每一帧都进行推理。可以每5帧或每0.1秒推理一次,这足以应对小车的运动速度。
- 使用硬件加速:树莓派有GPU和NPU(取决于型号)。确保你的TensorFlow Lite版本支持并启用了这些硬件加速器,可以大幅提升推理速度。
6.2 通信稳定性与错误处理
无论是串口还是网络通信,都可能出现数据丢失、连接中断的情况。健壮的程序必须有完善的错误处理和重连机制。
对于串口:在发送指令后,可以要求RSLK主控板回传一个确认信号(ACK)。树莓派端如果没有收到ACK,应在超时后重发指令。对于MQTT:设置client.on_connect和client.on_disconnect回调函数,在连接断开时尝试重连。发布消息时可以设置qos=1(至少送达一次),以保证重要指令不丢失。
6.3 电源管理与干扰
树莓派、摄像头、RSLK电机都从电池取电。电机启动瞬间会产生很大的电流尖峰,可能导致树莓派电压不稳而重启。
解决方案:
- 使用大容量、高放电倍率的锂电池组。
- 为树莓派和电机驱动部分使用独立的稳压模块供电,进行电源隔离。
- 在树莓派电源输入端并联一个大电容(如1000μF)以缓冲电压波动。
- 在软件上,避免电机急启急停,采用加速度控制。
7. 项目扩展与进阶玩法
完成基础功能后,这个平台还有巨大的扩展空间:
- 更复杂的模型与任务:将图像分类升级为目标检测(使用SSD MobileNet),让小车不仅能识别标志类型,还能定位标志在画面中的位置。甚至可以尝试语义分割,让小车理解可行驶区域。
- 多传感器融合:在RSLK上增加超声波传感器、红外传感器或激光雷达(如RPLidar A1),结合IMU数据,实现更精确的避障和同步定位与建图(SLAM)。
- 云端协同:将树莓派作为边缘节点,把关键的图像数据和非实时数据上传到云端(如AWS IoT或Azure IoT Hub),进行大数据分析、长期模型再训练,再将优化后的模型OTA推送到小车。
- 多车协同:部署多台RSLK,让它们通过MQTT或自组网通信,实现简单的编队行驶或任务分配,探索集群机器人(Swarm Robotics)的初级概念。
这个项目就像一把钥匙,为你打开了通往嵌入式AI、物联网和机器人系统的大门。每一个环节的调试和优化,都会让你对系统级思维有更深的理解。从看到小车第一次根据你训练的模型做出正确反应的那一刻起,所有的折腾都值了。