feat:add ibvs acc limit

This commit is contained in:
lgv 2026-03-30 13:39:14 +08:00
parent d02d23a3f9
commit 7148e1baf2
8 changed files with 180 additions and 118 deletions

View File

@ -116,6 +116,10 @@ public:
double ibvs_qdot_max{0.15}; double ibvs_qdot_max{0.15};
// 相机 twist 六维限幅 `[vx, vy, vz, wx, wy, wz]`。 // 相机 twist 六维限幅 `[vx, vy, vz, wx, wy, wz]`。
std::array<double, 6> ibvs_vmax6{{0.15, 0.15, 0.20, 0.6, 0.6, 0.6}}; std::array<double, 6> ibvs_vmax6{{0.15, 0.15, 0.20, 0.6, 0.6, 0.6}};
// 相机 twist 六维加速度限幅 `[ax, ay, az, alphax, alphay, alphaz]`。
std::array<double, 6> ibvs_amax6{{0.4, 0.4, 0.5, 1.5, 1.5, 1.5}};
// 相机 twist 一阶低通滤波系数,范围 [0, 1]。
double ibvs_twist_filter_alpha{0.35};
// 是否启用关节限位回避。 // 是否启用关节限位回避。
bool enable_joint_limit_avoidance{true}; bool enable_joint_limit_avoidance{true};
// 关节限位回避增益。 // 关节限位回避增益。
@ -221,7 +225,7 @@ public:
bool setTouchSpeedlForwardL(double forward_l); bool setTouchSpeedlForwardL(double forward_l);
bool startFromPixel(int u, int v); bool startFromPixel(int u, int v);
bool step(); bool step(double dt);
void stop(); void stop();
Phase phase() const { return phase_; } Phase phase() const { return phase_; }
@ -255,7 +259,7 @@ private:
void setOptions(const Options& options); void setOptions(const Options& options);
bool applyOptions(); bool applyOptions();
bool validateControlJointNames() const; bool validateControlJointNames() const;
bool stepAligning(); bool stepAligning(double dt);
bool stepTouching(); bool stepTouching();
bool stepDwelling(); bool stepDwelling();
bool stepRetracting(); bool stepRetracting();
@ -263,6 +267,7 @@ private:
bool readControlledJointPositions(std::vector<double>& q_out) const; bool readControlledJointPositions(std::vector<double>& q_out) const;
bool sendJointVelocity(const std::vector<double>& qdot) const; bool sendJointVelocity(const std::vector<double>& qdot) const;
bool sendZeroJointVelocity() const; bool sendZeroJointVelocity() const;
void hardStopIbvsMotion();
bool holdCurrentControlledPosition() const; bool holdCurrentControlledPosition() const;
bool moveToInitPositionIfEnabled() const; bool moveToInitPositionIfEnabled() const;
bool readCurrentTouchPointPositionBase(Eigen::Vector3d& p_out) const; bool readCurrentTouchPointPositionBase(Eigen::Vector3d& p_out) const;

View File

