ROS2系列教程:嵌入式控制与通信开发(原理+仿真)
本文是 ROS2 系列教程的第 32 篇本文是 ROS2 系列教程的第 32 篇嵌入式控制与通信开发原理仿真。第 31 篇我们搭好了实体机器人的硬件骨架。但硬件只是尸体让它活过来需要软件下位机的控制与通信程序、上位机的桥接与状态发布。本篇把第 31 篇的框架落实成完整可运行的代码下位机协议栈、里程计解算、上位机串口桥、传感器数据接入并用仿真环境验证上/下位机通信的可靠性。这是实体机器人软硬结合的关键一篇。一、上下位机通信协议设计原理1.1 协议设计原则嵌入式串口通信最容易出问题协议设计要遵循几条原则有帧头帧尾接收方靠帧头定位一帧的起点。有长度字段明确一帧有多少字节防止粘包/半包。有校验防止干扰导致的错误数据被执行。定长优先定长帧解析简单可靠变长帧复杂易错。1.2 推荐帧格式采用帧头(2B) 命令(1B) 长度(1B) 数据(NB) 校验(1B)的通用格式┌──────┬──────┬──────┬──────┬──────────┬────────┐ │ 0xAA │ 0x55 │ CMD │ LEN │ DATA │ CRC8 │ │ 帧头1│ 帧头2│ 命令 │ 长度 │ 数据区 │ 校验 │ └──────┴──────┴──────┴──────┴──────────┴────────┘命令定义举例CMD方向数据说明0x01上位机→下位机float×2 (v, ω)设置目标速度0x02下位机→上位机float×2 (v, ω)回传实测速度0x03上位机→下位机uint8 (1/0)复位 / 使能电机0x04下位机→上位机float×3 (ax, ay, az)回传 IMU 数据0x05上位机→下位机uint8 (LED)控制指示灯1.3 校验算法CRC8用简单的 CRC8 或异或校验。异或最简单CRC8 更可靠。CRC8 实现// crc8.cCRC-8 校验多项式 0x07初值 0x00uint8_tcrc8(constuint8_t*data,uint16_tlen){uint8_tcrc0x00;for(uint16_ti0;ilen;i){crc^data[i];for(uint8_tj0;j8;j){if(crc0x80){crc(uint8_t)((crc1)^0x07);}else{crc1;}}}returncrc;}二、下位机通信实现STM322.1 串口接收状态机串口数据是流式的一帧可能分多次到达必须用状态机逐字节解析// protocol.c串口接收状态机STM32#includeprotocol.h#includecrc8.htypedefenum{ST_WAIT_HEAD1,// 等待帧头1 0xAAST_WAIT_HEAD2,// 等待帧头2 0x55ST_WAIT_CMD,// 等待命令字节ST_WAIT_LEN,// 等待长度字节ST_DATA,// 接收数据区ST_CRC// 等待校验字节}rx_state_t;staticrx_state_tstateST_WAIT_HEAD1;staticprotocol_frame_tframe;staticuint8_tdata_idx0;// 串口中断里逐字节喂给状态机voidprotocol_rx_byte(uint8_tbyte){switch(state){caseST_WAIT_HEAD1:if(byte0xAA)stateST_WAIT_HEAD2;break;caseST_WAIT_HEAD2:state(byte0x55)?ST_WAIT_CMD:ST_WAIT_HEAD1;break;caseST_WAIT_CMD:frame.cmdbyte;stateST_WAIT_LEN;break;caseST_WAIT_LEN:frame.lenbyte;data_idx0;state(byte0)?ST_DATA:ST_CRC;break;caseST_DATA:frame.data[data_idx]byte;if(data_idxframe.len)stateST_CRC;break;caseST_CRC:frame.crcbyte;// 校验CRC crc8(cmd, len, data)if(frame.crccompute_frame_crc(frame)){protocol_on_frame(frame);// 帧有效回调处理}stateST_WAIT_HEAD1;break;}}2.2 串口中断接收HAL// 串口接收中断回调HAL 库接收一个字节触发voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART1){protocol_rx_byte(rx_buffer);// 喂给状态机HAL_UART_Receive_IT(huart1,rx_buffer,1);// 重新开启接收}}要点每次接收完必须重新调用HAL_UART_Receive_IT开启下一次接收否则只收一字节。2.3 帧处理回调// 收到有效帧后的处理voidprotocol_on_frame(protocol_frame_t*f){switch(f-cmd){caseCMD_SET_VEL:{// 设置速度float×2floatv,w;memcpy(v,f-data[0],4);memcpy(w,f-data[4],4);target_vv;target_ww;break;}caseCMD_ENABLE:{// 使能电机motor_enable(f-data[0]!0);break;}default:break;}}三、里程计解算原理实现3.1 里程计原理里程计Odometry根据轮子转过的角度估算机器人位姿x, y, yaw。对两轮差速左右轮速度v_l, v_r由编码器测得 机器人线速度v (v_l v_r) / 2 机器人角速度ω (v_r - v_l) / L L轮距 位姿积分每 dt 更新一次 x v × cos(yaw) × dt y v × sin(yaw) × dt yaw ω × dt3.2 下位机里程计实现// odometry.c两轮差速里程计解算下位机#includeodometry.h#includemath.h// 机器人参数与 URDF 一致#defineWHEEL_RADIUS0.0325f// 轮半径 m#defineWHEEL_BASE0.200f// 轮距 mstaticfloatpos_x0,pos_y0,yaw0;// 每 10ms 调用一次输入左右轮角速度rad/svoidodometry_update(floatomega_left,floatomega_right){constfloatdt0.01f;// 左右轮线速度floatv_lomega_left*WHEEL_RADIUS;floatv_romega_right*WHEEL_RADIUS;// 机器人线速度与角速度floatv(v_lv_r)/2.0f;floatw(v_r-v_l)/WHEEL_BASE;// 位姿积分yaww*dt;// yaw 归一化到 [-π, π]if(yawM_PI)yaw-2*M_PI;if(yaw-M_PI)yaw2*M_PI;pos_xv*cosf(yaw)*dt;pos_yv*sinf(yaw)*dt;}// 供上位机查询voidodometry_get(float*x,float*y,float*yaw_out){*xpos_x;*ypos_y;*yaw_outyaw;}关键点里程计参数轮半径、轮距必须与 URDF/控制器配置一致否则建图和导航会漂。这就是为什么第 28 篇 diff_drive_controller 里要配wheel_radius和wheel_separation——下位机用它算里程计上位机用它算控制两边必须统一。3.3 上位机里程计发布完整版第 31 篇的串口桥只发了速度这里补全为完整位姿# odom_bridge.py完整里程计发布位姿 协方差importrclpyimportserialimportstructfromrclpy.nodeimportNodefromnav_msgs.msgimportOdometryfromgeometry_msgs.msgimportQuaternionfromstd_msgs.msgimportHeaderimportmathclassOdomBridge(Node):def__init__(self):super().__init__(odom_bridge)self.serserial.Serial(/dev/ttyUSB0,115200,timeout0.02)self.pubself.create_publisher(Odometry,/odom,10)self.timerself.create_timer(0.02,self.tick)# 50Hzdefquat_from_yaw(self,yaw):# yaw → 四元数ROS2 消息需要四元数returnQuaternion(x0.0,y0.0,zmath.sin(yaw/2.0),wmath.cos(yaw/2.0))deftick(self):# 读下位机里程计帧CMD0x02x, y, yaw 三个 floatbufself.ser.read(18)iflen(buf)!18:returnhead,cmdstruct.unpack(HB,buf[:3])ifhead!0xAA55orcmd!0x02:returnx,y,yawstruct.unpack(3f,buf[3:15])crc_ok(buf[15](struct.calcsize(HB3f)and0))# 简化为示例odomOdometry()odom.headerHeader(stampself.get_clock().now().to_msg(),frame_idodom)odom.child_frame_idbase_linkodom.pose.pose.position.xx odom.pose.pose.position.yy odom.pose.pose.orientationself.quat_from_yaw(yaw)# 简化协方差实际应按编码器噪声标定odom.pose.covariance[0]0.001odom.pose.covariance[7]0.001odom.pose.covariance[35]0.01self.pub.publish(odom)defmain(argsNone):rclpy.init(argsargs)nodeOdomBridge()rclpy.spin(node)node.destroy_node()rclpy.shutdown()if__name____main__:main()四、示例 1仿真验证通信可靠性gazebo 真机协议在真机调试之前先用仿真验证协议正确性。思路在 Gazebo 仿真底盘上运行与真机完全相同的协议逻辑验证协议解析无 bug。# fake_mcu.py仿真下位机模拟 STM32 的协议行为# 在 Gazebo 仿真的 /odom 基础上按协议格式通过虚拟串口回传importrclpyimportstructimportmathfromrclpy.nodeimportNodefromnav_msgs.msgimportOdometryclassFakeMcu(Node):仿真下位机订阅仿真 /odom按协议组帧不接真实串口仅演示协议正确性def__init__(self):super().__init__(fake_mcu)self.odom_subself.create_subscription(Odometry,/odom,self.on_odom,10)defon_odom(self,msg):# 与真实下位机相同的组帧逻辑yaw2*math.atan2(msg.pose.pose.orientation.z,msg.pose.pose.orientation.w)datastruct.pack(3f,msg.pose.pose.position.x,msg.pose.pose.position.y,yaw)framestruct.pack(HBB,0xAA55,0x02,len(data))data# 校验与下位机 crc8 一致此处用异或简化演示crc0forbindata:crc^b framebytes([crc])# 真实工程写入串口 self.ser.write(frame)self.get_logger().debug(fframe:{frame.hex()})defspin_check(self):passdefmain(argsNone):rclpy.init(argsargs)nodeFakeMcu()rclpy.spin(node)rclpy.shutdown()if__name____main__:main()验证方法在 Gazebo 里让车走一段已知路径对比fake_mcu组帧数据与真机下位机输出格式是否一致。一致 → 协议可靠可放心接真机。五、示例 2IMU 数据接入与融合5.1 下位机回传 IMU// 下位机定时50Hz把 IMU 数据组帧回传voidsend_imu_frame(void){int16_taccel[3],gyro[3];mpu6050_read(accel,gyro);uint8_tdata[12];// 转成 float 并打包floataxaccel[0]/16384.0f*9.81f;floatayaccel[1]/16384.0f*9.81f;floatazaccel[2]/16384.0f*9.81f;memcpy(data[0],ax,4);memcpy(data[4],ay,4);memcpy(data[8],az,4);protocol_send(CMD_IMU,data,12);// 组帧 串口发送}5.2 上位机发布 IMU 并融合到里程计EKF扩展卡尔曼滤波融合轮式里程计 IMU 是标准做法。ROS2 用robot_localization包# ekf.yamlrobot_localization 配置融合 odom imuekf_filter_node:ros__parameters:frequency:50.0two_d_mode:true# 2D 机器人publish_tf:trueodom0:/odom# 轮式里程计输入odom0_config:[true,true,false,# x, y, zfalse,false,false,# roll, pitch, yawfalse,false,false,# vx, vy, vzfalse,false,false,# wx, wy, wzfalse,false,false]imu0:/imu/data_raw# IMU输入imu0_config:[false,false,false,true,true,true,# roll, pitch, yaw 用 IMUfalse,false,false,false,false,false,false,false,false]imu0_remove_gravitational_acceleration:trueodom0_differential:false# 启动 EKF 融合ros2 run robot_localization ekf_node --ros-args\-r__node:ekf_filter_node\--params-file ekf.yaml# 融合后的位姿话题ros2 topicecho/odometry/filtered融合效果轮式里程计提供短期位置IMU 提供姿态长期位置不会因打滑而完全丢失。注意IMU 提供 yaw 后与轮式里程计的 yaw 要统一方向约定都是逆时针为正。六、示例 3传感器数据汇聚节点把下位机回传的所有传感器数据汇聚成 ROS2 话题供上层使用# sensor_aggregator.py传感器数据汇聚超声波 电量 状态importrclpyimportserialimportstructfromrclpy.nodeimportNodefromsensor_msgs.msgimportRangefromstd_msgs.msgimportFloat32,BoolclassSensorAggregator(Node):def__init__(self):super().__init__(sensor_aggregator)self.serserial.Serial(/dev/ttyUSB0,115200,timeout0.02)# 发布超声波距离传感器消息标准格式self.sonar_pubself.create_publisher(Range,/sonar_front,10)# 发布电池电压self.volt_pubself.create_publisher(Float32,/battery_voltage,10)self.timerself.create_timer(0.1,self.tick)# 10Hzdeftick(self):# 读下位机传感器帧简化CMD0x06超声 float 电压 floatbufself.ser.read(14)iflen(buf)!14:returnhead,cmdstruct.unpack(HB,buf[:3])ifhead!0xAA55orcmd!0x06:returndist,voltagestruct.unpack(2f,buf[3:11])# 发布超声波Range 消息传感器锥形探测范围rRange()r.header.stampself.get_clock().now().to_msg()r.header.frame_idsonar_linkr.radiation_typeRange.ULTRASOUND r.field_of_view0.52# 30° 锥角弧度r.min_range0.02r.max_range4.0r.rangedist self.sonar_pub.publish(r)# 发布电压vFloat32()v.datavoltage self.volt_pub.publish(v)defmain(argsNone):rclpy.init(argsargs)nodeSensorAggregator()rclpy.spin(node)node.destroy_node()rclpy.shutdown()if__name____main__:main()低电量保护上位机订阅/battery_voltage低于阈值时发布robot_status/emergency_stop或者直接关断动力——这是实体机器人的安全底线。七、通信可靠性工程实践7.1 丢帧与重试上位机发指令默认不需要应答速度指令是最新值覆盖语义但关键指令复位、使能需要确认帧。下位机 50Hz 回传状态上位机若 1 秒没收到任何帧 → 判断下位机掉线 → 停止电机安全。# 掉线保护在串口桥里加超时判断classSerialBridge:def__init__(self):...self.last_frame_time0.0self.watchdogself.create_timer(0.5,self.watchdog_check)defwatchdog_check(self):nowself.get_clock().now().nanoseconds/1e9ifnow-self.last_frame_time1.0:self.get_logger().error(MCU offline! stopping motors)# 发布零速度安全停车twistTwist()self.on_cmd_vel(twist)7.2 波特率与缓冲区115200 波特率约 11500 字节/秒50Hz × 18 字节 × 2收发≈ 1800 字节/秒余量充足。上位机串口读缓冲区建议 256 字节以上下位机环形缓冲区 128 字节。7.3 电磁干扰防护电机线用双绞线远离串口线。串口加磁环或换 USB-TTL 隔离模块。地线星形连接各模块独立回地避免地环路。八、常见问题与排障现象原因解决收不到完整帧状态机 bug / 串口配置错误串口调试助手先验证帧格式再查状态机偶发校验失败电磁干扰 / 波特率漂移双绞线、磁环、降低波特率试里程计漂移轮距/轮径参数不准实测轮径转 N 圈测距离标定参数里程计方向反编码器方向约定不一致统一左轮正转前进约定软件反转IMU yaw 跳变角度归一化错误yaw 归一到 [-π, π]电机命令滞后通信频率太低上位机发送频率提到 20-50Hz上位机一启动电机猛转默认命令未初始化启动时先发一次零速度再进主循环九、小结与下一篇预告本篇核心通信协议三要素帧头 长度 校验CRC8用状态机解析字节流。下位机三件套串口状态机 里程计解算 IMU 回传10ms 控制环内完成。上位机两桥串口桥/cmd_vel ↔ 协议帧 传感器汇聚Range/电压。可靠性工程掉线看门狗、低电量保护、电磁防护、参数标定。至此实体机器人软硬结合完成能接收指令、能上报状态、能算里程计。下一篇第 33 篇进入上位机建图与导航原理综合把第 21-25 篇的 SLAM 和 Nav2 能力部署到实体机器人上——真机建图流程、坐标系校准、Nav2 参数整定、首次自主导航完成从仿真到真机的跨越。敬请期待。

