当前位置: 首页 > news >正文

Jetson进行旋转目标检测推理实现大疆无人机飞行控制

源码结构

大疆PSDK源码地址:

https://github.com/dji-sdk/Payload-SDK

其目录结构如下:

Payload-SDK-master
├── CMakeLists.txt
├── doc
│   ├── dji_sdk_code_style
│   └── simple_model
├── EULA.txt
├── LICENSE.txt
├── psdk_lib
│   ├── include
│   └── lib
├── README.md
├── samples
│   ├── sample_c
│   └── sample_c++
└── tools└── file2c
  • CMakeLists.txt:编译文件,用 Cmake 构建工程。
  • dji_sdk_code_style:SDK 代码相关文档,大致介绍了 PSDK 代码风格。
  • simple_model:其中 simple_model 中包含了无人机、PSDK 开发组件、OSDK 转接板的 3D 模型图,便于完成开发后定型产品结构。
  • EULA.txt:最终用户许可协议(end-user license agreements)。
  • LICENSE.txt:LICENSE 说明,内部包含了一个完整 tree 结构图。
  • psdk_lib: PSDK 的核心库文件,PSDK 3.X 以闭源库方式提供,在此文件夹下提供了各个平台的库(lib)以及头文件(include)。
  • README.md:说明文件,包括版本更新和功能更新的信息。
  • samples:PSDK 官方 sample 代码示例,此部分 sample 代码均以开源代码呈现,可以通过 sample 来学习 API 的调用方法,以及功能实现的参考逻辑。
  • tools:第三方工具,提供了一个 file2c 的工具。PSDK 中 FreeRTOS 平台使用 widget 功能会需要使用此工具将文件进行转换

其中,我们要进行代码开发主要是在samples/sample_c++文件夹

这里我们使用YOLOv8实现选中目标检测,其具体需要执行如下过程:

Yolov8Obb 类定义

class Yolov8Obb {public:Yolov8Obb() {}   ~Yolov8Obb() {}  bool ReadModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);bool Detect(cv::Mat& srcImg, cv::dnn::Net& net, std::vector<OutputParams>& output,int distance);int _netWidth = 640;   //图片输入宽度int _netHeight = 640;  //图片输入高度//类别名,自己的模型需要修改此项std::vector<std::string> _className ={"line","chuizi"};private:float _classThreshold = 0.3;float _nmsThreshold = 0.5;
};

🔹 类定义:class Yolov8Obb

  • 定义一个类,名字叫 Yolov8Obb

🔹 构造函数和析构函数

public:Yolov8Obb() { }~Yolov8Obb() {}
  • Yolov8Obb():构造函数,创建对象时调用。目前为空,没有初始化操作。
  • ~Yolov8Obb():析构函数,销毁对象时调用。目前也为空。

🔹 成员函数 1:ReadModel

bool ReadModel(cv::dnn::Net& net, std::string& netPath, bool isCuda);
  • 功能:加载 YOLOv8 模型文件(如 .onnx.pb 文件)。
  • 参数说明
    • cv::dnn::Net& netOpenCVDNN 网络对象,用来承载加载的模型。
    • std::string& netPath:模型文件路径(例如 "yolov8_obb.onnx")。
    • bool isCuda:是否使用 GPUCUDA)加速推理。
  • 返回值true 表示加载成功,false 失败。

具体实现代码:

