DH2URDF:机器人DH参数转URDF的自动映射与避坑指南
简介DH2URDF 是一份将 Denavit-Hartenberg 参数表转换为统一机器人描述格式URDF的 JavaScript 工具资源面向机器人建模、运动学仿真与 ROS 学习方向的开发者及学生。它解决的是手工编写 URDF 繁琐、易错的问题只需提供 DH 参数即可自动生成对应 URDF 结构并把转换后的坐标轴以 URDF 圆柱体形式放置在 DH 参考系上便于进一步搭建完整机器人模型。压缩包共 9 个文件约 138KB包含 4 个 js 脚本、1 个 html 演示页、1 个 urdf 示例、1 个 litcoffee 源码及 license、md 说明等核心转换逻辑与网页演示相互配合另附数学与模板辅助脚本。配套演示网站支持查看生成的 URDF 并调整关节值方便直观验证转换结果。目前已有 1049 人学习下载适合希望快速打通 DH 建模到 URDF 输出流程、减少重复手写工作的读者参考使用。1. DH2URDF为什么机器人建模的最后一公里总是卡在格式转换上做过机器人仿真的人都遇到过这个场景机械臂的 DH 参数表在论文或手册里写得清清楚楚四个参数一列看起来干净利落。但当你把这张表丢进仿真环境时发现它根本不认——仿真器要的是 URDF一种基于 XML 的树状描述格式里面全是 link、joint、origin、axis 这些标签。于是你开始手动换算一个关节一个关节地填填到第三个关节时发现姿态角符号搞反了整条臂在 RViz 里扭成了麻花。这不是个别现象。DH 参数和 URDF 之间的转换看起来只是数学等价变换实际上藏着一堆约定差异DH 表的坐标系定义有标准 DH 和改进 DH 两套体系URDF 的 joint origin 是相对父 link 的位姿而 DH 参数描述的是相邻关节轴之间的几何关系。两者的“参考系”根本不在同一个层面上。DH2URDF 这个工具要解决的就是把这件事从手工换算变成可复现的自动映射。它用 JavaScript 实现意味着你可以在浏览器里直接跑不需要装 ROS 环境就能完成转换。对于经常需要把论文里的 DH 表快速变成可仿真模型的人来说这个转换链路的打通比想象中更值得搞懂。2. DH 参数与 URDF 的语义鸿沟为什么不能直接对应2.1 DH 表的四参数到底描述了什么标准 DH 参数用四个量描述相邻两个关节坐标系之间的关系连杆长度 a、连杆扭角 α、关节偏移 d、关节转角 θ。这四个参数的排列顺序和旋转平移顺序是有严格约定的——标准 DH 是先绕 Z 轴转 θ再沿 Z 轴移 d然后沿 X 轴移 a最后绕 X 轴转 α。改进 DH 则把顺序调整为先绕 X 轴转 α再沿 X 轴移 a然后绕 Z 轴转 θ最后沿 Z 轴移 d。这个顺序差异不是学术洁癖它直接决定了坐标系原点落在哪里。标准 DH 把坐标系原点放在关节轴上改进 DH 把原点放在连杆的一端。如果你拿到的 DH 表没有标明用的是哪套约定转换结果大概率是错的。常见的实现思路是在转换前先确认 DH 表的约定类型然后在代码里用不同的变换矩阵构造顺序来区分。2.2 URDF 的 joint origin 是另一套语言URDF 里每个 joint 有一个 origin 标签包含 xyz 和 rpy 两个属性。xyz 是子坐标系原点在父坐标系中的位置rpy 是子坐标系相对父坐标系的旋转用 roll-pitch-yaw 表示。注意这里的旋转是“从父到子”的被动变换而 DH 参数描述的是“从当前关节到下一个关节”的主动变换链。更麻烦的是URDF 的 link 坐标系默认与 joint 坐标系重合而 DH 表的坐标系定义往往把 Z 轴对齐关节轴、X 轴对齐公垂线。这意味着你不能简单地把 DH 的四个参数塞进 origin 的六个自由度里中间需要做一次坐标系对齐先把 DH 变换矩阵算出来再从矩阵反解出 xyz 和 rpy。反解过程中欧拉角的顺序和符号约定又容易翻车。2.3 转换的核心从变换矩阵到 URDF 位姿假设你已经确认了 DH 约定类型下一步是构造相邻关节的齐次变换矩阵。以标准 DH 为例第 i 个关节到第 i1 个关节的变换可以写成四个基本变换的乘积// 标准 DH 变换矩阵构造 // theta: 关节转角, d: 连杆偏移, a: 连杆长度, alpha: 连杆扭角 function standardDHTransform(theta, d, a, alpha) { const ct Math.cos(theta), st Math.sin(theta); const ca Math.cos(alpha), sa Math.sin(alpha); // 返回 4x4 齐次变换矩阵 return [ [ct, -st * ca, st * sa, a * ct], [st, ct * ca, -ct * sa, a * st], [0, sa, ca, d ], [0, 0, 0, 1 ] ]; }这个矩阵的几何含义是先绕当前 Z 轴旋转 θ再沿 Z 轴平移 d然后沿 X 轴平移 a最后绕 X 轴旋转 α。矩阵的左上 3x3 是旋转部分右上 3x1 是平移部分。拿到这个矩阵后URDF 的 origin 需要的是父 link 到子 link 的位姿而 DH 矩阵描述的是关节 i 到关节 i1 的变换。如果 URDF 的 link 坐标系与 DH 坐标系重合那么直接把矩阵的平移部分作为 xyz旋转部分转成 rpy 即可。但实际情况往往更复杂URDF 的 base link 和 tool link 需要额外处理有些关节是旋转关节revolute有些是移动关节prismaticDH 表里的 θ 和 d 哪个是变量取决于关节类型。转换代码里必须根据关节类型决定哪个参数是常量、哪个是变量。2.4 旋转矩阵转 RPY 的数值陷阱从旋转矩阵反解 roll-pitch-yaw 时有一个经典陷阱当 pitch 角接近 ±90° 时roll 和 yaw 的解不唯一会出现万向节锁。常见的处理方式是用atan2函数并判断奇异条件// 旋转矩阵转 RPY (ZYX 顺序) function rotationMatrixToRPY(R) { let roll, pitch, yaw; const epsilon 1e-6; // 判断是否接近奇异位置 if (Math.abs(R[2][0]) 1 - epsilon) { // 万向节锁pitch ±90度 pitch Math.sign(-R[2][0]) * Math.PI / 2; roll 0; yaw Math.atan2(-R[0][1], R[1][1]); } else { pitch Math.asin(-R[2][0]); roll Math.atan2(R[2][1], R[2][2]); yaw Math.atan2(R[1][0], R[0][0]); } return [roll, pitch, yaw]; }这段代码里R[2][0]对应的是旋转矩阵第三行第一列在 ZYX 欧拉角约定下它等于 -sin(pitch)。当它接近 ±1 时pitch 接近 ±90°此时 roll 和 yaw 耦合在一起只能任意取一个值。URDF 解析器通常能处理这种情况但如果你手动填参数这里就是最容易翻车的地方。DH2URDF 在实现时应该对奇异位置做了特殊处理否则生成的 URDF 在某些构型下会出现姿态跳变。3. 从 DH 表到 URDF 文件转换流程的工程实现3.1 输入解析DH 表的格式与约定识别DH2URDF 的输入是一张 DH 参数表通常以数组或 CSV 形式给出。每一行包含四个参数外加关节类型和关节限位。转换的第一步是识别约定类型标准 DH 还是改进 DH。有些实现会让用户显式指定有些则通过参数排列特征自动判断。一个可靠的工程做法是在输入结构里加一个convention字段默认值为standard用户可切换为modified。输入解析还需要处理单位问题。DH 表里的角度可能是度或弧度长度可能是米或毫米。URDF 内部统一用弧度和米所以解析阶段必须做单位归一化。常见的做法是在参数表里加一列unit或者在转换配置里指定全局单位。如果这一步漏了生成的 URDF 在仿真器里要么尺寸差 1000 倍要么关节转一圈只动了 1 度。3.2 逐关节变换从基座到末端转换的核心循环是遍历 DH 表的每一行计算相邻坐标系之间的变换矩阵然后累积成从基座到当前关节的全局位姿。这里有一个容易混淆的点URDF 的 joint origin 是相对父 link 的而 DH 变换是相对前一个关节坐标系的。如果 URDF 的 link 坐标系与 DH 坐标系一一对应那么每个 joint 的 origin 就是当前 DH 变换矩阵反解出的位姿。但 URDF 的树状结构要求每个 link 只能有一个父 joint。对于串联机械臂这自然满足对于带分支的结构比如带夹爪的臂就需要在 DH 表里标明分支点。DH2URDF 的输入格式里应该支持一个parent字段用来指定当前关节的父 link 名称。如果没有这个字段默认按顺序串联。// 逐关节生成 URDF joint 定义 function generateJoint(dhRow, parentLink, childLink, convention) { const { theta, d, a, alpha, type } dhRow; // 根据约定类型选择变换矩阵构造方式 const T convention standard ? standardDHTransform(theta, d, a, alpha) : modifiedDHTransform(theta, d, a, alpha); // 提取平移和旋转 const xyz [T[0][3], T[1][3], T[2][3]]; const rpy rotationMatrixToRPY([ [T[0][0], T[0][1], T[0][2]], [T[1][0], T[1][1], T[1][2]], [T[2][0], T[2][1], T[2][2]] ]); // 根据关节类型决定 axis 和 limit const axis type revolute ? [0, 0, 1] : [0, 0, 1]; const limit type revolute ? { lower: -Math.PI, upper: Math.PI, effort: 100, velocity: 1 } : { lower: -0.5, upper: 0.5, effort: 100, velocity: 0.1 }; return { parentLink, childLink, xyz, rpy, axis, limit, type }; }这段代码里xyz和rpy直接来自变换矩阵的分解。axis对于旋转关节通常是 Z 轴因为 DH 约定里关节轴就是 Z 轴。limit是关节限位旋转关节默认 ±180°移动关节默认 ±0.5 米。这些默认值在实际使用中需要根据机械臂规格调整但作为转换工具的初始输出是合理的。3.3 URDF 序列化XML 结构与 link 命名拿到所有 joint 的数据后下一步是生成 URDF 的 XML 结构。URDF 的基本骨架是robot根节点下面包含若干link和joint。每个 joint 必须引用父 link 和子 link 的名称所以转换过程中需要维护一个 link 命名规则。常见的做法是用link_0、link_1这样的序号命名或者用base_link、joint1_link这样的语义命名。序列化时要注意 XML 转义如果 link 名称里包含特殊字符需要转义。另外URDF 支持material标签给 link 上色转换工具可以选择性地生成默认材质方便在 RViz 里区分不同连杆。惯性矩阵inertial是可选的但如果要做动力学仿真必须提供质量和转动惯量。DH2URDF 如果只做运动学转换可以省略 inertial但生成的 URDF 在 Gazebo 里会报缺少惯性参数的警告。// 生成 URDF XML 字符串 function serializeURDF(robotName, links, joints) { let xml ?xml version1.0?\nrobot name${robotName}\n; // 生成 link 定义 for (const link of links) { xml link name${link.name}\n; if (link.mass) { xml inertial\n; xml mass value${link.mass}/\n; xml inertia ixx${link.ixx} ixy0 ixz0; xml iyy${link.iyy} iyz0 izz${link.izz}/\n; xml /inertial\n; } xml /link\n; } // 生成 joint 定义 for (const joint of joints) { xml joint name${joint.name} type${joint.type}\n; xml parent link${joint.parentLink}/\n; xml child link${joint.childLink}/\n; xml origin xyz${joint.xyz.join( )}; xml rpy${joint.rpy.join( )}/\n; xml axis xyz${joint.axis.join( )}/\n; xml limit lower${joint.limit.lower}; xml upper${joint.limit.upper}; xml effort${joint.limit.effort}; xml velocity${joint.limit.velocity}/\n; xml /joint\n; } xml /robot; return xml; }这段序列化代码生成的 URDF 可以直接被 ROS 的robot_state_publisher和 RViz 加载。注意origin的xyz和rpy属性值用空格分隔这是 URDF 规范要求的格式。limit标签对于 revolute 和 prismatic 关节是必需的continuous 关节可以省略。3.4 验证转换结果用正运动学校验末端位姿生成 URDF 后怎么确认转换是对的最直接的方法是用正运动学算一遍末端位姿和 DH 表直接算出来的结果对比。如果两者一致说明转换正确如果有偏差说明某个关节的 origin 或 axis 搞错了。验证时可以用一个简单的测试构型把所有关节角设为 0计算末端位置。然后再把所有关节角设为某个非零值再算一次。两次结果都和 DH 正运动学的结果对比。常见的实现思路是在转换工具里内置一个verify函数输入 DH 表和生成的 URDF输出位姿误差。如果误差在数值精度范围内比如 1e-6就认为转换成功。这个验证步骤在手工转换时经常被忽略但它是区分“看起来对”和“确实对”的关键。很多转换错误在零位时看不出来一旦关节转动就暴露了。4. 避坑指南DH 转 URDF 时最容易翻车的几个地方4.1 现象RViz 里机械臂扭成麻花关节轴方向全乱原因DH 约定类型搞反了。标准 DH 和改进 DH 的变换顺序不同如果输入表是改进 DH 但代码按标准 DH 处理每个关节的旋转轴都会偏 90°。解决在转换前确认 DH 表的约定类型如果不确定用零位和 90° 两个构型分别验证看哪个约定下末端位姿和预期一致。4.2 现象URDF 加载成功但关节转不动或者转动方向相反原因axis标签的轴向搞错了。DH 约定里关节轴是 Z 轴但 URDF 的 joint axis 是相对 joint 坐标系的。如果 origin 的 rpy 已经把坐标系转过了axis 可能不再是 [0,0,1]。解决在生成 joint 时axis 应该始终设为 [0,0,1]因为 DH 变换已经把关节轴对齐到了 Z 轴。如果发现方向相反检查 DH 参数的符号约定。4.3 现象末端位置差了一个固定偏移所有关节角下都偏原因基座坐标系和第一个关节坐标系之间的变换漏了。DH 表通常从第一个关节开始描述但 URDF 需要一个 base link 到第一个 joint 的固定变换。如果这个变换没加整个臂会偏移一个连杆长度。解决在 DH 表前面加一行“基座到关节 1”的虚拟 DH 参数或者在生成 URDF 时手动插入一个 fixed joint。4.4 现象角度单位搞混关节转 1 弧度实际转了 57.3 度原因DH 表里的角度是度但代码按弧度处理。解决在输入解析阶段统一转成弧度。如果 DH 表里角度列的值普遍在 0-360 之间大概率是度如果在 0-6.28 之间大概率是弧度。不确定时加一个angleUnit配置项。4.5 现象生成的 URDF 在 Gazebo 里抖动或飞走原因缺少惯性参数或惯性矩阵不正定。Gazebo 做动力学仿真时需要每个 link 的质量和转动惯量。如果转换工具只输出运动学结构Gazebo 会用默认值可能导致数值不稳定。解决在 DH 表里补充质量和惯量信息或者在 URDF 里给每个 link 加一个合理的默认惯性。惯量矩阵必须满足三角不等式否则物理引擎会报错。5. 转换工具的边界与取舍什么时候该用 DH2URDF什么时候不该用DH2URDF 这类工具的价值在于把重复的手工换算自动化但它不是万能的。它的适用边界很清晰输入必须是结构化的 DH 参数表输出是运动学层面的 URDF。如果你的机器人模型来自 CAD 导出或者关节关系不是标准串联结构DH 表本身就不容易列出来这时候直接用 CAD 转 URDF 的工具更合适。另一个取舍点是精度和可读性的平衡。自动生成的 URDF 里origin 的 xyz 和 rpy 是一串浮点数可读性差但精度高。手工调整过的 URDF 可能把一些固定变换合并简化了可读性好但改一个参数可能影响多个关节。DH2URDF 选择的是前者保持每个关节的独立变换不做过多的代数化简。这样生成的 URDF 虽然看起来冗长但每个 joint 的 origin 都能追溯到 DH 表里的对应行调试时容易定位问题。还有一个容易被忽略的点DH 参数本身对机械臂的几何描述是不唯一的。同一台机械臂可以有多组等效的 DH 参数取决于坐标系原点的选择。这意味着 DH2URDF 的输出也不是唯一的。如果你拿到的 URDF 和别人的不一样不一定是谁错了可能只是 DH 表的约定不同。理解这一点就不会在“为什么我的 URDF 和参考模型对不上”这个问题上纠结太久。我自己的习惯是每次用 DH2URDF 生成 URDF 后先跑一遍正运动学校验确认零位和几个随机构型下的末端位姿误差在 1e-6 以内再丢进仿真环境。这个习惯帮我省掉了无数次在 RViz 里对着扭成麻花的机械臂发呆的时间。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