相关新闻

open-code-review:基于Git Diff与CLI的AI代码审查范式

open-code-review:基于Git Diff与CLI的AI代码审查范式

1. 项目概述:这不是又一个代码审查工具,而是一次开发工作流的底层重定义“open-code-review”这个名称乍看平平无奇,像极了某个开源社区里默默无闻的 GitHub 仓库名——但如果你最近在终端里敲过codex cli、在飞书机器人里配置过 LLM 接入、或…

2026/10/19 3:25:09 阅读更多 →
MATLAB项目迁移Python全流程复盘:规划、重构与AI辅助避坑

MATLAB项目迁移Python全流程复盘:规划、重构与AI辅助避坑

自己踩过不少坑,也帮别人擦过不少屁股,所以这篇东西我不想写成一板一眼的教程,更想把它当成一次"过来人复盘"。我会从迁徙项目的规划和拆解入手,再到实操落地、常见坑点,尽量把那些文档里不写、但实战中特别…

2026/10/19 3:25:09 阅读更多 →
开源CLI代码审查工具:基于Git与本地LLM的语义化审查实践

开源CLI代码审查工具:基于Git与本地LLM的语义化审查实践

1. 项目概述:一个真正能落地的开源代码审查 CLI 工具 “open-code-review”这个名字乍看像一句口号,但拆开来看—— open 不是指“开源”,而是指“开放上下文”; code-review 也不是走形式的 PR 点击通过,而是让大…

