【问题标题】:How to get contour of metallic, shiny objects using OpenCV如何使用 OpenCV 获得金属光泽物体的轮廓
【发布时间】:2020-02-13 09:22:52
【问题描述】:

我正在尝试寻找金属光泽物体的轮廓,如下图所示:

我使用 OpenCV 中的 Canny 来获取图像的轮廓;但是,结果(下图)确实绘制了原始图像的完整轮廓。它在右下角有一个很大的中断。

我恳请任何类型的资源来帮助我完善我的轮廓,使其连续并且(非常接近)与原始图像的形状相似。

【问题讨论】:

    标签: python image opencv image-processing computer-vision


    【解决方案1】:

    在 Python/OpenCV 中,您可以通过以下方式实现:

    • 读取输入
    • 转换为 HSV 颜色空间并提取饱和度通道(因为灰色没有饱和度,而绿色有)
    • 模糊图像以减少噪点
    • 阈值
    • 应用形态接近于填充闪亮对象的内部孔
    • 找到轮廓并过滤最大的(尽管应该只有一个)
    • 在输入图像上绘制轮廓
    • 保存结果

    输入:

    import cv2
    import numpy as np
    
    # read input
    img = cv2.imread('shiny.jpg')
    
    # convert to hsv and get saturation channel
    sat = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)[:,:,1]
    
    # do a little Gaussian filtering
    blur = cv2.GaussianBlur(sat, (3,3), 0)
    
    
    # threshold and invert to create initial mask
    mask = 255 - cv2.threshold(blur, 100, 255, cv2.THRESH_BINARY)[1]
    
    # apply morphology close to fill interior regions in mask
    kernel = np.ones((15,15), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
    
    
    # get outer contours from inverted mask and get the largest (presumably only one due to morphology filtering)
    cntrs = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cntrs = cntrs[0] if len(cntrs) == 2 else cntrs[1]
    result = img.copy()
    area_thresh = 0
    for c in cntrs:
        area = cv2.contourArea(c)
        if area > area_thresh:
            area = area_thresh
            big_contour = c
    
    # draw largest contour
    cv2.drawContours(result, [big_contour], -1, (0,0,255), 2)
    
    
    # write result to disk
    cv2.imwrite("shiny_mask.png", mask)
    cv2.imwrite("shiny_outline.png", result)
    
    # display it
    cv2.imshow("IMAGE", img)
    cv2.imshow("MASK", mask)
    cv2.imshow("RESULT", result)
    cv2.waitKey(0)
    


    阈值和过滤掩码:

    结果:

    另一种方法是在绿色上使用 cv2.inRange() 设置阈值。

    【讨论】:

      【解决方案2】:

      一个简单的方法是应用一个大的Gaussian blur 平滑图像然后adaptive threshold。假设对象是图像中最大的东西,我们可以找到轮廓,然后使用轮廓区域过滤对最大的轮廓进行排序。

      二值图像

      结果

      代码

      import cv2
      import numpy as np
      
      # Load image, convert to grayscale, Gaussian Blur, adaptive threshold
      image = cv2.imread('1.jpg')
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      blur = cv2.GaussianBlur(gray, (13,13), 0)
      thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,7)
      
      # Morph close
      kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
      close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)
      
      # Find contours, sort for largest contour, draw contour
      cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
      cnts = cnts[0] if len(cnts) == 2 else cnts[1]
      cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
      for c in cnts:
          cv2.drawContours(image, [c], -1, (36,255,12), 2)
          break
      
      cv2.imshow('thresh', thresh)
      cv2.imshow('image', image)
      cv2.waitKey()
      

      【讨论】:

      • 嗨@nathancy。代码输出与您在此处提供的输出不同。有什么区别?
      • 我正在使用您提供的输入图像和完全相同的代码。我得到了相同的正确输出。我正在使用python 3.7.4numpy==1.14.5、Windows 10、opencv-python==4.2.0.32
      【解决方案3】:

      这是另一种可能的解决方案,用 C++ 实现并使用 k-means 作为主要分割方法。这种分割背后的想法是 k-means(一种聚类方法)将对相似值的颜色进行分组。在这里,我设置 k-means 来查找 2 种颜色的簇:背景色和前景色。

      我们看一下代码:

      std::string imageName = "C://opencvImages/LSl42.jpg";
      cv::Mat testImage =  cv::imread( imageName );
      //apply Gaussian Blur to smooth out the input:
      cv::GaussianBlur( testImage, testImage, cv::Size(3,3), 0, 0 );
      

      您的图像具有嘈杂(高频)的背景。您可以稍微模糊一下以获得更平滑的渐变并改善分割。我应用了 Gaussian Blur,标准内核大小为 3 x 3。查看输入图像和平滑图像之间的差异:

      非常酷。现在,我可以将此图像传递给 K-means。 imageQuantization 是取自 here 的函数,它实现了基于 K-means 的分割。正如我所提到的,它可以将具有相似值的颜色分组到集群中。这非常方便!让我们将颜色分为两组:foreground 对象和 background

      int segmentationClusters = 2; //total number of clusters in which the input will be segmented...
      int iterations = 5; // k-means iterations
      cv::Mat segmentedImage = imageQuantization( testImage, segmentationClusters, iterations );
      

      结果:

      很不错,嗯?

      您可以直接在此图像上应用边缘检测,但我想使用一点形态学来改进它。我首先将图像转换为灰度,应用 Outsu 的阈值,然后执行形态闭合:

      //compute grayscale image of the segmented output:
      cv::Mat grayImage;
      cv::cvtColor( segmentedImage, grayImage, cv::COLOR_RGB2GRAY );
      
      //get binary image via Otsu:
      cv::Mat binImage;
      cv::threshold( grayImage, binImage, 0, 255, cv::THRESH_OTSU );
      
      //Perform a morphological closing to lose up holes in the target blob:
      cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );
      cv::morphologyEx( binImage, binImage, cv::MORPH_CLOSE, SE, cv::Point(-1,-1), 10 );
      

      我使用大小为 3x3 的矩形结构元素和 10 次关闭操作迭代,结果如下:

      接下来,使用 Canny 的边缘检测器检测边缘:

      cv::Mat testEdges;
      //setup lower and upper thresholds for Canny’s edge detection:
      float lowerThreshold = 30;
      float upperThreshold = 3 * lowerThreshold;
      cv::Canny( binImage, testEdges, lowerThreshold, upperThreshold );
      

      最后,得到blob的轮廓:

      std::vector<std::vector<cv::Point> > contours;
      std::vector<cv::Vec4i> hierarchy;
      
      cv::findContours( testEdges, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );
      
      for( int i = 0; i< contours.size(); i++ )
      {
       cv::Scalar color = cv::Scalar( 0,255,0 );
       cv::drawContours( resizedImage, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
      }
      

      这是我得到的最终结果:

      想通过扩展轮廓来改善结果吗?在将二值图像传递给 Canny 的边缘检测之前,尝试对二值图像进行几次迭代扩张。这是一个测试,将图像放大 5 倍:

      【讨论】:

      • 当你说“扩张”时,这是否意味着morphologyEx 方法的迭代?
      • @jsibs 是的。该方法具有参数“iterations”,指示将应用操作的次数。对于最后一张图片,我将迭代次数设置为 5。
      猜你喜欢
      • 2016-05-28
      • 1970-01-01
      • 2013-08-23
      • 2022-01-10
      • 2015-08-25
      • 1970-01-01
      • 2016-10-24
      • 2019-08-17
      • 2021-09-07
      相关资源
      最近更新 更多