AI敏捷招聘引擎:从行为分析到预测模型的实操指南

AI敏捷招聘引擎:从行为分析到预测模型的实操指南

1. 2026年的人才战争,拼的是“预测能力”而非“筛选速度”这两年做招聘的同行应该都有同感:简历收得越来越多,但真正合适的人反而越来越难找。职位发布出去,24小时内涌入几百份简历,HR团队加班刷完,最后约面…

2026/10/18 4:39:32 阅读更多 →
园区网实训报告全流程:从VLAN规划到NAT配置

园区网实训报告全流程:从VLAN规划到NAT配置

每年实训季我都会收到一堆让人头大的报告,拓扑图贴得挺漂亮,配置命令密密麻麻占了好几页,但你随便挑一句问“为什么网关要放在核心交换机上”,对方多半答不上来。实训报告既然叫“报告”,它就不是配置命令的粘贴板。它…

2026/10/18 4:38:32 阅读更多 →
RTP.NET实战指南:从实时传输协议到C#音视频开发示例

RTP.NET实战指南:从实时传输协议到C#音视频开发示例

简介:RTP.NET是一款基于.NET框架实现的RTP实时传输库,面向需要处理音视频、VoIP及流媒体等实时数据的C#开发者。库内封装了RTPSession会话管理、参与者标识、数据包化、负载类型识别、事件驱动API与丢包恢复等关键机制,能够显著降低基于RTP构…

