PX4地标识别及控制算法

使用轮廓提取,多边形抽象,过滤面积较小的图形,然后过滤出四边形,再过滤掉非凸形。得到的四边形里通过简单的聚类方法寻找中心距离最近的一类,其中心的平均值即为地标中心

创建包

mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg landing roscpp rospy std_msgs geometry_msgs mavros
cd ~/catkin_ws/src/landing
mkdir msg #存放自定义消息类型
mkdir scripts #存放python脚本,打开相机

自定义消息类型
vi src/landing/msg/center.msg

uint32 width
uint32 height
float64 x
float64 y
bool iffind

python脚本
vi src/landing/scripts/track.py

import rospy
from std_msgs.msg import Header
from sensor_msgs.msg import Image
from landing.msg import center
import os
import cv2
import numpy as np
import time

center_publish=rospy.Publisher('/center', center, queue_size=1)
def callback(Image):
    img = np.fromstring(Image.data, np.uint8)
    img = img.reshape(240,320,3)
    track(img, Image.width, Image.height)

def listener():
    rospy.init_node('track')
    rospy.Subscriber('/iris/usb_cam/image_raw', Image, callback)
    rospy.spin()

def track(frame, width, height):
    img = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    _, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
    contours = cv2.findContours(img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    rects = []
    centers = []
    for contour in contours[1]:
        if cv2.contourArea(contour) < 100: 
            continue
        epsilon = 0.02 * cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, epsilon, True)
        if approx.shape[0] == 4 and cv2.isContourConvex(approx):
            rects.append(approx)
            centers.append((approx[0]+approx[1]+approx[2]+approx[3]).squeeze()/4)

    center_iter = list(range(len(centers)))
    result = []
    threshold = 20
    while len(center_iter) is not 0:
        j = 1
        resultnow = []
        while j < len(center_iter):
            if np.sum((centers[center_iter[0]] - centers[center_iter[j]])**2) < threshold:
                resultnow.append(center_iter[j])
                center_iter.pop(j)
                j = j-1
            j = j+1
        resultnow.append(center_iter[0])
        center_iter.pop(0)
        if len(result) < len(resultnow):
            result = resultnow
    rects = np.array(rects)[result]
    if len(result) > 2:
                centers = np.sum(np.array(centers)[result], axis=0).astype(np.double) / len(result)
                publish(centers, width, height)
    else:
        center_temp = center()
        center_temp.iffind = False
        center_publish.publish(center_temp)
    
def publish(centers, width, height):
    center_temp = center()
    center_temp.width = width
    center_temp.height = height
    center_temp.x = centers[1]
    center_temp.y = centers[0]
    center_temp.iffind = True
    center_publish.publish(center_temp)

if __name__ == '__main__':
    listener()

飞机控制

#include <ros/ros.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/TwistStamped.h>
#include <mavros_msgs/CommandBool.h>
#include <mavros_msgs/SetMode.h>
#include <mavros_msgs/State.h>
#include <landing/center.h> // 导入自定义消息类型

mavros_msgs::State current_state;
void state_cb(const mavros_msgs::State::ConstPtr &msg)
{
   current_state = *msg;
}

geometry_msgs::PoseStamped local_position;
void position_cb(const geometry_msgs::PoseStamped::ConstPtr &msg)
{
   local_position = *msg;
}

landing::center landmark;
void center_cb(const landing::center::ConstPtr &msg)
{
   landmark = *msg;
}

int main(int argc, char **argv)
{
   ros::init(argc, argv, "landing_node");
   ros::NodeHandle nh;

   ros::Subscriber state_sub = nh.subscribe<mavros_msgs::State>
                               ("mavros/state", 10, state_cb);
   ros::Subscriber position_sub = nh.subscribe<geometry_msgs::PoseStamped>
                                  ("mavros/local_position/pose", 10, position_cb);
   ros::Subscriber center_sub = nh.subscribe<landing::center>
                                ("center", 10, center_cb);
   ros::Publisher local_pos_pub = nh.advertise<geometry_msgs::PoseStamped>
                                  ("mavros/setpoint_position/local", 10);
   ros::Publisher local_vel_pub = nh.advertise<geometry_msgs::TwistStamped>
                                  ("mavros/setpoint_velocity/cmd_vel", 10);
   ros::ServiceClient arming_client = nh.serviceClient<mavros_msgs::CommandBool>
                                      ("mavros/cmd/arming");
   ros::ServiceClient set_mode_client = nh.serviceClient<mavros_msgs::SetMode>
                                        ("mavros/set_mode");

   ros::Rate rate(20.0);

   while(ros::ok() && current_state.connected)
   {
      ros::spinOnce();
      rate.sleep();
   }

   geometry_msgs::PoseStamped pose;//姿态控制
   pose.pose.position.x = 0;
   pose.pose.position.y = 0;
   pose.pose.position.z = 2;

   geometry_msgs::TwistStamped vel;//速度控制

   for(int i = 100; ros::ok() && i > 0; --i)
   {
      local_pos_pub.publish(pose);
      ros::spinOnce();
      rate.sleep();
   }

   mavros_msgs::SetMode offb_set_mode;
   offb_set_mode.request.custom_mode = "OFFBOARD";

   mavros_msgs::CommandBool arm_cmd;
   arm_cmd.request.value = true;

   ros::Time last_request = ros::Time::now();

   //起飞
   while(ros::ok())
   {
      if(current_state.mode != "OFFBOARD" && (ros::Time::now() - last_request > ros::Duration(5.0)))
      {
         if(set_mode_client.call(offb_set_mode) && offb_set_mode.response.mode_sent)
         {
            ROS_INFO("Offboard enabled");
         }

         last_request = ros::Time::now();
      }
      else if(!current_state.armed && (ros::Time::now() - last_request > ros::Duration(5.0)))
      {
         if(arming_client.call(arm_cmd) && arm_cmd.response.success)
         {
            ROS_INFO("Vehicle armed");
         }

         last_request = ros::Time::now();
      }
      else if(ros::Time::now() - last_request > ros::Duration(5.0))
      {
         break;
      }

      local_pos_pub.publish(pose);

      ros::spinOnce();
      rate.sleep();
   }

   //逛一圈
   last_request = ros::Time::now();

   while(ros::ok())
   {
      if(ros::Time::now() - last_request > ros::Duration(5.0))
      {
         break;
      }

      vel.twist.linear.x = 1;
      vel.twist.linear.y = 0;
      vel.twist.linear.z = 0;
      local_vel_pub.publish(vel);
      ros::spinOnce();
      rate.sleep();
   }

   last_request = ros::Time::now();

   while(ros::ok())
   {
      if(ros::Time::now() - last_request > ros::Duration(5.0))
      {
         break;
      }

      vel.twist.linear.x = 0;
      vel.twist.linear.y = 1;
      vel.twist.linear.z = 0;
      local_vel_pub.publish(vel);
      ros::spinOnce();
      rate.sleep();
   }

   last_request = ros::Time::now();

   while(ros::ok())
   {
      if(ros::Time::now() - last_request > ros::Duration(5.0))
      {
         break;
      }

      vel.twist.linear.x = -1;
      vel.twist.linear.y = 0;
      vel.twist.linear.z = 0;
      local_vel_pub.publish(vel);
      ros::spinOnce();
      rate.sleep();
   }

   last_request = ros::Time::now();

   while(ros::ok())
   {
      if(ros::Time::now() - last_request > ros::Duration(5.0))
      {
         break;
      }

      vel.twist.linear.x = 0;
      vel.twist.linear.y = -1;
      vel.twist.linear.z = 0;
      local_vel_pub.publish(vel);
      ros::spinOnce();
      rate.sleep();
   }

   //控制降落部分
   while(ros::ok())
   {
      //高度低于0.3时转为降落模式
      if(local_position.pose.position.z < 0.3)
      {
         break;
      }

      //如果找到地标,控制方向
      if(landmark.iffind)
      {
         //计算两方向err
         double err_x = landmark.height / 2.0 - landmark.x;
         double err_y = landmark.width / 2.0 - landmark.y;
         ROS_INFO_STREAM("state=" << err_x << " " << err_y);
         //速度控制
         vel.twist.linear.x = err_x / 400;
         vel.twist.linear.y = err_y / 400;

         //如果位置很正开始降落
         if(err_x < 10 && err_y < 10)
         {
            vel.twist.linear.z = -0.2;
         }
         else
         {
            vel.twist.linear.z = 0;
         }

         local_vel_pub.publish(vel);
         ros::spinOnce();
         rate.sleep();
      }
      //如果找不到矩形地标,回到2m高度
      else
      {
         pose.pose.position.x = local_position.pose.position.x;
         pose.pose.position.y = local_position.pose.position.y;
         pose.pose.position.z = 2;
         local_pos_pub.publish(pose);
         ros::spinOnce();
         rate.sleep();
      }
   }

   offb_set_mode.request.custom_mode = "AUTO.LAND";

   if(set_mode_client.call(offb_set_mode) && offb_set_mode.response.mode_sent)
   {
      ROS_INFO("Offboard enabled");
      last_request = ros::Time::now();
   }

   return 0;
}

vi landing/CMakeLists.txt

# 按提示增加

## Generate messages in the 'msg' folder
# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )
add_message_files(FILES center.msg)

## Generate added messages and services with any dependencies listed here
# generate_messages(
#   DEPENDENCIES
#   geometry_msgs#   std_msgs
# )
generate_messages(DEPENDENCIES std_msgs)

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/landing_node.cpp)
add_executable(landing_node src/control.cpp)

## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
#   ${catkin_LIBRARIES}
# )
target_link_libraries(landing_node
  ${catkin_LIBRARIES}
)

vi landing/package.xml

<!-- 增加两行 -->
<build_depend>message_generation</build_depend>
<exec_depend>message_generation</exec_depend>

编译
catkin build

运行

roslaunch px4 mavros_posix_sitl.launch
python src/landing/scripts/track.py
rosrun landing landing_node

实际效果
https://www.bilibili.com/video/BV1RE411M755?from=search&seid=18007439676134041510

rqt_graph

rqt_console

posted @ 2021-06-30 11:33  thomas_blog  阅读(416)  评论(1编辑  收藏  举报