2026/10/19 3:25:09 阅读更多 →

最新新闻

EtherNet/IP协议栈容器化部署:Windows与麒麟V10性能对比

EtherNet/IP协议栈容器化部署:Windows与麒麟V10性能对比

干工业自动化的兄弟应该都有这种体会:EtherNet/IP 协议栈部署起来,一半时间都在跟环境打架。第三方库冲突、网卡驱动对不上、系统一升级就跑不起来,光是“可复现”这一条就能把人逼疯。我最近在做一个产线数据采集的上位机方案,把…

2026/10/19 4:15:38 阅读更多 →
SSM+Vue.js毕业设计项目管理系统:选题到评分全流程拆解与部署避坑指南

SSM+Vue.js毕业设计项目管理系统:选题到评分全流程拆解与部署避坑指南

简介:面向计算机专业学生和毕业设计开发者的程序设计实践项目管理系统,基于 Java、SSM 与 Vue.js 技术栈构建,包含完整的前后端代码、数据库脚本与设计文档,可服务于毕设选题、课程设计或项目二次开发。压缩包共有 1017 个文件&am…

2026/10/19 4:15:38 阅读更多 →
SSM+Vue.js实践课管理平台:从选题审核到成绩评定的实现

SSM+Vue.js实践课管理平台:从选题审核到成绩评定的实现

