VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam
VnRobo
Về chúng tôiBảng giáBlogLiên hệ
🇺🇸ENĐăng nhậpDùng thử miễn phí
🇺🇸EN
  1. Trang chủ
  2. Blog
  3. Bê và vận chuyển vật thể: Ổn định trong di chuyển
manipulationrlcarryingmanipulationstability

Bê và vận chuyển vật thể: Ổn định trong di chuyển

Huấn luyện robot bê vật thể ổn định bằng RL — từ carry-flat đến pouring, multi-objective reward cho speed vs stability.

Nguyễn Anh Tuấn26 tháng 3, 20269 phút đọcCập nhật: 16 thg 6, 2026

Bạn đã từng thấy robot bê khay đồ ăn trong nhà hàng chưa? Trông thì nhẹ nhàng, nhưng đằng sau đó là một bài toán cực kỳ phức tạp: robot phải vừa di chuyển nhanh (khách đang đợi), vừa giữ đồ ổn định (không đổ soup), vừa tránh chướng ngại (bàn ghế, người qua lại). Đây là bài toán carrying and transporting — và RL là công cụ mạnh mẽ để giải quyết nó.

Sau khi đã thành thạo grasping, force control, và precision placement, bây giờ chúng ta kết hợp tất cả để giải bài toán vận chuyển vật thể trong thế giới thực.

Phân loại Carrying Tasks

Task Mô tả Độ khó Thách thức chính
Carry Flat Bê vật phẳng (hộp, sách) Thấp Giữ không rơi
Carry Upright Bê vật thẳng đứng (chai, cốc) Trung bình Orientation stability
Carry on Tray Bê khay nhiều vật Cao Multi-object balance
Carry Through Obstacles Bê qua chướng ngại Cao Planning + control
Pour Rót nước/chất lỏng Rất cao Flow rate + tilt control

Multi-Objective Reward: Speed vs Stability

Carrying task là ví dụ điển hình của multi-objective optimization — bạn muốn nhanh nhưng cũng muốn ổn định, và hai mục tiêu này thường xung đột nhau.

Robot carrying objects in environment
Robot carrying objects in environment

import numpy as np

class CarryingReward:
    """Multi-objective reward cho carrying tasks."""
    
    def __init__(self, task_type="carry_upright",
                 speed_weight=1.0, stability_weight=2.0):
        self.task_type = task_type
        self.speed_weight = speed_weight
        self.stability_weight = stability_weight
        
        # Thresholds
        self.max_tilt = np.radians(10)      # Max nghiêng 10 độ
        self.max_accel = 2.0                 # Max gia tốc m/s^2
        self.spill_tilt = np.radians(25)     # Đổ nước
    
    def compute(self, state):
        """
        state: dict chứa tất cả thông tin cần thiết
            - obj_pos, obj_quat, obj_vel, obj_angvel
            - ee_pos, ee_vel, ee_accel
            - goal_pos, goal_dist
            - grasping: bool
            - obstacles_nearby: list of distances
        """
        if not state['grasping']:
            return -100.0, {'dropped': True}
        
        rewards = {}
        
        # ---- STABILITY REWARDS ----
        
        # 1. Tilt penalty — giữ vật thẳng đứng
        tilt = self._compute_tilt(state['obj_quat'])
        if tilt > self.spill_tilt:
            rewards['tilt'] = -50.0  # Catastrophic failure
        else:
            rewards['tilt'] = -3.0 * (tilt / self.max_tilt) ** 2
        
        # 2. Angular velocity — không xoay
        ang_vel = np.linalg.norm(state['obj_angvel'])
        rewards['angular_vel'] = -1.5 * np.tanh(2.0 * ang_vel)
        
        # 3. Acceleration penalty — chuyển động mượt
        accel = np.linalg.norm(state['ee_accel'])
        rewards['accel'] = -0.5 * np.tanh(accel / self.max_accel)
        
        # 4. Jerk penalty — đạo hàm gia tốc
        if 'ee_jerk' in state:
            jerk = np.linalg.norm(state['ee_jerk'])
            rewards['jerk'] = -0.3 * np.tanh(jerk / 5.0)
        
        # ---- PROGRESS REWARDS ----
        
        # 5. Distance to goal
        goal_dist = state['goal_dist']
        rewards['progress'] = 2.0 * (1.0 - np.tanh(2.0 * goal_dist))
        
        # 6. Speed reward (khi xa đích)
        speed = np.linalg.norm(state['ee_vel'])
        if goal_dist > 0.3:
            # Khuyến khích di chuyển nhanh khi xa đích
            desired_speed = min(0.5, goal_dist)
            speed_error = abs(speed - desired_speed)
            rewards['speed'] = 0.5 * (1.0 - np.tanh(3.0 * speed_error))
        else:
            # Chậm lại khi gần đích
            rewards['speed'] = -2.0 * speed
        
        # 7. Obstacle avoidance
        if state.get('obstacles_nearby'):
            min_obstacle_dist = min(state['obstacles_nearby'])
            if min_obstacle_dist < 0.1:
                rewards['obstacle'] = -5.0 * (1.0 - min_obstacle_dist / 0.1)
            else:
                rewards['obstacle'] = 0.0
        
        # 8. Success bonus
        if goal_dist < 0.05 and tilt < self.max_tilt:
            rewards['success'] = 30.0
        else:
            rewards['success'] = 0.0
        
        # Weighted sum
        stability = (self.stability_weight * 
                     (rewards['tilt'] + rewards['angular_vel'] + 
                      rewards['accel']))
        progress = self.speed_weight * (rewards['progress'] + rewards['speed'])
        total = stability + progress + rewards['success']
        total += rewards.get('obstacle', 0) + rewards.get('jerk', 0)
        
        rewards['total'] = total
        return total, rewards
    
    def _compute_tilt(self, quat):
        """Tính góc nghiêng từ quaternion."""
        # Quaternion to up-vector
        w, x, y, z = quat
        up_x = 2 * (x*z + w*y)
        up_y = 2 * (y*z - w*x)
        up_z = 1 - 2*(x*x + y*y)
        cos_tilt = up_z
        return np.arccos(np.clip(cos_tilt, -1, 1))