2026/10/18 4:38:32 阅读更多 →

最新新闻

国庆AI行业盘点:模型更新、开源生态与智能体落地加速

国庆AI行业盘点:模型更新、开源生态与智能体落地加速

国庆长假这两天,AI圈跟过年似的。10月1日到10月8日,前后八天,不断有模型更新、开源仓库爆点、智能体应用密集落地。作为一个长期跟踪这条赛道的人,我几乎每天都要刷好几遍测试榜单和社区讨论串,很多信息还没来得及沉淀…

2026/10/18 5:37:42 阅读更多 →
程序员必备20个网站:技术阅读、刷题、接单与效率工具盘点

程序员必备20个网站:技术阅读、刷题、接单与效率工具盘点

程序员必备网站这个老话题,几乎每隔几个月就会被重新拿出来聊一轮。技术、接单、刷题这三类网站,每个开发者的收藏夹里都躺着不止一个,但真到要用的时候,很多人会发现:收藏了 300 个网站,能稳定打开、准时解…

2026/10/18 5:37:42 阅读更多 →
全大写提示词触发AI风控?解析Claude互审封禁真相与安全写法

全大写提示词触发AI风控?解析Claude互审封禁真相与安全写法

有意思。“让Claude管Claude”本身没问题,问题是这句提示词是用全大写敲出来的。我是在一个技术讨论群里看到这件事的:某人做了个自动化流程,让一个Claude会话去审核另一个Claude会话生成的文案,这本来是个很常见的质检思路&#…

