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

opencv实战项目(三十三)奥比中光深度相机检测箱盖是否盖严

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、项目介绍与硬件选择
  • 二、算法流程
  • 三、代码实现
    • 3.1料箱区域分割
    • 3.2 料箱区域处理并获得料箱轮廓
    • 3.3 对轮廓进行处理获取四个角点并对角点排序
    • 3.4 优化角点让其贴合箱盖
    • 3.5 截取四个角点处的图像
    • 3.6 计算箱盖四个角的高度值
  • 四 效果:


前言

在现代化的生产与包装流程中,确保产品质量和包装完整性是至关重要的环节。奥比中光深度相机作为一种先进的视觉检测技术,为自动化生产线带来了革命性的变革。通过实时获取物体的三维信息,深度相机能够对产品进行精确的形状、位置和状态分析。本文旨在探讨如何利用奥比中光深度相机检测箱盖是否盖严,以保障产品质量,提高包装线的自动化水平和生产效率。


一、项目介绍与硬件选择

本次的项目需求为检测箱子四个角点与相机之间的高度差,用来判别箱盖是否盖严,箱子形状如下所示。
请添加图片描述
本次相机选择为奥比中光单目结构光相机Astra 2,Orbbec Astra 2 是奥比中光推出的新一代单目结构光3D相机。作为全新的旗舰级产品,Orbbec Astra 2搭载了全新自研深度引擎芯片MX6600,功能更强大,新的单目结构光3D相机在保留原有FoV、深度测量范围等经典规格参数的同时,全面升级了光学系统,重点提升测量精度和稳定性,可在多种环境温度下提供1m处RMSE < 1.5mm的稳定深度测量性能。

二、算法流程

  1. 读取深度相机深度流:
    启动奥比中光深度相机,并开始捕获深度数据流。
    使用相应的API或函数从相机中获取连续的深度图像帧。
  2. 处理深度信息:
    对获取的深度图像进行预处理,比如去除异常值、平滑处理等,以提高数据质量。
  3. 深度信息映射:
    将深度信息从相机的原始深度范围(通常是毫米级别)映射到一个0-255的灰1.度值区间。这样做的目的是为了方便后续的图像处理操作,因为许多图像处理算法都是基于灰度图像设计的。
  4. 使用深度信息进行分割:
    根据料箱与相机之间的距离,设定一个深度阈值,以区分箱盖和其他背景或物体。
  5. 形态学去噪与链接:
    使用形态学操作,如膨胀和腐蚀,去除二值图像中的噪声和小孔,同时连接相邻的白色区域,确保箱盖区域是连续的。
  6. 找到最大轮廓并计算角点与旋转角:
    在处理后的二值图像中,使用轮廓检测算法找到所有轮廓。
    从这些轮廓中找到面积最大的轮廓,这通常代表箱盖。
    计算这个最大轮廓的四个角点坐标,并估算箱盖的旋转角度。
  7. 优化角点位置:
    对检测到的角点进行优化,可能包括角点拟合、角点平滑处理等,以确保角点更准确地贴合箱盖的实际位置。
  8. 截取角点区域图像:
    以优化后的角点为中心,截取四个角点区域的图像。
    这些区域应该足够大,以包含足够的深度信息。
  9. 选择代表箱盖距离的像素值:
    在每个角点区域中,由于箱盖是离相机最近的物体,选择区域中像素值的最小值作为代表箱盖距离的值。
  10. 像素值映射回原始深度距离:
    将步骤9中选择的像素值从0-255区间映射回原始的深度距离(毫米)这样可以得到每个角点处的实际深度值,从而判断箱盖是否盖严。

三、代码实现

