当前位置: 首页 > news >正文

macOS源码编译ROS 2 Jazzy实战指南:绕过SIP、Xcode兼容与DDS构建陷阱

1. 项目概述:在 macOS 上从源码构建 ROS 2(Jazzy 版本)的真实实践手记

你正在看的,不是一份冷冰冰的官方文档快照,而是一位在 macOS 上持续三年、跨五个 ROS 2 大版本(Foxy → Humble → Iron → Jazzy)坚持源码构建、调试、定制和教学的开发者,把踩过的坑、绕过的弯、验证过的替代方案,连同所有“为什么必须这样”的底层逻辑,一股脑倒出来的实操笔记。关键词里那个L3 | Installation > Alternatives > macOS (source),说白了就是:当 Homebrew 二进制包不够用、官方预编译版不支持你的硬件或内核补丁、或者你需要深度修改底层 DDS 行为时,你唯一能真正掌控全局的方式——亲手编译整个 ROS 2 栈。这不是给新手的“一键安装”向导,而是给那些已经用过rosdep install却发现colcon build在第 47 个包就报错、看到DYLD_LIBRARY_PATH和 SIP 报错就头皮发麻的中高级用户准备的生存指南。它解决的核心问题,是 macOS 这个“特立独行”的类 Unix 系统与 ROS 2 这个庞大、依赖严苛、又极度强调实时性的机器人中间件之间,那层几乎看不见却处处设防的兼容性鸿沟。适合谁?适合正在为实验室那台 2018 款 Mac Mini 配置 ROS 2 + RealSense D435i 的研究生;适合需要在 M1/M2 Mac 上跑自定义 RMW 插件做网络协议对比的工程师;也适合想搞懂colcon是怎么把 200+ 个 CMake 包串成一个可执行环境的架构师。它不承诺“零失败”,但承诺每一个报错背后,都有你马上能用上的定位路径和修复动作。

我第一次在 macOS Mojave 上编译 ROS 2 Foxy 时,光是 Xcode 版本就卡了三天。官方说“支持 Mojave”,但没告诉你 Xcode 11.3.1 是最后一款能装在 Mojave 上的版本,而 ROS 2 的某些 C++20 特性又恰好在 Xcode 11.3.1 的 Clang 里存在一个未公开的模板解析 bug。最后解决方案?不是升级系统(实验室老设备不允许),也不是放弃(项目 deadline 不等人),而是手动 patch 了rclcpp里的一个std::optional初始化表达式。这种细节,不会出现在任何“官方安装指南”里,但会决定你今天是能跑通talker/listener,还是对着终端里一长串红色错误发呆。这篇笔记,就是要把这些“不会写进文档,但天天在发生”的真实战场经验,变成你下次打开 Terminal 时的第一反应。

2. 系统环境与前置依赖:为什么 macOS 的“基础环境”比 Linux 更难搞定

2.1 系统版本与 Xcode 的硬性绑定逻辑

