Mujoco制作模拟视频

录制的原理就是:按照帧率,不断地采集一幅幅模拟的图像,然后通过ffmepg制作成视频


This code sample simulates the passive dynamics of a given model, renders it offscreen, reads the color and depth pixel values, and saves them into a raw data file that can then be converted into a movie file with tools such as ffmpeg. The rendering is simplified compared to simulate.cc because there is no user interaction, visualization options or timing; instead we simply render with the default settings as fast as possible. The dimensions and number of multi-samples for the offscreen buffer are specified in the MuJoCo model, while the simulation duration, frames-per-second to be rendered (usually much less than the physics simulation rate), and output file name are specified as command-line arguments. For example, a 5 second animation at 60 frames per second is created with:

render humanoid.xml 5 60 rgb.out

The default humanoid.xml model specifies offscreen rendering with 800x800 resolution. With this information in hand, we can compress the (large) raw date file into a playable movie file:

ffmpeg -f rawvideo -pixel_format rgb24 -video_size 800x800
  -framerate 60 -i rgb.out -vf "vflip" video.mp4

但是我试验失败好几次,发现ffmpeg里面的有尺寸的要求,我们将
模型也做个尺寸设置:

  <visual>
    <map force="0.1" zfar="30"/>
    <rgba haze="0.15 0.25 0.35 1"/>
    <quality shadowsize="4096"/>
    <global offwidth="800" offheight="800"/>
  </visual>

这样就成功制作出视频了


将record.cc编译到自己项目的问题

目前来说,我没办法直接将录制功能嵌入到项目,感觉录制功能
很吃资源,而且录制时用OPENGL来做,测试发现会跟mujoco的UI
有点冲突,导致mujoco的UI不显示。

可能会遇到的问题

使用Mujoco自带的record.cc来改就可以
但是要注意,用g++编译,如果使用gcc编译,
可能会报cstdio找不到这样的错误。
另外,文件名不能过长,不然std::fopen会打不开
record.cc有用到mujoco的array_safe.h,这个是源码提供,可以直接复制
到自己的项目里面

例子:测试可以通过

Mujoco录制模拟视频


进一步的改进和封装

官方原始代码,用起来有点麻烦,需要将其代码摘抄出来,嵌入到我们自己的项目中。
这样子直接插入一大段录制功能的代码,容易让我们自己的项目代码不好看。
我做了代码提取和封装,这样只需在项目中简单调用几个接口就可以了

.h头文件

/**
 * 
 * created by Feisy,2022-02-23
 */
#ifndef RECODER_H_
#define RECODER_H_

#include "mujoco.h"
#include <string>

// select EGL, OSMESA or GLFW
#if defined(MJ_EGL)
#include <EGL/egl.h>
#elif defined(MJ_OSMESA)
#include <GL/osmesa.h>
OSMesaContext ctx;
unsigned char buffer[10000000];
#else
#include <GLFW/glfw3.h>
#endif

#include "array_safety.h"
namespace mju = ::mujoco::sample_util;

class Recoder
{
public:
    Recoder();
    virtual ~Recoder();

public:
    int Init(mjModel* m);
    int Setting(std::string &save_file,int fps,int duration);

    /**
     * cur_time is d->time or other type time you used,also yi can be real elapsed time
     */
    int Run(mjModel* m,mjData* d ,mjvCamera &cam,mjvOption &opt,double cur_time);
    void Release();


private:
    void init_opengl();

private:
    bool m_bInit;
private:
    int m_fps;
    int m_duration;
    std::string m_file;

private:
    double m_frametime;
    int m_framecount;

private:
    int m_W;
    int m_H;
    std::FILE *m_fp;
    unsigned char *m_rgb;
    float *m_depth;
    mjrRect m_viewport;
    mjvScene m_scn;
    mjrContext m_con;
};

#endif

.cpp文件

#include "Recoder.h"

Recoder::Recoder()
{
  m_bInit = false;
}
Recoder::~Recoder()
{
}

