news 2026/9/8 22:19:55

Isaac Sim与Isaac Lab实战指南:从环境搭建到具身智能RL训练

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Isaac Sim与Isaac Lab实战指南:从环境搭建到具身智能RL训练

如果你上一篇文章已经通读,会知道我在梳理具身智能仿真器这片地图时,特意把NVIDIA这套组合单独拎了出来。今天这篇就围绕 Isaac Sim 和 Isaac Lab 展开,把这两兄弟在具身智能研发里各自扛什么活、怎么配合、从零怎么搭起来,以及我在真实项目里踩过的坑,一次说清楚。文章适合两类人:一类是刚接触具身智能、想用仿真器做验证的算法工程师;另一类是想把机器人本体接入仿真环境做二次开发的硬件工程师。无论你是哪种,这套工具链都会占掉未来几年的仿真主流生态,早看清它的结构,后面能少走很多弯路。

1. 为什么Isaac Sim和Isaac Lab必须成对出现

1.1 Isaac Sim在具身智能仿真里到底扮演什么角色

很多第一次接触具身智能的人会把Isaac Sim理解成"一个渲染得比较好看的机器人仿真器",这个理解不算错,但太浅了。Isaac Sim本质上是一个基于Omniverse平台构建的机器人仿真应用,它的底层是USD(Universal Scene Description)场景描述体系加PhysX物理引擎,上层挂了机器人导入、传感器仿真、ROS2桥接、合成数据生成的扩展模块。它能把机器人的运动学、动力学、关节驱动、碰撞检测、相机图像、激光雷达点云这些东西在一个统一的场景图里组织起来。

这个统一的意义比大多数人想象得大。传统机器人仿真里,MuJoCo擅长接触和关节动力学,但渲染很弱;Gazebo能接ROS,但物理精度和画质都比较吃力;PyBullet轻量好用,但大规模并发场景支持有限。Isaac Sim的定位是"可扩展的高保真仿真底座",它可以跑数千个并行环境,支持RTX光线追踪,还能和Omniverse的其他模块打通,比如通过USD实现多软件协同。这个能力在具身智能任务里非常关键,因为具身智能的训练本质上需要海量环境交互数据,单机单环境跑强化学习根本不现实。

我在实际项目里对Isaac Sim最深的体感是:它不是一个拿来就跑的工具,而是一个需要你理解"场景即USD、扩展即OmniGraph节点"这种思维方式的平台。第一次从URDF导入机器人模型、在视图里拖动关节、给关节加上执行器属性,这一套流程走通之后,你会发现它比传统仿真器多了一个维度——一切都可以编程控制,一切都可以通过扩展脚本实现自动化。这个特性是后续能叠Isaac Lab或者其他学习框架的根本原因。

1.2 Isaac Lab补上的正是RL训练最缺的那一层抽象

但Isaac Sim本身并不关心你怎么训练一个强化学习策略。它提供的是仿真能力,不是学习框架。如果你直接在Isaac Sim的Python API里写一个RL环境,你会发现典型的"环境-智能体"循环里太多重复工程:批量环境管理、观测值收集、动作映射、奖励计算、终止条件判断、域随机化触发、训练日志记录。这些东西每个项目都要写,但每个项目写出来的版本都差不多,而且很容易和仿真器的底层API耦合得乱七八糟。

Isaac Lab解决的就是这个"中间层"问题。它是NVIDIA开源的、构建在Isaac Sim之上的机器人学习框架,提供了一套分层的环境抽象:ArticulationView负责批量控制机器人实体,ManagerBasedRLEnv负责用配置方式组合观测、动作、奖励、事件,DirectRLEnv则给需要细粒度控制的算法研究提供直接操作仿真器的接口。你可以把Isaac Sim理解成游戏引擎,把Isaac Lab理解成游戏AI训练框架,引擎负责渲染和物理,框架负责agent-loop、奖励管理和策略训练。

我见过不少团队绕开Isaac Lab,直接在Isaac Sim里自研训练框架,最终都会返工。原因很简单:Isaac Lab不光是省了你写样板代码,它把强化学习环境构建过程中的大量"约定俗成"变成了标准接口,官方文档、社区示例、开源checkpoint全都建立在这套接口之上。自己不采用这套抽象,等于放弃了整个生态的积累。所以我的建议很直接:除非你有极强的理由做彻底定制,否则新项目一律在Isaac Lab之上搭,别从底层自造轮子。

