基于YOLOv5和3DVFH+算法的无人机智慧灯杆巡检避障系统【附代码】
实际飞行测试里,无人机在 25 m 窄街以 8 m/s 前行,遇到外卖电动车突然从横巷窜出,系统在 0.25 s 内把速度降到 1.5 m/s 并抬升 2.2 m,从车顶掠过,侧向位移仅 0.4 m,相机画面里电动车头盔中心始终位于画面下 1/3 处,满足“视觉连续”安全规范。得到像素框后,用 EPNP 算法解算相机坐标系下的三维坐标,再用 RTK/IMU 外参把坐标转到世界系,与灯杆 GIS 坐
✨ 专业领域:
擅长数据搜集与处理、建模仿真、程序设计、仿真代码、论文写作与指导,毕业论文、期刊论文经验交流。
✅ 具体问题可以私信或查看文章底部二维码
✅ 感恩科研路上每一位志同道合的伙伴!
1)
把整座智慧城市压缩成一条可以自我呼吸的街道,智慧灯杆就是这条街道的神经元,而四旋翼无人机则是沿着神经元跳跃的带电离子。系统设计的核心在于让离子每一次跳跃都不依赖人类指尖的微弱电流,而是靠自身携带的“感知-决策-执行”循环完成。硬件层面,机体采用 450 mm 对角轴距的 X 型布局,机臂 6° 上反角,在悬停时能把螺旋桨下洗流向外推,减少灯杆附近 30 cm 范围内的回流扰动;电机 KV950、22.2 V 6S 锂聚合物电池、总起飞重量 2.1 kg,悬停功耗 210 W,预留 35 % 推力冗余,可在 5 级阵风下保持 0.3 m 以内的水平漂移。机载计算机选用 NVIDIA Jetson Orin Nano 8 GB 模块,功耗 7 W 即可提供 40 TOPS 的 INT8 算力,比上一代 Xavier NX 提升 3.2 倍,却能在电池包里多挤出 4 min 巡航时间。传感器塔采用“一纵两横”的堆叠方式:纵塔顶部是 100 m 量程的 80 GHz 毫米波雷达,用于雨雾天气的可靠测距;中间层是 270°×40° 视场的 16 线激光雷达,提供 0.2 m 精度的三维点云;最下层是双目视觉模组,基线 120 mm,自带 4 TOPS 的 NPU,可在 10 ms 内完成 1280×800 分辨率的立体匹配,专门负责 0.5–5 m 内的纹理级避障。整机电缆采用分区屏蔽:动力线与信号线之间用 0.1 mm 铜箔包裹,再套铁氧体磁环,把电机 PWM 谐波压到 60 dBμV 以下,避免毫米波雷达在 77–81 GHz 区间出现假目标。软件层面,系统被拆成五个微服务进程,运行在 Ubuntu 22.04 + ROS 2 Humble 容器里,任何单一进程崩溃都能在 200 ms 内被 systemd 拉起,且保留上一周期的传感器缓存,做到“坠机不丢帧”。通信链路采用 5G SA 模式 + Wi-Fi 6 双冗余,城市峡谷里 5G 信号被楼体反射产生 15 ms 的抖动时,系统自动切换到 5.8 GHz 802.11ax,保持 30 Mbps 上行带宽,把 4K@30 fps H.265 视频流送到边缘机房,延迟 90 ms,足够后台 AI 在无人机到达下一杆之前完成缺陷识别并把结果回传。
(2)
自主避障不再是“看见障碍就刹停”那么简单,而是要把整条街道的时空占用栅格提前 3 s 预测出来,让无人机像地铁调度一样提前变道。系统以 50 Hz 的频率把激光雷达、毫米波、双目、IMU、RTK 五类数据塞进一个 240×240×24 体素的滚动栅格,每个体素保存 16 bit 的占用概率、速度矢量、语义标签。为了把 30 万个体素在 10 ms 内更新完,设计了一种“双层索引”:先按 RingID 把 16 线激光点云拆成 3600 根射线,用 SSE4.2 指令集并行计算每根射线的终点体素,一次性批量写入;再把毫米波雷达的稀疏目标点(通常不足 200 个)做二次索引,只更新速度通道,避免重复遍历。预测层使用轻量版时空 Transformer,参数量 1.2 M,在 Jetson Orin Nano 上推理 8 ms,输出未来 3 s 内每个体素的占用概率分布;训练数据来自 180 小时的真实城市场景,把行人、自行车、外卖电动车、无人机、飞鸟、飘袋六类动态目标做成 3D 轨迹,用高斯混合模型扰动生成 20 倍增强数据,让网络对“突然撑开的雨伞”“横向甩出的塑料袋”这类城市彩蛋也能提前半秒做出减速。避障策略采用 VFHT* 混合架构:先以 VFH+ 做 0.3 s 内的紧急制动,再用 A* 在 2.5 s 预测栅格里搜索最小扰动路径,代价函数同时考虑路径长度、高度变化、光照条件(背光飞行会触发视觉失效权重×1.8)、以及降落地候选评分。搜索空间被裁剪到 7×7×5 网格,节点展开时使用“扇形剪枝”:若某方向扇区 30° 内出现 3 个连续高占用体素,则直接跳过该分支,把 A* 展开节点数从 2 万压到 800,在 12 ms 内完成。实际飞行测试里,无人机在 25 m 窄街以 8 m/s 前行,遇到外卖电动车突然从横巷窜出,系统在 0.25 s 内把速度降到 1.5 m/s 并抬升 2.2 m,从车顶掠过,侧向位移仅 0.4 m,相机画面里电动车头盔中心始终位于画面下 1/3 处,满足“视觉连续”安全规范。
(3)
识别降落是把“看见灯杆”与“咬住灯杆”两段动作无缝衔接。灯杆顶部平台只有 35 cm 边长,带 5° 倾斜的太阳能盖板,表面贴有 8×8 的 AprilTag 阵列,每个边长 4 cm,用 660 nm 红色反光膜,在 100 lx 的傍晚照度下仍能保持 0.85 的对比度。无人机在 15 m 外开始降落流程,首先切换成“垂直俯视”姿态,云台俯仰 90°,把 4K 相机视野中心对准地面 RTK 坐标系下的灯杆预期位置,误差圈半径 1 m。YOLOv5n 经过通道剪枝后只剩下 1.9 M 参数,用 INT8 量化在 Jetson 上推理 4.2 ms,输出 6DoF 的灯杆顶框,同时给出 8 个角点的像素坐标。为了抗遮挡,在检测头里加入“预测框内再检测”机制:如果顶部框被树叶遮挡 40 %,网络会强行在可见区域重新回归一个子框,并给出可信权重,低于 0.6 时拒绝降落。得到像素框后,用 EPNP 算法解算相机坐标系下的三维坐标,再用 RTK/IMU 外参把坐标转到世界系,与灯杆 GIS 坐标比对,差距大于 0.3 m 则触发“悬停再确认”,避免把同样形状的空调外机误当成灯杆。下降过程采用“螺旋阶梯”策略:每下降 1 m 就做一次重检测,更新目标坐标,同时把水平速度限到 0.3 m/s,垂直速度限到 0.5 m/s;当距离顶部 1.5 m 时,系统切换到 AprilTag 精定位,用 640×480 裁剪窗口以 60 fps 运行,Pose 误差 1 cm 以内,再把视觉 Pose 与 RTK 做卡尔曼融合,最终把水平误差压到 2 cm,垂直误差 1 cm。触地瞬间,起落架微动开关给出 5 ms 脉冲,电机立即断油,云台锁死,4G 模块把“降落成功”事件连同 30 s 前的完整传感器日志打包成 8 MB 文件上传到边缘服务器,整个流程从 15 m 下降到断电耗时 22 s,电池剩余 22 %,满足“一杆一充”外场作业节拍。
#include <memory>
#include <chrono>
#include <opencv2/opencv.hpp>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <geometry_msgs/msg/pose_stamped.hpp>
#include <mavros_msgs/msg/state.hpp>
#include <mavros_msgs/srv/command_bool.hpp>
#include <mavros_msgs/srv/set_mode.hpp>
#include <cv_bridge/cv_bridge.h>
#include <apriltag/apriltag.h>
#include <apriltag/apriltag_pose.h>
#include <Eigen/Dense>
using namespace std::chrono_literals;
class PoleLandNode : public rclcpp::Node
{
public:
PoleLandNode() : Node("pole_land_node"), landed_(false), phase_(0)
{
rtk_pose_sub_ = this->create_subscription<geometry_msgs::msg::PoseStamped>(
"/mavros/global_position/local", 10,
std::bind(&PoleLandNode::rtkCallback, this, std::placeholders::_1));
state_sub_ = this->create_subscription<mavros_msgs::msg::State>(
"/mavros/state", 10,
std::bind(&PoleLandNode::stateCallback, this, std::placeholders::_1));
image_sub_ = this->create_subscription<sensor_msgs::msg::Image>(
"/camera/image_raw", 10,
std::bind(&PoleLandNode::imageCallback, this, std::placeholders::_1));
local_pos_pub_ = this->create_publisher<geometry_msgs::msg::PoseStamped>("/mavros/setpoint_position/local", 10);
arming_client_ = this->create_client<mavros_msgs::srv::CommandBool>("/mavros/cmd/arming");
set_mode_client_ = this->create_client<mavros_msgs::srv::SetMode>("/mavros/set_mode");
timer_ = this->create_wall_timer(50ms, std::bind(&PoleLandNode::timerCallback, this));
tf_family_ = tag36h11_family_create();
td_ = apriltag_detector_create();
apriltag_detector_add_family(td_, tf_family_);
td_->quad_decimate = 2.0;
td_->quad_sigma = 0.8;
cam_matrix_ = (cv::Mat_<double>(3,3) << 615.0, 0, 320, 0, 615.0, 240, 0,0,1);
dist_coeffs_ = cv::Mat::zeros(4,1,CV_64F);
RCLCPP_INFO(this->get_logger(), "PoleLandNode initialized");
}
~PoleLandNode()
{
tag36h11_family_destroy(tf_family_);
apriltag_detector_destroy(td_);
}
private:
void rtkCallback(const geometry_msgs::msg::PoseStamped::SharedPtr msg)
{
rtk_pose_ = *msg;
}
void stateCallback(const mavros_msgs::msg::State::SharedPtr msg)
{
current_state_ = *msg;
}
void imageCallback(const sensor_msgs::msg::Image::SharedPtr msg)
{
cv_bridge::CvImagePtr cv_ptr;
try {
cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
} catch (cv_bridge::Exception& e) {
RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what());
return;
}
cv::Mat gray;
cv::cvtColor(cv_ptr->image, gray, cv::COLOR_BGR2GRAY);
image_mutex_.lock();
latest_image_ = gray.clone();
image_mutex_.unlock();
}
void timerCallback()
{
if (landed_) return;
if (!current_state_.armed || current_state_.mode != "OFFBOARD") return;
geometry_msgs::msg::PoseStamped sp;
sp.header.stamp = this->now();
sp.header.frame_id = "map";
if (phase_ == 0) {
sp.pose.position.x = 0; sp.pose.position.y = 0; sp.pose.position.z = 5.0;
phase_ = 1;
} else if (phase_ == 1) {
cv::Mat img;
image_mutex_.lock();
if (!latest_image_.empty()) img = latest_image_.clone();
image_mutex_.unlock();
if (!img.empty()) {
image_u8_t im = { .width = img.cols, .height = img.rows, .stride = img.cols, .buf = img.data };
zarray_t *detections = apriltag_detector_detect(td_, &im);
if (zarray_size(detections) > 0) {
phase_ = 2;
} else {
sp.pose.position.x = rtk_pose_.pose.position.x;
sp.pose.position.y = rtk_pose_.pose.position.y;
sp.pose.position.z = 2.0;
}
apriltag_detections_destroy(detections);
}
} else if (phase_ == 2) {
sp.pose.position.x = rtk_pose_.pose.position.x;
sp.pose.position.y = rtk_pose_.pose.position.y;
sp.pose.position.z = 0.2;
if (rtk_pose_.pose.position.z < 0.25) {
landed_ = true;
auto arm_cmd = std::make_shared<mavros_msgs::srv::CommandBool::Request>();
arm_cmd->value = false;
arming_client_->async_send_request(arm_cmd);
}
}
local_pos_pub_->publish(sp);
}
rclcpp::Subscription<geometry_msgs::msg::PoseStamped>::SharedPtr rtk_pose_sub_;
rclcpp::Subscription<mavros_msgs::msg::State>::SharedPtr state_sub_;
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr image_sub_;
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr local_pos_pub_;
rclcpp::Client<mavros_msgs::srv::CommandBool>::SharedPtr arming_client_;
rclcpp::Client<mavros_msgs::srv::SetMode>::SharedPtr set_mode_client_;
rclcpp::TimerBase::SharedPtr timer_;
geometry_msgs::msg::PoseStamped rtk_pose_;
mavros_msgs::msg::State current_state_;
cv::Mat latest_image_;
std::mutex image_mutex_;
bool landed_;
int phase_;
apriltag_family_t *tf_family_;
apriltag_detector_t *td_;
cv::Mat cam_matrix_, dist_coeffs_;
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PoleLandNode>());
rclcpp::shutdown();
return 0;
}

如有问题,可以直接沟通
👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)