如何使用OpenCV在深度图像中查找任意变换的矩形?

时间:2014-03-17 00:05:55

标签: c++ opencv feature-detection oculus

我正在尝试使用深度传感器为Oculus Rift开发套件添加位置跟踪。但是,我在生成可用结果的操作序列方面遇到了麻烦。

我从16位深度图像开始,其中值(但不是真的)对应于毫米。图像中未定义的值已设置为0。

首先,我通过更新遮罩图像来消除某些近距离和远距离以外的所有东西。

  cv::Mat result = cv::Mat::zeros(depthImage.size(), CV_8UC3);
  cv::Mat depthMask;
  depthImage.convertTo(depthMask, CV_8U);
  for_each_pixel<DepthImagePixel, uint8_t>(depthImage, depthMask, 
    [&](DepthImagePixel & depthPixel, uint8_t & maskPixel){
      if (!maskPixel) {
        return;
      }
      static const uint16_t depthMax = 1200;
      static const uint16_t depthMin = 200;
      if (depthPixel < depthMin || depthPixel > depthMax) {
        maskPixel = 0;
      }
  });

接下来,由于我想要的功能可能比整个场景平均值更接近相机,我再次更新掩码以排除任何不在中值的特定范围内的内容:

  const float depthAverage = cv::mean(depthImage, depthMask)[0];
  const uint16_t depthMax = depthAverage * 1.0;
  const uint16_t depthMin = depthAverage * 0.75;
  for_each_pixel<DepthImagePixel, uint8_t>(depthImage, depthMask, 
    [&](DepthImagePixel & depthPixel, uint8_t & maskPixel){
      if (!maskPixel) {
        return;
      }
      if (depthPixel < depthMin || depthPixel > depthMax) {
        maskPixel = 0;
      }
  });

最后,我将所有不在掩码中的内容归零,并将剩余值缩放到10和10之间。将图像格式转换为8位

之前的255
  cv::Mat outsideMask;
  cv::bitwise_not(depthMask, outsideMask);
  // Zero out outside the mask
  cv::subtract(depthImage, depthImage, depthImage, outsideMask);
  // Within the mask, normalize to the range + X
  cv::subtract(depthImage, depthMin, depthImage, depthMask);
  double minVal, maxVal;
  minMaxLoc(depthImage, &minVal, &maxVal);
  float range = depthMax - depthMin;
  float scale = (((float)(UINT8_MAX - 10) / range));
  depthImage *= scale;
  cv::add(depthImage, 10, depthImage, depthMask);
  depthImage.convertTo(depthImage, CV_8U);

结果如下:

Source image

我对代码的这一部分感到非常满意,因为它产生了非常清晰的视觉特征。

然后我应用了几个平滑操作来消除深度相机中可怕的噪音:

cv::medianBlur(depthImage, depthImage, 9);
cv::Mat blurred;
cv::bilateralFilter(depthImage, blurred, 5, 250, 250);
depthImage = blurred;
cv::Mat result = cv::Mat::zeros(depthImage.size(), CV_8UC3);
cv::insertChannel(depthImage, result, 0);

同样,这些功能在视觉上看起来非常清晰,但我想知道它们是否能够以某种方式被削尖:

enter image description here

接下来我使用canny进行边缘检测:

  cv::Mat canny_output;
  {
    cv::Canny(depthImage, canny_output, 20, 80, 3, true);
    cv::insertChannel(canny_output, result, 1);
  }

我正在寻找的线路在那里,但没有很好地代表角落:

enter image description here

最后我使用概率Hough来识别线条:

  std::vector<cv::Vec4i> lines;
  cv::HoughLinesP(canny_output, lines, pixelRes, degreeRes * CV_PI / 180, hughThreshold, hughMinLength, hughMaxGap);
  for (size_t i = 0; i < lines.size(); i++)
  {
    cv::Vec4i l = lines[i];
    glm::vec2 a((l[0], l[1]));
    glm::vec2 b((l[2], l[3]));
    float length = glm::length(a - b);
    cv::line(result, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(0, 0, 255), 3, CV_AA);
  }

这会生成此图像

enter image description here

此时我觉得自己已经脱轨了,因为我无法为Hough找到一套好的参数来产生合理数量的候选线来搜索我的形状,而我是不确定我是否应该摆弄Hough或者考虑改进前面步骤的输出。

是否有一种很好的方法可以在每个阶段客观地验证我的结果,而不是只是摆弄输入值,直到我觉得它“看起来不错”?给出起始图像是否有更好的方法来找到矩形(并且假设它不一定朝向特定方向?

1 个答案:

答案 0 :(得分:2)

很酷的项目!

尽管如此,我觉得你的方法并没有使用你从深度图中获得的所有信息(例如3D点,法线等),这会有很大的帮助。

Point Cloud Library(PCL)是一个专门处理RGB-D数据的C ++库,使用RANSAC进行tutorial平面分割,可以激发您的灵感。由于存在大量依赖关系,您可能不希望在程序中使用PCL,但由于它是开源的,您可以在Github(PCL SAC segmentation)上找到算法实现。但是,根据场景的不同,RANSAC可能会很慢并产生不需要的结果。

您还可以尝试使用“实时平面分割”中提供的方法 使用RGB-D相机“by Holz,Holzer,Rusu和Behnke,2011(PDF),它建议使用积分图像进行快速正态估计,然后使用法线聚类进行平面检测。