2. 环境搭建的坑,集中在版本和依赖上

2.1 Isaac Sim安装:桌面版和容器我各用的场景

安装Isaac Sim看起来很简单,实际上版本、驱动、依赖的匹配问题能把人折腾一整天。最常用的桌面安装方式是Omniverse Launcher。这个方案适合学习和交互调试,因为在Launcher里可以方便地切换不同版本的Isaac Sim,还能从界面里直接打开场景编辑器观察模型。

但如果你做的是正经训练项目,或者要在服务器上跑批量实验,我建议直接走Docker容器路线。生产环境用容器能避免本机Python环境、CUDA版本和图形库互相污染,还能用Docker Compose或者Kubernetes做调度。我当时在服务器上部署的时候,就遇到过宿主机显卡驱动太新导致容器内CUDA不兼容的诡异问题,后来固定了驱动版本和容器镜像标签,才彻底解决。

另外一个非常容易踩的坑是WSL2。很多Windows用户想通过WSL2跑Isaac Sim,虽然目前能跑通,但对GPU显存和Windows图形驱动的依赖很敏感,经常出现渲染黑屏或者物理仿真速度极慢。我的经验是:Windows上学习可以,正经跑实验还是准备一台Linux + NVIDIA GPU的机器。显卡方面,显存建议至少16GB,如果你要跑大量并行环境和相机渲染,24GB是起步线。

2.2 Isaac Lab的两种安装路径与验证方式

Isaac Lab的安装有两条主流路径。一种是通过pip直接安装,一条命令解决依赖;另一种是克隆源码后用脚本安装。我自己的做法是:日常用pip安装的稳定版本,做二次开发和调试时切换到源码方式。

pip方式最简洁:

pip install isaaclab

如果你是源码方式,通常是这样:

git clone https://github.com/isaac-sim/IsaacLab.git cd IsaacLab ./isaaclab.sh --install

不管哪种方式,有两点值得强调。一是必须确保Isaac Lab能找到Isaac Sim的位置,通常通过环境变量指定,源码方式的脚本会自动处理,但pip方式偶尔需要手动设置ISAAC_SIM_PATH或者让conda环境包含isaacsim包。二是别忽略训练依赖,Isaac Lab支持多个强化学习库后端,比如rsl_rl、skrl、rl_games,你至少要装一个,否则示例脚本跑到训练阶段会直接报找不到模块。

验证安装是否成功最好的方式不是跑教程脚本,而是直接跑一个简单RL任务。我常用来验证的组合是:

python scripts/train.py --task Isaac-Cartpole-Direct-v0 --num_envs 32 --headless

如果这个能正常在终端里看到PPO的日志输出,说明Isaac Sim、Isaac Lab、训练库三者的连接没有问题。这个验证比任何"安装成功"提示都靠谱,因为它把物理仿真、环境管理、策略更新一整条链路全串起来了。

2.3 第一遍跑示例时最常见的三个报错

第一次跑Isaac Lab示例时,大家碰上最多的报错基本集中在三个位置。第一个是导入层面,报ModuleNotFoundError: isaacsim,这个几乎都是环境变量或Python路径没配对,尤其常见于conda环境与系统Python混用。解决办法是确认当前环境的Python版本和Isaac Sim要求的版本一致,并把Isaac Sim包的路径加到PYTHONPATH里。

第二个是运行层面,报类似libpython3.x.so.1.0: cannot open shared object file,这个一般是conda环境的动态库链接问题,我处理过几次,最简单的方案是在启动前设置LD_LIBRARY_PATH指向对应Python版本的库目录,或者直接用Isaac Sim自带的Python解释器来跑Isaac Lab脚本。

第三个是显存问题,报CUDA out of memory,这在多环境并行训练时非常普遍。解决办法不是硬加显存,而是调低num_envs、降低渲染分辨率,或者使用--headless模式免去交互渲染的显存开销。我见过有人一上来就num_envs=4096结果直接OOM,这属于对仿真器资源消耗缺少预估,后面我会单独讲训练参数的配置经验。