Pareto Front: Speed-Stability Trade-off

Không có một reward function "tối ưu" cho carrying — mà có cả một Pareto front của trade-offs:

def train_pareto_front(n_points=5):
    """Train nhiều policies với weight ratios khác nhau."""
    results = []
    
    for speed_w in np.linspace(0.5, 3.0, n_points):
        stability_w = 3.5 - speed_w  # Tổng weights = 3.5
        
        env = CarryingEnv(
            speed_weight=speed_w,
            stability_weight=stability_w
        )
        
        model = SAC("MlpPolicy", env, ...)
        model.learn(total_timesteps=2_000_000)
        
        # Evaluate
        metrics = evaluate_carrying(model, env)
        results.append({
            'speed_weight': speed_w,
            'avg_time': metrics['avg_time'],
            'spill_rate': metrics['spill_rate'],
            'drop_rate': metrics['drop_rate'],
        })
    
    return results
Speed Weight Stability Weight Avg Time (s) Spill Rate Drop Rate
0.5 3.0 12.1 1% 0%
1.0 2.5 8.4 3% 1%
1.5 2.0 6.2 7% 2%
2.0 1.5 4.8 15% 4%
3.0 0.5 3.1 35% 12%

Task: Carry Cup Through Obstacle Course

import mujoco
import gymnasium as gym
from gymnasium import spaces

