Paper : Auto Filmer: Autonomous Aerial Videography Under Human Interaction (Zhiwei Zhang; Yuhang Zhong; Junlong Guo; Qianhao Wang; Chao Xu; Fei Gao / IEEE RAL 2023)
code: https://github.com/ZJU-FAST-Lab/Auto-Filmer/tree/main?tab=readme-ov-file
→ 기존의 trajectory planning 연구에서 shot constraint를 추가한 것이 핵심
최근에는 여러 vision recognition 연구와 motion planning 연구를 통해서 UAV가 자동으로 따라다니면서 촬영하는 시도가 많이 있었지만, 아직 촬영의 미적인 요소나 human interaction을 고려한 연구는 많이 없음 (Elastic-Tracker 등의 aerial tracker도 매우 성능이 좋긴 함)
이러한 사람들의 촬영 요소에 대한 요구를 robot motion으로 반영하는 것이 어려운 일
해당 연구인 Auto Filmer
은 촬영자의 촬영 조건 등을 실시간으로 고려하여 robot trajectory planning을 수행하는 interative videography system
이 trajectory planning은 hierarchical planning scheme을 가짐
videography에서 중요한 세 가지 factors
이러한 videography 요소들을 수학식으로 표현하기 위해서 아래의 parameter들이 필요
→ 이는 아래의 GUI처럼 표현할 수 있음
videography parameter
는 매 time stamp마다 할당되는 값 → 이 파라미터들이 촬영 모드를 정의한다고 볼 수 있을 듯Stable stage
: 드론이 타겟을 고정된 패턴을 유지하면서 타겟을 촬영
→ 는 constant, 는 0을 가짐
Transition state
: 이전 촬영 configuration에서 새로 지정된 config로 촬영 frame이 변화하는 과정.
→ 이 state에서 parameter들은 시간에 따라 interpolate됨
→ 는 constant
Videography platform은 같은 연구실의 FAST-TRACKER를 따랐다고 한다.
자율주행 videography 시스템에서 visual perception은 두 가지 중요한 역할을 수행
Environment sensing
: 장애물 회피 등을 위해 mapping하는 역할Target tracking
: 타겟을 추적하는 역할해당 연구에서는 mapping을 위한 depth camera와 촬영을 위한 monocular camera를 따로 사용했다고 한다.
또한 짐벌은 yaw방향 회전을 하는 것을 사용
→ 구현해놓은 것 보면 드론의 heading은 velocity방향이고, 실제로 타겟을 tracking하는 것은 짐벌의 몫. (Depth camera가 앞을 바라보고 있어야 mapping하면서 장애물 회피 가능하므로)
안정적인 frame을 담기 위해 gimbal motion도 함께 계획
Hybrid A* path search 알고리즘을 사용
각 node는 robot state를 represent하며, A star와 비슷하게 각 node에는 출발지부터 현재까지의 effort인 cost function 과 도착지까지 effort estimate인 heuristic function 이 할당됌
하지만 목적지까지 가장 짧은 경로를 찾는 대신, 해당 연구의 videography kinodynamic search는 주어진 videography parameter와 차이를 가장 적게하는 경로를 찾음
여기서 planner는 target motion의 미래 information을 필요로 한다.
이때 target의 motion을 예측하는 prediction module이 필요한데, 이는 motion model을 활용한 kalman filter를 활용하든, deep learning model을 활용하면 될 것 같다.
→ 이 논문의 scope는 아님.
어쨋든 prediction module은 아래의 정보를 줄 수 있어야 한다.
경로 탐색을 하는 동안, node들은 이 prediction과 같은 시간 간격으로 확장되어간다.
→ 그러므로 각 노드 에 대해 현 시간 에서 드론의 state뿐만 아니라 target의 위치 이 필요하다.
View Region을 생성하기 위해서 다음의 작업이 수행된다. (위 그림 참조)
먼저, 드론의 dynamic limit을 기반으로 각 time 에서 initial point로부터 adjustable한 영역을 추출한다.
Videography 특성상 video quality는 distance와 perspective의 영향을 크게 받으므로, voxel이 아닌 고리 형태로 이 영역을 여러 후보로 잘게 나눈다. (예를 들어 후보 영역 중 빨간색 영역은 그림에서 보다시피 특정 거리와 각도를 포함하는 영역)
각 영역은 타겟에 상대적으로 특정 거리와 각도를 가지게 되는데, 이 값들의 medium 값을 기반으로 아래의 evaluation 함수 를 계산할 수 있다.
여기서 마지막 term이 장애물에 가려지는 것에 대한 visibility를 측정해주며, occlusion angle 는 ray-casting을 통해 정해질 수 있는 각
장애물 등에 의해서 가려지는 영역은 후보는 제외하고, 최소의 를 갖는 후보 area가 view region으로 선택된다.
선택된 view region은 이후 최적화를 위해 convex area로 approximate된다.
이 연구에서는 이를 polytope로 표현
출처: Planning Dynamically Feasible Trajectories for Quadrotors Using Safe Flight Corridors in 3-D Complex Environments
MINCO trajectory class
를 활용하여 드론의 trajectory를 표현하고 최적화한다.** 최적화에서 최적화하는 변수는 와 !!
여기서 은 계수 행렬
는 natural basis
이러한 trajectory의 생성 과정은 아래의 multi-objective 최적화 문제를 풀어서 진행
는 각각의 constraint violation을 포함하는 cost function
는 cost term의 weight
은 polynomial pieces의 갯수
은 prediction points의 갯수
: smoothness cost로, trajectory로부터 바로 계산되는 값
뒤의 두개 term은 해당 방법론에서 최적화의 inequality constraint 에 관련되는 term들
일 때만 cost가 적용되고 아니면 0
이러한 constraint는 두 가지로 나뉨
Drone Induced constraints
해당 연구에서는 robot translation 를 표현하기 위해 trajectory를 활용하였고, drone yaw angle 와 gimbal angle 를 표현하기 위해 를 활용했다고 한다.
이제 각각의 constraint에는 어떤 것이 있는지 확인해보자.
** Differential flatness
한 시스템에서 어떤 flat output이 있어서, 시스템의 모든 state와 input을 이 flat output들과 이의 미분들의 smooth function으로 표현할 수 있다면 differentially flat하다고 한다.
Quadrotor에서 이러한 flat output은 position (x,y,z)와 yaw와 그 미분들 (vel, acc, jerk, yaw rate), 이를 통해 state 와 input (torque, thrust, …)를 표현할 수 있음
이렇게 하면, 내가 원하는 output을 specify하면 이를 달성하기 위한 control input을 직접적으로 구할 수 있게 된다.
즉, planning이나 optimize 시, 드론의 nonlinear dynamic를 explicit하게 다룰 필요가 없어진다.
또한 optimization을 수행 시, cost function에 대한 state나 input의 gradient를 flat output들로 propagate할 때, flatness mapping 함수의 역함수를 활용할 수 있는것이다.
또 다른 videography parameter인 는 state variables 에 의해 결정된다.
Camera frame에서 물체의 위치 는 아래와 같이 구해질 수 있다.
는 body attitude quaternion 로부터 구해질 수 있고,
는 gimbal rotation 로부터 구해질 수 있다.
는 이미 알고 있는 값 (translation)
Pinhole camera model을 가정하면, (Intrinsic known)
https://github.com/ZJU-FAST-Lab/Auto-Filmer/tree/main?tab=readme-ov-file
FASTLAB의 연구들 코드 베이스는 거의 다 비슷하다.
보통 크게는 아래와 같은 구조로 이루어져 있다.
다른 부분은 거의 공통적이고, planning
이 연구의 메인
planning_nodelet.cpp
: front-end planning + back-end optimization + mapping + target trackingplan_timer_callback()
→ main trajectory planning functionplanning_nodelet.cpp
에 사용되는 여러 모듈들gridmapPtr_ = std::make_shared<mapping::OccGridMap>();
envPtr_ = std::make_shared<env::Env>(nh, gridmapPtr_);
pathPtr_ = std::make_shared<path::PathSearch>(nh, gridmapPtr_);
visPtr_ = std::make_shared<visualization::Visualization>(nh);
trajOptPtr_ = std::make_shared<traj_opt::TrajOpt>(nh);
prePtr_ = std::make_shared<prediction::Predict>(nh);
shotPtr_ = std::make_shared<shot::ShotGenerator>(nh);
subscribed topics 확인
ReplanState
Shotconfig generation
shotPtr_ -> generate(shotcfg_list, predict_n);
Predict target state
predict_n
만큼 predictprePtr_->predict(target_p, target_v, target_predcit, target_vels);
: prePtr
은 target prediction module
Kinodynamic A-star
generate_new_traj_success = pathPtr_->findPath(target_predcit, target_headings, shotcfg_list,
iniState.col(0), iniState.col(1), kAstar_path);
kAstar_path
라는 3d vector에 담김논문 내용과 동일하게 타겟과 드론의 상대적인 벡터를 기반으로 distance, tilt, angle을 계산하여 사용
여기서, angle은 상대적인 위치 벡터를 기반으로 고려하기 때문에 orientation은 아직 영향을 주지 않음
inline double score(const Eigen::Vector3d& p, const Eigen::Vector3d& target, const double& target_yaw, const shot::ShotConfig& shotcfg)
{
double d, angle, des_d, des_angle, delta_angle, best_tilt, des_z;
Eigen::Vector3d dp = p - target;
// 1. distance
des_d = shotcfg.distance;
d = dp.norm();
// 2. tilt
best_tilt = atan((shotcfg.image_p(1) - cy_) / fy_);
des_z = target.z() + shotcfg.distance * sin(best_tilt);
// if ground limits, then best_z is clearance_d_
// if(des_z < clearance_d_)
// des_z = clearance_d_;
// des_tilt = asin((des_z - target.z()) / shotcfg.distance);
// tilt = asin(dp(2) / d);
// 3. pan
angle = std::atan2(dp(1), dp(0));
des_angle = shotcfg.view_angle + target_yaw;
delta_angle = fabs(des_angle - angle);
while(delta_angle > 2*M_PI)
delta_angle -= 2*M_PI;
delta_angle = std::min(delta_angle , 2*M_PI - delta_angle);
return lambda_cost * (fabs(d - des_d) + lambda_theta * delta_angle + lambda_z * fabs(p.z() - des_z));
}
View Region Construction
generate_new_traj_success = envPtr_->generate_view_regions(target_predcit, target_headings, shotcfg_list,
kAstar_path, visible_ps, view_polys);
visible_ps,
view_polys
를 생성replan decision
Corridor Generation
envPtr_->pts2path(visible_ps, path);
envPtr_->generateSFC(path, 2.0, hPolys, keyPts);
hPolys
Trajectory Optimization
generate_new_traj_success = trajOptPtr_->generate_traj(iniState,
finState,
iniAngle,
target_predcit,
target_vels,
view_polys,
des_pos_image,
des_vel_image,
hPolys, traj);
pub_traj(traj, replan_stamp);
initState
는 [odom_p, vel, acc , jerk] (column vector 4개 → 3x4 mat)initAngle
은 [[now_yaw, 0],[gimbal angle, gimbal rate]] (2x2 mat)finState
는 [path.back(), target_v, 0, 0] (col vec 4개 → 3x4 mat)des_pos_image
& des_vel_image
는 shot config로부터 받아온 값traj
→ x,y,z,psi,theta,duration 의 sequencetrajOptPtr
의 main 함수인 generate_traj
에서 initial trajectory와 input states를 통해 최적화된 경로 계획bool TrajOpt::generate_traj(const Eigen::MatrixXd& iniState,
const Eigen::MatrixXd& finState,
const Eigen::MatrixXd& iniAngle,
const std::vector<Eigen::Vector3d>& target_predcit,
const std::vector<Eigen::Vector3d>& target_vels,
const std::vector<Eigen::MatrixXd>& view_polys,
const std::vector<Eigen::Vector2d>& des_pos_image,
const std::vector<Eigen::Vector2d>& des_vel_image,
const std::vector<Eigen::MatrixXd>& hPolys,
Trajectory& traj) {
...
setBoundConds(iniState, finState, iniAngle);
...
int opt_ret = optimize();
...
mincoOpt_.setParameters(P, psi_, theta_, T);
mincoOpt_.getTrajectory(traj);
...
setBoundConds
함수 : 최적화시 initial, final boundary condition을 설정setAngleInitValue()
drone yaw : control point position을 기반으로 를 활용한 velocity direction으로 interYaw
를 계산
gimbal yaw : target에 heading을 맞추기 위해 target의 위치와 내 위치의 각도를 atan2로 interTheta
계산
→ iniAngle과 finAngle도 똑같이 선언
mincoOpt_
에 condition set
void TrajOpt::setBoundConds(const Eigen::MatrixXd& iniState,
const Eigen::MatrixXd& finState,
const Eigen::MatrixXd& iniAngle) {
...
initS.col(1) *= tempNorm > vmax_ ? (vmax_ / tempNorm) : 1.0;
tempNorm = finalS.col(1).norm();
finalS.col(1) *= tempNorm > vmax_ ? (vmax_ / tempNorm) : 1.0;
tempNorm = initS.col(2).norm();
initS.col(2) *= tempNorm > amax_ ? (amax_ / tempNorm) : 1.0;
tempNorm = finalS.col(2).norm();
finalS.col(2) *= tempNorm > amax_ ? (amax_ / tempNorm) : 1.0;
...
setAngleInitValue(iniAngle, T, P, finPos, iniAngleAdjust, finAngleAdjust, interAngle);
...
mincoOpt_.setConditions(initS, finalS, iniAngleAdjust, finAngleAdjust, N_);
...
}
Optimize
함수x_
를 필요로 함t, p, psi, theta
는 x_
를 각각 특정 slice별로 map시킨 변수들t_, p_, psi_, theta_
로 넣어줌x_
값을 t_, p_, psi_, theta_
에 넣어줌objectiveFunc
함수에 cost, grad 정의int TrajOpt::optimize(const double& delta) {
...
Eigen::Map<Eigen::VectorXd> t(x_, dim_t_);
Eigen::Map<Eigen::VectorXd> p(x_ + dim_t_, dim_p_);
Eigen::Map<Eigen::VectorXd> psi(x_ + dim_t_ + dim_p_, dim_psi_);
Eigen::Map<Eigen::VectorXd> theta(x_ + dim_t_ + dim_p_ + dim_psi_, dim_theta_);
t = t_;
p = p_;
psi = psi_;
theta = theta_;
...
auto ret = lbfgs::lbfgs_optimize(dim_t_ + dim_p_ + dim_psi_ + dim_theta_ + 1, x_, &minObjective,
&objectiveFunc, nullptr,
&earlyExit, this, &lbfgs_params);
...
t_ = t;
p_ = p;
psi_ = psi;
theta_ = theta;
return ret;
}
objectiveFunc
static inline double objectiveFunc(void* ptrObj,
const double* x,
double* grad,
const int n) {
...
obj.dbg_msg.cost_smooth = cost;
obj.mincoOpt_.getEnergy(cost, obj.rhoSmooth_);
...
// this function add intergral penalty like pos, vel and acc
obj.addTimeIntPenalty(T, b, b_angle, cost, obj.partialGradByTimes,
obj.partialGradByCoeffs, obj.partialGradByCoeffsAngle, obj.flatmap_);
obj.addTimeCost(T, b, b_angle, cost, obj.partialGradByTimes, obj.partialGradByCoeffs,
obj.partialGradByCoeffsAngle, obj.flatmap_);
obj.mincoOpt_.propogateGrad(obj.partialGradByCoeffs, obj.partialGradByCoeffsAngle, obj.partialGradByTimes,
obj.gradByPoints, obj.gradByAngles, obj.gradByTimes);
...
}
addTimeIntPenalty
: 논문에서 time integral penalty에 해당cost_corridor
) : drone이 polyhedron안에 위치하도록 (related : clearance_d)cost_v, cost_a, cost_omg
) : limit 값을 지키도록 (related : vmax, amax, ratemax)cost_mapping
) : 드론의 yaw와 heading이 같은 방향을 가르키도록 (related : rhoMapping)addTimeCost
: 논문에서 absolute time penalty에 해당cost_view
) : view region안으로 들어오도록 (related : rhoViewPoscost_actor_safe
) : drone이 target와 일정 수준의 거리를 유지 하도록 (related : rhoNoKilling)cost_visibility
) : image plane 내에서 보이는 타겟이 원하는 위치와 속도를 가지도록 (related : rhoVisibilityPos, rhoVisibilityVel)