Написати код, який сортує масив — одна справа. Написати код, який змушує робота тримати яйце, не розбивши його — зовсім інша.
Фізичний AI — це там, де алгоритми зустрічаються з матерією. Де latency вимірюється в мілісекундах, а помилка означає зламану деталь. Де немає Ctrl+Z, немає можливості перезавантажити симуляцію, немає другої спроби.
Тактильні сенсори, world models, real-time inference, sim-to-real transfer — все це збирається разом, щоб робот міг робити те, що людина робить не думаючи: взяти чашку, не розлити каву.
Проблема: чому це набагато складніше, ніж здається
Візуальне сприйняття недостатнє.
Камера бачить яйце. Але не знає:
- Яке воно тендітне (shell thickness ~0.3mm)
- Скільки сили треба прикласти (fracture force ~33N)
- Чи слизьке воно (coefficient of friction ~0.3)
- Чи гаряче (temperature affects grip)
- Як воно буде себе вести при русі (inertia, liquid inside)
Людина це відчуває через тактильні сенсори — шкіру.
Кількість mechanoreceptors в людській руці: ~17,000
Просторове розрізнення: ~1mm на кінчиках пальців
Частота sampling: ~1000Hz
Типи рецепторів: 4 (тиск, вібрація, текстура, розтягнення)
Роботи традиційно цього не мали. І це фундаментальний обмеження.
Тактильні сенсори: еволюція технологій
1. Resistive/Capacitive Arrays (1990s-2010s)
Принцип: зміна опору/ємності під тиском
Переваги: дешеві, прості
Недоліки: low resolution (~4mm), drift, температурна чутливість
Приклад: Tekscan, Interlink FSR
Resolution: 4-10mm
Frequency: 100-500Hz
Cost: $50-500
2. Optical Tactile Sensors — GelSight (2010s)
Принцип: камера дивиться на деформацію гелю
Переваги: ДУЖЕ високе resolution, 3D shape
Недоліки: розмір, чутливість до освітлення
Resolution: 0.1mm (!)
Frequency: 30-60Hz (camera limited)
Cost: $500-2000
class GelSightSensor:
"""Optical tactile sensor processing."""
def __init__(self, camera, calibration):
self.camera = camera
self.reference_image = None
self.calibration = calibration
def capture_contact(self):
"""Capture deformation image."""
image = self.camera.capture()
if self.reference_image is None:
self.reference_image = image
return None
# Compute deformation from reference
diff = image - self.reference_image
return diff
def compute_depth_map(self, diff_image):
"""Convert RGB difference to depth using photometric stereo."""
# GelSight uses colored LEDs at different angles
# R, G, B channels correspond to different lighting directions
r, g, b = diff_image[:,:,0], diff_image[:,:,1], diff_image[:,:,2]
# Gradient estimation (simplified)
dx = (r - g) / (r + g + 1e-6) # x-gradient
dy = (r - b) / (r + b + 1e-6) # y-gradient
# Integrate gradients to get depth (Poisson solver)
depth = self._poisson_reconstruct(dx, dy)
return depth
def detect_contact_force(self, depth_map):
"""Estimate contact force from penetration depth."""
# Assumes linear elasticity for small deformations
k = self.calibration['stiffness'] # N/m
contact_area = np.sum(depth_map > 0.01) * self.calibration['pixel_area']
avg_depth = np.mean(depth_map[depth_map > 0.01])
force = k * avg_depth * contact_area
return force
def detect_slip(self, history, window=5):
"""Detect incipient slip from tactile image sequence."""
if len(history) < window:
return False
# Compute optical flow between frames
flows = []
for i in range(window - 1):
flow = cv2.calcOpticalFlowFarneback(
history[i], history[i+1],
None, 0.5, 3, 15, 3, 5, 1.2, 0
)
flows.append(flow)
# Slip indicated by consistent flow direction at contact edges
avg_flow = np.mean([np.linalg.norm(f) for f in flows])
return avg_flow > self.calibration['slip_threshold']
3. BioTac (SynTouch, 2010s)
Принцип: імітує людський палець
Компоненти: rigid core + electrodes + conductive fluid + skin
Вимірює: force, vibration, temperature
Electrodes: 19 impedance channels
Temperature: 1 thermistor
Vibration: 1 hydrophone (up to 1000Hz)
Force: через pattern impedance changes
4. DIGIT (Meta AI, 2020s)
Принцип: miniaturized GelSight
Переваги: compact, cheap, open-source design
Використовується: research, commercial robots
Size: 2.5 x 2.5 cm sensing area
Resolution: 640x480 pixels
Frequency: 60Hz
Cost: ~$300 (DIY), ~$500 (commercial)
import torch
import torch.nn as nn
class DIGITEncoder(nn.Module):
"""Neural encoder for DIGIT tactile images."""
def __init__(self, output_dim=256):
super().__init__()
# CNN for spatial features
self.conv_layers = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((4, 4))
)
# FC for final embedding
self.fc = nn.Sequential(
nn.Linear(256 * 4 * 4, 512),
nn.ReLU(),
nn.Linear(512, output_dim)
)
def forward(self, tactile_image):
"""
Args:
tactile_image: [B, 3, H, W] tensor
Returns:
embedding: [B, output_dim] tensor
"""
x = self.conv_layers(tactile_image)
x = x.view(x.size(0), -1)
return self.fc(x)
Multimodal Fusion: Vision + Tactile
Архітектура системи:
┌─────────────────┐ ┌─────────────────┐
│ RGB Camera │ │ Depth Camera │
│ (30 Hz) │ │ (30 Hz) │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ Vision Encoder (ViT/ResNet) │
│ → Visual Features [B, 512] │
└────────────────────┬────────────────────────┘
│
│ ┌─────────────────┐
│ │ DIGIT Sensors │
│ │ (60 Hz × 2) │
│ └────────┬────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ Tactile Encoder │
│ │ → Tactile [256] │
│ └────────┬────────┘
│ │
▼ ▼
┌──────────────────────────────────┐
│ Multimodal Fusion │
│ (Cross-attention / Concat) │
│ → Fused Features [B, 768] │
└────────────────┬─────────────────┘
│
▼
┌──────────────────────────────────┐
│ World Model │
│ State estimation + Prediction │
└────────────────┬─────────────────┘
│
▼
┌──────────────────────────────────┐
│ Policy Network │
│ → Action [gripper, arm pose] │
└──────────────────────────────────┘
Реалізація fusion:
class VisionTactileFusion(nn.Module):
"""Cross-attention fusion of vision and tactile modalities."""
def __init__(self, vision_dim=512, tactile_dim=256, fusion_dim=768):
super().__init__()
self.vision_proj = nn.Linear(vision_dim, fusion_dim)
self.tactile_proj = nn.Linear(tactile_dim, fusion_dim)
# Cross-attention: tactile attends to vision
self.cross_attention = nn.MultiheadAttention(
embed_dim=fusion_dim,
num_heads=8,
batch_first=True
)
# Self-attention on combined
self.self_attention = nn.MultiheadAttention(
embed_dim=fusion_dim,
num_heads=8,
batch_first=True
)
self.layer_norm1 = nn.LayerNorm(fusion_dim)
self.layer_norm2 = nn.LayerNorm(fusion_dim)
self.ffn = nn.Sequential(
nn.Linear(fusion_dim, fusion_dim * 4),
nn.GELU(),
nn.Linear(fusion_dim * 4, fusion_dim)
)
def forward(self, vision_features, tactile_features):
"""
Args:
vision_features: [B, N_v, vision_dim] - може бути patch features
tactile_features: [B, N_t, tactile_dim] - від кількох сенсорів
Returns:
fused: [B, fusion_dim]
"""
# Project to common dimension
v = self.vision_proj(vision_features) # [B, N_v, fusion_dim]
t = self.tactile_proj(tactile_features) # [B, N_t, fusion_dim]
# Cross-attention: tactile queries, vision keys/values
# "What visual context is relevant to what I feel?"
t_attended, _ = self.cross_attention(t, v, v)
t = self.layer_norm1(t + t_attended)
# Concatenate and self-attention
combined = torch.cat([v, t], dim=1) # [B, N_v + N_t, fusion_dim]
combined_attended, _ = self.self_attention(combined, combined, combined)
combined = self.layer_norm2(combined + combined_attended)
# FFN + global pooling
combined = combined + self.ffn(combined)
fused = combined.mean(dim=1) # Global average pooling
return fused
class TactileWorldModel(nn.Module):
"""World model з тактильним входом для manipulation."""
def __init__(self, vision_encoder, tactile_encoder, fusion_module):
super().__init__()
self.vision_encoder = vision_encoder
self.tactile_encoder = tactile_encoder
self.fusion = fusion_module
# Dynamics model
self.dynamics = nn.GRUCell(768 + 7, 512) # +7 for action
# Decoders
self.state_decoder = nn.Linear(512, 768)
self.reward_decoder = nn.Linear(512, 1)
self.contact_decoder = nn.Linear(512, 1) # Predict contact force
def encode(self, image, tactile):
"""Encode current observation."""
vis_feat = self.vision_encoder(image)
tac_feat = self.tactile_encoder(tactile)
return self.fusion(vis_feat, tac_feat)
def step(self, hidden, observation_features, action):
"""One step of world model."""
x = torch.cat([observation_features, action], dim=-1)
hidden = self.dynamics(x, hidden)
state_pred = self.state_decoder(hidden)
reward_pred = self.reward_decoder(hidden)
contact_pred = torch.sigmoid(self.contact_decoder(hidden))
return hidden, state_pred, reward_pred, contact_pred
def imagine(self, initial_state, policy, horizon=10):
"""Imagine future trajectory."""
hidden = initial_state
states, rewards, contacts = [], [], []
for t in range(horizon):
# Policy gives action
action = policy(hidden)
# Predict next state (without observation)
hidden, state, reward, contact = self.step(hidden, hidden, action)
states.append(state)
rewards.append(reward)
contacts.append(contact)
return torch.stack(states), torch.stack(rewards), torch.stack(contacts)
Sim-to-Real Transfer
Проблема: Тренувати на реальному роботі — дорого, повільно, небезпечно. Симулятор швидкий і безпечний, але... не реальний.
Reality Gap:
├── Dynamics: friction, mass, compliance різні
├── Sensing: камера/тактильні сенсори ідеалізовані
├── Control: delays, noise, discretization
└── Environment: lighting, textures, backgrounds
Рішення 1: Domain Randomization
class DomainRandomizer:
"""Randomize simulation parameters for robust transfer."""
def __init__(self, config):
self.config = config
def randomize_physics(self, env):
"""Randomize physical parameters."""
# Friction
env.set_friction(np.random.uniform(
self.config['friction_min'],
self.config['friction_max']
))
# Mass
for obj in env.objects:
mass_scale = np.random.uniform(0.8, 1.2)
obj.set_mass(obj.nominal_mass * mass_scale)
# Joint damping
for joint in env.robot.joints:
damping_scale = np.random.uniform(0.5, 2.0)
joint.set_damping(joint.nominal_damping * damping_scale)
return env
def randomize_visuals(self, env):
"""Randomize visual appearance."""
# Lighting
env.set_light_intensity(np.random.uniform(0.3, 1.0))
env.set_light_direction(np.random.randn(3))
# Textures
for obj in env.objects:
if np.random.random() < 0.5:
texture = self._random_texture()
obj.set_texture(texture)
# Camera noise
env.camera.noise_std = np.random.uniform(0.01, 0.1)
return env
def randomize_tactile(self, env):
"""Randomize tactile sensor parameters."""
# Sensitivity
env.tactile_sensor.sensitivity = np.random.uniform(0.8, 1.2)
# Noise
env.tactile_sensor.noise_std = np.random.uniform(0.01, 0.1)
# Offset
env.tactile_sensor.offset = np.random.randn(3) * 0.002 # 2mm
return env
def randomize(self, env):
"""Full randomization."""
env = self.randomize_physics(env)
env = self.randomize_visuals(env)
env = self.randomize_tactile(env)
return env
# Training loop with domain randomization
def train_with_dr(policy, env_factory, n_episodes=10000):
randomizer = DomainRandomizer(DR_CONFIG)
for episode in range(n_episodes):
# Create new randomized environment
env = env_factory()
env = randomizer.randomize(env)
# Collect trajectory
obs = env.reset()
done = False
trajectory = []
while not done:
action = policy(obs)
next_obs, reward, done, info = env.step(action)
trajectory.append((obs, action, reward, next_obs))
obs = next_obs
# Update policy
policy.update(trajectory)
Рішення 2: System Identification + Fine-tuning
class SystemIdentification:
"""Learn simulation parameters from real data."""
def __init__(self, simulator):
self.simulator = simulator
# Learnable parameters
self.params = nn.ParameterDict({
'friction': nn.Parameter(torch.tensor(0.5)),
'mass_scale': nn.Parameter(torch.tensor(1.0)),
'damping': nn.Parameter(torch.tensor(0.1)),
'sensor_delay': nn.Parameter(torch.tensor(0.01)),
})
def simulate_with_params(self, initial_state, actions):
"""Run simulation with current parameters."""
self.simulator.set_friction(self.params['friction'].item())
self.simulator.set_mass_scale(self.params['mass_scale'].item())
# ... etc
states = [initial_state]
for action in actions:
next_state = self.simulator.step(states[-1], action)
states.append(next_state)
return torch.stack(states)
def fit(self, real_trajectories, n_iterations=1000):
"""Fit parameters to match real trajectories."""
optimizer = torch.optim.Adam(self.params.values(), lr=0.01)
for i in range(n_iterations):
total_loss = 0
for traj in real_trajectories:
initial_state = traj['states'][0]
actions = traj['actions']
real_states = traj['states']
# Simulate
sim_states = self.simulate_with_params(initial_state, actions)
# Loss: match trajectory
loss = F.mse_loss(sim_states, real_states)
total_loss += loss
optimizer.zero_grad()
total_loss.backward()
optimizer.step()
# Constrain parameters to valid ranges
with torch.no_grad():
self.params['friction'].clamp_(0.1, 1.0)
self.params['mass_scale'].clamp_(0.5, 2.0)
return self.params
Real-time Inference: жорсткі обмеження
Control Loop Requirements:
├── Position control: 1000 Hz (1ms)
├── Force control: 500 Hz (2ms)
├── Visual servoing: 30-60 Hz (16-33ms)
├── Tactile reactive: 100-500 Hz (2-10ms)
└── End-to-end policy: 10-30 Hz (33-100ms)
Оптимізація для edge deployment:
import torch
import torch.quantization
class OptimizedPolicy:
"""Policy оптимізована для edge deployment."""
def __init__(self, model, target_device='cuda'):
self.device = target_device
# 1. Quantization
self.model = torch.quantization.quantize_dynamic(
model,
{nn.Linear, nn.Conv2d},
dtype=torch.qint8
)
# 2. TorchScript compilation
self.model = torch.jit.script(self.model)
# 3. CUDA optimization (if available)
if target_device == 'cuda':
self.model = self.model.cuda()
# Use TensorRT for further optimization
# self.model = torch2trt(self.model, [example_input])
# 4. Warm-up
self._warmup()
def _warmup(self, n_iterations=100):
"""Warm up JIT compilation."""
dummy_input = torch.randn(1, 768).to(self.device)
for _ in range(n_iterations):
_ = self.model(dummy_input)
@torch.inference_mode()
def __call__(self, observation):
"""Inference with minimal latency."""
obs_tensor = torch.from_numpy(observation).unsqueeze(0).to(self.device)
action = self.model(obs_tensor)
return action.cpu().numpy().squeeze()
class LatencyProfiler:
"""Profile inference latency."""
def __init__(self, policy, n_samples=1000):
self.policy = policy
self.n_samples = n_samples
def profile(self, observation_shape):
"""Measure latency statistics."""
import time
latencies = []
dummy_obs = np.random.randn(*observation_shape).astype(np.float32)
# Warm up
for _ in range(100):
self.policy(dummy_obs)
# Profile
for _ in range(self.n_samples):
start = time.perf_counter()
self.policy(dummy_obs)
end = time.perf_counter()
latencies.append((end - start) * 1000) # ms
return {
'mean': np.mean(latencies),
'std': np.std(latencies),
'p50': np.percentile(latencies, 50),
'p95': np.percentile(latencies, 95),
'p99': np.percentile(latencies, 99),
'max': np.max(latencies)
}
Benchmark середовища та датасети
Simulation Environments:
| Environment | Physics | Tactile | Speed | Use Case |
|------------|---------|---------|-------|----------|
| MuJoCo | High accuracy | Limited | Fast | RL research |
| Isaac Gym | GPU-accelerated | Basic | Very fast | Parallel training |
| TACTO (Meta) | MuJoCo-based | GelSight sim | Medium | Tactile research |
| PyBullet | Good | Basic | Fast | Open source |
| RoboSuite | MuJoCo | Good | Medium | Manipulation |
Real Robot Platforms:
| Platform | DOF | Payload | Price | Research Use |
|----------|-----|---------|-------|--------------|
| Franka Panda | 7 | 3kg | ~$30k | Most common |
| UR5/UR10 | 6 | 5-10kg | ~$35k | Industrial |
| xArm | 6-7 | 3-5kg | ~$5k | Budget |
| Sawyer | 7 | 4kg | Discontinued | Legacy |
Datasets:
- YCB Object Set: 77 standard objects with 3D models
- ContactDB: grasping contact information
- DIGIT Dataset: tactile images during manipulation
- RoboTurk: large-scale teleoperation data
Ідеї для дослідження
Для бакалавра:
- Збір тактильного датасету для 10-20 об'єктів
- Класифікація матеріалів за тактильними даними (метал/пластик/дерево)
- Порівняння різних tactile encoders на простій задачі
Для магістра:
- Vision-tactile fusion для robust grasping
- Sim-to-real transfer для тактильних policies
- Model compression для edge deployment (Jetson)
- Slip detection та prevention з тактильним feedback
Для PhD:
- Self-supervised tactile pre-training на великих даних
- Unified vision-tactile-proprioception world model
- Theoretical sample complexity для tactile learning
- Generalizable manipulation skills через tactile reasoning
Якщо вас цікавить дослідження в робототехніці та тактильному AI, команда skp-degree.com.ua допоможе з технічною реалізацією, доступом до обладнання через партнерські лабораторії, та підготовкою академічних робіт. Звертайтесь у Telegram: @kursovi_diplomy.
Де брати матеріал
Конференції:
- CoRL — Conference on Robot Learning (топ для learning-based robotics)
- ICRA — IEEE International Conference on Robotics and Automation
- RSS — Robotics: Science and Systems
- IROS — IEEE/RSJ International Conference on Intelligent Robots
Провідні лабораторії:
- MIT CSAIL (tactile sensing pioneers, GelSight)
- Berkeley BAIR (RL for robotics)
- Stanford AI Lab (manipulation)
- Google DeepMind Robotics
- Meta AI (DIGIT, fundamental research)
Ключові статті:
- "Making Sense of Vision and Touch" (Calandra et al., 2017)
- "The Feeling of Success" (tactile for grasping, 2017)
- "DIGIT: A Novel Design for a Low-Cost Compact" (2020)
- "Tactile Transformers" (2023)
- "RoboCat" (DeepMind, 2023)
Code & Tools:
- github.com/facebookresearch/tacto — TACTO simulator
- github.com/facebookresearch/digit — DIGIT design
- github.com/NVIDIA-Omniverse/IsaacGymEnvs — Isaac Gym
- github.com/StanfordVL/robosuite — RoboSuite
Складність: ? Магістерський рівень
Потрібні знання:
- Deep Learning (CNN, Transformer, VAE)
- Robotics basics (kinematics, dynamics, control)
- Reinforcement Learning fundamentals
- Python + PyTorch
- ROS (для реальних роботів)
Hardware access: бажано для повноцінного дослідження, але можна працювати в симуляції (Isaac Gym, MuJoCo, TACTO).
Чому це майбутнє
Роботи в factories — це 20 століття. Жорсткі, запрограмовані, в клітках. Вони роблять одну операцію мільйони разів.
Роботи в домівках — це 21 століття. Гнучкі, адаптивні, безпечні поруч з людьми. Вони повинні маніпулювати тисячами різних об'єктів.
Друге неможливе без тактильного AI. Бо ти не хочеш, щоб робот, який приносить тобі каву, не відрізняв чашку від кота. І не знав, скільки сили прикласти, щоб не розбити чашку і не образити кота.
Ринок: домашні роботи, healthcare assistance, warehouse automation, space exploration — все це потребує тактильного сприйняття. I компанії інвестують мільярди: Physical Intelligence ($70M), Covariant, Sanctuary AI.
Це не віддалене майбутнє. Це відбувається зараз.
Ключові слова: robotic manipulation, tactile sensing, GelSight, DIGIT, vision-tactile fusion, sim-to-real transfer, domain randomization, world models, real-time inference, edge deployment, MuJoCo, Isaac Gym, дипломна робота, магістерська, PhD, робототехніка.