CARRY_OBSTACLE_XML = """
<mujoco model="carry_obstacles">
  <option timestep="0.002" gravity="0 0 -9.81"/>
  
  <worldbody>
    <light pos="0 0 3" dir="0 0 -1"/>
    <geom type="plane" size="3 3 0.1" rgba="0.85 0.85 0.85 1"/>
    
    <!-- Tables -->
    <body name="table_start" pos="0 0 0.35">
      <geom type="box" size="0.3 0.3 0.02" rgba="0.5 0.3 0.1 1" mass="100"/>
    </body>
    <body name="table_end" pos="1.5 0.8 0.35">
      <geom type="box" size="0.3 0.3 0.02" rgba="0.5 0.3 0.1 1" mass="100"/>
    </body>
    
    <!-- Obstacles -->
    <body name="obs1" pos="0.5 0.2 0.3">
      <geom type="box" size="0.05 0.3 0.3" rgba="0.8 0.2 0.2 0.7"/>
    </body>
    <body name="obs2" pos="1.0 0.5 0.3">
      <geom type="cylinder" size="0.08 0.3" rgba="0.8 0.2 0.2 0.7"/>
    </body>
    
    <!-- Mobile base + arm (simplified) -->
    <body name="mobile_base" pos="0 0 0.15">
      <freejoint name="base_free"/>
      <geom type="box" size="0.15 0.1 0.05" mass="10" rgba="0.3 0.3 0.8 1"/>
      
      <body name="arm_base" pos="0 0 0.05">
        <joint name="j0" type="hinge" axis="0 0 1" range="-3.14 3.14" damping="2"/>
        <geom type="cylinder" size="0.03 0.03" rgba="0.5 0.5 0.5 1"/>
        
        <body name="arm1" pos="0 0 0.06">
          <joint name="j1" type="hinge" axis="0 1 0" range="-1.5 1.5" damping="1.5"/>
          <geom type="capsule" fromto="0 0 0 0.2 0 0" size="0.025" rgba="0.6 0.6 0.6 1"/>
          
          <body name="arm2" pos="0.2 0 0">
            <joint name="j2" type="hinge" axis="0 1 0" range="-2 2" damping="1"/>
            <geom type="capsule" fromto="0 0 0 0.15 0 0" size="0.02" rgba="0.6 0.6 0.6 1"/>
            
            <body name="gripper_body" pos="0.15 0 0">
              <joint name="j3" type="hinge" axis="1 0 0" range="-1.57 1.57" damping="0.5"/>
              <site name="ee" pos="0 0 0"/>
              
              <body name="fl" pos="0 0.015 0">
                <joint name="jfl" type="slide" axis="0 1 0" range="0 0.03" damping="3"/>
                <geom type="box" size="0.008 0.003 0.03" rgba="0.8 0.3 0.3 1"
                      friction="2 0.5 0.01" contype="1" conaffinity="1"/>
              </body>
              <body name="fr" pos="0 -0.015 0">
                <joint name="jfr" type="slide" axis="0 -1 0" range="0 0.03" damping="3"/>
                <geom type="box" size="0.008 0.003 0.03" rgba="0.8 0.3 0.3 1"
                      friction="2 0.5 0.01" contype="1" conaffinity="1"/>
              </body>
            </body>
          </body>
        </body>
      </body>
    </body>
    
    <!-- Cup with liquid -->
    <body name="cup" pos="0 0 0.4">
      <freejoint name="cup_free"/>
      <geom type="cylinder" size="0.025 0.04" rgba="0.9 0.9 1 0.8" mass="0.1"
            contype="1" conaffinity="1"/>
      <body name="liquid" pos="0 0 0.02">
        <joint name="liq_x" type="slide" axis="1 0 0" range="-0.015 0.015" damping="3"/>
        <joint name="liq_y" type="slide" axis="0 1 0" range="-0.015 0.015" damping="3"/>
        <geom type="sphere" size="0.015" rgba="0.2 0.5 1 0.5" mass="0.15"
              contype="0" conaffinity="0"/>
      </body>
    </body>
    
    <!-- Goal marker -->
    <body name="goal" pos="1.5 0.8 0.45">
      <geom type="sphere" size="0.03" rgba="0 1 0 0.3" contype="0" conaffinity="0"/>
    </body>
  </worldbody>
  
  <actuator>
    <!-- Mobile base: forward, strafe, rotate -->
    <motor name="base_fwd" joint="base_free" gear="0 0 0 1 0 0" ctrlrange="-5 5"/>
    <motor name="base_strafe" joint="base_free" gear="0 0 0 0 1 0" ctrlrange="-3 3"/>
    <motor name="base_rot" joint="base_free" gear="0 0 0 0 0 1" ctrlrange="-2 2"/>
    <!-- Arm joints -->
    <position name="a0" joint="j0" kp="100"/>
    <position name="a1" joint="j1" kp="100"/>
    <position name="a2" joint="j2" kp="80"/>
    <position name="a3" joint="j3" kp="50"/>
    <position name="afl" joint="jfl" kp="50"/>
    <position name="afr" joint="jfr" kp="50"/>
  </actuator>
</mujoco>
"""

Pouring: Rót nước bằng RL

Pouring là một trong những tác vụ manipulation tinh tế nhất — robot phải kiểm soát góc nghiêng, tốc độ rót, và dừng đúng lúc.

class PouringReward:
    """Reward function cho pouring task."""
    
    def __init__(self, target_volume=0.8):
        self.target_volume = target_volume  # 80% capacity
        self.pour_started = False
    
    def compute(self, source_tilt, target_fill, flow_rate,
                spill_amount, source_pos, target_pos):
        """
        Args:
            source_tilt: Góc nghiêng bình nguồn (rad)
            target_fill: Mức nước trong bình đích (0-1)
            flow_rate: Tốc độ rót (ml/s approximation)
            spill_amount: Lượng đổ ra ngoài
            source_pos: Vị trí bình nguồn
            target_pos: Vị trí bình đích
        """
        rewards = {}
        
        # 1. Alignment — bình nguồn phải ở trên bình đích
        horizontal_dist = np.linalg.norm(source_pos[:2] - target_pos[:2])
        rewards['align'] = 1.0 - np.tanh(5.0 * horizontal_dist)
        
        # 2. Fill progress — đổ dần
        fill_error = abs(target_fill - self.target_volume)
        if target_fill <= self.target_volume:
            rewards['fill'] = 3.0 * target_fill / self.target_volume
        else:
            rewards['fill'] = -5.0 * (target_fill - self.target_volume)
        
        # 3. Flow rate — không quá nhanh, không quá chậm
        if self.pour_started:
            ideal_flow = 0.1  # ml/s
            flow_error = abs(flow_rate - ideal_flow)
            rewards['flow'] = 1.0 - np.tanh(5.0 * flow_error)
        
        # 4. Spill penalty
        rewards['spill'] = -20.0 * spill_amount
        
        # 5. Completion bonus
        if abs(target_fill - self.target_volume) < 0.05:
            rewards['complete'] = 50.0
        else:
            rewards['complete'] = 0.0
        
        total = sum(rewards.values())
        return total, rewards