3. 从URDF到可训练RL环境,核心链路拆解

3.1 模型导入:URDF转USD后必须检查的物理参数

绝大多数具身智能项目的第一步,是把机器人描述文件导入Isaac Sim。最常见的输入是URDF,一部分人形机器人或灵巧手项目会用MJCF。Isaac Sim内置了URDF导入器,能把URDF和网格文件转换成USD,但"能导入"和"能训练"完全是两码事。

导入之后我通常会按固定清单检查物理参数:关节类型是不是符合预期、mass是否合理、惯性张量有没有异常、摩擦系数是否被重置成了默认值、关节的阻尼和刚度是否设了初值。尤其是惯性参数,很多CAD导出的URDF里惯性矩阵的单位或坐标系和高仿真库的假设不一致,导入后机器人会浑身发抖或者直接穿模,这种情况大概率不是仿真器的问题,而是模型数据本身就有瑕疵。

还有一个被忽视的细节是刚体坐标系。URDF里的link坐标系和USD的up axis、scale需要对齐,否则机械臂末端位置看起来对,实际运动方向是偏的。我通常会在导入后把机器人放在一个固定base上,手动给每个关节一个正弦驱动,观察一段时间,确认运动学符合预期才开始搭环境。这一步花30分钟,能省后面训练跑偏的三天时间。

3.2 环境四件套:Scene、Action、Observation、Event

在Isaac Lab里构建一个RL环境,可以拆成四个核心部分。Scene负责在仿真世界中布置实体,包括机器人本体、被抓物体、工作台、传感器;Action定义智能体的动作接口,常见的有关节位置目标、关节速度目标和关节力矩控制;Observation定义强化学习智能体从环境中获取的状态信息;Event则管理环境生命周期中的变化,包括重置、随机化、物理材质替换等。

这四个部分在ManagerBasedRLEnv里通过配置类组合,我用一个机械臂任务举个例子。Scene里注册机器人Articulation和一个被抓物体RigidObject;Action部分把机械臂的关节位置作为动作空间,并设置动作缩放和限幅;Observation部分把关节位置、关节速度、抓手与目标物体的相对位姿拼成策略观测;Event部分注册了一个随机重置函数,每次episode开始时把目标物体的位置在一定范围内随机撒点。

刚开始接触这个体系的人会觉得配置比手写环境还复杂,但它的优势在于可组合、可复用。同一个机器人,想换奖励函数就改RewardsCfg;想换随机化策略,就加一个EventCfg里的随机化函数。这种模块化让实验管理的成本大幅降低,也让团队协作时每个人能独立改自己负责的那一块。

如果你需要高度定制,比如要在step函数里做特殊的物理操作,或者要控制仿真过程中某一条传感器数据流,那可以考虑DirectRLEnv。它的写法和经典gym环境更像,直接在_setup_scene_get_observations_apply_actions里写逻辑,自由度最高。

3.3 ManagerBasedRLEnv与DirectRLEnv的选型逻辑

这两个基类怎么选,我自己的标准很朴素:任务逻辑是否需要直接操控仿真API的moment级细节。如果答案是"基本不需要",选ManagerBasedRLEnv;如果答案是"经常需要",选DirectRLEnv。

ManagerBasedRLEnv最大的好处是声明式。你不需要关心环境step函数内部的调度顺序,只需要声明好ActionCfg、ObservationCfg、RewardCfg、EventCfg,框架会自动处理批量环境的遍历。团队协作时,新人看配置文件比看代码快,所以那些需要频繁调奖励、换随机化的上层任务,用ManagerBased效率高得多。

DirectRLEnv适合算法研究者。比如你在做多机器人协同控制,需要在同一个step里对不同机器人施加不同的控制策略,或者你想在仿真过程中动态修改物理材质、改变控制频率,这时候ManagerBased的抽象反而成为阻碍。我做过一个双机械臂协作任务,因为需要精确控制两个臂的相位差,最终被迫从ManagerBased迁移到DirectRLEnv,工作量不小,所以如果预判任务有这类需求,一开始就别用ManagerBased硬凑。

4. 奖励塑形、域随机化与sim2real的关键一步

4.1 具身任务奖励设计的组合思路