2026/10/18 5:37:42 阅读更多 →
Image GPS Editor实战:照片GPS数据的读取、修改与批量处理

Image GPS Editor实战:照片GPS数据的读取、修改与批量处理

前阵子帮朋友整理一趟长途自驾的素材,几百张照片导进电脑,结果发现相机里明明开了GPS记录,出来的照片却有一大半坐标信息是空的。另一批用手机拍的照片倒是带位置,但其中有几张因为当时在船上漂着,定位偏到了海里。用看…

2026/10/18 5:37:42 阅读更多 →
变量之路:理清编译与链接中的符号、地址与重定位机制

变量之路:理清编译与链接中的符号、地址与重定位机制

先问一个问题:你在 IDE 里写了int counter 0;,点一下运行按钮,程序跑起来了。可是那个叫counter的变量,它的地址到底是谁给它的?它为什么能从一个名字变成一个真实存在于内存里的地址?我在带新人的时候发现…

2026/10/18 5:37:42 阅读更多 →
律所案件管理系统设计与落地:角色权限、流程引擎与文书协同实践

律所案件管理系统设计与落地:角色权限、流程引擎与文书协同实践

1. 这个系统到底在解决什么问题做律师的朋友应该都有过这种体验:一个案子从收到材料到最终归档,中间要经过立案、排期、取证、文书起草、审核、盖章、送达、开庭、归档这么一串流程。传统做法是什么?微信群接龙、Excel表格、共享文件夹、Outl…

2026/10/18 5:36:42 阅读更多 →

日新闻

基于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/18 0:00:55 阅读更多 →

周新闻

基于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/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/16 14:23:36 阅读更多 →