马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本博客泉源于CSDN呆板鱼,未同意任何人转载。
更多内容,接待点击本专栏,检察更多内容。
目次
0 弁言
1 奥比中光相机测试
1.1 SDK相机初体验
1.2 将SDK的Example改成所必要步伐
2 YOLO11S-SEG的onnxruntime推理
3 结语
0 弁言
项目接纳六轴呆板臂搭配末了真空吸盘,从无序包裹中抓取想要的包裹。AI算法必要提供各包裹的抓取点的3D坐标与3D姿态。由于快递包裹含有多个面,且大多为倾斜状态,为了顺遂抓取,我们的算法必要如下几个步调:
①从彩色图的包裹堆内里辨认、分割出每个单独的包裹,得到2D掩码Mask1;
②从对齐的深度图中提取每个包裹Mask1对应的深度掩码Mask2;
③根据相机参数,盘算Mask2对应的三维点云DisMask;
④将DisMask中的非常点云值剔除后做平面分割,只要含点云数目最多的谁人平面;
⑤求平面的质心作为抓取点,求平面的法向量与x、y、z轴的角度为末了抓取姿态。
终极得到6个值返回给下位机做呆板臂的控制。
本次是在windows上举行开发,我用到的软件与SDK有:cmak-3.28.1,vs2019,opencv-4.6.1,open3d-0.18.0,orbbecsdk1.10.18、cuda-11.6、cudnn-8.5.0、onnxruntime-gpu-1.14.0(本来仍然用tensorrt的究竟快,但是我之前已经出过yolov8-seg的tensorrt推理博客了,而yolov11-seg的可以直接用,再写一遍没啥意思,以是改成onnxruntime-gpu举行推理,实测比tensorrt差不到很多,还可以省掉onnx转tensorrt模子的步调,巴适的板。我的PC显卡是3060,yolo11s-seg的推理速率是11ms,而onnxruntime的速率是24ms左右)。
模子用的ultralytics最新版练习的yolo11s-seg,练习好之后转好的onnx模子与练习数据可以在【这里】下载,练习方法网上随处都有,不是此博客的重点。
1 奥比中光相机测试
1.1 SDK相机初体验
从前做这种对精度要求不那么高项目用的都是intel的realsense系列,但代价越来越贵了,加上奥博中光每天网上吹牛批,以是某鱼买了一款二手的GeminiPro相机,是奥比中光与轮趣科技一同研发的。
起首设置奥比中光相机的驱动与SDK,必须要装驱动才气利用,下载所在:所在1。由于我接纳的win11,以是下载的win_x64这个版本。
解压后的目次为D:/Project/OrbbecSDK/(这是我放置的目次),也可以换其他路径存放。有个driver的文件夹,内里的exe无脑安装即可。
安装完成后,打开CMAKE_GUI与VS2019,CMAKE我接纳的版本是3.28.1。起首打开CMakeLists.txt将OpenCV_DIR的路径改成自己的实际路径,关于opencv的安装编译网上有很多,这里就不赘述了。
https://github.com/orbbec/OrbbecSDK/releases然后打开cmakegui按照下列步调设置并天生vs的工程文件,天生好之后打开,末了ALL_BUILD。
ALL_BUILD右键天生之后会在bin目次下天生很多exe文件,插上相机、运行color_viewer有画面就代表已经可以精确利用SDK了,下面我们对其举行改写,得到我们想要的步伐。
1.2 将SDK的Example改成所必要步伐
在我的案例中,对于SDK必要有以下几个功能:
①设定参数与初始化相机;②获取彩色图与深度图;③对彩色图与深度图举行对齐;④传入检测得到的box与mask盘算3d点云;⑤点云处理惩罚得到三维抓取点与三维姿态。
起首创建一个camera.h,界说必要的函数,代码如下:
- #ifndef CAMERAUTIL_H
- #define CAMERAUTIL_H
- #include "libobsensor/ObSensor.hpp"
- #include "libobsensor/hpp/Pipeline.hpp"
- #include <Eigen/Dense>
- #include <open3d/Open3D.h>
- #include "opencv2/opencv.hpp"
- #include <fstream>
- #include <iostream>
- #include <cmath>
- #include <vector>
- class Orbbec {
- public:
- bool Init(void);//初始化相机
- bool GetRgbAndDepth(cv::Mat& src, cv::Mat& dep);//获取当前帧,并进行深度图与彩色图的对齐
- void GetDistance(int x, int y);//打印提取某个点的x、y、z坐标
- void GetDistance(int box[4], cv::Mat& mask, cv::Mat& dis_mask);//提取某个box中mask对应的所有点的三维点云,并组成dis_mask
- void ProcessPointcloud(cv::Mat& dis_mask, cv::Mat imgcrop, std::vector<float>& result, float *ylength, float *xlength);//对点云进行后处理得到抓取坐标与抓取姿态
- void Postprocess(int box[4], cv::Mat& mask, cv::Mat srcimg, float result[10]);//对提取的dis_mask进行后处理,GetDistance与ProcessPointcloud
- //都是后处理需要用到的函数
- void close(void);
- private:
- ob::Pipeline pipe;
- OBCameraParam cameraParam;
- cv::Mat depth;
- float scale;
- ob::Align* align=nullptr;
- };
- #endif
复制代码 然后新建一个camera.cpp,orbbec的函数举行实现,代码如下:
- #include "camera.h"
- bool Orbbec::Init(void) {
- // Query the list of connected devices
- // Create a Context.
- ob::Context ctx;
- auto devList = ctx.queryDeviceList();
- // Get the number of connected devices
- if (devList->deviceCount() == 0) {
- std::cerr << "Device not found!" << std::endl;
- return false;
- }
-
- // Configure which streams to enable or disable for the Pipeline by creating a Config
- std::shared_ptr<ob::Config> config = std::make_shared<ob::Config>();
- // Get all stream profiles of the color camera, including stream resolution, frame rate, and frame format
- auto colorProfiles = pipe.getStreamProfileList(OB_SENSOR_COLOR);
- // Get the default color profile
- //auto colorProfile = colorProfiles->getProfile(OB_PROFILE_DEFAULT);
- std::shared_ptr<ob::VideoStreamProfile> colorProfile = nullptr;
- colorProfile = colorProfiles->getVideoStreamProfile(640, 480, OB_FORMAT_RGB, 30);
- config->enableStream(colorProfile);
- // Get all stream profiles of the depth camera, including stream resolution, frame rate, and frame format
- auto depthProfiles = pipe.getStreamProfileList(OB_SENSOR_DEPTH);
- // Get the default depth profile
- //auto depthProfile = depthProfiles->getProfile(OB_PROFILE_DEFAULT);
- std::shared_ptr<ob::VideoStreamProfile> depthProfile = nullptr;
- depthProfile = depthProfiles->getVideoStreamProfile(640, OB_HEIGHT_ANY, OB_FORMAT_Y11, 30);
- config->enableStream(depthProfile);
- // Configure the alignment mode as hardware D2C alignment
- config->setAlignMode(ALIGN_D2C_HW_MODE);
- align = new ob::Align(OB_STREAM_COLOR);
- try {
- pipe.start(config);
- cameraParam = pipe.getCameraParam();
- }
- catch (ob::Error& e) {
- std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl;
- return false;
- }
- return true;
- };
- bool Orbbec::GetRgbAndDepth(cv::Mat& src, cv::Mat& dep) {
- auto frameset = pipe.waitForFrames(100);
- if (frameset == nullptr || frameset->depthFrame() == nullptr || frameset->colorFrame() == nullptr) {
- return false;
- }
- auto newFrame = align->process(frameset);
- auto newFrameSet = newFrame->as<ob::FrameSet>();
- auto colorFrame = newFrameSet->colorFrame();
- auto depthFrame = newFrameSet->depthFrame();
- uint32_t width = colorFrame->width();
- uint32_t height = colorFrame->height();
- cv::Mat rawMat(height, width, CV_8UC3, (uint16_t*)colorFrame->data(), cv::Mat::AUTO_STEP);
- cv::cvtColor(rawMat, src, cv::COLOR_RGB2BGR);
- width = depthFrame->width();
- height = depthFrame->height();
- scale = depthFrame->getValueScale();
- uint16_t* data = (uint16_t*)depthFrame->data();
- cv::Mat depimg(height, width, CV_16U, data, cv::Mat::AUTO_STEP);
- depimg.copyTo(depth);
- // 将深度图转换为8位图像,进行可视化
- cv::Mat depth_image_8bit;
- double minVal, maxVal;
- cv::Point minLoc, maxLoc;
- cv::minMaxLoc(depimg, &minVal, &maxVal, &minLoc, &maxLoc);
- cv::convertScaleAbs(depimg, depth_image_8bit, 255.0 / maxVal);
- cv::applyColorMap(depth_image_8bit, dep, cv::COLORMAP_JET);
- //float centerDistance = data[width * height / 2 + width / 2] * scale;
- return true;
- };
- void Orbbec::GetDistance(int x,int y) {
- float Z = (float)depth.at<uint16_t>(y,x) * scale;
- float X= (x - cameraParam.depthIntrinsic.cx) * Z / cameraParam.depthIntrinsic.fx;
- float Y= (y - cameraParam.depthIntrinsic.cy) * Z / cameraParam.depthIntrinsic.fy;
- printf("X,Y,Z:%f,%f.%f\n", X, Y, Z);
- }
- void Orbbec::GetDistance(int box[4], cv::Mat& mask, cv::Mat& dis_mask) {
- for (int i = box[0]; i < box[2]; i++) {
- for (int j = box[1]; j < box[3]; j++) {
- if (mask.at<uchar>(j - box[1], i - box[0]) != 0){
- float upoint[3];
- upoint[2] = (float)depth.at<uint16_t>(j, i)* scale/1000.f;//转换成米
- upoint[0] = (i - cameraParam.depthIntrinsic.cx) * upoint[2] / cameraParam.depthIntrinsic.fx;
- upoint[1] = (j - cameraParam.depthIntrinsic.cy) * upoint[2] / cameraParam.depthIntrinsic.fy;
- dis_mask.at<cv::Vec3f>(j - box[1], i - box[0]) = cv::Vec3f(upoint[0], upoint[1], upoint[2]);
- if (upoint[2] == 0.0f)
- {
- mask.at<uchar>(j - box[1], i - box[0]) = 0; // 深度值为0的地方 mask也改成0
- }
- }
- }
- }
- }
- void Orbbec::Postprocess(int box[4], cv::Mat& mask, cv::Mat srcimg,float result[10]) {
- cv::Rect r = cv::Rect(box[0], box[1], box[2] - box[0], box[3] - box[1]);
- cv::Mat imgcrop = srcimg(r).clone();
- mask = mask(r).clone();
- cv::Mat dis_mask = cv::Mat::zeros(r.height,r.width, CV_32FC3);
- GetDistance(box, mask, dis_mask);
- //cv::Mat dis_show;
- //cv::convertScaleAbs(dis_mask, dis_show, 255.0 / 1000);
- //cv::applyColorMap(dis_show, dis_show, cv::COLORMAP_JET);
- //cv::imshow("dismask", dis_show);
-
- // 利用open3d进行点云的后处理
- // segment plane分割平面,因为快递分割结果里面会含有侧面,因此先分割,取最大的那个面,然后提取这个面的中点作抓取点
- // 以及该面的法向量
- float ylength = 0, xlength = 0;
- std::vector<float> Normals_and_Axis;
- ProcessPointcloud(dis_mask, imgcrop, Normals_and_Axis, &ylength, &xlength);
-
- // 通过抓取点反算彩色图上的抓取点,用来观察是否抓对点
- float CX = Normals_and_Axis[3] * cameraParam.depthIntrinsic.fx / Normals_and_Axis[5] + cameraParam.depthIntrinsic.cx;
- float CY = Normals_and_Axis[4] * cameraParam.depthIntrinsic.fy / Normals_and_Axis[5] + cameraParam.depthIntrinsic.cy;
- // 通过点云算出来的包裹长宽来反算彩色图中的2D最小包围框,用于观察是否抓准平面
- float Width = 0, Height = 0;
- if (Normals_and_Axis[5] > 0) {//只计算有合理深度值的
- //根据xlength与ylength,反算框的wh,与XY形成抓取面的2d最小包围框
- Width = xlength * cameraParam.depthIntrinsic.fx / Normals_and_Axis[5];
- Height = ylength * cameraParam.depthIntrinsic.fy / Normals_and_Axis[5];
- }
- for (int i = 0; i < 6; i++) {
- result[i] = Normals_and_Axis[i];
- }
- result[6] = CX;
- result[7] = CY;
- result[8] = Width;
- result[9] = Height;
- }
- void Orbbec::ProcessPointcloud(cv::Mat& dis_mask, cv::Mat image, std::vector<float>& result, float* ylength, float* xlength)
- {
- // //dis_mask to pointcloud
- auto outputPC = std::make_shared<open3d::geometry::PointCloud>();
- int num_points = dis_mask.cols * dis_mask.rows;
- for (int i = 0; i < dis_mask.rows; i++)
- {
- for (int j = 0; j < dis_mask.cols; j++)
- {
- Eigen::Vector3d pc = { dis_mask.at<cv::Vec3f>(i, j)[0], dis_mask.at<cv::Vec3f>(i, j)[1], dis_mask.at<cv::Vec3f>(i, j)[2] };
- Eigen::Vector3d clr = { (float)image.at<cv::Vec3b>(i,j)[2] / 255.0,
- (float)image.at<cv::Vec3b>(i,j)[1] / 255.0,
- (float)image.at<cv::Vec3b>(i,j)[0] / 255.0 };
- outputPC->points_.push_back(pc);
- outputPC->colors_.push_back(clr);
- }
- }
- //open3d::visualization::DrawGeometries({ outputPC}, "outputPC", 800, 450);
- //异常值剔除,剔除z距离为0的无效点云
- std::vector<size_t> indices;
- for (size_t i = 0; i < outputPC->points_.size(); i++) {
- if (outputPC->points_[i][2] == 0.0) {
- indices.push_back(i);
- }
- }
- outputPC = outputPC->SelectByIndex(indices, true);
- if (outputPC->points_.size() <= 3) {
- result = { 0,0,0,0,0,0,0 };
- printf("points size too small\n");
- return;
- }
- // 平面分割 取出来最大的平面
- auto start = std::chrono::system_clock::now();
- double tDis = 0.01; // 邻域距离
- int minNum = 3; // 最小点云数
- int numIter = 200; // 迭代数
- std::tuple<Eigen::Vector4d, std::vector<size_t>> vRes = outputPC->SegmentPlane(tDis, minNum, numIter);
- // 平面参数
- // AX+BY+CZ+D=0 ==> Z=-(AX+BY+D)/C
- Eigen::Vector4d para = std::get<0>(vRes);
- //printf("Segment plane:%.3fX+%.3fY+%.3fZ+%.3f=0\n",para[0],para[1],para[2],para[3]);
- // 内点索引
- std::vector<size_t> selectedIndex = std::get<1>(vRes);
- std::shared_ptr<open3d::geometry::PointCloud> inPC = outputPC->SelectByIndex(selectedIndex, false);
- //open3d::visualization::DrawGeometries({ inPC}, "inPC", 800, 450);
- // 点云质心作为距离坐标
- Eigen::Vector3d Axis;
- Axis = inPC->GetCenter();
- // 计算法向量z夹角
- Eigen::Vector3d A(para[0], para[1], para[2]), X(-1, 0, 0), Y(0, -1, 0), Z(0, 0, 1);
- double angleX0, angleX, angleY, angleZ;
- double PI = 3.1415926;
- angleZ = std::acos(A.dot(Z) / (A.norm() * Z.norm())) * 180 / PI;
- angleX0 = std::acos(A.dot(X) / (A.norm() * X.norm())) * 180 / PI;
- angleY = std::acos(A.dot(Y) / (A.norm() * Y.norm())) * 180 / PI;
- angleX = std::atan2(A[1], A[0]) * 180 / PI;
- if (angleZ > 90) {
- angleZ = 180 - angleZ;
- X = { 1, 0, 0 };
- Y = { 0, 1, 0 };
- angleX0 = std::acos(A.dot(X) / (A.norm() * X.norm())) * 180 / PI;
- angleY = std::acos(A.dot(Y) / (A.norm() * Y.norm())) * 180 / PI;
- angleX = std::atan2(-A[1], A[0]) * 180 / PI;
-
- }
- //printf("angle:%f,%f,%f,xplane:%f\n", angleX0, angleY, angleZ, angleX);
- //angleX的角度是与法向量投影到xoy平面的并于x负方向的夹角,顺时针是0-180,逆时针是0-负180
- //
-
- result.push_back(angleX0);
- result.push_back(angleY);
- result.push_back(angleZ);
- result.push_back(Axis[0]);
- result.push_back(Axis[1]);
- result.push_back(Axis[2]);
-
- auto end = std::chrono::system_clock::now();
- //std::cout << "open3d process time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
- // // 绘制分割出来的平面模型
- Eigen::Vector3d min_pt, max_pt;
- min_pt = inPC->GetMinBound();
- max_pt = inPC->GetMaxBound();
- double Xmin = min_pt[0], Ymin = min_pt[1], Zmin = min_pt[2];
- double Xmax = max_pt[0], Ymax = max_pt[1], Zmax = max_pt[2];
- *ylength = (float)Ymax - (float)Ymin;
- *xlength = (float)Xmax - (float)Xmin;
- //展示结果
- if (0) {
- // 外点 绿色
- std::shared_ptr<open3d::geometry::PointCloud> outPC = outputPC->SelectByIndex(selectedIndex, true);
- const Eigen::Vector3d colorOut = { 0,1,0 };
- outPC->PaintUniformColor(colorOut);
- // open3d::visualization::DrawGeometries({ outPC}, "outPC", 800, 450);
- auto Plane = std::make_shared<open3d::geometry::PointCloud>();
- int width = 20, height = 20;
- for (int i = 0; i < width; i++) {
- double x = Xmin + (Xmax - Xmin) / width * i;
- for (int j = 0; j < height; j++) {
- double y = Ymin + (Ymax - Ymin) / height * j;
- double z = -(para[0] * x + para[1] * y + para[3]) / para[2];
- Eigen::Vector3d pc = { x, y, z };
- Plane->points_.push_back(pc);
- }
- }
- const Eigen::Vector3d color = { 1,0,1 };
- Plane->PaintUniformColor(color);
- // 夹爪方向
- auto lineCloud = std::make_shared<open3d::geometry::PointCloud>();
- lineCloud->points_.resize(20);
- for (size_t i = 0; i < lineCloud->points_.size(); ++i)
- {
- lineCloud->points_[i] = Axis - i * 0.01 * A;
- }
- lineCloud->PaintUniformColor(color);
- // 抓取点
- auto mesh = open3d::geometry::TriangleMesh::CreateSphere(0.01);
- mesh->ComputeVertexNormals();
- mesh->PaintUniformColor(Eigen::Vector3d(0.1, 0.1, 0.1));
- mesh->Translate(Axis);
- //圆心坐标
- auto mesh1 = open3d::geometry::TriangleMesh::CreateCoordinateFrame(0.1);
- mesh1->ComputeVertexNormals();
- //旋转到相机方向 方便我们观看
- Eigen::Matrix4d transform;
- transform << 1.0, 0.0, 0.0, 0.0,
- 0.0, -1.0, 0.0, 0.0,
- 0.0, 0.0, -1.0, 0.0,
- 0.0, 0.0, 0.0, 1.0;
- inPC->Transform(transform);
- outPC->Transform(transform);
- lineCloud->Transform(transform);
- Plane->Transform(transform);
- mesh->Transform(transform);
- mesh1->Transform(transform);
- // open3d::io::WritePointCloudToPCD("inPC.pcd", *inPC,false);
- // open3d::io::WritePointCloudToPCD("outPC.pcd", *outPC,false);
- // open3d::io::WritePointCloudToPCD("lineCloud.pcd", *lineCloud,false);
- // open3d::io::WritePointCloudToPCD("Plane.pcd", *Plane,false);
- open3d::visualization::DrawGeometries({ inPC,outPC,lineCloud,Plane,mesh,mesh1 }, "Result", 800, 450);
- }
- }
- void Orbbec::close(void) {
- pipe.stop();
- };
复制代码 再新建一个主函数,用来调用上面的函数,名字就叫main1_testcamera.cpp写,主体代码如下所示:
- #include "camera.h"
- // #include "opencv2/opencv.hpp"
- #include <fstream>
- #include <iostream>
- #include <cmath>
- #include <conio.h>
- using namespace std;
- int main(int argc, char** argv) try {
- Orbbec camera;
- camera.Init();
- while (true) {
- cv::Mat src, dep;
- if (!camera.GetRgbAndDepth(src, dep)) {
- continue;
- }
- cv::Mat show=src.clone();
- //cv::addWeighted(src,0.5,dep,0.8,0.5,show);
- int box[4] = { 280,200,360,280};//检测框的xmin,ymin,xmax,ymax
- cv::Mat mask = cv::Mat::ones(cv::Size(src.cols, src.rows), CV_8UC1);
- float result[10] = {0};
- camera.Postprocess(box, mask, src, result);
- printf("ax,ay,az,x,y,z,:%f,%f,%f,%f,%f,%f\n", result[0], result[1], result[2], result[3], result[4], result[5]);
-
- cv::Point p1((int)(0.5 * box[0] + 0.5 * box[2]), (int)(0.5 * box[1] + 0.5 * box[3]));
- cv::circle(show, p1, 3, cv::Scalar(0, 255, 255), 3);//center point
- cv::Point p2((int)result[6], (int)result[7]);
- cv::circle(show, p2, 3, cv::Scalar(0, 0, 255), 3);//pick point
- cv::Rect rdraw0 = cv::Rect((int)box[0], (int)box[1], (int)(box[2]-box[0]), (int)(box[3] - box[1]));
- cv::rectangle(show, rdraw0, cv::Scalar(0, 255, 255), 2);//detect box
- cv::Rect rdraw1 = cv::Rect((int)(result[6] - 0.5 * result[8]),(int)(result[7] - 0.5 * result[9]),
- (int)result[8], (int)result[9]);
- cv::rectangle(show, rdraw1, cv::Scalar(0, 0, 255), 2);//postprocess box
- printf("-------------------------------------------------\n");
- cv::imshow("show", show);
- char c = cv::waitKey(1);
- if (c == 27) {
- cv::destroyAllWindows();
- break;
- }
- }
- camera.close();
- return 0;
- }
- catch (ob::Error& e) {
- std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl;
- exit(EXIT_FAILURE);
- }
复制代码 末了,新建一个CMakeLists.txt设置我们的环境,如下所示,必要根据现真相况修改OpenCV_DIR、OrbbecSDK_ROOT_DIR、Open3D_DIR,
- cmake_minimum_required(VERSION 3.10)
- project(orbbec)
- add_definitions(-std=c++11)
- add_definitions(-DAPI_EXPORTS)
- set(CMAKE_CXX_STANDARD 11)
- set(CMAKE_BUILD_TYPE Release)
- set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
- include_directories(include/)
- link_directories(lib/) #添加特定的库文件搜索路径 相当于g++的 -L参数
- include_directories(${PROJECT_SOURCE_DIR}/src/)
- file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp)
- #opencv
- Set(OpenCV_DIR D:/Project/opencv/build/)
- find_package(OpenCV)
- include_directories(${OpenCV_INCLUDE_DIRS})
- include_directories(${OpenCV_DIR}/include/)
- link_directories(${OpenCV_DIR}/x64/vc15/lib/)
- #orbbec
- set(OrbbecSDK_ROOT_DIR D:/Project/OrbbecSDK/SDK)
- set(OrbbecSDK_LIBRARY_DIRS ${OrbbecSDK_ROOT_DIR}/lib)
- set(OrbbecSDK_INCLUDE_DIR ${OrbbecSDK_ROOT_DIR}/include)
- include_directories(${OrbbecSDK_INCLUDE_DIR})
- link_directories(${OrbbecSDK_LIBRARY_DIRS})
- #open3d
- Set(Open3D_DIR D:/Project/open3d-0.18.0/CMake/)
- find_package( Open3D REQUIRED)
- include_directories(${Open3D_INCLUDE_DIRS})
- link_directories(${Open3D_LIBRARY_DIRS})
- add_executable(test main1_test_camera.cpp ${SRCS})
- target_link_libraries(test OrbbecSDK ${OpenCV_LIBS})
- target_link_libraries(test ${Open3D_LIBRARIES})
- target_include_directories(test PUBLIC ${Open3D_INCLUDE_DIRS})
复制代码 末了,利用Cmake-gui与vs2019天生可实行文件即可,运行步伐时间将这3个dll文件放在test.exe的同级目次即可。