Pouring task visualization
Pouring task visualization

Training Strategy: Two-Phase Learning

Cho carrying tasks phức tạp, train 2 phases riêng biệt hiệu quả hơn end-to-end:

# Phase 1: Learn stable carrying (không có obstacles)
carry_env = CarryingEnv(obstacles=False)
carry_model = SAC("MlpPolicy", carry_env, ...)
carry_model.learn(total_timesteps=2_000_000)

# Phase 2: Fine-tune với obstacles (dùng pretrained weights)
obstacle_env = CarryingEnv(obstacles=True)
obstacle_model = SAC("MlpPolicy", obstacle_env, ...)
# Load pretrained policy
obstacle_model.policy.load_state_dict(carry_model.policy.state_dict())
# Lower learning rate cho fine-tuning
obstacle_model.learning_rate = 1e-5
obstacle_model.learn(total_timesteps=1_000_000)
Method Spill Rate Collision Rate Avg Time
Train from scratch 22% 18% 9.5s
Two-phase 8% 5% 7.2s
Two-phase + curriculum 4% 3% 6.8s

Tài liệu tham khảo

  1. Whole-Body Manipulation Planning for Humanoid Robots — Murooka et al., 2021
  2. A Survey on Dynamic Manipulation — Survey, 2022
  3. Learning to Pour with a Robot Arm — IROS 2022

Tiếp theo trong Series

Bài tiếp — Contact-Rich Manipulation: Assembly, Insertion và Peg-in-Hole — chúng ta bước vào thế giới contact-rich: lắp ráp, cắm chốt, và meshing gear — nơi ma sát và clearance quyết định tất cả.

Khuyến nghị công cụ

Stack train/deploy cho VLA

Train trên cloud/workstation, deploy bản tối ưu xuống Jetson hoặc robot computer.

Cloud GPU for VLA / policy training Dùng cho imitation learning, diffusion policy, RL và fine-tuning model robotics. Xem cloud GPU → NVIDIA Jetson Orin NX / Orin Nano Máy deploy edge cho perception, logging và inference đã tối ưu. Xem Jetson → Hugging Face / robotics dataset hosting Lưu dataset, checkpoint và model card để workflow LeRobot/VLA dễ chia sẻ hơn. Xem platform →

Bài viết liên quan

  • Pick-and-Place chính xác: Position và Orientation Control
  • Force Control bằng RL: Giữ cốc nước thăng bằng
  • Long-Horizon Manipulation với VLA
NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Khám phá VnRobo

Fleet MonitoringROS 2 IntegrationAMR Solutions
rl-manipulation — Phần 5/8
← Pick-and-Place chính xác: Position và Orientation ControlContact-Rich Manipulation: Assembly, Insertion và Peg-in-Hole →

Bài viết liên quan

Deep Dive
Manipulation
rlcurriculum-learninglong-horizonPhần 8
manipulation

Multi-Step Manipulation: Curriculum Learning cho Long-Horizon

Giải quyết manipulation dài hơi bằng RL — curriculum learning, hierarchical RL, skill chaining, và benchmark IKEA furniture assembly.

7/4/202610 phút đọc
NT
Deep Dive
Manipulation
rltool-usemanipulationPhần 7
manipulation

Tool Use: Robot học sử dụng dụng cụ bằng RL

Dạy robot sử dụng dụng cụ bằng RL — affordance learning, two-phase grasp-then-use, và screwdriver insertion trong MuJoCo.

3/4/20268 phút đọc
NT
Tutorial
Manipulation
rlpick-placemanipulationPhần 4
manipulation

Pick-and-Place chính xác: Position và Orientation Control

Huấn luyện robot pick-and-place chính xác dưới 1cm với RL — HER cho sparse rewards, 6-DOF placement, và orientation alignment.

22/3/20268 phút đọc
NT
VnRobo logo

Hạ tầng AI cho robot công nghiệp thế hệ mới.

Sản phẩm

  • Tính năng
  • Bảng giá
  • Kiến thức
  • Dịch vụ

Công ty

  • Về chúng tôi
  • Blog
  • Liên hệ

Pháp lý

  • Chính sách bảo mật
  • Điều khoản sử dụng

© 2026 VnRobo. Bảo lưu mọi quyền.

Được tạo với♥tại Việt Nam