int Recoder::Init(mjModel *m)
{
  init_opengl();// should call first

  mjv_defaultScene(&m_scn);
  mjr_defaultContext(&m_con);

  mjv_makeScene(m, &m_scn, 2000);
  mjr_makeContext(m, &m_con, 200);

  // set rendering to offscreen buffer
  mjr_setBuffer(mjFB_OFFSCREEN, &m_con);
  if (m_con.currentBuffer != mjFB_OFFSCREEN)
  {
    std::printf("Warning: offscreen rendering not supported, using default/window framebuffer\n");
  }

  // get size of active renderbuffer
  m_viewport = mjr_maxViewport(&m_con);
  m_W = m_viewport.width;
  m_H = m_viewport.height;

  // allocate rgb and depth buffers
  m_rgb = (unsigned char *)std::malloc(3 * m_W * m_H);
  m_depth = (float *)std::malloc(sizeof(float) * m_W * m_H);
  if (!m_rgb || !m_depth)
  {
    mju_error("Could not allocate buffers");
  }
  m_frametime = 0.0;
  m_framecount = 0;
  m_bInit = true;
  printf("recoder init finish\n");
  return 0;
}

int Recoder::Setting(std::string &save_file, int fps, int duration)
{
  m_fps = fps;
  m_duration = duration;
  m_file = std::string(save_file.data());

  // create output rgb file
  m_fp = std::fopen(m_file.data(), "wb");
  if (!m_fp)
  {
    mju_error("Could not open rgbfile for writing");
  }
  printf("recoder setting finish\n");
  return 0;
}

int Recoder::Run(mjModel *m, mjData *d, mjvCamera &cam, mjvOption &opt, double cur_time)
{
  if (cur_time > m_duration)
  {
    return 1;
  }

  // render new frame if it is time (or first frame)
  if ((cur_time - m_frametime) > 1 / m_fps || m_frametime == 0)
  {
    // update abstract scene
    mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &m_scn);

    // render scene in offscreen buffer
    mjr_render(m_viewport, &m_scn, &m_con);

    // add time stamp in upper-left corner
    char stamp[50];
    mju::sprintf_arr(stamp, "Time = %.3f", cur_time);
    mjr_overlay(mjFONT_NORMAL, mjGRID_TOPLEFT, m_viewport, stamp, NULL, &m_con);

    // read rgb and depth buffers
    mjr_readPixels(m_rgb, m_depth, m_viewport, &m_con);

    // insert subsampled depth image in lower-left corner of rgb image
    const int NS = 3; // depth image sub-sampling
    for (int r = 0; r < m_H; r += NS)
      for (int c = 0; c < m_W; c += NS)
      {
        int adr = (r / NS) * m_W + c / NS;
        m_rgb[3 * adr] = m_rgb[3 * adr + 1] = m_rgb[3 * adr + 2] = (unsigned char)((1.0f - m_depth[r * m_W + c]) * 255.0f);
      }

    // write rgb image to file
    std::fwrite(m_rgb, 3, m_W * m_H, m_fp);

    // print every 10 frames: '.' if ok, 'x' if OpenGL error
    if (((m_framecount++) % 10) == 0)
    {
      if (mjr_getError())
      {
        std::printf("x");
      }
      else
      {
        std::printf(".");
      }
    }

    // save simulation time
    m_frametime = cur_time;
  }
  return 0;
}

void Recoder::Release()
{
  if (false == m_bInit)
  {
    return;
  }
  m_bInit = false;
  mjr_freeContext(&m_con);
  mjv_freeScene(&m_scn);

  // close file, free buffers
  std::fclose(m_fp);
  std::free(m_rgb);
  std::free(m_depth);

#if defined(MJ_EGL)
  // get current display
  EGLDisplay eglDpy = eglGetCurrentDisplay();
  if (eglDpy == EGL_NO_DISPLAY)
  {
    return;
  }

  // get current context
  EGLContext eglCtx = eglGetCurrentContext();

  // release context
  eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

  // destroy context if valid
  if (eglCtx != EGL_NO_CONTEXT)
  {
    eglDestroyContext(eglDpy, eglCtx);
  }

  // terminate display
  eglTerminate(eglDpy);

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  OSMesaDestroyContext(ctx);

  //------------------------ GLFW
#else
// terminate GLFW (crashes with Linux NVidia drivers)
#if defined(__APPLE__) || defined(_WIN32)
  glfwTerminate();
#endif
#endif
}

