从A*到JPS:三维路径规划算法jps3d的核心原理与C++实现
1. 项目概述从2D到3D的路径规划进化在机器人、无人机导航以及游戏AI领域路径规划算法是决定智能体能否高效、智能移动的核心。A*A-Star算法因其通用性和最优性成为了这个领域的基石。然而随着地图复杂度和维度从2D平面到3D空间的提升传统A*算法需要遍历大量节点的缺点被急剧放大计算效率成为瓶颈。这时一种名为“跳点搜索”Jump Point Search, JPS的优化技术应运而生它通过识别地图中的“跳转点”来大幅剪枝搜索空间在栅格地图上能获得惊人的加速比。jps3d这个项目正是将经典的JPS算法从2D平面推广到3D立体空间的C实现。它不仅仅是一个算法的简单移植更是面对三维空间搜索时对算法逻辑、数据结构乃至工程实践的一次深度重构。想象一下你的无人机要在布满楼宇和树木的城市峡谷中穿梭或者你的游戏角色需要在多层建筑、高低错落的地形中寻找最优路径jps3d提供的就是解决这类问题的核心引擎。这个实现的价值在于其纯粹性和高效性。它专注于算法本身用标准的C写成不依赖庞大的游戏引擎或机器人框架使得它可以轻松地集成到各种需要3D路径规划的项目中无论是学术研究、算法竞赛还是实际的工业应用。对于开发者而言理解并掌握jps3d就等于握住了打开高效三维空间搜索大门的一把钥匙。2. JPS核心原理为什么“跳”比“走”更快要理解jps3d必须先吃透JPS算法的精髓。我们可以把它看作是对A算法的一次“智能”瘦身手术。A算法在搜索时会平等地考虑当前节点的所有邻居节点在2D八方向、3D二十六方向连通网格中这导致了大量不必要的计算。2.1 对称性剪枝与强迫邻居JPS的核心思想基于一个关键观察在均匀成本的栅格地图中许多路径是对称且冗余的。例如从起点到终点如果有一条路径是先向右再向上那么先向上再向右可能会到达同样的中间状态。JPS通过定义“跳转点”来打破这种对称性避免探索这些等效的路径。其操作依赖于两个核心概念自然邻居在沿着某个方向移动时根据当前节点的父节点方向可以推断出大部分邻居节点会被父节点以更优或等价的路径访问到因此这些“自然邻居”在当前节点可以被安全地忽略。强迫邻居这是JPS算法的“火花塞”。如果当前节点n的某个邻居x被障碍物阻挡使得从n的父节点到x的路径如果不经过n就会变得更差例如距离变长或不可达那么x就被称为n的强迫邻居。强迫邻居的出现意味着当前节点n是一个潜在的跳转点因为路径在这里发生了“转折”绕过了障碍物。2.2 跳跃过程寻找下一个关键节点JPS算法不再一步步地移动到每个栅格。相反它从当前节点开始沿着一个方向“跳跃”。跳跃规则如下如果当前节点是目标节点跳跃停止。如果当前节点存在强迫邻居那么当前节点就是一个跳转点跳跃停止。否则沿着当前方向继续移动到下一个节点并重复此检查。这个过程在2D地图上表现为水平、垂直和对角线方向的射线扫描。在3D空间中则扩展到沿X, Y, Z轴的方向以及所有体对角线方向进行扫描逻辑更为复杂但原理一致。2.3 从2D到3D的挑战与演进将JPS从2D扩展到3D绝非仅仅是增加一个Z坐标那么简单。其挑战和演进主要体现在搜索方向激增2D八方向扩展到3D二十六方向6个面邻接12个边邻接8个角邻接。跳跃逻辑需要在所有这26个方向上实现。强迫邻居判定复杂化在3D中一个体素3D栅格的强迫邻居可能存在于更复杂的空间关系中。需要判断的不仅仅是同一平面上的障碍物还有垂直方向上的遮挡。例如一个在X轴正方向移动的搜索可能因为侧上方YZ方向的一个障碍物而产生强迫邻居。跳跃扫描维度提升2D的跳跃是在平面上沿直线扫描。3D的跳跃则是在三维空间中沿直线扫描需要同时检查三个维度上的碰撞和强迫邻居条件实现难度和计算量都有所增加。启发式函数设计A*算法中的启发式函数如欧几里得距离、曼哈顿距离在3D中需要重新评估其适用性和计算效率。jps3d通常采用3D欧几里得距离或对角线距离Octile distance的3D推广作为启发式函数以保持搜索的高效性。jps3d的实现正是要严谨地处理这些3D特有的问题将2D JPS优雅的剪枝思想成功地移植到三维体素网格的复杂世界中。3. jps3d项目设计与关键实现解析一个健壮、高效的jps3d实现需要在算法逻辑、数据结构和工程细节上做出精心设计。下面我们拆解其核心模块。3.1 地图表示与初始化三维地图通常用一个三维数组或一维数组模拟来表示每个元素是一个体素Voxel标记为“空闲”可通行或“障碍”不可通行。在C中为了追求极致性能往往会使用std::vectorbool或位操作来压缩存储因为vectorbool有特化实现可能节省空间。class GridMap3D { public: GridMap3D(size_t dim_x, size_t dim_y, size_t dim_z) : dim_x_(dim_x), dim_y_(dim_y), dim_z_(dim_z), data_(dim_x * dim_y * dim_z, false) {} // false 表示空闲 bool isOccupied(size_t x, size_t y, size_t z) const { return data_[index(x, y, z)]; } void setOccupied(size_t x, size_t y, size_t z, bool occ) { data_[index(x, y, z)] occ; } private: size_t index(size_t x, size_t y, size_t z) const { // 行主序存储: (z * dim_y y) * dim_x x return (z * dim_y_ y) * dim_x_ x; } size_t dim_x_, dim_y_, dim_z_; std::vectorbool data_; };注意地图坐标系的定义X, Y, Z 分别对应什么空间方向必须与你的跳跃方向枚举、邻居偏移量计算保持一致否则会导致搜索逻辑完全错误。这是初期最容易踩的坑之一。3.2 三维跳跃方向与邻居偏移我们需要定义26个方向的单位偏移量。为了方便跳跃逻辑通常将这些方向分类直线方向6个、平面对角线方向12个、空间对角线方向8个。struct Vec3i { int x, y, z; }; const std::arrayVec3i, 26 dirs {{ // 直线方向 (6) {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}, // 平面对角线方向 (12): XY平面 XZ平面 YZ平面 {1, 1, 0}, {1, -1, 0}, {-1, 1, 0}, {-1, -1, 0}, {1, 0, 1}, {1, 0, -1}, {-1, 0, 1}, {-1, 0, -1}, {0, 1, 1}, {0, 1, -1}, {0, -1, 1}, {0, -1, -1}, // 空间对角线方向 (8) {1, 1, 1}, {1, 1, -1}, {1, -1, 1}, {1, -1, -1}, {-1, 1, 1}, {-1, 1, -1}, {-1, -1, 1}, {-1, -1, -1} }};3.3 核心跳跃函数jump的实现这是整个算法的灵魂。函数jump(current, direction, goal)从current点开始沿direction方向寻找下一个跳转点或目标。其伪代码逻辑如下next current direction如果next是障碍物或超出地图边界返回nullptr跳跃失败。如果next就是目标点返回next找到跳转点。检查next点是否存在强迫邻居。这是最复杂的部分需要根据移动方向dir检查特定位置的邻居。如果存在强迫邻居则next本身就是一个跳转点返回next。递归跳跃如果dir是对角线方向平面或空间对角线则需要进行“自然分解”的递归跳跃。例如沿(1,1,1)方向跳跃时需要同时尝试沿(1,0,0),(0,1,0),(0,0,1)这三个直线方向的子跳跃。如果任何一个子跳跃找到了跳转点那么当前的next点也可能成为跳转点或者子跳跃的结果就是跳转点。这是3D JPS比2D复杂得多的关键。如果以上条件都不满足则令current next回到步骤1继续沿原方向跳跃。强迫邻居检查的3D实现示例假设当前移动方向dir (1, 0, 0)沿X轴正方向。我们需要检查的不仅仅是Y轴正负方向的邻居还有Z轴正负方向的邻居以及它们的组合。规则是如果next点在这些侧向方向上的邻居是障碍物并且“绕过”next点到达该邻居后方格子的路径成本更高或不可行则该邻居是强迫邻居。在代码中这体现为一组预先定义好的、与方向相关的相对位置检查。3.4 主搜索循环与A*框架集成JPS仍然运行在A*的框架内只是扩展或替换了“获取邻居节点”这一步。Node* JPS3D::findPath(const Vec3i start, const Vec3i goal) { openSet_.push(new Node(start, 0, heuristic(start, goal))); while (!openSet_.empty()) { Node* current openSet_.pop(); // 取出f值最小的节点 if (current-pos goal) { /* 重构路径并返回 */ } closeSet_.insert(current-pos); // 关键区别不是获取所有邻居而是获取所有可能的跳跃方向产生的跳转点 for (const auto dir : pruneDirections(current)) { Vec3i jump_point jump(current-pos, dir, goal); if (jump_point 有效) { // 计算从current到jump_point的成本通常是欧几里得距离 double cost distance(current-pos, jump_point); Node* successor getOrCreateNode(jump_point); if (closeSet_.count(successor-pos)) continue; double new_g current-g cost; if (new_g successor-g) { successor-parent current; successor-g new_g; successor-f new_g heuristic(successor-pos, goal); openSet_.update(successor); } } } } return nullptr; // 未找到路径 }pruneDirections(current)函数基于当前节点的父节点方向应用对称性剪枝规则减少需要探索的方向数量。例如如果是从父节点沿X轴正方向移动过来的那么很多反方向如X轴负方向和部分侧向方向就不需要再探索。4. 性能优化与工程实践要点实现一个能用的jps3d是一回事实现一个高性能、稳定的jps3d是另一回事。以下是一些关键的优化和实践经验。4.1 数据结构优化优先队列A*的开放集OpenSet需要使用优先队列。C的std::priority_queue默认是最大堆且不支持元素的优先级更新decrease_key操作。通常需要自己实现一个基于std::make_heap的堆或者使用第三方库如boost::heap::fibonacci_heap它支持高效的优先级更新。这是影响大规模搜索性能的关键点。闭集合关闭集CloseSet用于记录已访问节点。使用std::unordered_set并为其自定义三维向量的哈希函数如((x * 73856093) ^ (y * 19349663) ^ (z * 83492791))是常见选择。对于已知大小范围的地图甚至可以使用三维布尔数组来达到O(1)的访问速度。节点池频繁的new和delete操作Node对象会严重影响性能。可以使用对象池Memory Pool一次性分配一大块内存来管理节点显著减少内存碎片和分配开销。4.2 跳跃过程优化提前终止在跳跃扫描时如果发现路径成本已经超过当前最优路径的估计值可以提前终止该方向的跳跃。方向剪枝表pruneDirections函数可以根据父节点方向预先计算出一个需要探索的方向列表避免在运行时进行复杂的逻辑判断。这是一个用空间换时间的典型优化。强迫邻居查找表同样对于26个方向中的每一个可以预先计算好需要检查哪些相对位置来判定强迫邻居。将这些偏移量存储在静态表中跳跃函数中直接查表遍历比运行时计算要快得多。4.3 内存与缓存友好性节点信息压缩Node结构体应尽可能小。pos可以用一个32位整数编码如果地图维度允许g和f值使用float而非double在大多数路径规划场景中精度足够。parent指针可以用节点在池中的索引uint32_t代替进一步节省空间。局部性访问地图数据检查障碍物时尽量保证内存访问的连续性。跳跃是沿直线进行的这本身具有较好的空间局部性。4.4 启发式函数的选择在3D中常用的启发式函数有欧几里得距离sqrt(dx^2 dy^2 dz^2)。最准确但计算平方根开销较大。通常可以比较距离的平方来避免开方。曼哈顿距离|dx| |dy| |dz|。在只允许直线移动的网格中可用但对于允许对角线移动的JPS它会高估成本导致A*搜索更多节点性能下降。对角线距离Octile的3D推广先按空间对角线移动再按平面对角线移动最后按直线移动。计算方式为d_min min(dx, dy, dz); d_mid mid(dx, dy, dz); d_max max(dx, dy, dz); heuristic sqrt(3)*d_min sqrt(2)*(d_mid - d_min) (d_max - d_mid)。这是对3D欧几里得距离的一个很好且可接受的近似计算比开方快且比曼哈顿距离更准确。在jps3d中对角线距离通常是性价比最高的选择。它既保持了搜索的快速性启发式值接近真实成本又避免了欧几里得距离的开方运算。5. 常见问题、调试技巧与实战心得即使理解了原理实现过程中也一定会遇到各种“坑”。下面分享一些常见问题和解决思路。5.1 路径非最优或找不到路径这是最令人头疼的问题。排查步骤应像侦探破案一样有条理检查基础A*框架首先用一个极其简单的、没有障碍物的2D或3D地图测试。暂时禁用JPS用A的标准邻居扩展8方向或26方向是否能找到最优路径如果不能问题出在A的openSet/closeSet管理、启发式函数或节点成本计算上。验证跳跃函数在简单地图上手动模拟跳跃过程。在关键点设置断点打印jump函数的输入、输出。检查它是否在应该停止的地方遇到强迫邻居或目标正确停止是否漏掉了跳转点。强迫邻居逻辑这是错误的重灾区。用一个“L”形或“U”形的简单障碍物测试。在障碍物拐角处你的算法识别出强迫邻居了吗绘制出当前节点、移动方向、障碍物和疑似强迫邻居的位置与论文中的图示逐一比对。对角线跳跃的递归在3D中沿对角线方向跳跃时对直线子方向的递归调用是否正确子跳跃找到的跳转点是否正确地传递回上层使当前点成为跳转点这里很容易出现逻辑遗漏。方向剪枝pruneDirections是否剪掉了不该剪的方向一个检查方法是暂时禁用所有剪枝让算法探索所有26个方向。如果此时能找到路径但启用剪枝后找不到问题就出在剪枝逻辑上。5.2 性能不如预期甚至更慢JPS在复杂空旷地图上优势明显但在某些情况下可能不如朴素A*地图障碍物极度密集几乎每一步都会遇到强迫邻居导致跳转点就是每一个节点JPS的剪枝优势丧失反而增加了跳跃扫描的开销。这时可以考虑混合策略当障碍物密度超过某个阈值时切换回A*。启发式函数不准确如果启发式函数严重低估真实成本如用曼哈顿距离A*会搜索非常广的范围JPS的剪枝效果被淹没。确保使用对角线距离或欧几里得距离。数据结构开销大检查你的Node结构体大小、哈希表冲突是否严重、优先队列的更新操作是否高效。对于小地图这些开销可能占比很大。跳跃扫描的边界检查在跳跃循环中每次next current dir后都要进行边界和障碍物检查。确保这些检查是内联的、高效的。可以将边界检查整合到索引计算中。5.3 内存消耗过大对于非常大的3D地图如512x512x512即使每个体素用1 bit地图数据也有16MB。再加上成千上万的Node对象内存可能吃紧。使用更紧凑的地图表示如位图bitmap。节点池与索引如4.3节所述用索引代替指针。懒加载闭集合不一定需要存储所有访问过的节点。对于某些应用可以只存储最近的一部分或者使用布隆过滤器等概率数据结构但这会牺牲最优性保证。路径重构后释放内存一旦找到路径可以立即释放所有Node对象除了构成路径的那些。5.4 与具体应用集成地图预处理实际应用中的地图可能不是简单的二值障碍物。可能有代价地图costmap、多层语义信息。JPS可以扩展为JPS预先计算每个单元格到最近障碍物的距离等信息进一步加速。在jps3d中集成代价需要修改跳跃终止条件当累积代价超过阈值时停止和强迫邻居的判定逻辑。动态障碍物基础JPS适用于静态地图。对于动态障碍物需要增量式重规划算法如D* Lite或者以较高频率重新运行JPS。这时算法的单次执行速度至关重要jps3d的高效性正好派上用场。非网格路径平滑JPS在网格上找到的路径是由跳转点连接的折线可能不够平滑。在实际机器人或游戏中通常需要在路径生成后使用样条曲线或贝塞尔曲线进行平滑或者使用“后处理”步骤拉直路径String Pulling。个人实操心得实现jps3d时我强烈建议采用测试驱动开发。先写一系列单元测试从最简单的直线移动、单障碍物拐弯到复杂的多层迷宫。每实现一个功能点如直线跳跃、强迫邻居检查、对角线递归就运行对应的测试。这能帮你快速定位问题而不是等到整个算法写完才发现一堆错误纠缠在一起无从下手。另外使用一个简单的3D可视化工具哪怕是输出点云到文件用MeshLab或Python的matplotlib查看来调试路径比单纯看日志要直观得多。最后记住性能分析是你的朋友。用perf或valgrind工具分析热点你会发现优化往往集中在跳跃循环和数据结构操作上而不是算法本身的理论部分。

相关新闻

JNI深度实践:从内存管理到性能优化的C++/Java混合编程指南

JNI深度实践:从内存管理到性能优化的C++/Java混合编程指南

1. 项目概述:为什么我们需要深入JNI?如果你是一名C开发者,面对一个需要调用Java库的遗留系统,或者你是一名Java工程师,需要榨干硬件性能来处理海量数据,那么JNI(Java Native Interface&#xff…

2026/10/12 2:35:44 阅读更多 →
Android Activity数据保存与横竖屏切换处理指南

Android Activity数据保存与横竖屏切换处理指南

1. Activity数据保存与横竖屏切换基础概念 作为一名Android开发者,掌握Activity数据保存和横竖屏切换的处理是必备技能。在实际开发中,我们经常会遇到用户旋转设备导致界面重新加载、数据丢失的问题。理解这些机制的工作原理,能够帮助我们构建…

2026/10/12 2:35:47 阅读更多 →
深入解析TI AM62L GPMC:NOR/NAND Flash时序配置与调试实践

深入解析TI AM62L GPMC:NOR/NAND Flash时序配置与调试实践

1. 项目概述:为什么嵌入式开发者需要吃透GPMC?在嵌入式系统开发中,处理器与外部存储器的连接是决定系统性能、稳定性和成本的关键一环。无论是运行在NOR Flash上的启动代码,还是存储在NAND Flash中的海量应用数据,都需…

2026/10/12 2:35:48 阅读更多 →

最新新闻

基于COMSOL的输变电工程工频电磁场仿真与环评应用实践

基于COMSOL的输变电工程工频电磁场仿真与环评应用实践

最近在做某输变电工程的环评支撑时,正好碰到一段三相高压电缆要从居住区地下穿过。评审专家和住户最关心的就一件事:电缆埋在地下,地面的人站久了到底有没有影响?这个问题落到技术层面,其实是两个量:电缆运…

2026/10/12 19:58:15 阅读更多 →
大模型能力爆发:界面生成、合规监管与端侧部署的实战解析

大模型能力爆发:界面生成、合规监管与端侧部署的实战解析

10月的AI圈像是被按了快进键。我早上还在刷一组产品更新,下午就看到三条消息前后脚占据了讨论区:新一代(第六代)旗舰大模型全面开放,还突然“长出了界面”;某头部AI公司在一个海外市场公开道歉,…

2026/10/12 19:58:15 阅读更多 →
Qt触摸屏滑动卡死:QScroller与事件冲突的排查与优化

Qt触摸屏滑动卡死:QScroller与事件冲突的排查与优化

1. 触摸屏滑动卡死:一个让Qt桌面端开发者头疼的经典问题如果你做过带触摸屏的Qt桌面项目,尤其是那种跑在工控机、自助终端、医疗设备或者车载中控上的应用,大概率遇到过这个场景:手指在QTableView、QTableWidget或者QListWidget上…

2026/10/12 19:58:15 阅读更多 →
open-code-review:一种提升代码可理解性与可维护性的评审方法论

open-code-review:一种提升代码可理解性与可维护性的评审方法论

1. “open-code-review”不是开源项目,而是一套可落地的代码评审实践方法论“open-code-review”这个词乍看像某个 GitHub 上的新仓库名,或是某款刚发布的开源工具——但实际翻遍主流代码托管平台、技术社区和工具索引库,都找不到一个以该名称…

2026/10/12 19:58:15 阅读更多 →
YOLO抽烟目标检测数据集:从标注校验到训练避坑全指南

YOLO抽烟目标检测数据集:从标注校验到训练避坑全指南

简介:面向计算机视觉目标检测项目与YOLO系列算法学习者,这份数据集聚焦公共场所吸烟行为识别,提供训练模型所需的原始图像与标定数据。压缩包内共594个文件,由297张jpg图片和297个xml标注文件一一对应组成;xml中记录目…

2026/10/12 19:58:15 阅读更多 →
在手机上玩转 Claude Code:用 Termux 把安卓变成「掌上 IDE」(含国内模型配置)

在手机上玩转 Claude Code:用 Termux 把安卓变成「掌上 IDE」(含国内模型配置)

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

2026/10/12 19:57:15 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →