技术分享 | 避坑指南-无人机自主降落代码解析

前言 本主要讲解promtheus仿真环境中静态目标的自主降落, 涉及整体逻辑, 识别降落点, 坐标系变换. 不会涉及仿真环境搭建。本人之前的属于纯作计算机视觉工作的, 如果你和我一样在此之前没有接触过机器人控制, 无人机相关的内容, 那这篇文章对于入门prometheus的目标检测模块很适合, 视觉方面简单(opencv 写好的接口), 控制方面简单但全面。刚开始接触这方面知识, 如有错误请指正。

launch地址: Simulator/gazebo_simulator/launch_detection/sitl_landing_static_target.launch

promtheus自主降落-静态目标-仿真环境 静态目标自主降落的代码有3个部分组成仿真环境, 降落点识别, 控制逻辑组成。 file 重点关注在降落点识别模块, 即 prometheus_detection的landpad_det, 其次是逻辑控制 prometheus_mission的 autonomous_landing, 对于仿真环境部分为公有模块暂时忽略。

旋转矩阵, 坐标系变换不熟悉的强烈建议先看台大机器人学之运动学——林沛群的P2-P16部分。

网址: https://www.bilibili.com/video/BV1v4411H7ez?p=1

1、降落点识别 Prometheus/Modules/object_detection/cpp_nodes/landpad_det.cpp

输入:

  • 图像数据: 用于识别降落点。
  • 开关: 用于控制是否进行识别(暂时定主无人机)。

输出:

  • 图像数据: 将检测结果画在原始图片上。
  • 位置数据: 降落点在相机坐标系下的位置等信息。
  • Debug信息。

流程:

  • 获取数据。
  • 调用ArUco Marker库识别对象,获得识别到Marker(二维码)的四个角位置, Marker ID对
  • 筛选一个最好的Marker。
  • 计算降落点: 计算, Marker对于相机坐标系的旋转矩阵, 以及Marker中心点在相机坐标系的坐标。
  • 目标数据发布: 转化为prometheus_msgs::DetectionInfo格式的数据发布。

** 1.1 ArUco Marker** 官方: OpenCV: Detection of ArUco Markers 网址: https://docs.opencv.org/4.5.3/d5/dae/tutorial_aruco_detection.html

1.1.1 获取Marker的id, 坐标 // ArUco Marker字典选择以及旋转向量和评议向量初始化 Ptrcv::aruco::Dictionary dictionary = cv::aruco::getPredefinedDictionary(10) //------------------调用ArUco Marker库对图像进行识别-------------- // markerids存储每个识别到二维码的编号 markerCorners每个二维码对应的四个角点的像素坐标 std::vector markerids, markerids_deted; vector<vector > markerCorners, markerCorners_deted, rejectedCandidate; Ptr cv::aruco::DetectorParameters parameters = cv::aruco::DetectorParameters::create(); cv::aruco::detectMarkers(img, dictionary, markerCorners_deted, markerids_deted, parameters, rejectedCandidate);

  • cv::aruco::getPredefinedDictionary(10) 获取一个 Marker_id --> Marker 字典. 参数(10)表示获取的那个字典, 不同的字典的区别在于Marker的大小不同。
  • cv::aruco::DetectorParameters::create() 获取默认的识别器识别参数, 比如图像二值化阈值等。
  • cv::aruco::detectMarkers(img, dictionary, markerCorners_deted, markerids_deted, parameters, rejectedCandidate)。 a)img 要识别的图像。 b) dictionary 和 parameters 上面定义的。 c)markerCorners_deted 保存Marker识别结果四个点的图片坐标系的坐。 d)markerids_deted 与 markerCorners_deted 一一对应的id。 e)rejectedCandidate 形状和Marker相似但不是Marker, 结构和markerCorners_deted一样。

1.1.2 计算旋转向量, 转移向量 旋转向量: 用于表示Marker在相机坐标系的姿态 偏移向量: 用于表示Marker在相机坐标系的位置 aruco::estimatePoseSingleMarkers(markerCornersONE, landpad_det_len * 0.133334, camera_matrix, distortion_coefficients, rvecs, tvecs);

  • 第一个参数(MarkerCornersONE): Marker 四个角的坐标(图片坐标系为基)。
  • 第二个参数(landpad_det_len * ....): Marker的实际大小。
  • 第三, 四个参数(camera_matrix, distortion_coefficients)为相机的参数, 相机畸变参数。
  • 最后两个参数为输出, 旋转向量, 偏移向量(以相机坐标系为基)。

*1.2 Marker 筛选 * 降落板,以及每个Marker对应的id, 程序每次只处理一个Marker, 如果同时检测到多个Marker则各个Marker的优先级为: 43 --> 1,2,3,4 --> 19; 理想情况下在远处的无人机会最先发现最大的Marker 19, 然后检测到1,2,3,4 Marker调整位置, 最后检测到最小的Marker 43 提高降落位置精度。 file

if (markerids_deted.size() > 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (19 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } if (markerids.size() == 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (43 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } } if (markerids.size() == 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (1 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } } if (markerids.size() == 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (2 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } } if (markerids.size() == 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (3 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } } if (markerids.size() == 0) { for (int tt = 0; tt < markerids_deted.size(); tt++) { if (4 == markerids_deted[tt]) { markerids.push_back(markerids_deted[tt]); markerCorners.push_back(markerCorners_deted[tt]); } } } }

** 1.3 计算降落点** 旋转向量 --> 旋转矩阵 --> 旋转四元数

cv::Mat rotation_matrix; cv::Rodrigues(rvecs[0], rotation_matrix); Eigen::Matrix3d rotation_matrix_eigen; cv::cv2eigen(rotation_matrix, rotation_matrix_eigen); Eigen::Quaterniond q = Eigen::Quaterniond(rotation_matrix_eigen); q.normalize();

6个Maker下, 计算旋转矩阵 --> 降落点(相机坐标系为基)