// create OpenGL context/window
void Recoder::init_opengl()
{
  //------------------------ EGL
#if defined(MJ_EGL)
  // desired config
  const EGLint configAttribs[] = {
      EGL_RED_SIZE, 8,
      EGL_GREEN_SIZE, 8,
      EGL_BLUE_SIZE, 8,
      EGL_ALPHA_SIZE, 8,
      EGL_DEPTH_SIZE, 24,
      EGL_STENCIL_SIZE, 8,
      EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
      EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
      EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
      EGL_NONE};

  // get default display
  EGLDisplay eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
  if (eglDpy == EGL_NO_DISPLAY)
  {
    mju_error_i("Could not get EGL display, error 0x%x\n", eglGetError());
  }

  // initialize
  EGLint major, minor;
  if (eglInitialize(eglDpy, &major, &minor) != EGL_TRUE)
  {
    mju_error_i("Could not initialize EGL, error 0x%x\n", eglGetError());
  }

  // choose config
  EGLint numConfigs;
  EGLConfig eglCfg;
  if (eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs) != EGL_TRUE)
  {
    mju_error_i("Could not choose EGL config, error 0x%x\n", eglGetError());
  }

  // bind OpenGL API
  if (eglBindAPI(EGL_OPENGL_API) != EGL_TRUE)
  {
    mju_error_i("Could not bind EGL OpenGL API, error 0x%x\n", eglGetError());
  }

  // create context
  EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL);
  if (eglCtx == EGL_NO_CONTEXT)
  {
    mju_error_i("Could not create EGL context, error 0x%x\n", eglGetError());
  }

  // make context current, no surface (let OpenGL handle FBO)
  if (eglMakeCurrent(eglDpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglCtx) != EGL_TRUE)
  {
    mju_error_i("Could not make EGL context current, error 0x%x\n", eglGetError());
  }

  //------------------------ OSMESA
#elif defined(MJ_OSMESA)
  // create context
  ctx = OSMesaCreateContextExt(GL_RGBA, 24, 8, 8, 0);
  if (!ctx)
  {
    mju_error("OSMesa context creation failed");
  }

  // make current
  if (!OSMesaMakeCurrent(ctx, buffer, GL_UNSIGNED_BYTE, 800, 800))
  {
    mju_error("OSMesa make current failed");
  }

  //------------------------ GLFW
#else
  // init GLFW
  if (!glfwInit())
  {
    mju_error("Could not initialize GLFW");
  }

  // create invisible window, single-buffered
  glfwWindowHint(GLFW_VISIBLE, 0);
  glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
  GLFWwindow *window = glfwCreateWindow(800, 800, "Invisible window", NULL, NULL);
  if (!window)
  {
    mju_error("Could not create GLFW window");
  }

  // make context current
  glfwMakeContextCurrent(window);
#endif
}


原始官方的代码

// Copyright 2021 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "mujoco.h"

// select EGL, OSMESA or GLFW
#if defined(MJ_EGL)
  #include <EGL/egl.h>
#elif defined(MJ_OSMESA)
  #include <GL/osmesa.h>
  OSMesaContext ctx;
  unsigned char buffer[10000000];
#else
  #include <GLFW/glfw3.h>
#endif

#include "array_safety.h"
namespace mju = ::mujoco::sample_util;

//-------------------------------- global data ------------------------------------------
char xmlpath[] = "./dbpendulum/doublependulum.xml";
char rgb_file[] = "./rgb.out";
// MuJoCo model and data
mjModel* m = 0;
mjData* d = 0;

// MuJoCo visualization
mjvScene scn;//关于使用OPENGL渲染的设置
mjvCamera cam;
mjvOption opt;
mjrContext con;//关于OPENGL的上下文


//-------------------------------- utility functions ------------------------------------

// load model, init simulation and rendering
//加载模型,以及设置相机的参数
void initMuJoCo(const char* filename) {
   
   
  // load and compile
  char error[1000] = "Could not load binary model";
  if (std::strlen(filename)>4 && !std::strcmp(filename+std::strlen(filename)-4, ".mjb")) {
   
   
    m = mj_loadModel(filename, 0);
  } else {
   
   
    m = mj_loadXML(filename, 0, error, 1000);
  }
  if (!m) {
   
   
    mju_error_s("Load model error: %s", error);
  }

  // make data, run one computation to initialize all fields
  d = mj_makeData(m);
  mj_forward(m, d);

  // initialize visualization data structures
  mjv_defaultCamera(&cam);
  mjv_defaultOption(&opt);
  mjv_defaultScene(&scn);
  mjr_defaultContext(&con);

  // create scene and context
  mjv_makeScene(m, &scn, 2000);
  mjr_makeContext(m, &con, 200);

  // center and scale view
  //这里的相机是看向一个固定点,对于动态移动的物体,考虑用d->qpos等信息动态追踪
    double arr_view[] = {
   
   89.608063, -11.588379, 5, 0.000000, 0.000000, 2.000000};
    cam.azimuth = arr_view[0];
    cam.elevation = arr_view[1];
    cam.distance =</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值