很多新人在奖励设计上的第一个误区是:把奖励想成"怎么让网络达到最终目标"。实际操作中,奖励更应该被拆解成多个可以独立调整的子项,每一项负责一个约束或者一个阶段目标。拿机械臂抓取举例子,一个我常用的奖励模板是:位置逼近奖励加一个小的指数衰减项,让机械臂末端越靠近目标奖励越大;抓手接近奖励,用于在抓取阶段引导抓手张开到合适宽度;动作变化率的惩罚项,防止策略输出高频抖动;关节力矩惩罚项,防止出现暴力控制;最后加一个稀疏的成功奖励,在判定抓取成功时一次性给出大额正反馈。

这些子项的系数不是一次调好的,而是先让每一项单独存在,观察策略行为是否合理,再逐步增加。我在实践中发现,很多人一次性把所有奖励项堆上去,结果网络根本不收敛,因为各奖励项的尺度差异太大,某个惩罚项直接淹没了其他信号。正确的做法是先保证每个奖励项的量级差不多,再用系数做微调。

终止条件的设置同样重要。成功终止要配合大额bonus,失败终止要给出合理的负反馈,但失败条件不能太激进,否则网络学不到任何中间阶段的探索信号。比如机械臂轻微碰到桌子就判定失败,这个episode就没法让智能体学会"绕过障碍物"这个更复杂的技能。

4.2 域随机化怎么配置才能不白给

域随机化是sim2real迁移最常用也最有效的手段之一,但配置不好经常会白给。Isaac Lab的Event机制可以在环境reset或者训练过程中动态修改物理参数,比如刚体质量、关节摩擦、关节阻尼、摩擦系数、观察噪声。

我第一次做域随机化时犯的错误是随机范围拍脑袋。摩擦系数设置成0.1到2.0这么宽,训练出来的策略在仿真里确实鲁棒,但在真机上反而表现不稳定,因为策略为了适应极端参数,学出来的动作变得保守。后来我改成了围绕真机标称值做小范围随机,先和机械臂厂家的标称参数对齐,再逐步扩大范围。有效的域随机化不是范围越大越好,而是要让随机分布尽量覆盖真机可能遇到的参数漂移区间,同时不把训练分布拉得太散。

除了物理参数,观察噪声和动作延迟抖动也是sim2real的关键。真实传感器的观测一定有噪声,控制指令从发出到执行也一定有延迟。在EventCfg里加入高斯观测噪声、在动作接口里加入延迟buffer模拟执行延迟,能让策略在部署到真机时少掉一截精度。还有一个容易忽略的点是仿真步长和控制频率要匹配真机控制器,比如真机控制频率是100Hz,仿真里最好也别跑500Hz,否则训练出来的策略对时间尺度过度敏感。

4.3 训练输出与策略导出:checkpoint、ONNX和TensorRT

训练完成后,把策略导出并部署到真机是具身智能流程里非常关键的一步。Isaac Lab配合rsl_rl训练出来的默认产物是PyTorch checkpoint,包含网络权重和优化器状态。如果你只是继续训练,直接用checkpoint即可;如果想部署到Jetson或者其他嵌入式平台,通常需要转成ONNX,再视情况转成TensorRT加速。

我在导出ONNX上踩过一个坑:训练时策略输入的观测是GPU张量,归一化参数在训练环境里自动维护;但导出ONNX时如果忘记把归一化层一起封装进网络,部署后输入分布和训练时不一致,策略表现会骤降。正确做法是导出前把running mean和running variance作为常量固化进模型,导出后再在仿真环境里用一组没有参与训练的随机观测做验证,确认输入输出维度、数值范围都一致再部署。

还有一个经验是:sim2real从来不是"导出模型、复制过去"这么简单。即使做了域随机化,真机上的动力学、延迟、传感器噪声还是会和仿真有差距。我通常的做法是先在真机上用小幅度动作做开环测试,确认关节方向和限位正确,再做闭环测试,并记录实际控制频率和延迟,回到仿真里校对这些参数。这个过程需要迭代好几轮,但每轮都能让策略在真机上更稳。

5. 用ROS2桥接开发板:连接问题往往不在仿真器

5.1 HIL调试的常见架构