if (19 == markerids[tt] || 43 == markerids[tt]) { id_to8_t[0] = 0.; id_to8_t[1] = 0.; id_to8_t[2] = 0.; } else if (1 == markerids[tt]) { id_to8_t[0] = -(landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[1] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[2] = 0.; } else if (2 == markerids[tt]) { id_to8_t[0] = -(landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[1] = -(landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[2] = 0.; } else if (3 == markerids[tt]) { id_to8_t[0] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[1] = -(landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[2] = 0.; } else if (4 == markerids[tt]) { id_to8_t[0] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[1] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[2] = 0.; }

cv::Mat id_to8_t_mat{id_to8_t}; id_to8_t_mat.convertTo(id_to8_t_mat, CV_32FC1);

rotation_matrix.convertTo(rotation_matrix, CV_32FC1); // cv::invert(rotation_matrix, rotation_matrix); 旋转向量 --> 旋转矩阵 + 偏移向量 // id_to8_mat 定位中心转换到纸面中心 // rotation_matrix * id_to8_t_mat 在Marker为基的坐标系下的坐标乘上,旋转向量等于在以相机坐标系下为基的坐标 cv::Mat id_8_t = rotation_matrix * id_to8_t_mat + vec_t_mat; // cv::Mat id_8_t = vec_t_mat;

最开始, 我一没有想明白 cv::Mat id_8_t = rotation_matrix * id_to8_t_mat + vec_t_mat id_to8_t_mat为什么前面没有负号, 如果没有负号, 无人机在看到1,2,3,4时会远离飞行, 而不会往中间飞。 file

上图红色为x轴, 绿色为y轴皆指向正方向. 以右下角4号Marker为例子, id_to8_t_mat为正时, 计算得到的id_8_t不应该在4号的右下角去了, 而不会在左上角的中心了,

else if (4 == markerids[tt]) { id_to8_t[0] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[1] = (landpad_det_len * 0.666667 + landpad_det_len * 0.133334) / 2.; id_to8_t[2] = 0.; } cv::Mat id_8_t = rotation_matrix * id_to8_t_mat + vec_t_mat;

直到看到 , "相机是朝向下方" 以及以下文字。"最后的坐标是要换算在机体坐标下的, 而不是在相机坐标系之下。”

关于坐标系转换的说明:识别算法发布的目标位置位于相机坐标系(从相机往前看,物体在相机右方x为正,下方y为正,前方z为正) 首先,从相机坐标系转换至机体坐标系(从机体往前看,物体在相机前方x为正,左方y为正,上方z为正):由于此demo相机朝下安装,且xy方向无偏移量。

pos_body_frame[0] = - Detection_raw.position[1]; pos_body_frame[1] = - Detection_raw.position[0]; pos_body_frame[2] = - Detection_raw.position[2];

2、控制逻辑 主要输入:

  • 键盘控制指令
  • 降落点坐标(相机坐标轴下): prometheus_msgs::DetectionInfo
  • 无人机当前状态 prometheus_msgs::DroneState

主要输出:

  • 无人机控制数据
  • 无人机共有4种状态

enum EXEC_STATE { WAITING_RESULT, TRACKING, LOST, LANDING, };

初始时为WATING_RESULT状态, 等待降落点识别模块找到降落点, 找到降落点后进入TRACKING状态。

if(landpad_det.is_detected) { exec_state = TRACKING; message = "Get the detection result."; cout << message <<endl; pub_message(message_pub, prometheus_msgs::Message::WARN, NODE_NAME, message); break; }

在TRACKING状态下, 如果当前不再悬停指令下且没有再找到降落点则转为LOST状态。

if(!landpad_det.is_detected && !hold_mode) { exec_state = LOST; message = "Lost the Landing Pad."; cout << message <<endl; pub_message(message_pub, prometheus_msgs::Message::WARN, NODE_NAME, message); break; }

在TRACKING状态下, 如果机体离降落点的距离(欧式距离)小于阈值, 或则飞行高度过低, 进入LANDING状态。

// 抵达上锁点,进入LANDING distance_to_pad = landpad_det.pos_body_enu_frame.norm(); // 达到降落距离,上锁降落 if(distance_to_pad < arm_distance_to_pad) { exec_state = LANDING; message = "Catched the Landing Pad."; cout << message <<endl; pub_message(message_pub, prometheus_msgs::Message::WARN, NODE_NAME, message); break; } // 达到最低高度,上锁降落 else if(abs(landpad_det.pos_body_enu_frame[2]) < arm_height_to_ground) { exec_state = LANDING; message = "Reach the lowest height."; cout << message <<endl; pub_message(message_pub, prometheus_msgs::Message::WARN, NODE_NAME, message); break; }

在TRACKING状态下, 如果未满足进入LANDING的条件, 则以机体距离降落点的距离设置的一定比例设置飞机的数据, 即机体离目标越远速度越快, 越近降落点速度越慢(机体惯性坐标系下)

Command_Now.header.stamp = ros::Time::now(); Command_Now.Command_ID = Command_Now.Command_ID + 1; Command_Now.source = NODE_NAME; Command_Now.Mode = prometheus_msgs::ControlCommand::Move; Command_Now.Reference_State.Move_frame = prometheus_msgs::PositionReference::ENU_FRAME; Command_Now.Reference_State.Move_mode = prometheus_msgs::PositionReference::XYZ_VEL; //xy velocity z position

for (int i=0; i<3; i++) { Command_Now.Reference_State.velocity_ref[i] = kp_land[i] * landpad_det.pos_body_enu_frame[i]; }

// 如果目标也在移动, 则加上目标的速度 if(moving_target) { Command_Now.Reference_State.velocity_ref[0] += target_vel_xy[0]; Command_Now.Reference_State.velocity_ref[1] += target_vel_xy[1]; }

在LOST状态下, 机体原地向上飞行, 尝试找到降落点. 如果机体的高度在达到阈值高度仍然未找到目标, 则判定为定点降落失败, 并进入LANDING。

2.1 坐标系变换 从降落点识别模块获得降落点坐标是基于相机坐标系的, 需要处理转换为机体坐标系, 惯性坐标系下的点。

相机坐标系 --> 机体坐标系: camera_offset是相机距离机体质心的偏移量. 对于机体来说机头方向为x为正, 机体左边为y为正, 机体上方z为正。

// 识别算法发布的目标位置位于相机坐标系(从相机往前看,物体在相机右方x为正,下方y为正,前方z为正) // 相机安装误差 在mission_utils.h中设置 landpad_det.pos_body_frame[0] = -landpad_det.Detection_info.position[1] + camera_offset[0]; landpad_det.pos_body_frame[1] = -landpad_det.Detection_info.position[0] + camera_offset[1]; landpad_det.pos_body_frame[2] = -landpad_det.Detection_info.position[2] + camera_offset[2];

机体系 -> 机体惯性系 (原点在机体的惯性系) (对无人机姿态进行解耦): R_Body_to_ENU, 机体坐标系到惯性坐标系的转移矩阵, 有飞机当前姿态(欧拉角) --> 转为旋转矩阵。

landpad_det.pos_body_enu_frame = R_Body_to_ENU * landpad_det.pos_body_frame;

Eigen::Matrix3f get_rotation_matrix(float phi, float theta, float psi) { Eigen::Matrix3f R_Body_to_ENU;

float r11 = cos(theta)*cos(psi);
float r12 = - cos(phi)*sin(psi) + sin(phi)*sin(theta)*cos(psi);
float r13 = sin(phi)*sin(psi) + cos(phi)*sin(theta)*cos(psi);
float r21 = cos(theta)*sin(psi);
float r22 = cos(phi)*cos(psi) + sin(phi)*sin(theta)*sin(psi);
float r23 = - sin(phi)*cos(psi) + cos(phi)*sin(theta)*sin(psi);
float r31 = - sin(theta);
float r32 = sin(phi)*cos(theta);
float r33 = cos(phi)*cos(theta);
R_Body_to_ENU << r11,r12,r13,r21,r22,r23,r31,r32,r33;
return R_Body_to_ENU;

}

机体惯性系 --> 惯性系: 机体质心到惯性坐标系原点的偏移量。

landpad_det.pos_enu_frame[0] = _DroneState.position[0] + landpad_det.pos_body_enu_frame[0]; landpad_det.pos_enu_frame[1] = _DroneState.position[1] + landpad_det.pos_body_enu_frame[1]; landpad_det.pos_enu_frame[2] = _DroneState.position[2] + landpad_det.pos_body_enu_frame[2];

结尾 最后作为这方面刚入门者, 总结下在阅读这部分代码时所踩的坑, 先简单过一遍代码, 忽略细节了解逻辑, 大概了解代码那些可以当作黑盒使用, 那些是需要深入的。对于需要深入的且之前未曾接触过的不要一来就直接看文章, 要先看相关视频。遇到和自己看法不同的代码, 先忽略往后面看代码有些时候答案就藏在后面, 如果还是为解决就再仔细阅读一遍代码相关的文章介绍。

  • End -

技术发展的日新月异,阿木实验室将紧跟技术的脚步,不断把机器人行业最新的技术和硬件推荐给大家。看到经过我们培训的学员在技术上突飞猛进,是我们培训最大的价值。如果你在机器人行业,就请关注我们的公众号,我们将持续发布机器人行业最有价值的信息和技术。 阿木实验室致力于前沿IT科技的教育和智能装备,让机器人研发更高效!

  • 3
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 无人机自主降落ROS(机器人操作系统)是一种通过ROS控制的自动降落系统。该系统利用ROS中的传感器和算法来实现无人机自主降落功能。 首先,无人机需要搭载各种传感器,如GPS、惯性测量单元(IMU)和视觉传感器等。这些传感器可以提供无人机当前的位置、速度和方向等信息。 ROS可以将这些传感器的数据接收并进行处理。通过使用ROS包中的导航软件包,可以根据传感器数据来实现无人机的定位和导航功能。例如,通过接收GPS数据,ROS可以确定无人机当前的GPS坐标,并根据设定的坐标进行导航。 同时,ROS还包含了一些地标识别和跟踪算法。无人机装备有视觉传感器,可以识别和跟踪地面上的标志物,如着陆区域。ROS可以处理这些视觉数据,以确定无人机相对于标志物的位置和姿态。 当无人机接近着陆区域时,ROS会根据传感器数据和预设的降落模型,计算出无人机降落轨迹,并生成对应的控制指令。这些指令可以通过ROS接口发送给无人机的飞行控制系统,使其实现自主降落。 总的来说,无人机自主降落ROS是一套集成了传感器数据处理、导航算法和控制指令生成等功能的系统。通过ROS的强大功能,无人机可以准确地完成自主降落任务,提高了降落的安全性和精确度。 ### 回答2: ROS(机器人操作系统)是一个开源的机器人软件框架,它提供了一系列工具、库和函数,帮助开发者构建机器人应用程序。而无人机自主降落是指无人机在没有人为干预的情况下,根据预定的算法和传感器信息,能够自主降落在指定的位置上。 在ROS中,可以利用其提供的功能包来实现无人机自主降落。首先,需要使用ROS中的导航功能包,如move_base和amcl,来进行无人机的导航规划和定位。move_base是一个全局路线规划器,能够根据地图和目标位置,生成无人机的航迹轨迹。amcl是一个自适应蒙特卡洛定位(AMCL)算法的包装器,能够利用传感器信息对无人机进行实时定位,以便实现精确的降落。 接着,需要利用视觉传感器或其他距离传感器来获取无人机降落目标之间的距离和位置信息。通过ROS中的图像处理和感知功能包,可以进行目标检测和跟踪,实现无人机降落目标的精确定位。 最后,利用ROS中的控制功能包,如mavros或mavlink,可以将导航和定位的结果以及降落目标的位置信息传递给无人机的飞行控制器。飞行控制器根据这些信息,通过调整无人机的推力和姿态控制,使无人机能够自主降落在指定的位置上。 综上所述,可以通过ROS的导航、定位、感知和控制功能包,结合相应的传感器和算法,实现无人机自主降落。它不仅可以提高无人机的飞行安全性和精度,还能为无人机在应急情况下的自主着陆提供支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值