bool Yolov8Obb::ReadModel(cv::dnn::Net& net, std::string& netPath, bool isCuda = false) {try {if (!CheckModelPath(netPath))return false;#if CV_VERSION_MAJOR==4 &&CV_VERSION_MINOR<7std::cout << "OBB Need OpenCV Version >=4.7.0" << std::endl;return false;#endifnet = cv::dnn::readNetFromONNX(netPath);#if CV_VERSION_MAJOR==4 &&CV_VERSION_MINOR==7&&CV_VERSION_REVISION==0net.enableWinograd(false);#endif}catch (const std::exception&) {return false;}if (isCuda) {net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); //or DNN_TARGET_CUDA_FP16}else {std::cout << "Inference device: CPU" << std::endl;net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);}return true;
}

✅ 示例用法:

cv::dnn::Net net;
std::string path = "yolov8_obb.onnx";
yolov8obb.ReadModel(net, path, true); // 使用 GPU

🔹 成员函数 2:Detect

bool Detect(cv::Mat& srcImg, cv::dnn::Net& net, std::vector<OutputParams>& output, int distance);
  • 功能:对输入图像进行目标检测。
  • 参数说明
    • cv::Mat& srcImg:输入的原始图像(OpenCVMat 格式)。
    • cv::dnn::Net& net:已加载的 YOLOv8 模型。
    • std::vector<OutputParams>& output:检测结果输出,包含每个检测到的物体的类别、置信度、旋转框坐标等。
      • OutputParams 是一个自定义结构体(未在代码中给出,但通常包含:类别、置信度、中心点、宽高、角度等)。
    • int distance:可能用于计算目标距离或缩放参数(具体用途需看实现)。
  • 返回值true 表示检测成功。

旋转检测框的结构体定义如下:

struct OutputParams {int id;             float confidence;   cv::Rect box;       cv::RotatedRect rotatedBox;  //OpenCV 中的一个内置结构体类型,专门用来表示一个带有旋转角度的矩形框  cv::Mat boxMask;       std::vector<PoseKeyPoint> keyPoints; 
};

Detect 方法具体实现:

bool Yolov8Obb::Detect(cv::Mat& srcImg, cv::dnn::Net& net, std::vector<OutputParams>& output,int distance=0) {cv::Mat blob;output.clear();// 设置网络使用 CUDA 后端net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);int col = srcImg.cols;int row = srcImg.rows;cv::Mat netInputImg;cv::Vec4d params;LetterBox(srcImg, netInputImg, params, cv::Size(_netWidth, _netHeight));// 预处理:blobFromImagecv::dnn::blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(_netWidth, _netHeight), cv::Scalar(), true, false);net.setInput(blob);// GPU 推理输出std::vector<cv::Mat> net_output_img;net.forward(net_output_img, net.getUnconnectedOutLayersNames()); // GPU 自动执行std::vector<int> class_ids;std::vector<float> confidences;std::vector<cv::RotatedRect> boxes;// 假设输出是 [bs, 21504, 20]cv::Mat output0 = cv::Mat(cv::Size(net_output_img[0].size[2], net_output_img[0].size[1]), CV_32F, (float*)net_output_img[0].data).t();int rows = output0.rows;         // 21504int net_width = output0.cols;   // 20int class_score_length = net_width - 5; // 15 classesint angle_index = net_width - 1;        // 最后一列是 anglefloat* pdata = (float*)output0.data;for (int r = 0; r < rows; ++r) {cv::Mat scores(1, class_score_length, CV_32FC1, pdata + 4);cv::Point classIdPoint;double max_class_socre;minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);max_class_socre = (float)max_class_socre;if (max_class_socre >= _classThreshold) {//rect [x,y,w,h]float x = (pdata[0] - params[2]) / params[0];float y = (pdata[1] - params[3]) / params[1];float w = pdata[2] / params[0];float h = pdata[3] / params[1];float angle = pdata[angle_index]  / CV_PI *180.0;class_ids.push_back(classIdPoint.x);confidences.push_back(max_class_socre);boxes.push_back(cv::RotatedRect(cv::Point2f(x, y), cv::Size(w, h), angle));}pdata += net_width;}//NMSstd::vector<int> nms_result;cv::dnn::NMSBoxes(boxes, confidences, _classThreshold, _nmsThreshold, nms_result);std::vector<std::vector<float>> temp_mask_proposals;//cv::Rect holeImgRect(0, 0, srcImg.cols, srcImg.rows);for (int i = 0; i < nms_result.size(); ++i) {int idx = nms_result[i];OutputParams result;result.id = class_ids[idx];result.confidence = confidences[idx];result.rotatedBox = boxes[idx];if (result.rotatedBox.size.width<1|| result.rotatedBox.size.height<1)continue;output.push_back(result);}OutputParams best;if (selectBestDetection(output, srcImg.size(),0, 0.5f, best,distance)) {output.clear();output.push_back(best);  // 只保留最佳目标return true;} else {output.clear();return false;}if (output.size())return true;elsereturn false;
}

🔹 成员变量:网络输入尺寸

int _netWidth = 640;   // 图片输入宽度
int _netHeight = 640;  // 图片输入高度
  • YOLOv8 模型通常要求输入图像为固定大小(这里是 640x640)。
  • 在检测前,原图会被缩放到这个尺寸。

🔹 成员变量:类别名称

std::vector<std::string> _className = {"line", "chuizi"};

🔹 私有成员:置信度与 NMS 阈值

private:float _classThreshold = 0.3;  // 分类置信度阈值float _nmsThreshold = 0.5;    // 非极大值抑制(NMS)阈值

执行旋转目标检测与飞机姿态控制

DJI(大疆)无人机平台上的一个 RGB 图像回调函数,实现AI图像处理:

首先每当接收到新图像时,就会执行DjiUser_ShowRgbImageCallback回调函数,因此能够实时接收相机图像 ,随后 使用 OpenCV + YOLOv8 OBB 模型进行旋转目标检测 ,然后计算目标角度、运动方向、距离, 最后通过消息队列(mq_send)将结果发送给飞控或其他模块。

static void DjiUser_ShowRgbImageCallback(CameraRGBImage img, void *userData)
{string name = string(reinterpret_cast<char *>(userData));#ifdef OPEN_CV_INSTALLEDMat mat(img.height, img.width, CV_8UC3, img.rawData.data(), img.width * 3);if (s_demoIndex == 3) {int32_t distance = 0;static bool cameraInited = false;T_DjiReturnCode ret = DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS;if (!cameraInited){ret = DjiCameraManager_Init();if (DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS == ret)cameraInited = true;elseprintf("Init camera manager failed, error code: 0x%08X\r\n", ret);}if (ret == DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {T_DjiCameraManagerLaserRangingInfo laserRangingInfo;T_DjiReturnCode ret = DjiCameraManager_GetLaserRangingInfo(DJI_MOUNT_POSITION_PAYLOAD_PORT_NO1,&laserRangingInfo);if (ret != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {printf("Get mounted position %d laser ranging info failed", DJI_MOUNT_POSITION_PAYLOAD_PORT_NO1, ret);}else{distance = laserRangingInfo.distance/10;printf("distance is %d.\n", distance);}}// ********************************************执行旋转目标检测***************************************std::vector<OutputParams> result;auto start = std::chrono::high_resolution_clock::now();task_obb_ocv.Detect(mat, net, result,distance);cv::Mat mask = mat.clone();json j;std::map<std::string, std::vector<float>> message;float image_center_x = mat.cols / 2.0f;float image_center_y = mat.rows / 2.0f;// *************************************计算飞行姿态:上升下降,加减速*************************************if(result.size()>0){Movement calc;json boxes_array = json::array();json scores_array = json::array();json class_ids_array = json::array();int i=0;Point2f vertices[4];result[i].rotatedBox.points(vertices); // OpenCV 提供的方法,自动计算四个角点json box_coords = nlohmann::json::array();// 按顺序添加四个角点(默认顺序:左下 -> 左上 -> 右上 -> 右下)for (int k = 0; k < 4; ++k) {box_coords.push_back({vertices[k].x, vertices[k].y});}// 按 "左上、右上、右下、左下" 的顺序排列:json reordered_coords = nlohmann::json::array();reordered_coords.push_back({vertices[1].x, vertices[1].y}); // 左上reordered_coords.push_back({vertices[2].x, vertices[2].y}); // 右上reordered_coords.push_back({vertices[3].x, vertices[3].y}); // 右下reordered_coords.push_back({vertices[0].x, vertices[0].y}); // 左下cv::Point2f ordered[4];order_points(vertices, ordered);  // 排序成标准顺序int angle = computeHorizontalAngle(ordered);auto ratios = get_velocity_ratios(angle*10);// 添加到最终数组中for (const auto& point : reordered_coords) {boxes_array.push_back(point);}// 分数scores_array.push_back(0.6f);// 类别名称映射(假设 id=0 表示“导线”)std::string class_name = (result[0].id == 0) ? "导线" : "未知";class_ids_array.push_back(class_name);// 计算移动方向float center_x = result[0].rotatedBox.center.x;float center_y = result[0].rotatedBox.center.y;json movement = calc.move(center_x, center_y, image_center_x, image_center_y);if (distance > 0 && distance < 8) {distance = -1;} else if (distance > 12 && distance < 18) {distance = 1;} else {distance = 0; }// *********************************************数据传输*********************************************j["boxes"] = boxes_array;j["scores"] = scores_array;j["class_ids"] = class_ids_array;j["movement"] = movement;j["distance"] = distance; // 默认值j["hspeed"] = ratios.at("horizontal_ratio");//chuaij["vspeed"] = ratios.at("vertical_ratio");//  把这个对象 发过去std::string dataToBeSent = j.dump(); // 参数4表示缩进空格数(美化格式)// mop 发送消息到消息队列char buffer[1024] = {0};const char *ptr = buffer;// 拷贝字符串内容(不含终止符)std::copy(dataToBeSent.begin(), dataToBeSent.end(), buffer);// 若需要字符串终止符,手动添加buffer[dataToBeSent.size()] = '\0';if (mq_send(s_mqFd, ptr, dataToBeSent.length()+1, 0) == -1) {printf("mq_send error");}printf("%s\n", ptr);printf("Message sent successfully.\n");}elsej["boxes"] = nlohmann::json::array();j["scores"] = nlohmann::json::array();j["class_ids"] = nlohmann::json::array();j["movement"] = {};j["distance"] = 0;j["hspeed"] = 0;j["vspeed"] = 0;std::string dataToBeSent = j.dump(); // 参数4表示缩进空格数(美化格式)// 发送消息到消息队列char buffer[1024] = {0};const char *ptr = buffer;// 拷贝字符串内容(不含终止符)std::copy(dataToBeSent.begin(), dataToBeSent.end(), buffer);// 若需要字符串终止符,手动添加buffer[dataToBeSent.size()] = '\0';if (mq_send(s_mqFd, ptr, dataToBeSent.length()+1, 0) == -1) {printf("mq_send error");}}cv::waitKey(1);#endif
}

这里我们发送数据使用的是mq_send方法,其是 POSIX 标准中的一个函数,用于向消息队列发送消息。它通常在 <mqueue.h> 头文件中声明。
而如果要实现接收数据,则可以通过 mq_receive 方法实现。

实现效果如下:

在这里插入图片描述

http://www.dtcms.com/a/357725.html

相关文章:

  • Python-GEE遥感云大数据分析、可视化与Satellite Embedding应用
  • leetcode算法刷题的第二十一天
  • 阿里云服务器购买流程:四种主要购买方式图文教程详解与选择参考
  • Cherrystudio的搭建和使用
  • Silvaco TCAD | Victory DoE的基本使用方法(三)
  • 小杰机器视觉(six)——模板匹配
  • LeetCode 01背包 494. 目标和
  • 顶点 (VS)vs 片段(FS):OpenGL纹理滚动着色器的性能博弈与设计哲学
  • Java进阶教程之多线程与并发编程
  • Windows下快速配置UDF编译环境的详细步骤
  • VexCL并行异构库介绍和使用
  • Python Imaging Library (PIL) 全面指南:PIL图像处理异常处理与优化
  • oceanbase-参数及变量的记录
  • LeetCode 刷题【56. 合并区间】
  • 新人桌球笔记
  • Apisix工作流程
  • 主流国产数据库:文档完备性
  • 进程与线程的根本区别
  • 【双指针 - LeetCode】42. 接雨水
  • gstreamer使用hook的简单示例
  • 用户自定义字段(Custom Fields)设计方案,兼顾多语言、分组、校验、权限、查询性能、审计与多租户
  • LeetCode - 128. 最长连续序列
  • LeetCode第二题知识点3 ----引用类型
  • lxml库如何使用
  • DSP280049 CLA可访问资源
  • 【开题答辩全过程】以 非遗信息管理系统为例,包含答辩的问题和答案
  • 2025年企业管理与经济、文化发展国际会议(MECD 2025)
  • 拎包入住搭建 Browser Use Agent:基于PPIO Model API +Agent 沙箱的一体化构建
  • React-Native项目回忆
  • QML Chart组件之坐标轴共有属性