@ -606,11 +606,15 @@ bool TouchScreenApp::startFromPixel(int u, int v) {
return true; return true;
} }
bool TouchScreenApp::step() { bool TouchScreenApp::step(const double dt) {
if (!initialized_) { if (!initialized_) {
last_status_ = Status::NOT_INITIALIZED; last_status_ = Status::NOT_INITIALIZED;
return false; return false;
} }
if (!std::isfinite(dt) || dt <= 0.0) {
last_status_ = Status::INVALID_CONFIG;
return false;
}
if (dexhand_) { if (dexhand_) {
const bool tactile_ok = updateTouchPressure(); const bool tactile_ok = updateTouchPressure();
@ -629,7 +633,7 @@ bool TouchScreenApp::step() {
last_status_ = Status::IDLE; last_status_ = Status::IDLE;
return true; return true;
case Phase::ALIGNING: case Phase::ALIGNING:
return stepAligning(); return stepAligning(dt);
case Phase::ALIGN_REACHED: case Phase::ALIGN_REACHED:
last_status_ = Status::ALIGN_REACHED; last_status_ = Status::ALIGN_REACHED;
if (options_.pause_after_align_reached) { if (options_.pause_after_align_reached) {
@ -669,6 +673,7 @@ void TouchScreenApp::stop() {
} }
sendZeroJointVelocity(); sendZeroJointVelocity();
ibvs_.resetTwistCommandState();
holdCurrentControlledPosition(); holdCurrentControlledPosition();
phase_ = Phase::IDLE; phase_ = Phase::IDLE;
@ -814,6 +819,18 @@ bool TouchScreenApp::optionsFromConfig(const cmvr::config::TouchScreenAppConfig&
options.ibvs_vmax6[static_cast<size_t>(i)] = ibvs_vmax[i]; options.ibvs_vmax6[static_cast<size_t>(i)] = ibvs_vmax[i];
} }
} }
if (config.has_ibvs_amax6()) {
Eigen::Matrix<double, 6, 1> ibvs_amax;
ibvs_amax << options.ibvs_amax6[0], options.ibvs_amax6[1], options.ibvs_amax6[2],
options.ibvs_amax6[3], options.ibvs_amax6[4], options.ibvs_amax6[5];
applyTwist6FromConfig(config.ibvs_amax6(), ibvs_amax);
for (int i = 0; i < 6; ++i) {
options.ibvs_amax6[static_cast<size_t>(i)] = ibvs_amax[i];
}
}
if (config.has_ibvs_twist_filter_alpha()) {
options.ibvs_twist_filter_alpha = std::clamp(config.ibvs_twist_filter_alpha(), 0.0, 1.0);
}
if (config.has_align_error_threshold6()) { if (config.has_align_error_threshold6()) {
Eigen::Matrix<double, 6, 1> align_err; Eigen::Matrix<double, 6, 1> align_err;
align_err << options.align_error_threshold6[0], options.align_error_threshold6[1], options.align_error_threshold6[2], align_err << options.align_error_threshold6[0], options.align_error_threshold6[1], options.align_error_threshold6[2],
@ -1007,6 +1024,8 @@ bool TouchScreenApp::applyOptions() {
ibvs_.setMu(options_.ibvs_mu); ibvs_.setMu(options_.ibvs_mu);
ibvs_.setQdotMax(options_.ibvs_qdot_max); ibvs_.setQdotMax(options_.ibvs_qdot_max);
ibvs_.setVelocityLimit6(options_.ibvs_vmax6); ibvs_.setVelocityLimit6(options_.ibvs_vmax6);
ibvs_.setAccelerationLimit6(options_.ibvs_amax6);
ibvs_.setTwistFilterAlpha(options_.ibvs_twist_filter_alpha);
ibvs_.setJointLimitAvoidance(options_.enable_joint_limit_avoidance, ibvs_.setJointLimitAvoidance(options_.enable_joint_limit_avoidance,
options_.joint_limit_avoidance_gain, options_.joint_limit_avoidance_gain,
options_.joint_limit_avoidance_margin_ratio, options_.joint_limit_avoidance_margin_ratio,
@ -1038,20 +1057,21 @@ bool TouchScreenApp::validateControlJointNames() const {
return false; return false;
} }
bool TouchScreenApp::stepAligning() { bool TouchScreenApp::stepAligning(const double dt) {
const auto now = Clock::now(); const auto now = Clock::now();
const double elapsed = std::chrono::duration<double>(now - phase_start_time_).count(); const double elapsed = std::chrono::duration<double>(now - phase_start_time_).count();
if (elapsed > options_.align_timeout_s) { if (elapsed > options_.align_timeout_s) {
enterFailed(Status::ALIGN_TIMEOUT); enterFailed(Status::ALIGN_TIMEOUT);
return false; return false;
} }
const double ibvs_dt = std::clamp(dt, 0.005, 0.05);
perception::AprilTagPerception::Options perception_options; perception::AprilTagPerception::Options perception_options;
perception_options.depth_policy = options_.depth_policy; perception_options.depth_policy = options_.depth_policy;
perception_options.detect_tags = true; perception_options.detect_tags = true;
perception_options.fetch_encoded = false; perception_options.fetch_encoded = false;
if (!perception_->update(perception_options)) { if (!perception_->update(perception_options)) {
sendZeroJointVelocity(); hardStopIbvsMotion();
last_status_ = Status::ALIGN_WAITING_PERCEPTION; last_status_ = Status::ALIGN_WAITING_PERCEPTION;
return true; return true;
} }
@ -1069,7 +1089,7 @@ bool TouchScreenApp::stepAligning() {
} }
if (!tracking_ok) { if (!tracking_ok) {
sendZeroJointVelocity(); hardStopIbvsMotion();
align_stable_count_ = 0; align_stable_count_ = 0;
last_status_ = Status::ALIGN_WAITING_TRACK; last_status_ = Status::ALIGN_WAITING_TRACK;
return true; return true;
@ -1078,7 +1098,7 @@ bool TouchScreenApp::stepAligning() {
const int tag_id = tracker_.activeTagId(); const int tag_id = tracker_.activeTagId();
last_active_tag_id_ = tag_id; last_active_tag_id_ = tag_id;
if (tag_id < 0) { if (tag_id < 0) {
sendZeroJointVelocity(); hardStopIbvsMotion();
align_stable_count_ = 0; align_stable_count_ = 0;
last_status_ = Status::ALIGN_WAITING_TRACK; last_status_ = Status::ALIGN_WAITING_TRACK;
return true; return true;
@ -1086,7 +1106,7 @@ bool TouchScreenApp::stepAligning() {
Eigen::Vector3d p_t_target = Eigen::Vector3d::Zero(); Eigen::Vector3d p_t_target = Eigen::Vector3d::Zero();
if (!tracker_.getAnchorInTag(tag_id, p_t_target)) { if (!tracker_.getAnchorInTag(tag_id, p_t_target)) {
sendZeroJointVelocity(); hardStopIbvsMotion();
align_stable_count_ = 0; align_stable_count_ = 0;
last_status_ = Status::ALIGN_WAITING_TRACK; last_status_ = Status::ALIGN_WAITING_TRACK;
return true; return true;
@ -1094,7 +1114,7 @@ bool TouchScreenApp::stepAligning() {
const auto* current_tag = perception_->findTag(tag_id); const auto* current_tag = perception_->findTag(tag_id);
if (!current_tag) { if (!current_tag) {
sendZeroJointVelocity(); hardStopIbvsMotion();
align_stable_count_ = 0; align_stable_count_ = 0;
last_status_ = Status::ALIGN_WAITING_TRACK; last_status_ = Status::ALIGN_WAITING_TRACK;
return true; return true;
@ -1151,13 +1171,13 @@ bool TouchScreenApp::stepAligning() {
} }
std::vector<double> qdot_cmd; std::vector<double> qdot_cmd;
if (!ibvs_.compute(q_now, qdot_cmd)) { if (!ibvs_.compute(q_now, ibvs_dt, qdot_cmd)) {
switch (ibvs_.lastComputeStatus()) { switch (ibvs_.lastComputeStatus()) {
case IbvsController::ComputeStatus::NO_NEW_FRAME: case IbvsController::ComputeStatus::NO_NEW_FRAME:
case IbvsController::ComputeStatus::NO_TAG: case IbvsController::ComputeStatus::NO_TAG:
case IbvsController::ComputeStatus::TAG_MISMATCH: case IbvsController::ComputeStatus::TAG_MISMATCH:
case IbvsController::ComputeStatus::NO_DEPTH: case IbvsController::ComputeStatus::NO_DEPTH:
sendZeroJointVelocity(); hardStopIbvsMotion();
align_stable_count_ = 0; align_stable_count_ = 0;
last_status_ = Status::ALIGN_WAITING_TRACK; last_status_ = Status::ALIGN_WAITING_TRACK;
return true; return true;
@ -1206,7 +1226,7 @@ bool TouchScreenApp::stepAligning() {
} }
if (align_stable_count_ >= options_.align_stable_frames) { if (align_stable_count_ >= options_.align_stable_frames) {
sendZeroJointVelocity(); hardStopIbvsMotion();
phase_ = Phase::ALIGN_REACHED; phase_ = Phase::ALIGN_REACHED;
phase_start_time_ = Clock::now(); phase_start_time_ = Clock::now();
touch_command_started_ = false; touch_command_started_ = false;
@ -1390,6 +1410,11 @@ bool TouchScreenApp::sendZeroJointVelocity() const {
return sendJointVelocity(zero); return sendJointVelocity(zero);
} }
void TouchScreenApp::hardStopIbvsMotion() {
sendZeroJointVelocity();
ibvs_.resetTwistCommandState();
}
bool TouchScreenApp::readCurrentTouchPointPositionBase(Eigen::Vector3d& p_out) const { bool TouchScreenApp::readCurrentTouchPointPositionBase(Eigen::Vector3d& p_out) const {
if (!robot_) { if (!robot_) {
return false; return false;
@ -1622,7 +1647,7 @@ void TouchScreenApp::enterFailed(const Status status) {
} }
} catch (...) { } catch (...) {
} }
sendZeroJointVelocity(); hardStopIbvsMotion();
holdCurrentControlledPosition(); holdCurrentControlledPosition();
phase_ = Phase::FAILED; phase_ = Phase::FAILED;
touch_command_started_ = false; touch_command_started_ = false;

View File

@ -1,5 +1,6 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include <algorithm>
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
@ -36,8 +37,19 @@ void run_touch_once(int u, int v) {
bool align_reached = false; bool align_reached = false;
bool touch_triggered = false; bool touch_triggered = false;
auto last_step_time = std::chrono::steady_clock::now();
bool first_step = true;
while (app.isBusy()) { while (app.isBusy()) {
const bool step_ok = app.step(); const auto now = std::chrono::steady_clock::now();
double dt = 0.02;
if (!first_step) {
dt = std::chrono::duration<double>(now - last_step_time).count();
dt = std::clamp(dt, 0.005, 0.05);
}
last_step_time = now;
first_step = false;
const bool step_ok = app.step(dt);
const auto& p_c_target = app.tracker().lastTargetInCamera(); const auto& p_c_target = app.tracker().lastTargetInCamera();
std::cout << "phase=" << cmvr::app::TouchScreenApp::phaseToString(app.phase()) std::cout << "phase=" << cmvr::app::TouchScreenApp::phaseToString(app.phase())
<< ", status=" << cmvr::app::TouchScreenApp::statusToString(app.lastStatus()) << ", status=" << cmvr::app::TouchScreenApp::statusToString(app.lastStatus())

View File

@ -64,6 +64,15 @@ ibvs_vmax6 {
wy: 0.6 wy: 0.6
wz: 0.6 wz: 0.6
} }
ibvs_amax6 {
vx: 0.4
vy: 0.4
vz: 0.5
wx: 1.5
wy: 1.5
wz: 1.5
}
ibvs_twist_filter_alpha: 0.35
enable_joint_limit_avoidance: true enable_joint_limit_avoidance: true
joint_limit_avoidance_gain: 0.2 joint_limit_avoidance_gain: 0.2
joint_limit_avoidance_margin_ratio: 0.15 joint_limit_avoidance_margin_ratio: 0.15

View File

@ -109,6 +109,9 @@ public:
* @param joints_angle * @param joints_angle
* @param qdot_out * @param qdot_out
* @return `true` * @return `true`
*
* @note dt 使 twist
* 使 dt compute
*/ */
bool compute(const std::vector<double>& joints_angle, bool compute(const std::vector<double>& joints_angle,
std::vector<double>& qdot_out); std::vector<double>& qdot_out);
@ -190,6 +193,25 @@ public:
*/ */
void setVelocityLimit6(const std::array<double, 6>& vmax6); void setVelocityLimit6(const std::array<double, 6>& vmax6);
/**
* @brief twist
* @param amax6 线
*/
void setAccelerationLimit6(const std::array<double, 6>& amax6);
/**
* @brief twist
* @param alpha [0, 1]0 1
*/
void setTwistFilterAlpha(double alpha);
/**
* @brief twist
*
* 0 沿 twist
*/
void resetTwistCommandState();
/** /**
* @brief * @brief
* @param enable * @param enable
@ -214,31 +236,14 @@ public:
*/ */
void setAlignCameraToUrdf(const Eigen::Matrix3d& R_camera_urdf); void setAlignCameraToUrdf(const Eigen::Matrix3d& R_camera_urdf);
// 最近一帧是否检测到了当前被跟踪的 tag。
bool isTagDetected() const { return last_tag_detected_; } bool isTagDetected() const { return last_tag_detected_; }
// 最近一次 `compute()` 的状态。
ComputeStatus lastComputeStatus() const { return last_compute_status_; } ComputeStatus lastComputeStatus() const { return last_compute_status_; }
// 计算状态转字符串。
static const char* statusToString(ComputeStatus status); static const char* statusToString(ComputeStatus status);
// 最近一次 `compute()` 实际使用的深度来源。
DepthUsage lastDepthUsage() const { return last_depth_usage_; } DepthUsage lastDepthUsage() const { return last_depth_usage_; }
// 深度来源转字符串。
static const char* depthUsageToString(DepthUsage usage); static const char* depthUsageToString(DepthUsage usage);
// 最近一次使用到的 tag 原点在 ViSP 相机坐标系 `c` 中的位置。
const Eigen::Vector3d& lastTagPositionVisp() const { return last_tag_pos_visp_; } const Eigen::Vector3d& lastTagPositionVisp() const { return last_tag_pos_visp_; }
// 当前配置要跟踪的 tag id。
int trackedTagId() const { return tracked_tag_id_; } int trackedTagId() const { return tracked_tag_id_; }
// 最近一次成功控制时实际使用的 tag id。
int lastUsedTagId() const { return last_used_tag_id_; } int lastUsedTagId() const { return last_used_tag_id_; }
// 最近一次输出的相机 twist位于 ViSP 相机坐标系 `c`。
const Eigen::Matrix<double, 6, 1>& lastCameraTwistVisp() const { return last_v_camera_visp_; } const Eigen::Matrix<double, 6, 1>& lastCameraTwistVisp() const { return last_v_camera_visp_; }
/** /**
@ -252,10 +257,12 @@ private:
/** /**
* @brief * @brief
* @param joints_angle * @param joints_angle
* @param dt
* @param qdot_out * @param qdot_out
* @return `true` * @return `true`
*/ */
bool computeInternal(const std::vector<double>& joints_angle, bool computeInternal(const std::vector<double>& joints_angle,
double dt,
std::vector<double>& qdot_out); std::vector<double>& qdot_out);
/** /**
@ -281,128 +288,72 @@ private:
void initTask(); void initTask();
private: private:
// 控制器和 IK 求解器是否已成功初始化。
bool initialized_{false}; bool initialized_{false};
// IK 链基座 frame 名称。
std::string base_frame_name_; std::string base_frame_name_;
// URDF 中相机 frame 名称,对应坐标系 `u`。
std::string camera_frame_name_; std::string camera_frame_name_;
// 共享感知前端。
std::shared_ptr<cmvr::perception::AprilTagPerception> perception_{nullptr}; std::shared_ptr<cmvr::perception::AprilTagPerception> perception_{nullptr};
// ViSP 视觉伺服增益。
double lambda_{0.7}; double lambda_{0.7};
// 控制模型使用的 tag 边长,单位米。
double tag_size_m_{0.12}; double tag_size_m_{0.12};
// 控制模型使用的 tag 半边长,单位米。
double tag_half_{0.06}; double tag_half_{0.06};
// 期望 tag 原点在 ViSP 相机坐标系 `c` 中的 x 坐标,单位米。
double target_x_{0.0}; double target_x_{0.0};
// 期望 tag 原点在 ViSP 相机坐标系 `c` 中的 y 坐标,单位米。
double target_y_{0.0}; double target_y_{0.0};
// 期望 tag 原点在 ViSP 相机坐标系 `c` 中的 z 坐标,单位米。
double target_z_{0.33}; double target_z_{0.33};
// 期望位姿 `cMo_des` 的旋转参数 rx单位弧度。
double target_rx_{3.14159265358979323846}; double target_rx_{3.14159265358979323846};
// 期望位姿 `cMo_des` 的旋转参数 ry单位弧度。
double target_ry_{0.0}; double target_ry_{0.0};
// 期望位姿 `cMo_des` 的旋转参数 rz单位弧度。
double target_rz_{0.0}; double target_rz_{0.0};
// DLS IK 阻尼系数。
double mu_{0.02}; double mu_{0.02};
// 关节速度上限。
double qdot_max_{0.6}; double qdot_max_{0.6};
// 深度使用模式。
DepthMode depth_mode_{DepthMode::MONOCULAR}; DepthMode depth_mode_{DepthMode::MONOCULAR};
// 深度闭环比例增益。
double depth_z_kp_{1.0}; double depth_z_kp_{1.0};
// 相机 twist 六维限幅。
std::array<double, 6> vmax6_{{0.15, 0.15, 0.20, 0.6, 0.6, 0.6}}; std::array<double, 6> vmax6_{{0.15, 0.15, 0.20, 0.6, 0.6, 0.6}};
// 是否启用关节限位回避。 // 新增:相机 twist 六维加速度限幅
std::array<double, 6> amax6_{{0.4, 0.4, 0.5, 1.5, 1.5, 1.5}};
// 新增:相机 twist 一阶低通滤波系数
double twist_lpf_alpha_{0.35};
bool limit_avoidance_enabled_{false}; bool limit_avoidance_enabled_{false};
// 关节限位回避增益。
double limit_avoidance_gain_{0.2}; double limit_avoidance_gain_{0.2};
// 关节限位回避触发边界比例。
double limit_avoidance_margin_ratio_{0.15}; double limit_avoidance_margin_ratio_{0.15};
// 单关节最大推回速度。
double limit_avoidance_max_push_{0.25}; double limit_avoidance_max_push_{0.25};
// 用于深度闭环的 tag 平面控制点,位于 tag 坐标系 `t` 的 xy 平面,单位米。
Eigen::Vector2d depth_control_point_tag_{Eigen::Vector2d::Zero()}; Eigen::Vector2d depth_control_point_tag_{Eigen::Vector2d::Zero()};
// 从 `AbstractCamera` 相机坐标系 `cam` 到 ViSP 相机坐标系 `c` 的旋转矩阵。
Eigen::Matrix3d R_cv_{Eigen::Matrix3d::Identity()}; Eigen::Matrix3d R_cv_{Eigen::Matrix3d::Identity()};
// 从 `AbstractCamera` 相机坐标系 `cam` 到 URDF 相机坐标系 `u` 的旋转矩阵。
Eigen::Matrix3d R_camera_urdf_{Eigen::Matrix3d::Identity()}; Eigen::Matrix3d R_camera_urdf_{Eigen::Matrix3d::Identity()};
// ViSP 视觉伺服任务。
std::unique_ptr<vpServo> task_{nullptr}; std::unique_ptr<vpServo> task_{nullptr};
// tag 四个角点在 tag 坐标系 `t` 中的 3D 模型点。
vpPoint obj_pts_[4]; vpPoint obj_pts_[4];
// 当前观测特征。
vpFeaturePoint s_cur_[4]; vpFeaturePoint s_cur_[4];
// 期望特征。
vpFeaturePoint s_star_[4]; vpFeaturePoint s_star_[4];
// 当前配置要跟踪的 tag id。
int tracked_tag_id_{-1}; int tracked_tag_id_{-1};
// 速度 IK 求解器。
std::unique_ptr<PinocchioDlsIKSolver> dls_solver_{nullptr}; std::unique_ptr<PinocchioDlsIKSolver> dls_solver_{nullptr};
// 是否成功读取 URDF 中的关节位置限位。
bool has_joint_position_limits_{false}; bool has_joint_position_limits_{false};
// 关节位置下限。
Eigen::VectorXd q_lower_limits_; Eigen::VectorXd q_lower_limits_;
// 关节位置上限。
Eigen::VectorXd q_upper_limits_; Eigen::VectorXd q_upper_limits_;
// 最近一次成功控制时实际使用的 tag id。
int last_used_tag_id_{-1}; int last_used_tag_id_{-1};
// 内部积分得到的关节位置命令缓存。
std::vector<double> q_cmd_; std::vector<double> q_cmd_;
// 最近一帧是否检测到了当前被跟踪的 tag。
bool last_tag_detected_{false}; bool last_tag_detected_{false};
// 最近一次 `compute()` 的状态。
ComputeStatus last_compute_status_{ComputeStatus::NOT_READY}; ComputeStatus last_compute_status_{ComputeStatus::NOT_READY};
// 最近一次 `compute()` 实际使用的深度来源。
DepthUsage last_depth_usage_{DepthUsage::NONE}; DepthUsage last_depth_usage_{DepthUsage::NONE};
// 最近一次使用到的 tag 原点在 ViSP 相机坐标系 `c` 中的位置。
Eigen::Vector3d last_tag_pos_visp_{Eigen::Vector3d::Zero()}; Eigen::Vector3d last_tag_pos_visp_{Eigen::Vector3d::Zero()};
// 最近一次输出的相机 twist位于 ViSP 相机坐标系 `c`。
Eigen::Matrix<double, 6, 1> last_v_camera_visp_{Eigen::Matrix<double, 6, 1>::Zero()}; Eigen::Matrix<double, 6, 1> last_v_camera_visp_{Eigen::Matrix<double, 6, 1>::Zero()};
// 新增:上一拍实际发送给 IK 的相机 twistViSP 相机系)
Eigen::Matrix<double, 6, 1> v_camera_cmd_prev_{Eigen::Matrix<double, 6, 1>::Zero()};
bool has_v_camera_cmd_prev_{false};
}; };
} // namespace cmvr } // namespace cmvr

View File

@ -42,6 +42,7 @@ const char* IbvsController::depthUsageToString(DepthUsage usage) {
IbvsController::IbvsController() { IbvsController::IbvsController() {
R_cv_.setIdentity(); R_cv_.setIdentity();
R_camera_urdf_.setIdentity(); R_camera_urdf_.setIdentity();
resetTwistCommandState();
updateDepthControlPointInTag(); updateDepthControlPointInTag();
initTask(); initTask();
@ -77,7 +78,6 @@ bool IbvsController::init(const std::string& urdf_path,
void IbvsController::setPerception(const std::shared_ptr<cmvr::perception::AprilTagPerception>& perception) { void IbvsController::setPerception(const std::shared_ptr<cmvr::perception::AprilTagPerception>& perception) {
perception_ = perception; perception_ = perception;
// 注入后立刻同步一次 tag size用于控制模型
syncTagSizeFromPerception(true); syncTagSizeFromPerception(true);
} }
@ -92,10 +92,7 @@ void IbvsController::syncTagSizeFromPerception(bool force) {
tag_size_m_ = s; tag_size_m_ = s;
tag_half_ = 0.5 * tag_size_m_; tag_half_ = 0.5 * tag_size_m_;
// 深度控制点约束依赖 tag_half_
updateDepthControlPointInTag(); updateDepthControlPointInTag();
// 任务几何依赖 tag_half_
initTask(); initTask();
} }
@ -113,6 +110,7 @@ void IbvsController::reset(const std::vector<double>& q_init) {
last_depth_usage_ = DepthUsage::NONE; last_depth_usage_ = DepthUsage::NONE;
last_tag_pos_visp_.setZero(); last_tag_pos_visp_.setZero();
last_v_camera_visp_.setZero(); last_v_camera_visp_.setZero();
resetTwistCommandState();
} }
bool IbvsController::compute(const std::vector<double>& joints_angle, bool IbvsController::compute(const std::vector<double>& joints_angle,
@ -128,7 +126,7 @@ bool IbvsController::compute(const std::vector<double>& joints_angle,
} }
std::vector<double> qdot; std::vector<double> qdot;
if (!computeInternal(joints_angle, qdot)) { if (!computeInternal(joints_angle, dt, qdot)) {
return false; return false;
} }
@ -143,7 +141,8 @@ bool IbvsController::compute(const std::vector<double>& joints_angle,
bool IbvsController::compute(const std::vector<double>& joints_angle, bool IbvsController::compute(const std::vector<double>& joints_angle,
std::vector<double>& qdot_out) { std::vector<double>& qdot_out) {
return computeInternal(joints_angle, qdot_out); constexpr double kDefaultDt = 0.02; // 50 Hz
return computeInternal(joints_angle, kDefaultDt, qdot_out);
} }
bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) const { bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) const {
@ -155,6 +154,7 @@ bool IbvsController::getChainJointNames(std::vector<std::string>& joint_names) c
} }
bool IbvsController::computeInternal(const std::vector<double>& joints_angle, bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
double dt,
std::vector<double>& qdot_out) { std::vector<double>& qdot_out) {
last_depth_usage_ = DepthUsage::NONE; last_depth_usage_ = DepthUsage::NONE;
last_tag_detected_ = false; last_tag_detected_ = false;
@ -170,6 +170,10 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
last_compute_status_ = ComputeStatus::NOT_READY; last_compute_status_ = ComputeStatus::NOT_READY;
return false; return false;
} }
if (dt <= 0.0 || !std::isfinite(dt)) {
last_compute_status_ = ComputeStatus::INVALID_INPUT;
return false;
}
if (joints_angle.empty()) { if (joints_angle.empty()) {
last_compute_status_ = ComputeStatus::INVALID_INPUT; last_compute_status_ = ComputeStatus::INVALID_INPUT;
return false; return false;
@ -179,10 +183,8 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
return false; return false;
} }
// 每帧确保 tag_size 同步(你可能运行时调 perception->setTagSize()
syncTagSizeFromPerception(false); syncTagSizeFromPerception(false);
// perception 必须先 update(),这里以 color 是否为空作为“是否ready”的简单判据
if (perception_->color().empty()) { if (perception_->color().empty()) {
last_compute_status_ = ComputeStatus::NO_NEW_FRAME; last_compute_status_ = ComputeStatus::NO_NEW_FRAME;
return false; return false;
@ -205,7 +207,6 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
const vpHomogeneousMatrix& cMo = tag->cMo; const vpHomogeneousMatrix& cMo = tag->cMo;
last_tag_pos_visp_ << cMo[0][3], cMo[1][3], cMo[2][3]; last_tag_pos_visp_ << cMo[0][3], cMo[1][3], cMo[2][3];
// intrinsics
const auto& intr = perception_->intrinsics(); const auto& intr = perception_->intrinsics();
const double fx = static_cast<double>(intr.fx); const double fx = static_cast<double>(intr.fx);
const double fy = static_cast<double>(intr.fy); const double fy = static_cast<double>(intr.fy);
@ -250,7 +251,7 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
s_cur_[i].buildFrom(x, y, Z); s_cur_[i].buildFrom(x, y, Z);
} }
last_depth_usage_ = depth_ctrl_used ? DepthUsage::DEPTH_ONLY : DepthUsage::POSE_ONLY; last_depth_usage_ = depth_ctrl_used ? DepthUsage::MIXED : DepthUsage::POSE_ONLY;
vpColVector v_c = task_->computeControlLaw(); vpColVector v_c = task_->computeControlLaw();
@ -259,9 +260,41 @@ bool IbvsController::computeInternal(const std::vector<double>& joints_angle,
v_c[2] = depth_z_kp_ * (z_depth_ctrl - target_z_); v_c[2] = depth_z_kp_ * (z_depth_ctrl - target_z_);
} }
// 限幅+缓存ViSP camera系 // ---------- 视觉速度整形:速度限幅 + 加速度限幅 + 一阶低通 ----------
Eigen::Matrix<double, 6, 1> v_raw;
for (int i = 0; i < 6; ++i) { for (int i = 0; i < 6; ++i) {
v_c[i] = SupportFunctions::clamp(v_c[i], -vmax6_[i], vmax6_[i]); v_raw[i] = std::isfinite(v_c[i]) ? v_c[i] : 0.0;
}
// 1) 速度限幅
for (int i = 0; i < 6; ++i) {
v_raw[i] = SupportFunctions::clamp(v_raw[i], -vmax6_[i], vmax6_[i]);
}
if (!has_v_camera_cmd_prev_) {
resetTwistCommandState();
}
// 2) 加速度限幅
Eigen::Matrix<double, 6, 1> v_acc_limited = v_camera_cmd_prev_;
for (int i = 0; i < 6; ++i) {
const double amax = std::max(0.0, amax6_[i]);
const double dv_max = amax * dt;
const double dv_des = v_raw[i] - v_camera_cmd_prev_[i];
const double dv = SupportFunctions::clamp(dv_des, -dv_max, dv_max);
v_acc_limited[i] = v_camera_cmd_prev_[i] + dv;
}
// 3) 一阶低通
const double alpha = std::clamp(twist_lpf_alpha_, 0.0, 1.0);
if (alpha <= 0.0 || alpha >= 1.0) {
v_camera_cmd_prev_ = v_acc_limited;
} else {
v_camera_cmd_prev_ = alpha * v_acc_limited + (1.0 - alpha) * v_camera_cmd_prev_;
}
for (int i = 0; i < 6; ++i) {
v_c[i] = v_camera_cmd_prev_[i];
last_v_camera_visp_[i] = v_c[i]; last_v_camera_visp_[i] = v_c[i];
} }
@ -346,7 +379,7 @@ void IbvsController::setTarget(double x,
initTask(); initTask();
} }
bool IbvsController::setTargetFromPointInTag(const Eigen::Vector3d& p_t_target, bool IbvsController::setTargetFromPointInTag(const Eigen::Vector3d& p_t_target,
const Eigen::Vector3d& p_c_target_des, const Eigen::Vector3d& p_c_target_des,
double rx, double rx,
double ry, double ry,
@ -356,7 +389,7 @@ void IbvsController::setTarget(double x,
} }
vpRotationMatrix R_des_visp; vpRotationMatrix R_des_visp;
R_des_visp.buildFrom(rx, ry, rz); // 注意:这是 rotvectheta*u R_des_visp.buildFrom(rx, ry, rz);
Eigen::Matrix3d R_des; Eigen::Matrix3d R_des;
for (int r = 0; r < 3; ++r) { for (int r = 0; r < 3; ++r) {
@ -399,6 +432,20 @@ void IbvsController::setVelocityLimit6(const std::array<double, 6>& vmax6) {
vmax6_ = vmax6; vmax6_ = vmax6;
} }
void IbvsController::setAccelerationLimit6(const std::array<double, 6>& amax6) {
amax6_ = amax6;
}
void IbvsController::setTwistFilterAlpha(double alpha) {
twist_lpf_alpha_ = std::clamp(alpha, 0.0, 1.0);
}
void IbvsController::resetTwistCommandState() {
v_camera_cmd_prev_.setZero();
last_v_camera_visp_.setZero();
has_v_camera_cmd_prev_ = true;
}
void IbvsController::setJointLimitAvoidance(bool enable, void IbvsController::setJointLimitAvoidance(bool enable,
double gain, double gain,
double margin_ratio, double margin_ratio,
@ -440,7 +487,6 @@ void IbvsController::updateDepthControlPointInTag() {
depth_control_point_tag_ = A.fullPivLu().solve(b); depth_control_point_tag_ = A.fullPivLu().solve(b);
// 限制在 tag 边界内
depth_control_point_tag_.x() = SupportFunctions::clamp(depth_control_point_tag_.x(), -tag_half_, tag_half_); depth_control_point_tag_.x() = SupportFunctions::clamp(depth_control_point_tag_.x(), -tag_half_, tag_half_);
depth_control_point_tag_.y() = SupportFunctions::clamp(depth_control_point_tag_.y(), -tag_half_, tag_half_); depth_control_point_tag_.y() = SupportFunctions::clamp(depth_control_point_tag_.y(), -tag_half_, tag_half_);
} }

View File

@ -5,6 +5,7 @@
#include "../include/grpc_hlc_service.h" #include "../include/grpc_hlc_service.h"
#include <algorithm>
#include <chrono> #include <chrono>
#include <stdexcept> #include <stdexcept>
#include <thread> #include <thread>
@ -92,6 +93,8 @@ grpc::Status gRPCHlcServiceImpl::touch(grpc::ServerContext *context, const cmvr:
bool align_reached = false; bool align_reached = false;
bool touch_triggered = false; bool touch_triggered = false;
auto last_logged_status = cmvr::app::TouchScreenApp::Status::IDLE; auto last_logged_status = cmvr::app::TouchScreenApp::Status::IDLE;
auto last_step_time = std::chrono::steady_clock::now();
bool first_step = true;
while (touch_app_.isBusy()) { while (touch_app_.isBusy()) {
if (context != nullptr && context->IsCancelled()) { if (context != nullptr && context->IsCancelled()) {
touch_app_.stop(); touch_app_.stop();
@ -100,7 +103,16 @@ grpc::Status gRPCHlcServiceImpl::touch(grpc::ServerContext *context, const cmvr:
return grpc::Status(grpc::StatusCode::CANCELLED, error); return grpc::Status(grpc::StatusCode::CANCELLED, error);
} }
if (!touch_app_.step()) { const auto now = std::chrono::steady_clock::now();
double dt = 0.02;
if (!first_step) {
dt = std::chrono::duration<double>(now - last_step_time).count();
dt = std::clamp(dt, 0.005, 0.05);
}
last_step_time = now;
first_step = false;
if (!touch_app_.step(dt)) {
throw std::runtime_error( throw std::runtime_error(
buildTouchFailureMessage(touch_app_, "touch flow failed")); buildTouchFailureMessage(touch_app_, "touch flow failed"));
} }

View File

@ -104,6 +104,8 @@ message TouchScreenAppConfig {
optional double ibvs_mu = 13; optional double ibvs_mu = 13;
optional double ibvs_qdot_max = 14; optional double ibvs_qdot_max = 14;
TouchScreenTwist6 ibvs_vmax6 = 15; TouchScreenTwist6 ibvs_vmax6 = 15;
TouchScreenTwist6 ibvs_amax6 = 57;
optional double ibvs_twist_filter_alpha = 58;
optional bool enable_joint_limit_avoidance = 16; optional bool enable_joint_limit_avoidance = 16;
optional double joint_limit_avoidance_gain = 17; optional double joint_limit_avoidance_gain = 17;
optional double joint_limit_avoidance_margin_ratio = 18; optional double joint_limit_avoidance_margin_ratio = 18;