当你需要在Isaac Sim里运行仿真,同时把控制指令发给真实开发板执行,或者从开发板读取真实传感器数据回灌仿真,最常用的是ROS2桥接。Isaac Sim提供了omni.isaac.ros2_bridge扩展,启动后可以发布/joint_states/odom等状态话题,也可以订阅/cmd_vel/joint_command这样的控制话题。

硬件在环的常见架构有两种。第一种是仿真器作为"虚拟机器人",开发板跑控制算法,控制频率由开发板主导,仿真器只负责回传状态。第二种是仿真器训练出的策略直接接收真实传感器信息并把动作指令下发到执行器,仿真器退化为物理验证平台。无论哪种,两端交换的都是标准化ROS2消息,所以只要话题名称、消息类型、坐标系对齐,理论上可以非常灵活地组合。

5.2 时间戳、domain_id和控制频率,这三个坑最隐蔽

ROS2桥接看起来简单,实际联调时最容易出问题的是三个隐蔽环节。

第一个是时间戳。仿真器有仿真时钟,开发板有真实时钟,如果两端各自按自己的时间戳处理数据,策略观测到的状态就会有时间偏移。解决办法是让仿真时间作为主时钟发出来,开发板通过同步机制对齐,或者反过来以真实时钟为准,仿真器按对应比例推进。最怕的是没人管时间基准,两边各算各的,各种随机抖动就来了。

第二个是ROS2的domain_id。默认情况下domain_id是0,单机调试没问题,但如果仿真器和开发板运行在不同机器上而且没有正确配置domain_id,会发现ROS2话题完全发现不了对方。排查时先确认两端能不能PING通,再用ros2 topic list对比两端话题列表,最后确认ROS_DOMAIN_ID一致。

第三个是控制频率。仿真器内的物理步长和ROS2节点的控制频率必须匹配。如果Isaac Sim的物理步长是500Hz,而你发布关节状态只发100Hz,那么策略的输入会丢失高频动力学信息,出现抖动。另一个常见情况是开发板上的控制循环频率和策略期望的动作频率不一致,需要加一个缓冲或者插值逻辑,我的做法是在仿真端订阅控制指令后做一次平滑滤波,减少频率失配造成的冲击。

5.3 连接类报错的排查思路

联调过程中最让人头疼的是一大堆"连接失败"类报错。我之前在一套嵌入式开发板上调试时,遇到过调试器反复提示目标连接异常,类似c674x_0: error connecting to the target,后面跟着错误码-1180。这类问题看似奇怪,其实本质都指向同一个方向:上位机没能和目标硬件建立稳定连接。

排查这类连接错误,不要一头扎进协议细节,先按链路分层拆。第一步确认物理层:线缆、接口、供电、目标板有没有正常上电运行,这是最容易被忽略的;第二步确认使能层:目标板上的调试接口是否被程序占用,是否需要先解除复位或者进入特定启动模式;第三步确认协议层:驱动是否安装、权限是否足够、通信速率和时钟参数是否匹配。很多时候把通信速率降一档,原来频繁报的连接错误就消失了。仿真器与开发板的ROS2连接也是同样的思路,先通网络、再通中间件、最后查应用层,盲目重装驱动解决不了任何问题。

6. 具身智能学习路线与二次开发的个人建议

6.1 系统化学习顺序:不要上来就啃源码

很多读者问我具身智能应该怎么学,尤其是看到Isaac Sim和Isaac Lab这套复杂工具链之后,完全不知道从哪里下手。我的建议是有一个明确的先后顺序,千万不要一上来就啃源码。

第一步,先把Isaac Sim当作一个交互式仿真器玩熟,在界面里导入一个现成机器人,拖动关节、添加传感器、开启ROS2 bridge,感受一下这个平台的边界。第二步,跑通Isaac Lab的自带示例任务,哪怕是最简单的cartpole,重点不是看训练效果,而是理解一个RL环境在这里是怎么被组织起来的。第三步,把一个示例任务彻底拆开,看看它的config类里每一项是干什么的,改一个奖励系数,重新训练,观察行为变化。第四步,再去接触URDF导入、USD编辑、OmniGraph扩展这些偏底层的内容。这个顺序能让你在最短时间里建立起对整套工具链的整体认知,而不是陷在某个细节里出不来。

学习过程中肯定会遇到版本更新带来的API变动。我的经验是:不要死记API,要学会看懂官方文档和源码里的类型定义。Isaac Lab的代码风格比较现代,大量使用@configclass装饰器和类型注解,读起来比很多开源项目舒服,遇到问题直接在源码里搜关键词,往往比等社区回复更快。

6.2 二次开发最有价值的三件事

最后聊聊二次开发。具身智能项目里,真正有价值、也最值得投入时间做的,我认为是这三件事。

第一,让自己团队的真实机器人资产能够在仿真环境里跑起来。很多人以为"把URDF导入成功"就算完事,但真正的资产化还包括物理参数标定、关节执行器属性设置、与真实传感器噪声的匹配。这个基础打好了,后续所有训练实验才可信。

第二,把标准化的仿真实验流程建立起来。包括配置文件的版本管理、训练日志的统一格式、不同任务共享的通用技能库。具身智能研发非常依赖大量对比实验,实验流程混乱是最大的隐性成本。

第三,掌握扩展开发能力。当你的任务超出官方示例范围后,一定会需要写自己的Scene配置、自定义传感器、自定义奖励函数乃至自定义训练回调。这些能力的核心是理解Isaac Lab扩展的加载机制和配置类的写法。

关于自定义机器人资产,一个典型的ArticulationCfg大致长这样:

my_robot_cfg = ArticulationCfg( prim_path="{ENV_REGEX_NS}/Robot", spawn=UsdFileCfg(usd_path=f"{PATH_TO_USD}/my_robot.usd"), init_state=ArticulationCfg.InitialStateCfg( pos=(0.0, 0.0, 0.0), joint_pos={"joint_0": 0.0, "joint_1": 0.0}, ), actuators={ "arm": ImplicitActuatorCfg( joint_names_expr=["joint_.*"], stiffness=100.0, damping=10.0, ), }, )

每次写这种配置类,我都会想一遍它背后对应的物理含义:prim_path决定机器人在环境场景树里的挂载位置,init_state决定初始关节位置,actuators里的刚度和阻尼决定了关节对外力的响应方式。只有把这些概念想通了,配置才能写对,而不是照抄。

如果你现在刚准备进入这个领域,我最后想分享一个实在的建议:先把机器人的基础运动学、逆运动学和简单的PID控制弄明白,再跑到RL训练阶段。具身智能看着门槛在算法,实际门槛很大程度在机器人本身。仿真器再强也只是工具,你对机器人的理解深度,决定了你借助仿真器能做多远。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:19:15

不用手写 RL 循环,5 分钟跑通 TRL 大模型强化学习对齐

不用手写 RL 循环,5 分钟跑通 TRL 大模型强化学习对齐 【免费下载链接】trl Train transformer language models with reinforcement learning. 项目地址: https://gitcode.com/GitHub_Trending/tr/trl 想给大模型做 RLHF(基于人类反馈的强化学习…

作者头像 李华
网站建设 2026/9/8 22:18:04

五轴机械臂运动学分析全流程:MATLAB仿真与SolidWorks建模实战

简介:一套完整的五轴机械臂运动学分析学习资料,面向机器人方向工程技术人员、科研人员及高校学生,帮助系统掌握运动学建模、求解与仿真方法。资源共17个文件,压缩包仅1.62MB,涵盖SolidWorks三维模型(12个零…

作者头像 李华
网站建设 2026/9/8 22:17:58

Android声波通信源码深度实践:从4FSK调制到Goertzel解调

简介:面向Android开发者的声波通信实现源码包,聚焦声波编解码、信号调制与收发链路,适合具备基础Android开发经验、希望探索近场声波数据传输技术的读者,也可作为课程设计或毕业设计的参考。包内自带可运行演示,界面与…

作者头像 李华
网站建设 2026/9/8 22:16:39

Gogs 自托管 Git 服务实战:从部署、配置到单二进制架构解析

Gogs 自托管 Git 服务实战:从部署、配置到单二进制架构解析 【免费下载链接】gogs The painless way to host your own Git service 项目地址: https://gitcode.com/GitHub_Trending/go/gogs Gogs 是一个用 Go 编写的自托管 Git 服务,其设计目标是"以最小的痛苦(pa…

作者头像 李华