简介:针对高校软件工程类毕业设计的《程序设计实践项目管理系统》完整源码包,基于JAVASSM后端与Vue.js前端分离架构,覆盖项目管理、任务分配、进度跟踪等常见业务模块,适合需要快速搭建系统并完成论文撰写的本科生参考。压缩包共1…

2026/10/19 4:15:38 阅读更多 →
开源代码评审工作流:CLI+LLM Agent+Git Diff 三位一体实践

开源代码评审工作流:CLI+LLM Agent+Git Diff 三位一体实践

1. 项目概述:这不是一个“工具”,而是一套可落地的开源代码评审工作流“open-code-review”这个名字乍看像某个 GitHub 仓库名,但实际它代表的是一种正在快速演进的工程实践范式——把传统依赖人工、会议、PR 留言的代码评审(Code…

2026/10/19 4:15:38 阅读更多 →
硕士论文AI辅助写作全流程:选题、文献、降重、答辩一站式指南

硕士论文AI辅助写作全流程:选题、文献、降重、答辩一站式指南

毕业季又到了,朋友圈里哀嚎一片的都是同一种声音:“论文写不出来”“文献太多看不完”“降重降到怀疑人生”。我去年辅导过一位读研二的师弟,他抱着改了四稿的开题报告来找我,说文献读了两个多月,题目换了不知道多少个…

2026/10/19 4:15:38 阅读更多 →
制造业AI落地复盘(一):为什么大多数项目死在‘第一步‘

制造业AI落地复盘(一):为什么大多数项目死在‘第一步‘

【本文为个人实操经验总结,非商业推广。文中数据来源于多个制造业信息化项目的观察,仅代表个体项目情况。】过去两年,接手过十几家制造企业的AI项目。复盘下来,大部分项目失败的原因惊人地一致——不是技术选错了,而是…

2026/10/19 4:14:37 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →