feat(actpred): 多帧注意力架构改造(Transformer 替换 concat 融合,支持多帧图像+历史动作序列)

This commit is contained in:
qing committed 2026-08-09 18:17:03 +00:00
1 parent 2959197597
commit 4dd25486f8
9 files changed
+272 -147

No files matched your search

+23 -6
View File
@@ -28,6 +28,7 @@ class ActionPredictorInference:
self.action_dim = self._data_cfg.action_dim
self.history_len = self._data_cfg.history_len
self.frame_len = self._data_cfg.frame_len
self._transform = transforms.Compose([
transforms.Resize((self._data_cfg.image_size, self._data_cfg.image_size)),
@@ -61,13 +62,23 @@ class ActionPredictorInference:
self._action_std = stats[1].astype(np.float32)
@torch.no_grad()
def predict(self, image: np.ndarray, action_history: np.ndarray) -> np.ndarray:
def predict(self, images, action_history: np.ndarray) -> np.ndarray:
"""images: 长度为 frame_len 的 RGB 图像序列(旧→新,末帧为当前帧)"""
if not isinstance(images, (list, tuple)):
images = [images]
if len(images) != self._data_cfg.frame_len:
raise ValueError(
f"images 应为 {self._data_cfg.frame_len} 帧,实际为 {len(images)} 帧。"
f"冷启动时可重复传入同一帧。")
expected_shape = (self._data_cfg.history_len, self._data_cfg.action_dim)
if action_history.shape != expected_shape:
raise ValueError(
f"action_history 形状应为 {expected_shape},实际为 {action_history.shape}")
img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
img_tensor = self._transform(img).unsqueeze(0).to(self._device)
frames = []
for image in images:
img = Image.fromarray(image) if isinstance(image, np.ndarray) else image
frames.append(self._transform(img))
img_tensor = torch.stack(frames).unsqueeze(0).to(self._device) # (1, F, 3, S, S)
history = torch.tensor(action_history, dtype=torch.float32)
if self._action_mean is not None:
history = (history - self._action_mean) / self._action_std
@@ -93,7 +104,9 @@ def parse_action_history(s: str, action_dim: int) -> np.ndarray:
def main():
parser = argparse.ArgumentParser(description="actpred inference")
parser.add_argument("--model", required=True, help="checkpoint path")
parser.add_argument("--image", required=True, help="image file path")
parser.add_argument("--images", required=True, nargs="+",
help="按时间顺序(旧→新)传入 frame_len 个图像路径;"
"冷启动时可重复传同一帧路径")
parser.add_argument("--action-history", required=True,
help="冒号分隔各步,每步 action_dim 个值,共 history_len 步。"
"e.g. '0,0,0,0:1,0,0,0' (action_dim=4, history_len=2)")
@@ -106,9 +119,13 @@ def main():
predictor = ActionPredictorInference(args.model, args.device)
action_dim = args.action_dim or predictor.action_dim
image = Image.open(args.image).convert("RGB")
if len(args.images) != predictor.frame_len:
raise SystemExit(
f"--images 需传入 {predictor.frame_len} 个路径(旧→新),"
f"实际为 {len(args.images)} 个。冷启动时可重复传同一帧路径。")
images = [np.array(Image.open(p).convert("RGB")) for p in args.images]
history = parse_action_history(args.action_history, action_dim)
predicted = predictor.predict(np.array(image), history)
predicted = predictor.predict(images, history)
if args.output_format == "json":
print(json.dumps({"predicted_action": predicted.tolist()}, indent=2))