3.1料箱区域分割

     if(depthFrame->format() == OB_FORMAT_Y16) {
            uint32_t  width  = depthFrame->width();
            uint32_t  height = depthFrame->height();
            float     scale  = depthFrame->getValueScale();
            uint16_t *data   = (uint16_t *)depthFrame->data();
            cv::Mat depthMask = cv::Mat::zeros(height, width, CV_8UC1);


            // pixel value multiplied by scale is the actual distance value in millimeters
            for (uint32_t y = 0; y < height; ++y) {
                for (uint32_t x = 0; x < width; ++x) {
                    // 计算当前像素在数组中的索引
                    int index = y * width + x;
                    // 获取当前像素的深度值
                    float distance = data[index] * scale;

                    // 如果深度值小于500且大于800,则将蒙版上的对应位置设置为白色
                    if (distance < 800 && distance > 500) {
                        depthMask.at<uchar>(y, x) = distance-500; // 255代表白色
                    }
                }
            }

3.2 料箱区域处理并获得料箱轮廓

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); // 5x5的矩形结构元素

               // 执行闭运算
           cv::Mat closed;
           cv::morphologyEx(depthMask, closed, cv::MORPH_CLOSE, kernel);

           std::vector<std::vector<cv::Point>> contours;
           cv::findContours(closed, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
           if (contours.empty()) {
               std::cerr << "没有找到轮廓,跳过当前帧" << std::endl;
                continue; // 或者继续读取下一帧,取决于你的应用场景
           }
          // 查找最大轮廓
          int maxArea = 0;
          int maxAreaIndex = 0;
          for (int i = 0; i < contours.size(); i++) {
              double area = cv::contourArea(contours[i]);
              if (area > maxArea) {
                  maxArea = area;
                  maxAreaIndex = i;
              }
          }
          if (maxArea < 200) {
              std::cerr << "最大轮廓面积小于200,跳过当前帧" << std::endl;
              continue;
          }

          // 绘制最大轮廓
          cv::Mat drawing = cv::Mat::zeros(closed.size(), CV_8UC3);
          cv::Scalar color = cv::Scalar(255, 255,255);
          cv::drawContours(drawing, contours, maxAreaIndex, color, -1);

3.3 对轮廓进行处理获取四个角点并对角点排序

 cv::RotatedRect minRect = cv::minAreaRect(contours[maxAreaIndex]);
          cv::Point2f rect_points[4];
          minRect.points(rect_points);



          int min_y_index1 = 0;
          int min_y_index2 = 1;
          if (rect_points[1].y < rect_points[0].y) {
              min_y_index1 = 1;
              min_y_index2 = 0;
          }

          // 查找y坐标最小的两个点
          for (int i = 2; i < 4; ++i) {
              if (rect_points[i].y < rect_points[min_y_index1].y) {
                  min_y_index2 = min_y_index1;
                  min_y_index1 = i;
              } else if (rect_points[i].y < rect_points[min_y_index2].y) {
                  min_y_index2 = i;
              }
          }

              // 在y坐标最小的两个点中选择x坐标最小的点作为左上角点



              // 在y坐标最小的两个点中选择x坐标最小的点作为左上角点
          int left_top_index = (rect_points[min_y_index1].x < rect_points[min_y_index2].x) ? min_y_index1 : min_y_index2;
          int right_top_index = (rect_points[min_y_index1].x > rect_points[min_y_index2].x) ? min_y_index1 : min_y_index2;
          int remaining_indices[2];
          int remaining_index_count = 0;
          for (int i = 0; i < 4; ++i) {
                if (i != left_top_index && i != right_top_index) {
                    remaining_indices[remaining_index_count++] = i;
                }
            }

            // 假设 remaining_indices[0] 是左下角点,remaining_indices[1] 是右下角点
            // 如果你需要根据坐标进一步确认哪个是左下角点,哪个是右下角点,可以这样做:
            int left_bottom_index = (rect_points[remaining_indices[0]].x < rect_points[remaining_indices[1]].x) ? remaining_indices[0] : remaining_indices[1];
            int right_bottom_index = (rect_points[remaining_indices[0]].x < rect_points[remaining_indices[1]].x) ? remaining_indices[1] : remaining_indices[0];

3.4 优化角点让其贴合箱盖

       //new_point[0] // 左上角的点
          //new_point[1] // 右上角的点
          //new_point[2] // 右下角的点
          //new_point[3] // 左下角的点
 cv::Point2f new_point[4];
          new_point[0].x=rect_points[left_top_index].x+20;
          new_point[0].y=rect_points[left_top_index].y+20;
          new_point[1].x=rect_points[right_top_index].x-20;
          new_point[1].y=rect_points[right_top_index].y+20;
          new_point[2].x=rect_points[right_bottom_index].x-20;
          new_point[2].y=rect_points[right_bottom_index].y-20;
          new_point[3].x=rect_points[left_bottom_index].x+20;
          new_point[3].y=rect_points[left_bottom_index].y-20;

3.5 截取四个角点处的图像

在原图中截取小图并防止图片过限:


cv::Mat safeCropROI(const cv::Mat& image, cv::Rect roi) {
    // 检查并调整ROI的起始坐标
    if (roi.x < 0) roi.x = 0;
    if (roi.y < 0) roi.y = 0;

    // 检查并调整ROI的尺寸
    if (roi.width <= 0) roi.width = 1; // 避免宽度为0
    if (roi.height <= 0) roi.height = 1; // 避免高度为0

    // 检查并调整ROI的结束坐标
    if (roi.x + roi.width > image.cols) roi.width = image.cols - roi.x;
    if (roi.y + roi.height > image.rows) roi.height = image.rows - roi.y;

    // 裁剪并返回ROI
    return image(roi);
}


整体代码:

  int width_rio = 50;  // 矩形区域的宽度
          int height_rio = 50; // 矩形区域的高度
         cv::Point2i top_left_0(new_point[0].x - width_rio / 2, new_point[0].y - height_rio / 2);
         cv::Point2i bottom_right_0(new_point[0].x + width_rio / 2, new_point[0].y + height_rio / 2);

        // 确保矩形区域在图像内
         top_left_0.x = std::max(0, top_left_0.x);
         top_left_0.y = std::max(0, top_left_0.y);
         bottom_right_0.x = std::min(depthMask.cols - 1, bottom_right_0.x);
         bottom_right_0.y = std::min(depthMask.rows - 1, bottom_right_0.y);


         cv::Point2i top_left_1(new_point[1].x - width_rio / 2, new_point[1].y - height_rio / 2);
         cv::Point2i bottom_right_1(new_point[1].x + width_rio / 2, new_point[1].y + height_rio / 2);
        // 确保矩形区域在图像内
         top_left_1.x = std::max(0, top_left_1.x);
         top_left_1.y = std::max(0, top_left_1.y);
         bottom_right_1.x = std::min(depthMask.cols - 1, bottom_right_1.x);
         bottom_right_1.y = std::min(depthMask.rows - 1, bottom_right_1.y);


         cv::Point2i top_left_2(new_point[2].x - width_rio / 2, new_point[2].y - height_rio / 2);
         cv::Point2i bottom_right_2(new_point[2].x + width_rio / 2, new_point[2].y + height_rio / 2);
        // 确保矩形区域在图像内
         top_left_2.x = std::max(0, top_left_2.x);
         top_left_2.y = std::max(0, top_left_2.y);
         bottom_right_2.x = std::min(depthMask.cols - 1, bottom_right_2.x);
         bottom_right_2.y = std::min(depthMask.rows - 1, bottom_right_2.y);


         cv::Point2i top_left_3(new_point[3].x - width_rio / 2, new_point[3].y - height_rio / 2);
         cv::Point2i bottom_right_3(new_point[3].x + width_rio / 2, new_point[3].y + height_rio / 2);
        // 确保矩形区域在图像内
         top_left_3.x = std::max(0, top_left_3.x);
         top_left_3.y = std::max(0, top_left_3.y);
         bottom_right_3.x = std::min(depthMask.cols - 1, bottom_right_3.x);
         bottom_right_3.y = std::min(depthMask.rows - 1, bottom_right_3.y);

        // 提取矩形区域的深度图


          cv::Mat region_depth_top_left =  safeCropROI(depthMask,cv::Rect(top_left_0, bottom_right_0));
          cv::Mat region_depth_top_right = safeCropROI(depthMask,cv::Rect(top_left_1, bottom_right_1));
          cv::Mat region_depth_bottom_right = safeCropROI(depthMask,cv::Rect(top_left_2, bottom_right_2));
          cv::Mat region_depth_bottom_left = safeCropROI(depthMask,cv::Rect(top_left_3, bottom_right_3));

3.6 计算箱盖四个角的高度值

选择图像中十一个最小像素值并选择中位数

double CalculateMedianOfSmallest11(cv::Mat& image) {
    // 检查图像是否为空
    if (image.empty()) {
        return -1;
    }

    // 存储非零像素值
    std::vector<uchar> non_zero_pixels;

    // 遍历图像并收集非零像素值
    for (int i = 0; i < image.rows; ++i) {
        for (int j = 0; j < image.cols; ++j) {
            uchar pixel = image.at<uchar>(i, j);
            if (pixel != 0) {
                non_zero_pixels.push_back(pixel);
            }
        }
    }

    // 检查非零像素的数量是否足够
    if (non_zero_pixels.size() < 11) {
        return -1;
    }

    // 对非零像素值进行排序
    std::sort(non_zero_pixels.begin(), non_zero_pixels.end());

    // 选择最小的11个值
    std::vector<uchar> smallest_11(non_zero_pixels.begin(), non_zero_pixels.begin() + 11);

    // 计算中位数
    int mid_index = smallest_11.size() / 2;
    double median_value = smallest_11[mid_index]; // 如果数组大小是奇数,直接取中间的值

    // 如果数组大小是偶数,取中间两个值的平均值
    if (smallest_11.size() % 2 == 0) {
        median_value = (smallest_11[mid_index - 1] + smallest_11[mid_index]) / 2.0;
    }

    return median_value;
}

整体代码:

   double median_value_top_left=CalculateMedianOfSmallest11(region_depth_top_left);
          double median_value_top_right=CalculateMedianOfSmallest11(region_depth_top_right);
          double median_value_bottom_left=CalculateMedianOfSmallest11(region_depth_bottom_right);
          double median_value_bottom_right=CalculateMedianOfSmallest11(region_depth_bottom_left);

          std::cout << "median_value_top" <<"   "<< median_value_top_left+500<<"   "<< median_value_top_right+500<<"   "<< median_value_bottom_right+500<<"   "<<median_value_bottom_left+500<<std::endl;

计算出四个点深度后就可以根据给定的逻辑进行判断,不仅可以判断箱盖是否盖严还可以哦

四 效果:

左上为500mm到800mm的深度图右上为轮廓图左下为检测结果
请添加图片描述


http://www.kler.cn/news/364960.html

相关文章:

  • 基于Multisim红外接近报警电路设计(含仿真和报告)
  • Java.6--多态-设计模式-抽象父类-抽象方法
  • H7-TOOL的LUA小程序教程第15期:电压,电流,NTC热敏电阻以及4-20mA输入(2024-10-21,已经发布)
  • 反向代理服务器---NGINX
  • STM32之OLED驱动函数
  • 实践OpenVINO™ GenAI
  • 大数据-188 Elasticsearch - ELK 家族 Logstash Output 插件
  • LinkedList作者:我虽然开发了LinkedList,但是我更爱用ArrayList
  • 【Conda】Conda 超时设置及优化指南:提升包管理效率的关键
  • 【C#】调用本机AI大模型流式返回
  • 十八、行为型(状态模式)
  • Notepad++通过自定义语言实现日志按照不同级别高亮
  • AI写PPT工具:四款人工智能软件全面解析!!
  • 5G超级上行,到底有多行?
  • Spring Boot在线考试系统:JavaWeb技术的最佳实践
  • vue文件报Cannot find module ‘webpack/lib/RuleSet‘错误处理
  • 3.cpp基本数据类型
  • 设计模式(二)工厂模式详解
  • 数据结构_day3
  • 在Spring中,什么是配置类
  • 【C语言】自定义类型:结构体(下)
  • 《首尔破笑组:在欢笑中触摸生活的温度》
  • 给已经写好的裸机程序移植freeRTOS操作系统(二)
  • 6.Three.js贴图与uv映射(uv坐标)理解和实践
  • 鸿蒙应用示例:仿钉钉日历新建日程
  • C语言中的分支与循环(中 1)