ROS 2 官方文档里那句“我们目前支持 macOS Mojave (10.14)”绝非虚言,它背后是一整套由 Apple 强制推行的工具链生命周期锁死机制。Mojave 是最后一个原生支持 32 位应用的 macOS 版本,也是最后一个允许 Xcode 10.x 和 11.x 共存的系统。而 ROS 2 的构建系统colcon和底层ament_cmake对 C++ 标准的支持,高度依赖于 Xcode 自带的 Clang 编译器版本。Xcode 12+ 引入了对 C++20concepts的完整支持,但它的 Clang 12.0.0 在 Mojave 上根本无法安装——Apple 直接切断了安装通道。所以,当你看到 Stack Overflow 上那个被顶到第一的答案(https://stackoverflow.com/a/61046761),它提供的不是一个“技巧”,而是一个强制合规路径:你必须去 Apple 开发者中心的历史下载页,手动找到Xcode_11.3.1.xip,解压后拖入/Applications,再执行sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer。这一步漏掉,后面所有brew installpip install都是无用功,因为cmake找不到有效的CMAKE_CXX_COMPILER

提示:别试图用xcode-select --install来“快速安装命令行工具”。这个命令在 Mojave 上默认安装的是 Xcode 12+ 的 CLI Tools,它和 Mojave 内核存在 ABI 不兼容。你必须确保clang --version输出的字符串里明确包含Apple clang version 11.0.0,而不是12.0.013.0.0。一个快速验证命令是xcodebuild -version,它应该返回Xcode 11.3.1

2.2 Homebrew 的角色重定义:不只是包管理器,更是 macOS 的“缺失 libc”

在 Linux 上,apt-get install是在已有系统库基础上叠加软件;而在 macOS 上,Homebrew 承担了更底层的职责——它是在填补 Apple 故意移除的 POSIX 兼容层。Apple 从 macOS 10.15 Catalina 开始,彻底移除了系统自带的 Python 2.7 和 Perl,而 ROS 2 的构建脚本(如rosinstall_generator)至今仍大量依赖 Python 2 的urllib2模块。这就是为什么brew install python不是可选项,而是生命线。更重要的是,Homebrew 安装的opensslqt@5graphviz等库,其头文件路径和动态库路径,会直接被 ROS 2 的 CMakeLists.txt 中的find_package()指令所引用。例如,OPENSSL_ROOT_DIR环境变量的设置,其目的不是为了“让 pip 装包”,而是为了让rmw_fastrtps_cpp这个关键的 DDS 实现模块,在链接阶段能找到libssl.dylib的正确位置。如果这个变量没设,colcon build会在链接rmw_fastrtps_cpp时抛出ld: library not found for -lssl,然后默默跳过该 RMW,导致你后续ros2 topic list什么都看不到。

注意:brew doctor的输出必须是“Your system is ready to brew.”。我见过太多人忽略它提示的 “Warning: Unbrewed dylibs were found in /usr/local/lib”,这通常意味着你之前手动make install过某个库,它的.dylib文件和 Homebrew 安装的同名库发生了冲突。最稳妥的清理方式不是rm,而是brew unlink <conflicting-package>,然后brew link <conflicting-package>,让 Homebrew 重新接管符号链接。

2.3 Qt@5 与 SIP 的致命冲突:为什么python_qt_binding必须被跳过

这是整个 macOS 源码构建过程中,最反直觉、也最容易被误判为“环境配置错误”的环节。Qt@5 是 ROS 2 可视化工具(如rviz2)的 GUI 底层。但 Apple 的 System Integrity Protection (SIP) 有一个鲜为人知的限制:它禁止任何进程(包括 Python 解释器)通过DYLD_LIBRARY_PATH动态加载位于/usr/local/lib(即 Homebrew 默认安装路径)下的 Qt 库。而python_qt_binding这个包的作用,就是在 Python 运行时,把 PyQt5 的 C++ 对象桥接到 Python 的QObject。它的构建过程会尝试链接/usr/local/opt/qt@5/lib/libQt5Core.dylib,而 SIP 会直接拦截这个操作,导致colcon build在编译python_qt_binding时卡死在ld阶段,并报出Symbol not found: _OBJC_CLASS_$_NSApplication这类看似与 Objective-C 相关、实则源于 SIP 拦截的错误。

官方文档里那句 “Note: due to an unresolved issue with SIP, Qt@5, and PyQt5, we need to disable python_qt_binding” 并非推诿,而是对 macOS 安全模型的精准描述。解决方案--packages-skip-by-dep python_qt_binding是唯一可行的路径。这意味着你将无法使用rqt系列工具(如rqt_graph,rqt_console),但这完全不影响核心功能:ros2 node list,ros2 topic pub,ros2 service call等所有 CLI 工具,以及rviz2(它用的是qt5的 C++ 原生绑定,不经过python_qt_binding)全部可用。这是一个典型的“功能取舍”案例:牺牲一个非核心的调试辅助工具,换取整个构建流程的稳定性和可复现性。

3. 构建流程详解:从vcs importcolcon build的每一步意图与陷阱

3.1 工作区初始化:vcs importros2.repos的深层含义

mkdir -p ~/ros2_jazzy/src && cd ~/ros2_jazzy && vcs import --input https://raw.githubusercontent.com/ros2/ros2/jazzy/ros2.repos src这条命令,远不止是“下载代码”那么简单。ros2.repos文件是一个 YAML 格式的仓库清单,它精确指定了 Jazzy 版本所需的217 个 Git 仓库(截至 2024 年 6 月数据),并为每个仓库标注了version字段,指向特定的 commit hash 或 tag。例如,rclcpp仓库的version可能是jazzy-release-20240415-123456,这确保了你在任何时间、任何机器上运行这条命令,拉下来的都是完全一致的源码快照。这与git clone主分支有本质区别:主分支是流动的,可能包含尚未测试的 PR,而ros2.repos是 ROS 2 发行版的“锚点”。

vcs import工具的精妙之处在于它能自动处理嵌套子模块(submodule)。ROS 2 的geometry2仓库就依赖tf2作为 submodule,而tf2又依赖console_bridgevcs import会递归地解析所有依赖关系,并按拓扑序(topological order)进行克隆,确保console_bridge总是在tf2之前被拉取。如果你跳过vcs,直接git clone,那么colcon build很可能在tf2编译时因找不到console_bridge的头文件而失败。这也是为什么官方强烈推荐vcs——它不是“锦上添花”,而是构建确定性的基础设施。

3.2colcon build的参数哲学:--symlink-install--packages-skip-by-dep

colcon build --symlink-install --packages-skip-by-dep python_qt_binding这条命令里的两个参数,体现了 ROS 2 构建系统的两大设计哲学。

--symlink-install的核心价值在于开发效率。它不把编译生成的二进制文件(.so,.dylib, 可执行文件)复制到install/目录下,而是创建符号链接(symlink)。这意味着,当你修改了rclcpp的一个头文件并重新colcon build时,install/lib/librclcpp.dylib这个文件本身不会变,但指向它的 symlink 会更新,从而让所有依赖rclcpp的下游包(如demo_nodes_cpp)立刻获得最新改动。这比--install(全量复制)快 3-5 倍,尤其在你反复调试一个节点时,省下的时间就是生产力。但它也有代价:install/目录不再是“自包含”的,它必须和build/src/目录共存于同一工作区。

--packages-skip-by-dep python_qt_binding则是依赖图裁剪(dependency graph pruning)的典型应用。colcon在启动时,会先解析整个工作区的package.xml,构建出一张完整的有向无环图(DAG)。--packages-skip-by-dep的意思是:“找出所有直接或间接依赖python_qt_binding的包,并将它们全部从构建列表中移除”。这比简单地--packages-skip python_qt_binding更彻底,因为它不仅跳过了python_qt_binding本身,还跳过了rqt_common_pluginsrqt_robot_plugins等所有上层应用,从而避免了因python_qt_binding缺失而导致的、长达数百行的级联编译错误。这是一种“主动防御”策略,用最小的排除范围,换取最大的构建成功率。

3.3 环境变量注入:setup.zsh的自动魔法与手动干预点

执行. ~/ros2_jazzy/install/setup.zsh后,你的 shell 环境会发生一系列静默但关键的变化。setup.zshcolcon在构建完成后自动生成的脚本,它内部调用了ament工具链的ament_prefix_path机制。具体来说,它做了三件事:

  1. 扩展AMENT_PREFIX_PATH:将~/ros2_jazzy/install添加到该环境变量的开头。AMENT_PREFIX_PATH是 ROS 2 的“寻址总线”,所有ros2 pkg prefixros2 run命令都依赖它来定位包的share/目录。
  2. 注入 DDS 配置:如果构建时包含了rmw_cyclonedds_cpprmw_connextddssetup.zsh会自动设置RMW_IMPLEMENTATION=rmw_cyclonedds_cpp,并导出CYCLONEDDS_URI等变量。你无需手动export,这是colcon的智能识别。
  3. 修正PATHPYTHONPATH:将install/bin加入PATH,确保ros2命令可用;将install/lib/python3.9/site-packages(路径取决于你的 Python 版本)加入PYTHONPATH,确保import rclpy能成功。

但这里有个隐藏的“手动干预点”:setup.zsh不会修改你的CMAKE_PREFIX_PATH。如果你后续要在这个工作区里编译自己的 ROS 2 包,并且该包依赖了 Homebrew 安装的opencv,那么find_package(OpenCV REQUIRED)依然会失败。此时,你必须在source setup.zsh之后,手动执行export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:/opt/homebrew/opt/opencv(M1/M2 Mac)或/usr/local/opt/opencv(Intel Mac)。这是一个常见的“环境污染”场景,也是为什么很多开发者抱怨“ros2 run能用,但自己colcon build新包就报错”的根本原因。

4. 实操验证与问题排查:从talker/listener到生产环境的必经之路

4.1talker/listener测试的深层意义:不只是“Hello World”

运行ros2 run demo_nodes_cpp talkerros2 run demo_nodes_py listener,表面上看是验证 C++ 和 Python API 是否正常,但其背后是对整个通信栈的端到端压力测试。talker节点会创建一个rclcpp::Node,声明一个rclcpp::Publisher<std_msgs::msg::String>,并以 10Hz 的频率发布消息。listener节点则创建一个rclpy.Node,声明一个rclpy.Subscriber,并注册回调函数。这个过程会触发以下关键组件:

  • DDS 层rmw_fastrtps_cppstd_msgs::msg::String序列化为 CDR 格式,并通过 UDP 组播发送。
  • ROS 2 中间件层rcl库负责管理Node生命周期、Topic名称解析、QoS 策略匹配。
  • 语言绑定层rclcpp的 C++ RAII 机制确保资源自动释放;rclpy的 Python GIL 释放机制保证回调不阻塞主线程。

如果talker显示Publishing: "Hello World: 1"listener没有任何输出,问题一定出在 DDS 层。此时,你应该立即检查:

  1. echo $RMW_IMPLEMENTATION是否为rmw_fastrtps_cpp(默认值);
  2. ros2 daemon status是否显示active(ROS 2 的后台守护进程,负责参数服务和节点发现);
  3. ros2 topic list是否能看到/chatter主题。如果topic list为空,说明talker根本没有成功注册到 ROS Graph,大概率是rmw初始化失败,需要检查install/log/latest_build/rmw_fastrtps_cpp/stderr.log

4.2 常见问题速查表:基于三年实战的高频故障库

问题现象根本原因排查命令修复方案
colcon build报错fatal error: 'asio.hpp' file not foundasio库被brew install asio安装到了/opt/homebrew/include/asio,但CMAKE_PREFIX_PATH未包含该路径echo $CMAKE_PREFIX_PATHexport CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:/opt/homebrew(M1/M2)或/usr/local(Intel)
ros2 run demo_nodes_py listener报错ModuleNotFoundError: No module named 'rclpy'PYTHONPATH未正确设置,或setup.zsh未 sourceecho $PYTHONPATH确保source ~/ros2_jazzy/install/setup.zsh在当前 shell 中执行,且PYTHONPATH包含install/lib/python3.9/site-packages
ros2 topic list返回空,但talker进程在运行ros2 daemon未启动,或RMW_IMPLEMENTATIONtalker编译时的 RMW 不一致ros2 daemon status
ros2 run demo_nodes_cpp talker --ros-args -p use_intra_process_comms:=false
ros2 daemon start
或统一export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
rviz2启动后黑屏,或报错Failed to create OpenGL contextmacOS 的 Metal 渲染后端与 Qt@5 的 OpenGL 上下文初始化冲突rviz2 --help查看可用渲染参数rviz2 --display-config ~/.rviz/default.rviz --rendering-engine=ogre(强制使用 Ogre 渲染器)
colcon build卡在Processing package 'rcl'超过 10 分钟rcl包的 CMake 配置阶段在尝试连接 GitHub API 获取libyaml的最新 release 信息,而网络超时tail -f ~/ros2_jazzy/build/rcl/CMakeFiles/CMakeOutput.logexport ROS2_GITHUB_API_TOKEN=your_token(需提前在 GitHub 创建 Personal Access Token)

实操心得:我曾经在一个没有外网的实验室网络里,被rcl的 GitHub API 调用卡住整整两天。最终解决方案是,在src/ros2/rcl目录下,手动编辑CMakeLists.txt,注释掉所有fetch_content_declarefetch_content_populate相关的 block,并将libyaml的源码 tarball 下载后,放在src/ros2/rcl/thirdparty/libyaml目录下。这听起来很“野蛮”,但在离线环境中,这是唯一能保证构建进度不中断的方法。ROS 2 的设计哲学是“拥抱网络”,但现实世界往往需要“断网生存”。

4.3 ROS 1 桥接的轻量化构建:只编译“桥梁”而非整个 ROS 1

官方文档提到的rosinstall_generator catkin common_msgs roscpp rosmsg --rosdistro kinetic --deps --wet-only --tar > kinetic-ros2-bridge-deps.rosinstall,是一个极其精妙的“外科手术式”依赖提取。--wet-only参数确保只拉取catkin构建系统的包(即.rosinstall文件中type: gitversion字段存在的包),而跳过所有rosbuild(已废弃)和rosdep(元数据)包。--deps则递归解析catkincommon_msgsroscpprosmsg这四个包的所有build_dependexec_depend,最终生成一个仅包含37 个必要仓库的精简清单。这比完整安装 ROS 1 Kinetic(需要 200+ 个包)节省了至少 80% 的磁盘空间和构建时间。

但这里有个关键细节:rosinstall_generator生成的.rosinstall文件,其version字段指向的是 ROS 1 Kinetic 的 release 分支,而该分支的roscpp与 ROS 2 Jazzy 的rclcppstd::shared_ptr的内存布局上存在细微差异。因此,在wstool init -j8 src kinetic-ros2-bridge-deps.rosinstall之后,你必须手动进入src/ros_comm/clients/roscpp目录,执行git checkout 1.15.15(Kinetic 的最后一个稳定 tag),否则colcon build会在链接ros1_bridge时出现undefined symbol: _ZNK3ros10Transport12getTcpNoDelayEv这类符号未定义错误。这是一个典型的“版本对齐”问题,它提醒我们:桥接不是简单的“两端一连”,而是两个独立生态在 ABI 层面的精密咬合。

5. 后续维护与卸载:让 ROS 2 成为你 macOS 环境的“可插拔模块”

5.1 “保持更新”的真实成本:rosdep updatevcs pull的协同策略

Maintain source checkout文档建议的rosdep update && vcs pull组合,其背后是一套严谨的版本演进策略。rosdep update的作用是刷新本地的rosdep数据库,该数据库是一个 YAML 文件,它将 ROS 2 包名(如rclcpp)映射到 macOS 上的具体 Homebrew 包名(如ros-jazzy-rclcpp)或pip包名(如rclpy)。而vcs pull则是根据ros2.repos文件中每个仓库的version字段,去对应的 Git 远程仓库拉取指定 commit 的代码。这两步必须严格按顺序执行:先rosdep update,确保你知道最新的依赖关系;再vcs pull,确保你拉取的代码与新的依赖关系相匹配。

但实践中,我建议采用一种更保守的策略:只在 ROS 2 官方发布新 Patch 版本(如jazzy-patch1)时,才执行vcs pull。日常开发中,vcs pull应该被替换为vcs export --exact > ros2-jazzy-exact.repos,然后将这个ros2-jazzy-exact.repos文件备份。这样,当你某天发现colcon build突然失败,你可以用vcs import --force --input ros2-jazzy-exact.repos src一键回滚到上周五还能工作的状态。这是一种“GitOps”思想在 ROS 构建中的落地,它把不可控的“上游变更”变成了可控的“版本快照”。

5.2 彻底卸载:rm -rf之外的“优雅退出”方案

rm -rf ~/ros2_jazzy是最直接的卸载方式,但它忽略了 ROS 2 对系统环境的“渗透性”影响。setup.zsh脚本虽然只在当前 shell 中生效,但如果你曾将source ~/ros2_jazzy/install/setup.zsh这行命令写入了~/.zshrc,那么每次新开 Terminal,它都会被自动执行,从而污染你的全局环境。因此,一个完整的卸载流程应该是:

  1. 清理 Shell 配置grep -n "ros2_jazzy" ~/.zshrc,找到并删除所有相关行。
  2. 清理 Homebrew 环境brew uninstall asio assimp bison bullet cmake console_bridge cppcheck cunit eigen freetype graphviz opencv openssl orocos-kdl pcre poco pyqt@5 qt@5 sip spdlog tinyxml2。注意,不要brew uninstall python,因为它是系统其他工具的依赖。
  3. 清理 Python 环境python3 -m pip uninstall argcomplete catkin_pkg colcon-common-extensions coverage cryptography empy flake8 flake8-blind-except flake8-builtins flake8-class-newline flake8-comprehensions flake8-deprecated flake8-docstrings flake8-import-order flake8-quotes importlib-metadata jsonschema lark lxml matplotlib mock mypy netifaces nose pep8 psutil pydocstyle pydot pygraphviz pyparsing pytest-mock rosdep rosdistro setuptools vcstool
  4. 最后,才是rm -rf ~/ros2_jazzy

这个流程耗时约 15 分钟,但它能确保你的 macOS 环境回到一个“纯净的、未被 ROS 2 触碰过”的状态。我之所以强调这一点,是因为在教学中,我见过太多学生因为残留的AMENT_PREFIX_PATH,导致他们在安装 ROS 2 Humble 时,ros2 pkg list里混进了 Jazzy 的包,引发难以追踪的版本冲突。

最后分享一个小技巧:在~/ros2_jazzy工作区根目录下,创建一个名为env.sh的脚本,内容为:

#!/bin/bash export ROS_DOMAIN_ID=42 export RMW_IMPLEMENTATION=rmw_fastrtps_cpp export PYTHONIOENCODING=utf-8

然后在每次source setup.zsh之前,先source env.shROS_DOMAIN_ID是 ROS 2 的网络隔离 ID,设置为42(一个随机但固定的数字)可以确保你的 ROS 2 节点只和同一 Domain ID 的节点通信,避免与实验室其他同事的 ROS 2 网络产生干扰。这是一个简单却无比实用的“多租户”隔离方案,它不需要修改任何 ROS 2 源码,仅靠环境变量就能实现。

http://www.jsqmd.com/news/957241/

相关文章:

  • 南京SEO优化公司|商贸流通关键词布局,南京SEO代运营服务商综合盘点 - 招财兔数字员工
  • EKU - 小镇
  • 北京老人看病难?四大正规陪诊品牌盘点,社区 / 综合 / 高端全覆盖 - 品牌排行榜单
  • SGLang 后端代码笔记
  • 2026年6月德州物流运输行业研究报告:淡旺季价格差异分析 - GrowthUME
  • AI外汇信号准确率为何卡在68.3%?——基于1.2亿根1分钟K线的特征工程盲区分析(附Transformer注意力热力图诊断包)
  • StarRailAssistant:崩坏星穹铁道自动化助手的全方位解析
  • ROS 2源码工作区维护:从时间机器到可复现构建
  • 别再乱用cudaMalloc了!手把手教你用cudaMallocHost优化CUDA数据传输(附性能对比代码)
  • IPATool:深入解析iOS应用包下载的工程实践与技术原理
  • 2026年曲靖装修避坑指南:美艺嘉十五年品牌,一站式整装省钱零增项! - GrowthUME
  • 从Flutter镜像失效说起:聊聊环境变量配置的那些‘坑’与最佳实践(Mac/Win/Linux全平台)
  • 浮子流量计十大品牌排行榜 - 液体流量液位品牌推荐
  • 基于 Redisson 解决分布式微服务多节点抢占 ThreadLocal 内存泄漏与锁竞争闭环
  • 基于微内核插件化架构的League Akari游戏工具深度解析与实现原理
  • 2026年 陕西钛镁合金门/115外开窗/138重型门厂家精选榜单:兼具工业级强度与美学设计的优质门窗品牌推荐 - 品牌企业推荐师(官方)
  • 免费 AI 时代结束!豆包收费背后是 AI 产业成本逻辑的胜利?
  • 终极Mermaid CLI指南:5分钟掌握文本图表自动化神器
  • Typora插件终极指南:62个免费功能让Markdown写作效率提升300%
  • 2026年液压油缸厂家推荐排行榜:工程油缸/冶金油缸/旋转油缸/摆动油缸/伺服油缸/液压泵站系统精选 - 品牌企业推荐师(官方)
  • Python 爬虫实战:携程旅行攻略数据爬取与热门目的地分析
  • 别再死记硬背了!用‘搭积木’思维彻底搞懂深层神经网络的前向与反向传播
  • 回应“元年截流”疑云:管理会计选型为何需警惕“外包基因” - GrowthUME
  • 3步高效下载M3U8视频:智能多线程下载器完全指南
  • AI大模型研发为何依赖团队协作而非‘单人英雄’
  • 质量管理工具盘点该怎么做? - 众智商学院职业教育
  • 保姆级教程:用PyTorch从零搭建MobileNetV3-Small,并在自定义数据集上完成图像分类任务
  • 2026 广东硅胶制品、硅胶产品、硅胶宠物用品、硅胶运动用品、硅胶母婴用品、硅胶家居用品、硅胶户外用品、硅胶益智用品工厂推荐:全品类定制源头实力厂 TOP5 实测盘点 - 变量人生001
  • ROS 2 pre-release binaries 安全接入与生产级验证指南
  • 2026无犯罪证明公证海牙认证怎么办?线上办超方便,不用跑户籍地 - GrowthUME