2 YOLO11S-SEG的onnxruntime推理
直接上代码,起首创建Segment.h,用来界说推理用到的函数,如下
- #ifndef SEGMENT_H
- #define SEGMENT_H
- #include <fstream>
- #include <sstream>
- #include <iostream>
- #include <opencv2/imgproc.hpp>
- #include <opencv2/highgui.hpp>
- //#include <cuda_provider_factory.h>
- #include <onnxruntime_cxx_api.h>
- #include <chrono>
- using namespace std;
- using namespace Ort;
- struct BoxInfo
- {
- float x;
- float y;
- float w;
- float h;
- float score;
- int label;
- float mask[32];
- };
- struct Net_config
- {
- float confThreshold; // Confidence threshold
- float nmsThreshold; // Non-maximum suppression threshold
- std::string modelpath;
- std::string labelpath;
- };
- constexpr static int num_class = 1;//我们只有一类就是1,多类就要改这儿
- const static int kOutputSize = num_class + 4 + 32;
- class YOLOV8
- {
- public:
- YOLOV8(Net_config config);//加载模型
- void preprocess_img(cv::Mat& img, cv::Mat& dis, int input_w, int input_h);//将图像letterbox resize到640*640
- void normalize_(cv::Mat img);//除以255,归一化,同时将rgbrgbrgb转成rrrgggbbb
- void detect(cv::Mat& frame, std::vector<BoxInfo>& generate_boxes, std::vector<cv::Mat>& masks);//输入图像进行检测与后处理
- //得到每个目标的检测框与每个目标的mask1
- float iou(float lbox[4], float rbox[4]);//iou计算
- void nms(vector<BoxInfo>& input_boxes);//nms
- std::vector<cv::Mat> process_mask(const float* proto, int proto_size, std::vector<BoxInfo>& dets, cv::Mat img);//后处理得到mask
- cv::Rect get_downscale_rect(float bbox[4], float scale);//将640*640输入的box尺寸下采样到160*160的特征图上
- cv::Mat scale_mask(cv::Mat mask, cv::Mat img); // 将640 * 640输入得到的掩码还原到原图上, 很好理解,就跟processimg反着操作
- cv::Rect get_rect(cv::Mat& img, float bbox[4]);// 将640 * 640输入得到的box还原到原图上, 很好理解,就跟processimg反着操作
-
-
- private:
- int inpWidth;
- int inpHeight;
- int nout;
- int num_proposal;
- std::vector<string> class_names;
- float confThreshold;
- float nmsThreshold;
- std::vector<float> input_image_;//ONNXRUMTIME接收的数据是一个vector
-
- Env env = Env(ORT_LOGGING_LEVEL_ERROR, "YOLOV8");
- Ort::Session* ort_session = nullptr;
- SessionOptions sessionOptions = SessionOptions();
- std::vector<char*> input_names;
- std::vector<char*> output_names;
- std::vector<vector<int64_t>> input_node_dims; // >=1 outputs
- std::vector<vector<int64_t>> output_node_dims; // >=1 outputs
- const std::vector<uint32_t> colors = { 0xFF3838, 0xFF9D97, 0xFF701F, 0xFFB21D, 0xCFD231, 0x48F90A,
- 0x92CC17, 0x3DDB86, 0x1A9334, 0x00D4BB, 0x2C99A8, 0x00C2FF,
- 0x344593, 0x6473FF, 0x0018EC, 0x8438FF, 0x520085, 0xCB38FF,
- 0xFF95C8, 0xFF37C7 };
- };
- #endif
复制代码 然后新建Segment.cpp开详细实现这些函数,代码如下:
- #include "Segment.h"
- YOLOV8::YOLOV8(Net_config config)
- {
- confThreshold = config.confThreshold;
- nmsThreshold = config.nmsThreshold;
- string classesFile = config.labelpath;
- string model_path = config.modelpath;
- std::wstring widestr = std::wstring(model_path.begin(), model_path.end());
- OrtStatus* status = OrtSessionOptionsAppendExecutionProvider_CUDA(sessionOptions, 0);
- sessionOptions.SetGraphOptimizationLevel(ORT_ENABLE_BASIC);
- ort_session = new Session(env, widestr.c_str(), sessionOptions);
- size_t numInputNodes = ort_session->GetInputCount();
- size_t numOutputNodes = ort_session->GetOutputCount();
- AllocatorWithDefaultOptions allocator;
- for (int i = 0; i < numInputNodes; i++)
- {
- AllocatedStringPtr input_name_Ptr = ort_session->GetInputNameAllocated(i, allocator);
- input_names.push_back(input_name_Ptr.get());
- Ort::TypeInfo input_type_info = ort_session->GetInputTypeInfo(i);
- auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();
- auto input_dims = input_tensor_info.GetShape();
- input_node_dims.push_back(input_dims);
- }
- for (int i = 0; i < numOutputNodes; i++)
- {
- AllocatedStringPtr output_name_Ptr = ort_session->GetOutputNameAllocated(i, allocator);
- output_names.push_back(output_name_Ptr.get());
- Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);
- auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();
- auto output_dims = output_tensor_info.GetShape();
- output_node_dims.push_back(output_dims);
- }
- inpHeight = input_node_dims[0][2];
- inpWidth = input_node_dims[0][3];
- nout = output_node_dims[0][1];
- num_proposal = output_node_dims[0][2];
- ifstream ifs(classesFile.c_str());
- string line;
- while (getline(ifs, line)) class_names.push_back(line);
- printf("classsize is:%d\n", num_class);
- assert(num_class = class_names.size());
- }
- cv::Rect YOLOV8::get_rect(cv::Mat& img, float bbox[4]) {
- float l, r, t, b;
- float r_w = inpWidth / (img.cols * 1.0);
- float r_h = inpHeight / (img.rows * 1.0);//inpHeight inpWidth
- if (r_h > r_w) {
- l = bbox[0] - bbox[2] / 2.f;
- r = bbox[0] + bbox[2] / 2.f;
- t = bbox[1] - bbox[3] / 2.f - (inpHeight - r_w * img.rows) / 2;
- b = bbox[1] + bbox[3] / 2.f - (inpHeight - r_w * img.rows) / 2;
- l = l / r_w;
- r = r / r_w;
- t = t / r_w;
- b = b / r_w;
- }
- else {
- l = bbox[0] - bbox[2] / 2.f - (inpWidth - r_h * img.cols) / 2;
- r = bbox[0] + bbox[2] / 2.f - (inpWidth - r_h * img.cols) / 2;
- t = bbox[1] - bbox[3] / 2.f;
- b = bbox[1] + bbox[3] / 2.f;
- l = l / r_h;
- r = r / r_h;
- t = t / r_h;
- b = b / r_h;
- }
- return cv::Rect(round(l), round(t), round(r - l), round(b - t));
- }
- cv::Rect YOLOV8::get_downscale_rect(float bbox[4], float scale) {
- float left = bbox[0] - bbox[2] / 2;
- float top = bbox[1] - bbox[3] / 2;
- float right = bbox[0] + bbox[2] / 2;
- float bottom = bbox[1] + bbox[3] / 2;
- left /= scale;
- top /= scale;
- right /= scale;
- bottom /= scale;
- return cv::Rect(round(left), round(top), round(right - left), round(bottom - top));
- }
- cv::Mat YOLOV8::scale_mask(cv::Mat mask, cv::Mat img) {
- int x, y, w, h;
- float r_w = inpWidth / (img.cols * 1.0);
- float r_h = inpHeight / (img.rows * 1.0);
- if (r_h > r_w) {
- w = inpWidth;
- h = r_w * img.rows;
- x = 0;
- y = (inpHeight - h) / 2;
- }
- else {
- w = r_h * img.cols;
- h = inpHeight;
- x = (inpWidth - w) / 2;
- y = 0;
- }
- cv::Rect r(x, y, w, h);
- cv::Mat res;
- cv::resize(mask(r), res, img.size());
- return res;
- }
- std::vector<cv::Mat> YOLOV8::process_mask(const float* proto, int proto_size, std::vector<BoxInfo>& dets, cv::Mat img) {
- std::vector<cv::Mat> masks;
- //proto 32x(H/4)x(W/4)
- //auto start = std::chrono::system_clock::now();
- for (size_t i = 0; i < dets.size(); i++) {
- cv::Mat mask_mat = cv::Mat::zeros(inpHeight / 4, inpWidth / 4, CV_8UC1);//160*160
- float bbox[4] = { dets[i].x, dets[i].y, dets[i].w, dets[i].h };
- auto r = get_downscale_rect(bbox, 4);//将640*640输入的box尺寸下采样到160*160的特征图上
- for (int x = r.x; x < r.x + r.width; x++) {
- for (int y = r.y; y < r.y + r.height; y++) {
- float e = 0.0f;
- for (int j = 0; j < 32; j++) {
- //1x32 x 1*32*160*160 =1*160*160 可以理解成160*160特征图上的每一个像素有32个值,要与mask的32个值分别相乘再相加
- //而1*32*160*160在内存中的存储方式是160*160 160*160 160*160 .... 因此每次乘法操作的时候都要跳过160*160个内存空间
- e += dets[i].mask[j] * proto[j * proto_size / 32 + y * mask_mat.cols + x];
- }
- e = 1.0f / (1.0f + expf(-e));
- if (e > 0.5) {
- mask_mat.at<uchar>(y, x) = 1;
- }
- }
- }
- //160*160的掩码resize到640*640上
- cv::resize(mask_mat, mask_mat, cv::Size(inpWidth, inpHeight), 0, 0, 1);
- //再将640*640的掩码还原到原图上,很好理解,就跟processimg反着操作
- cv::Mat img_mask = scale_mask(mask_mat, img);
- //将640*640的box还原到原图上,很好理解,就跟processimg反着操作
- r = get_rect(img, bbox);
- cv::Rect holeImgRect(0, 0, img.cols, img.rows);
- r = r & holeImgRect;
- dets[i].x = r.x;
- dets[i].y = r.y;
- dets[i].w = r.width;
- dets[i].h = r.height;
- masks.push_back(img_mask);
- }
- return masks;
- }
- void YOLOV8::normalize_(cv::Mat img)
- {
- int row = img.rows;
- int col = img.cols;
- input_image_.resize(row * col * img.channels());
- // bgrbgrbgr to rrrgggbbb
- for (int c = 0; c < 3; c++)
- {
- for (int i = 0; i < row; i++)
- {
- for (int j = 0; j < col; j++)
- {
- float pix = img.ptr<uchar>(i)[j * 3 + 2 - c];
- input_image_[c * row * col + i * col + j] = pix / 255.0;
- }
- }
- }
- }
- float YOLOV8::iou(float lbox[4], float rbox[4]) {
- float interBox[] = {
- (std::max)(lbox[0] - lbox[2] / 2.f , rbox[0] - rbox[2] / 2.f), //left
- (std::min)(lbox[0] + lbox[2] / 2.f , rbox[0] + rbox[2] / 2.f), //right
- (std::max)(lbox[1] - lbox[3] / 2.f , rbox[1] - rbox[3] / 2.f), //top
- (std::min)(lbox[1] + lbox[3] / 2.f , rbox[1] + rbox[3] / 2.f), //bottom
- };
- if (interBox[2] > interBox[3] || interBox[0] > interBox[1])
- return 0.0f;
- float interBoxS = (interBox[1] - interBox[0]) * (interBox[3] - interBox[2]);
- return interBoxS / (lbox[2] * lbox[3] + rbox[2] * rbox[3] - interBoxS);
- }
- void YOLOV8::nms(vector<BoxInfo>& input_boxes)
- {
- sort(input_boxes.begin(), input_boxes.end(), [](BoxInfo a, BoxInfo b) { return a.score > b.score; });
- vector<bool> isSuppressed(input_boxes.size(), false);
- for (int i = 0; i < int(input_boxes.size()); ++i)
- {
- if (isSuppressed[i]) { continue; }
- for (int j = i + 1; j < int(input_boxes.size()); ++j)
- {
- if (isSuppressed[j]) { continue; }
- float lbox[4] = { input_boxes.at(i).x,input_boxes.at(i).y,input_boxes.at(i).w,input_boxes.at(i).h };
- float rbox[4] = { input_boxes.at(j).x,input_boxes.at(j).y,input_boxes.at(j).w,input_boxes.at(j).h };
- float ovr = iou(lbox, rbox);
- if (ovr >= nmsThreshold)
- {
- isSuppressed[j] = true;
- }
- }
- }
- int idx_t = 0;
- input_boxes.erase(remove_if(input_boxes.begin(), input_boxes.end(), [&idx_t, &isSuppressed](const BoxInfo& f) { return isSuppressed[idx_t++]; }), input_boxes.end());
- }
- void YOLOV8::preprocess_img(cv::Mat& img, cv::Mat& dis, int input_w, int input_h) {
- int w, h, x, y;
- float r_w = input_w / (img.cols * 1.0);
- float r_h = input_h / (img.rows * 1.0);
- if (r_h > r_w) {//宽大于高
- w = input_w;
- h = r_w * img.rows;
- x = 0;
- y = (input_h - h) / 2;
- }
- else {
- w = r_h * img.cols;
- h = input_h;
- x = (input_w - w) / 2;
- y = 0;
- }
- cv::Mat re(h, w, CV_8UC3);
- cv::resize(img, re, re.size(), 0, 0, cv::INTER_LINEAR);
- cv::Mat out(input_h, input_w, CV_8UC3, cv::Scalar(128, 128, 128));
- re.copyTo(out(cv::Rect(x, y, re.cols, re.rows)));
- out.copyTo(dis);
- }
- void YOLOV8::detect(cv::Mat& frame, vector<BoxInfo>& generate_boxes,std::vector<cv::Mat>& masks)
- {
- cv::Mat dstimg;
- preprocess_img(frame, dstimg, inpWidth, inpHeight);
- normalize_(dstimg);
- array<int64_t, 4> input_shape_{ 1, 3, inpHeight, inpWidth };
- auto allocator_info = MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
- Value input_tensor_ = Value::CreateTensor<float>(allocator_info, input_image_.data(), input_image_.size(), input_shape_.data(), input_shape_.size());
- // 开始推理
- const std::array<const char*, 1> inputNames = { "images" };
- const std::array<const char*, 2> outNames = { "output0","output1" };
- vector<Value> ort_outputs = ort_session->Run(Ort::RunOptions{ nullptr }, inputNames.data(), &input_tensor_, 1, outNames.data(), outNames.size());
- /////generate proposals
- //vector<BoxInfo> generate_boxes;
- int n = 0, k = 0;
- const float* pdata = ort_outputs[0].GetTensorMutableData<float>();// 1 * 116 *8400
- const float* pdata1 = ort_outputs[1].GetTensorMutableData<float>();//1*32*160*160 mask propsal
- for (n = 0; n < num_proposal; n++)
- {
- float out[kOutputSize];//4+class+32
- float mask[32];
- for (int j = 0; j < nout; j++) {
- out[j] = pdata[n + num_proposal * j];
- }
- memcpy(&mask, &out[4 + num_class], 32 * sizeof(float));
- int max_ind = 0;
- float max_class_socre = 0;
- // 计算最大的置信度得分
- for (k = 0; k < num_class; k++)
- {
- if (out[k + 4] > max_class_socre)
- {
- max_class_socre = out[k + 4];
- max_ind = k;
- }
- }
- if (max_class_socre > confThreshold)
- {
- float cx = out[0];
- float cy = out[1];
- float w = out[2];
- float h = out[3];
- BoxInfo det;
- det.x = cx;
- det.y = cy;
- det.w = w;
- det.h = h;
- det.score = max_class_socre;
- det.label = max_ind;
- for (int jj = 0; jj < 32; jj++) {
- det.mask[jj] = mask[jj];
- }
- generate_boxes.push_back(det);
- }
- }
- nms(generate_boxes);
- int kOutputSize2 = 32 * (inpHeight / 4) * (inpWidth / 4);
- masks = process_mask(pdata1, kOutputSize2, generate_boxes, frame);
- // draw at original frame
- for (size_t i = 0; i < generate_boxes.size(); i++) {
- cv::Mat img_mask = masks[i];
- auto color = colors[(int)generate_boxes[i].label % colors.size()];
- auto bgr = cv::Scalar(color & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF);
- cv::Rect r = cv::Rect((int)generate_boxes[i].x, (int)generate_boxes[i].y, (int)generate_boxes[i].w, (int)generate_boxes[i].h);
- cv::rectangle(frame, r, bgr, 2);
- // mask 画原图上
- for (int x = r.x; x < r.x + r.width; x++) {
- for (int y = r.y; y < r.y + r.height; y++) {
- float val = img_mask.at<uchar>(y, x);
- if (val == 0) continue;
- frame.at<cv::Vec3b>(y, x)[0] = frame.at<cv::Vec3b>(y, x)[0] / 2 + bgr[0] / 2;
- frame.at<cv::Vec3b>(y, x)[1] = frame.at<cv::Vec3b>(y, x)[1] / 2 + bgr[1] / 2;
- frame.at<cv::Vec3b>(y, x)[2] = frame.at<cv::Vec3b>(y, x)[2] / 2 + bgr[2] / 2;
- }
- }
- char label[100];
- sprintf(label, "%s:%.2f", class_names[generate_boxes[i].label], generate_boxes[i].score);
- cv::putText(frame, label, cv::Point(r.x, r.y - 4), cv::FONT_HERSHEY_PLAIN, 1.2, cv::Scalar(128, 128, 255), 2);
- }
- }
复制代码 然后再创建一个主步伐,叫main.cpp用来调用这些步伐,代码如下:
- #include <iostream>
- #include <chrono>
- #include <cmath>
- #include <assert.h>
- #include "Segment.h"
- #include "camera.h"
- #include <stdio.h>
- using namespace std;
- int main(int argc, char** argv) {
- Net_config YOLOV8_nets = { 0.9, 0.5,
- "D:/Project/inference/Orbbec_example/model/kuaidi.onnx" ,
- "D:/Project/inference/Orbbec_example/model/kuaidi.names" };
- YOLOV8 net(YOLOV8_nets);
- Orbbec camera;
- camera.Init();
- while (1){
- cv::Mat src, dep;
- if (!camera.GetRgbAndDepth(src, dep)) {
- continue;
- }
- cv::Mat show = src.clone();
- // Run inference
- auto start = std::chrono::system_clock::now();
- std::vector<BoxInfo> res;
- std::vector<cv::Mat> masks;
- net.detect(src, res, masks);
- auto end = std::chrono::system_clock::now();
- std::cout << "inference time: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
- if (res.size()==0){printf("no target detect\n");}
- cv::imshow("reult", src);
- char c = cv::waitKey(1);
- for (int i = 0; i < res.size(); i++){
- int box[4] = { (int)res[i].x, (int)res[i].y,(int)(res[i].x + res[i].w),(int)(res[i].y + res[i].h) };
- cv::Mat mask = masks[i].clone();
- float result[10] = { 0 };
- camera.Postprocess(box, mask, src, result);
- cv::Point p1((int)(0.5 * box[0] + 0.5 * box[2]), (int)(0.5 * box[1] + 0.5 * box[3]));
- cv::circle(show, p1, 3, cv::Scalar(0, 255, 255), 3);//center point
- cv::Point p2((int)result[6], (int)result[7]);
- cv::circle(show, p2, 3, cv::Scalar(0, 0, 255), 3);//pick point
- cv::Rect rdraw0 = cv::Rect((int)box[0], (int)box[1], (int)(box[2] - box[0]), (int)(box[3] - box[1]));
- cv::rectangle(show, rdraw0, cv::Scalar(0, 255, 255), 2);//detect box
- cv::Rect rdraw1 = cv::Rect((int)(result[6] - 0.5 * result[8]), (int)(result[7] - 0.5 * result[9]),
- (int)result[8], (int)result[9]);
- cv::rectangle(show, rdraw1, cv::Scalar(0, 0, 255), 2);//postprocess box
- }
- printf("-------------------------------------------------\n");
- cv::imshow("show", show);
- c = cv::waitKey(1);
- if (c == 27) {
- cv::destroyAllWindows();
- break;
- }
-
- }
-
- camera.close();
- printf("exit prgram\n");
- return 0;
- }
复制代码 改写以下CMakeLists.txt,使其可以同时利用1.2与这一章节,如下:
- cmake_minimum_required(VERSION 3.10)
- project(orbbec)
- add_definitions(-std=c++11)
- add_definitions(-DAPI_EXPORTS)
- set(CMAKE_CXX_STANDARD 11)
- set(CMAKE_BUILD_TYPE Release)
- set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
- include_directories(include/)
- link_directories(lib/) #添加特定的库文件搜索路径 相当于g++的 -L参数
- include_directories(${PROJECT_SOURCE_DIR}/src/)
- file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp)
- #opencv
- Set(OpenCV_DIR D:/Project/opencv/build/)
- find_package(OpenCV)
- include_directories(${OpenCV_INCLUDE_DIRS})
- include_directories(${OpenCV_DIR}/include/)
- link_directories(${OpenCV_DIR}/x64/vc15/lib/)
- #orbbec
- set(OrbbecSDK_ROOT_DIR D:/Project/OrbbecSDK/SDK)
- set(OrbbecSDK_LIBRARY_DIRS ${OrbbecSDK_ROOT_DIR}/lib)
- set(OrbbecSDK_INCLUDE_DIR ${OrbbecSDK_ROOT_DIR}/include)
- include_directories(${OrbbecSDK_INCLUDE_DIR})
- link_directories(${OrbbecSDK_LIBRARY_DIRS})
- #open3d
- Set(Open3D_DIR D:/Project/open3d-0.18.0/CMake/)
- find_package( Open3D REQUIRED)
- include_directories(${Open3D_INCLUDE_DIRS})
- link_directories(${Open3D_LIBRARY_DIRS})
- #onnxruntinme
- Set(ONNXRUNTIME_ROOT_DIR D:/Project/onnxruntime-win-x64-gpu-1.14.0/)
- include_directories(${ONNXRUNTIME_ROOT_DIR}/include/)
- link_directories(${ONNXRUNTIME_ROOT_DIR}/lib/)
- add_executable(test main1_test_camera.cpp ${SRCS})
- target_link_libraries(test OrbbecSDK ${OpenCV_LIBS})
- target_link_libraries(test ${Open3D_LIBRARIES})
- target_include_directories(test PUBLIC ${Open3D_INCLUDE_DIRS})
- target_link_libraries(test onnxruntime)
- add_executable(main main.cpp ${SRCS})
- target_link_libraries(main OrbbecSDK ${OpenCV_LIBS})
- target_link_libraries(main ${Open3D_LIBRARIES})
- target_include_directories(main PUBLIC ${Open3D_INCLUDE_DIRS})
- target_link_libraries(main onnxruntime)
复制代码 仍然基于cmake-gui与vs,天生对应的exe文件,运行exe的时间记得将下面几个dll拷贝到同一个目次下,否则会报错。

别的将camra.cpp中的ProcessPointcloud下面的改成1,就可以可视化抓取点与姿态。


3 结语
实测奥比中光的相机彩色图很暗,调了好久曝光参数啥的也没realsense的彩色图看起来真实。大概跟我买的相机型号有关。
末了说一下,这个博客的内容恰当有一点c++底子的食用,至少要会用cmake、vs嘛。
|