fix(camera): decouple MuJoCo rendering and camera reads
This commit is contained in:
parent
724da3d000
commit
708c585f28
@ -83,8 +83,8 @@ camera {
|
|||||||
stream_mode: STREAM_MODE_RGBD
|
stream_mode: STREAM_MODE_RGBD
|
||||||
}
|
}
|
||||||
encoder {
|
encoder {
|
||||||
width: 1280
|
width: 480
|
||||||
height: 720
|
height: 320
|
||||||
fps: 30
|
fps: 30
|
||||||
codec: "H264"
|
codec: "H264"
|
||||||
enable_stream_timestamp: true
|
enable_stream_timestamp: true
|
||||||
|
|||||||
@ -30,7 +30,7 @@ logger {
|
|||||||
max_file_size_mb: 100
|
max_file_size_mb: 100
|
||||||
flush_interval_seconds: 1
|
flush_interval_seconds: 1
|
||||||
format {
|
format {
|
||||||
show_time: false
|
show_time: true
|
||||||
show_level: true
|
show_level: true
|
||||||
show_thread_id: false
|
show_thread_id: false
|
||||||
show_source_location: true
|
show_source_location: true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
add_library(aubo_arm SHARED
|
add_library(aubo_arm SHARED
|
||||||
aubo_arm.cpp
|
src/aubo_arm.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(aubo_arm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(aubo_arm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|||||||
@ -5,10 +5,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
@ -57,6 +60,11 @@ private:
|
|||||||
bool initOffscreen_();
|
bool initOffscreen_();
|
||||||
void destroyOffscreen_();
|
void destroyOffscreen_();
|
||||||
bool renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsics);
|
bool renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsics);
|
||||||
|
void renderLoop_();
|
||||||
|
bool fetchCached_(cv::Mat& color,
|
||||||
|
cv::Mat& depth,
|
||||||
|
Rs2Intrinsics& intrinsics,
|
||||||
|
bool consume_new_frame_only);
|
||||||
bool ensureEncoder_(int width, int height, int fps);
|
bool ensureEncoder_(int width, int height, int fps);
|
||||||
void setError_(const std::string& error);
|
void setError_(const std::string& error);
|
||||||
static void flipRgbAndDepth_(std::vector<unsigned char>& rgb,
|
static void flipRgbAndDepth_(std::vector<unsigned char>& rgb,
|
||||||
@ -65,6 +73,14 @@ private:
|
|||||||
int height);
|
int height);
|
||||||
static void linearizeDepth_(const mjModel* model, std::vector<float>& depth);
|
static void linearizeDepth_(const mjModel* model, std::vector<float>& depth);
|
||||||
|
|
||||||
|
struct CachedFrame {
|
||||||
|
cv::Mat color;
|
||||||
|
cv::Mat depth;
|
||||||
|
Rs2Intrinsics intrinsics{};
|
||||||
|
uint64_t frame_id{0};
|
||||||
|
bool valid{false};
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FetchRgbdFn fetch_rgbd_fn_;
|
FetchRgbdFn fetch_rgbd_fn_;
|
||||||
mutable std::mutex mtx_;
|
mutable std::mutex mtx_;
|
||||||
@ -78,6 +94,7 @@ private:
|
|||||||
mjrContext context_{};
|
mjrContext context_{};
|
||||||
bool scene_initialized_{false};
|
bool scene_initialized_{false};
|
||||||
bool context_initialized_{false};
|
bool context_initialized_{false};
|
||||||
|
mjData* render_data_{nullptr};
|
||||||
int camera_id_{-1};
|
int camera_id_{-1};
|
||||||
int width_{640};
|
int width_{640};
|
||||||
int height_{480};
|
int height_{480};
|
||||||
@ -92,6 +109,13 @@ private:
|
|||||||
size_t stream_frame_index_{0};
|
size_t stream_frame_index_{0};
|
||||||
bool streaming_{false};
|
bool streaming_{false};
|
||||||
std::shared_ptr<FfmpegEncoderInfo> rgb_encoder_;
|
std::shared_ptr<FfmpegEncoderInfo> rgb_encoder_;
|
||||||
|
|
||||||
|
mutable std::mutex cache_mtx_;
|
||||||
|
std::condition_variable cache_cv_;
|
||||||
|
CachedFrame latest_frame_;
|
||||||
|
std::thread render_thread_;
|
||||||
|
bool render_thread_running_{false};
|
||||||
|
bool render_stop_requested_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cmvr::device
|
} // namespace cmvr::device
|
||||||
|
|||||||
@ -106,10 +106,6 @@ bool MujocoCamera::init()
|
|||||||
fovy_deg_ = model->cam_fovy[camera_id_];
|
fovy_deg_ = model->cam_fovy[camera_id_];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initOffscreen_()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
state_.is_initialized = true;
|
state_.is_initialized = true;
|
||||||
state_.is_opened = true;
|
state_.is_opened = true;
|
||||||
state_.fps = positiveOrDefault(config_.render().fps(), 30);
|
state_.fps = positiveOrDefault(config_.render().fps(), 30);
|
||||||
@ -125,37 +121,132 @@ bool MujocoCamera::init()
|
|||||||
|
|
||||||
bool MujocoCamera::start()
|
bool MujocoCamera::start()
|
||||||
{
|
{
|
||||||
if (!state_.is_initialized) {
|
bool initialized = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
initialized = state_.is_initialized;
|
||||||
|
}
|
||||||
|
if (!initialized) {
|
||||||
if (!init()) {
|
if (!init()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard<std::mutex> lock(mtx_);
|
|
||||||
auto world = world_.lock();
|
auto world = world_.lock();
|
||||||
if (world && !world->isRunning() && !world->start()) {
|
if (world && !world->isRunning() && !world->start()) {
|
||||||
setError_("[MujocoCamera] failed to start MuJoCo world: " + world->lastError());
|
setError_("[MujocoCamera] failed to start MuJoCo world: " + world->lastError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool use_external_frames = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
state_.is_streaming = true;
|
state_.is_streaming = true;
|
||||||
state_.is_opened = true;
|
state_.is_opened = true;
|
||||||
|
use_external_frames = static_cast<bool>(fetch_rgbd_fn_);
|
||||||
|
}
|
||||||
|
std::thread stale_thread;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
if (render_thread_running_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
latest_frame_ = CachedFrame{};
|
||||||
|
last_frame_id_ = 0;
|
||||||
|
has_last_frame_id_ = false;
|
||||||
|
render_stop_requested_ = false;
|
||||||
|
render_thread_running_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
stale_thread = std::move(render_thread_);
|
||||||
|
}
|
||||||
|
if (stale_thread.joinable()) {
|
||||||
|
stale_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
render_thread_ = std::thread(&MujocoCamera::renderLoop_, this);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_running_ = false;
|
||||||
|
render_stop_requested_ = true;
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
|
setError_("[MujocoCamera] failed to start render thread: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// External PiP callbacks are not ready until the viewer enters its render
|
||||||
|
// loop, so let their polling thread warm up asynchronously.
|
||||||
|
if (use_external_frames) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> cache_lock(cache_mtx_);
|
||||||
|
const bool ready = cache_cv_.wait_for(
|
||||||
|
cache_lock,
|
||||||
|
std::chrono::seconds(5),
|
||||||
|
[this] { return latest_frame_.valid || !render_thread_running_ || render_stop_requested_; });
|
||||||
|
const bool has_frame = latest_frame_.valid;
|
||||||
|
cache_lock.unlock();
|
||||||
|
if (!ready || !has_frame) {
|
||||||
|
stop();
|
||||||
|
if (ready) {
|
||||||
|
setError_("[MujocoCamera] render thread stopped before producing a frame");
|
||||||
|
} else {
|
||||||
|
setError_("[MujocoCamera] timed out waiting for the first rendered frame");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MujocoCamera::stop()
|
bool MujocoCamera::stop()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_stop_requested_ = true;
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
|
|
||||||
|
std::thread thread_to_join;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mtx_);
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
state_.is_streaming = false;
|
state_.is_streaming = false;
|
||||||
state_.is_opened = false;
|
state_.is_opened = false;
|
||||||
destroyOffscreen_();
|
thread_to_join = std::move(render_thread_);
|
||||||
|
}
|
||||||
|
if (thread_to_join.joinable()) {
|
||||||
|
thread_to_join.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_running_ = false;
|
||||||
|
latest_frame_ = CachedFrame{};
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MujocoCamera::setFetchRgbdFn(FetchRgbdFn fetch_rgbd_fn)
|
void MujocoCamera::setFetchRgbdFn(FetchRgbdFn fetch_rgbd_fn)
|
||||||
{
|
{
|
||||||
|
bool render_thread_active = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_active = render_thread_running_;
|
||||||
|
}
|
||||||
|
if (render_thread_active) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mtx_);
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
fetch_rgbd_fn_ = std::move(fetch_rgbd_fn);
|
fetch_rgbd_fn_ = std::move(fetch_rgbd_fn);
|
||||||
if (fetch_rgbd_fn_) {
|
if (fetch_rgbd_fn_) {
|
||||||
destroyOffscreen_();
|
|
||||||
state_.is_initialized = true;
|
state_.is_initialized = true;
|
||||||
state_.is_opened = true;
|
state_.is_opened = true;
|
||||||
state_.fps = positiveOrDefault(config_.render().fps(), 30);
|
state_.fps = positiveOrDefault(config_.render().fps(), 30);
|
||||||
@ -203,9 +294,10 @@ void MujocoCamera::getRGBDImages(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics&
|
|||||||
|
|
||||||
bool MujocoCamera::startStreaming()
|
bool MujocoCamera::startStreaming()
|
||||||
{
|
{
|
||||||
if (!state_.is_initialized && !init()) {
|
if (!start()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mtx_);
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
streaming_ = true;
|
streaming_ = true;
|
||||||
state_.is_streaming = true;
|
state_.is_streaming = true;
|
||||||
@ -272,15 +364,30 @@ bool MujocoCamera::getLatestEncodedFrame(StreamFrameData& frame_data, size_t& ne
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MujocoCamera::fetch(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsics)
|
bool MujocoCamera::fetch(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsics)
|
||||||
|
{
|
||||||
|
FetchRgbdFn fetch_rgbd_fn;
|
||||||
|
bool consume_new_frame_only = false;
|
||||||
|
bool render_thread_active = false;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mtx_);
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
if (fetch_rgbd_fn_) {
|
fetch_rgbd_fn = fetch_rgbd_fn_;
|
||||||
|
consume_new_frame_only = consume_new_frame_only_;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_active = render_thread_running_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep compatibility with callback-only cameras that have not been
|
||||||
|
// started. Once start() owns a polling thread, reads are cache-only.
|
||||||
|
if (fetch_rgbd_fn && !render_thread_active) {
|
||||||
std::vector<unsigned char> rgb_raw;
|
std::vector<unsigned char> rgb_raw;
|
||||||
std::vector<float> depth_raw;
|
std::vector<float> depth_raw;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
std::uint64_t frame_id = 0;
|
std::uint64_t frame_id = 0;
|
||||||
if (!fetch_rgbd_fn_(rgb_raw, depth_raw, width, height, frame_id)) {
|
if (!fetch_rgbd_fn(rgb_raw, depth_raw, width, height, frame_id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (width <= 0 || height <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
@ -292,11 +399,14 @@ bool MujocoCamera::fetch(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsi
|
|||||||
if (!depth_raw.empty() && static_cast<int>(depth_raw.size()) != width * height) {
|
if (!depth_raw.empty() && static_cast<int>(depth_raw.size()) != width * height) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (consume_new_frame_only_ && has_last_frame_id_ && frame_id == last_frame_id_) {
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
if (consume_new_frame_only && has_last_frame_id_ && frame_id == last_frame_id_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
last_frame_id_ = frame_id;
|
last_frame_id_ = frame_id;
|
||||||
has_last_frame_id_ = true;
|
has_last_frame_id_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
cv::Mat rgb(height, width, CV_8UC3, rgb_raw.data());
|
cv::Mat rgb(height, width, CV_8UC3, rgb_raw.data());
|
||||||
cv::cvtColor(rgb, color, cv::COLOR_RGB2BGR);
|
cv::cvtColor(rgb, color, cv::COLOR_RGB2BGR);
|
||||||
@ -310,7 +420,31 @@ bool MujocoCamera::fetch(cv::Mat& color, cv::Mat& depth, Rs2Intrinsics& intrinsi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderOffscreen_(color, depth, intrinsics);
|
return fetchCached_(color, depth, intrinsics, consume_new_frame_only);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MujocoCamera::fetchCached_(cv::Mat& color,
|
||||||
|
cv::Mat& depth,
|
||||||
|
Rs2Intrinsics& intrinsics,
|
||||||
|
const bool consume_new_frame_only)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
if (!latest_frame_.valid || latest_frame_.color.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (consume_new_frame_only && has_last_frame_id_ &&
|
||||||
|
latest_frame_.frame_id == last_frame_id_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cv::Mat copies are reference-counted; keep the cache immutable while the
|
||||||
|
// consumer reads the published frame and avoid a full image copy per poll.
|
||||||
|
color = latest_frame_.color;
|
||||||
|
depth = latest_frame_.depth;
|
||||||
|
intrinsics = latest_frame_.intrinsics;
|
||||||
|
last_frame_id_ = latest_frame_.frame_id;
|
||||||
|
has_last_frame_id_ = true;
|
||||||
|
return !color.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MujocoCamera::initOffscreen_()
|
bool MujocoCamera::initOffscreen_()
|
||||||
@ -345,12 +479,27 @@ bool MujocoCamera::initOffscreen_()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mjModel* model = nullptr;
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> world_lock(world->mutex());
|
std::lock_guard<std::mutex> world_lock(world->mutex());
|
||||||
const mjModel* model = world->model();
|
model = world->model();
|
||||||
if (model == nullptr) {
|
if (model == nullptr) {
|
||||||
setError_("[MujocoCamera] world model is null");
|
setError_("[MujocoCamera] world model is null");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MuJoCo clips rendering to the model's offscreen buffer. Make sure
|
||||||
|
// the buffer is large enough before creating this camera's context;
|
||||||
|
// otherwise a larger requested frame is only partially populated.
|
||||||
|
model->vis.global.offwidth = std::max(model->vis.global.offwidth, width_);
|
||||||
|
model->vis.global.offheight = std::max(model->vis.global.offheight, height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_data_ = mj_makeData(model);
|
||||||
|
if (render_data_ == nullptr) {
|
||||||
|
setError_("[MujocoCamera] failed to allocate render data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
mjv_makeScene(model, &scene_, kMaxGeom);
|
mjv_makeScene(model, &scene_, kMaxGeom);
|
||||||
scene_initialized_ = true;
|
scene_initialized_ = true;
|
||||||
mjr_makeContext(model, &context_, mjFONTSCALE_150);
|
mjr_makeContext(model, &context_, mjFONTSCALE_150);
|
||||||
@ -360,9 +509,116 @@ bool MujocoCamera::initOffscreen_()
|
|||||||
setError_("[MujocoCamera] MuJoCo offscreen buffer is not available");
|
setError_("[MujocoCamera] MuJoCo offscreen buffer is not available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (context_.offWidth < width_ || context_.offHeight < height_) {
|
||||||
|
setError_("[MujocoCamera] offscreen buffer is smaller than requested frame: " +
|
||||||
|
std::to_string(context_.offWidth) + "x" +
|
||||||
|
std::to_string(context_.offHeight) + " < " +
|
||||||
|
std::to_string(width_) + "x" + std::to_string(height_));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MujocoCamera::renderLoop_()
|
||||||
|
{
|
||||||
|
FetchRgbdFn external_fetch;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
external_fetch = fetch_rgbd_fn_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool use_external_frames = static_cast<bool>(external_fetch);
|
||||||
|
if (!use_external_frames && !initOffscreen_()) {
|
||||||
|
destroyOffscreen_();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_running_ = false;
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int fps = positiveOrDefault(config_.render().fps(), 30);
|
||||||
|
const auto period = std::chrono::duration<double>(1.0 / static_cast<double>(fps));
|
||||||
|
const auto period_ticks = std::chrono::duration_cast<std::chrono::steady_clock::duration>(period);
|
||||||
|
auto next_tick = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
if (render_stop_requested_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cv::Mat color;
|
||||||
|
cv::Mat depth;
|
||||||
|
Rs2Intrinsics intrinsics{};
|
||||||
|
uint64_t external_frame_id = 0;
|
||||||
|
bool got_frame = false;
|
||||||
|
|
||||||
|
if (use_external_frames) {
|
||||||
|
std::vector<unsigned char> rgb_raw;
|
||||||
|
std::vector<float> depth_raw;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
if (external_fetch(rgb_raw, depth_raw, width, height, external_frame_id) &&
|
||||||
|
width > 0 && height > 0 &&
|
||||||
|
static_cast<int>(rgb_raw.size()) == width * height * 3 &&
|
||||||
|
(depth_raw.empty() || static_cast<int>(depth_raw.size()) == width * height)) {
|
||||||
|
cv::Mat rgb(height, width, CV_8UC3, rgb_raw.data());
|
||||||
|
cv::cvtColor(rgb, color, cv::COLOR_RGB2BGR);
|
||||||
|
if (!depth_raw.empty()) {
|
||||||
|
cv::Mat dep(height, width, CV_32FC1, depth_raw.data());
|
||||||
|
depth = dep.clone();
|
||||||
|
}
|
||||||
|
fillIntrinsics(width, height, intrinsics);
|
||||||
|
got_frame = !color.empty();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
got_frame = renderOffscreen_(color, depth, intrinsics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (got_frame) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
latest_frame_.color = std::move(color);
|
||||||
|
latest_frame_.depth = std::move(depth);
|
||||||
|
latest_frame_.intrinsics = intrinsics;
|
||||||
|
latest_frame_.frame_id = use_external_frames && external_frame_id != 0
|
||||||
|
? external_frame_id
|
||||||
|
: latest_frame_.frame_id + 1;
|
||||||
|
latest_frame_.valid = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mtx_);
|
||||||
|
clear_error_();
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
next_tick += period_ticks;
|
||||||
|
std::unique_lock<std::mutex> lock(cache_mtx_);
|
||||||
|
if (cache_cv_.wait_until(lock, next_tick, [this] { return render_stop_requested_; })) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
if (next_tick < now) {
|
||||||
|
next_tick = now + period_ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!use_external_frames) {
|
||||||
|
destroyOffscreen_();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(cache_mtx_);
|
||||||
|
render_thread_running_ = false;
|
||||||
|
}
|
||||||
|
cache_cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
void MujocoCamera::destroyOffscreen_()
|
void MujocoCamera::destroyOffscreen_()
|
||||||
{
|
{
|
||||||
if (window_ != nullptr) {
|
if (window_ != nullptr) {
|
||||||
@ -376,6 +632,10 @@ void MujocoCamera::destroyOffscreen_()
|
|||||||
mjv_freeScene(&scene_);
|
mjv_freeScene(&scene_);
|
||||||
scene_initialized_ = false;
|
scene_initialized_ = false;
|
||||||
}
|
}
|
||||||
|
if (render_data_ != nullptr) {
|
||||||
|
mj_deleteData(render_data_);
|
||||||
|
render_data_ = nullptr;
|
||||||
|
}
|
||||||
if (window_ != nullptr) {
|
if (window_ != nullptr) {
|
||||||
glfwDestroyWindow(window_);
|
glfwDestroyWindow(window_);
|
||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
@ -388,7 +648,8 @@ bool MujocoCamera::renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsic
|
|||||||
setError_("[MujocoCamera] camera is not initialized: " + id_);
|
setError_("[MujocoCamera] camera is not initialized: " + id_);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!initOffscreen_()) {
|
if (window_ == nullptr || !context_initialized_ || !scene_initialized_) {
|
||||||
|
setError_("[MujocoCamera] offscreen renderer is not initialized: " + id_);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,15 +663,27 @@ bool MujocoCamera::renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsic
|
|||||||
std::vector<unsigned char> rgb(static_cast<std::size_t>(width_) * height_ * 3);
|
std::vector<unsigned char> rgb(static_cast<std::size_t>(width_) * height_ * 3);
|
||||||
std::vector<float> depth_raw(static_cast<std::size_t>(width_) * height_);
|
std::vector<float> depth_raw(static_cast<std::size_t>(width_) * height_);
|
||||||
|
|
||||||
|
mjModel* model = nullptr;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> world_lock(world->mutex());
|
std::unique_lock<std::mutex> world_lock(world->mutex(), std::try_to_lock);
|
||||||
mjModel* model = world->model();
|
if (!world_lock.owns_lock()) {
|
||||||
mjData* data = world->data();
|
// Never make the simulation wait for a camera frame. The next
|
||||||
if (model == nullptr || data == nullptr) {
|
// scheduled capture will use a newer state if this one is busy.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
model = world->model();
|
||||||
|
const mjData* data = world->data();
|
||||||
|
if (model == nullptr || data == nullptr || render_data_ == nullptr) {
|
||||||
setError_("[MujocoCamera] world model/data is null");
|
setError_("[MujocoCamera] world model/data is null");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the world lock limited to the state copy. GPU rendering runs on
|
||||||
|
// the camera thread using its private data snapshot.
|
||||||
|
mjv_copyData(render_data_, model, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
camera_.type = mjCAMERA_FIXED;
|
camera_.type = mjCAMERA_FIXED;
|
||||||
camera_.fixedcamid = camera_id_;
|
camera_.fixedcamid = camera_id_;
|
||||||
camera_.trackbodyid = -1;
|
camera_.trackbodyid = -1;
|
||||||
@ -421,7 +694,7 @@ bool MujocoCamera::renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsic
|
|||||||
viewport.width = width_;
|
viewport.width = width_;
|
||||||
viewport.height = height_;
|
viewport.height = height_;
|
||||||
|
|
||||||
mjv_updateScene(model, data, &option_, &perturb_, &camera_, mjCAT_ALL, &scene_);
|
mjv_updateScene(model, render_data_, &option_, &perturb_, &camera_, mjCAT_ALL, &scene_);
|
||||||
mjr_render(viewport, &scene_, &context_);
|
mjr_render(viewport, &scene_, &context_);
|
||||||
mjr_readPixels(rgb.data(), depth_raw.data(), viewport, &context_);
|
mjr_readPixels(rgb.data(), depth_raw.data(), viewport, &context_);
|
||||||
flipRgbAndDepth_(rgb, depth_raw, width_, height_);
|
flipRgbAndDepth_(rgb, depth_raw, width_, height_);
|
||||||
@ -429,13 +702,12 @@ bool MujocoCamera::renderOffscreen_(cv::Mat& color, cv::Mat& depth, Rs2Intrinsic
|
|||||||
}
|
}
|
||||||
|
|
||||||
cv::Mat rgb_mat(height_, width_, CV_8UC3, rgb.data());
|
cv::Mat rgb_mat(height_, width_, CV_8UC3, rgb.data());
|
||||||
color = rgb_mat.clone();
|
// mjr_readPixels returns RGB, while the rest of the camera API exposes
|
||||||
|
// OpenCV-compatible BGR frames (as UVC and RealSense do).
|
||||||
|
cv::cvtColor(rgb_mat, color, cv::COLOR_RGB2BGR);
|
||||||
cv::Mat depth_mat(height_, width_, CV_32FC1, depth_raw.data());
|
cv::Mat depth_mat(height_, width_, CV_32FC1, depth_raw.data());
|
||||||
depth = depth_mat.clone();
|
depth = depth_mat.clone();
|
||||||
fillIntrinsics(width_, height_, intrinsics);
|
fillIntrinsics(width_, height_, intrinsics);
|
||||||
++last_frame_id_;
|
|
||||||
has_last_frame_id_ = true;
|
|
||||||
clear_error_();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user