8

我试图检测并精确定位轮廓图像中的某些对象。我得到的轮廓经常包括一些噪音(可能是背景,我不知道)。这些对象应类似于长方形或正方形状:如何裁剪凸面缺陷?

enter image description here

我与形状匹配(cv::matchShapes)来检测在他们的对象,有和无噪音的轮廓非常不错的成绩,但我有问题在噪音情况下的精确位置。

噪声的样子:

enter image description hereenter image description here例如。

我的想法是找到凹凸缺陷,如果它们变得太强,以某种方式将会导致凹陷的部分割除。检测缺陷是可以的,通常我会在每个“不需要的结构”上得到两个缺陷,但我一直在坚持如何确定我应该从轮廓中删除点的位置和位置。

这里有一些轮廓,其掩码(从而可以提取的轮廓容易)和凸包包括阈值化凸缺陷:

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

enter image description hereenter image description hereenter image description here

难道我步行穿过轮廓和局部决定是否“左转”是由轮廓(如果走顺时针方向)进行,如果是这样,直到下一个左转采取删除轮廓点?也许从一个凸面缺陷开始?

我正在寻找算法或代码,编程语言应该不重要,算法更重要。

+0

你看着'convexityDefects'? http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#convexitydefects – zeFrenchy

+0

@zeFrenchy是的,凸包图像中的红点来自阈值凸起缺陷的结果。我无法想象如何从那里继续的算法。 – Micka

+1

得到你,从来没有使用过它,但我只是把它放在那里,以防万一:) – zeFrenchy

回答

9

此方法仅适用于点。你不需要为此创建掩码。

的主要思想是:

  1. 查找轮廓
  2. 缺陷如果我发现至少有两个缺陷,找到最接近的两个缺陷
  3. 从轮廓上取下两个最接近的缺陷
  4. 之间的分
  5. 从新轮廓上的1重新启动

我得到以下结果。正如你所看到的,它有一些缺陷(例如第7张图像),但对于清晰可见的缺陷非常有效。我不知道这是否能解决您的问题,但可以作为一个起点。在实践中应该是相当快的(你可以肯定优化下面的代码,特别是removeFromContour函数)。此外,这种方法的唯一参数是凸面缺陷的数量,所以它适用于小的和大的缺陷斑点。

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here

#include <opencv2/opencv.hpp> 
using namespace cv; 
using namespace std; 

int ed2(const Point& lhs, const Point& rhs) 
{ 
    return (lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y); 
} 

vector<Point> removeFromContour(const vector<Point>& contour, const vector<int>& defectsIdx) 
{ 
    int minDist = INT_MAX; 
    int startIdx; 
    int endIdx; 

    // Find nearest defects 
    for (int i = 0; i < defectsIdx.size(); ++i) 
    { 
     for (int j = i + 1; j < defectsIdx.size(); ++j) 
     { 
      float dist = ed2(contour[defectsIdx[i]], contour[defectsIdx[j]]); 
      if (minDist > dist) 
      { 
       minDist = dist; 
       startIdx = defectsIdx[i]; 
       endIdx = defectsIdx[j]; 
      } 
     } 
    } 

    // Check if intervals are swapped 
    if (startIdx <= endIdx) 
    { 
     int len1 = endIdx - startIdx; 
     int len2 = contour.size() - endIdx + startIdx; 
     if (len2 < len1) 
     { 
      swap(startIdx, endIdx); 
     } 
    } 
    else 
    { 
     int len1 = startIdx - endIdx; 
     int len2 = contour.size() - startIdx + endIdx; 
     if (len1 < len2) 
     { 
      swap(startIdx, endIdx); 
     } 
    } 

    // Remove unwanted points 
    vector<Point> out; 
    if (startIdx <= endIdx) 
    { 
     out.insert(out.end(), contour.begin(), contour.begin() + startIdx); 
     out.insert(out.end(), contour.begin() + endIdx, contour.end()); 
    } 
    else 
    { 
     out.insert(out.end(), contour.begin() + endIdx, contour.begin() + startIdx); 
    } 

    return out; 
} 

int main() 
{ 
    Mat1b img = imread("path_to_mask", IMREAD_GRAYSCALE); 

    Mat3b out; 
    cvtColor(img, out, COLOR_GRAY2BGR); 

    vector<vector<Point>> contours; 
    findContours(img.clone(), contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); 

    vector<Point> pts = contours[0]; 

    vector<int> hullIdx; 
    convexHull(pts, hullIdx, false); 

    vector<Vec4i> defects; 
    convexityDefects(pts, hullIdx, defects); 

    while (true) 
    { 
     // For debug 
     Mat3b dbg; 
     cvtColor(img, dbg, COLOR_GRAY2BGR); 

     vector<vector<Point>> tmp = {pts}; 
     drawContours(dbg, tmp, 0, Scalar(255, 127, 0)); 

     vector<int> defectsIdx; 
     for (const Vec4i& v : defects) 
     { 
      float depth = float(v[3])/256.f; 
      if (depth > 2) // filter defects by depth 
      { 
       // Defect found 
       defectsIdx.push_back(v[2]); 

       int startidx = v[0]; Point ptStart(pts[startidx]); 
       int endidx = v[1]; Point ptEnd(pts[endidx]); 
       int faridx = v[2]; Point ptFar(pts[faridx]); 

       line(dbg, ptStart, ptEnd, Scalar(255, 0, 0), 1); 
       line(dbg, ptStart, ptFar, Scalar(0, 255, 0), 1); 
       line(dbg, ptEnd, ptFar, Scalar(0, 0, 255), 1); 
       circle(dbg, ptFar, 4, Scalar(127, 127, 255), 2); 
      } 
     } 

     if (defectsIdx.size() < 2) 
     { 
      break; 
     } 

     // If I have more than two defects, remove the points between the two nearest defects 
     pts = removeFromContour(pts, defectsIdx); 
     convexHull(pts, hullIdx, false); 
     convexityDefects(pts, hullIdx, defects); 
    } 


    // Draw result contour 
    vector<vector<Point>> tmp = { pts }; 
    drawContours(out, tmp, 0, Scalar(0, 0, 255), 1); 

    imshow("Result", out); 
    waitKey(); 

    return 0; 
} 

UPDATE

使用近似轮廓(例如,使用CHAIN_APPROX_SIMPLE,findContours)可能会更快,但轮廓长度必须使用arcLength()来计算。

这是更换交换的removeFromContour部分片段:

// Check if intervals are swapped 
if (startIdx <= endIdx) 
{ 
    //int len11 = endIdx - startIdx; 
    vector<Point> inside(contour.begin() + startIdx, contour.begin() + endIdx); 
    int len1 = (inside.empty()) ? 0 : arcLength(inside, false); 

    //int len22 = contour.size() - endIdx + startIdx; 
    vector<Point> outside1(contour.begin(), contour.begin() + startIdx); 
    vector<Point> outside2(contour.begin() + endIdx, contour.end()); 
    int len2 = (outside1.empty() ? 0 : arcLength(outside1, false)) + (outside2.empty() ? 0 : arcLength(outside2, false)); 

    if (len2 < len1) 
    { 
     swap(startIdx, endIdx); 
    } 
} 
else 
{ 
    //int len1 = startIdx - endIdx; 
    vector<Point> inside(contour.begin() + endIdx, contour.begin() + startIdx); 
    int len1 = (inside.empty()) ? 0 : arcLength(inside, false); 


    //int len2 = contour.size() - startIdx + endIdx; 
    vector<Point> outside1(contour.begin(), contour.begin() + endIdx); 
    vector<Point> outside2(contour.begin() + startIdx, contour.end()); 
    int len2 = (outside1.empty() ? 0 : arcLength(outside1, false)) + (outside2.empty() ? 0 : arcLength(outside2, false)); 

    if (len1 < len2) 
    { 
     swap(startIdx, endIdx); 
    } 
} 
+0

谢谢,我会试试看。 – Micka

+1

@Micka可能通过上述代码的更明智的实现,使用近似轮廓(类似CHAIN_APPROX_SIMPLE),这实际上可能非常快。如果您发现某些功能符合您的要求,可以发布一个答案,它可能非常有用:D – Miki

+0

目前决定是否进行交换是由轮廓内的索引距离决定的?这可能是为什么'CV_CHAIN_APPROX_SIMPLE'有时会将错误的部分裁剪掉(错误的方向)?可能arcLength是一个合适的启发式代替吗? – Micka

1

作为一个起点,假设相对于您尝试识别的对象来说缺陷永远不会太大,您可以在使用cv::matchShapes之前尝试一个简单的erode +扩张策略,如下所示。

int max = 40; // depending on expected object and defect size 
cv::Mat img = cv::imread("example.png"); 
cv::Mat eroded, dilated; 
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(max*2,max*2), cv::Point(max,max)); 
cv::erode(img, eroded, element); 
cv::dilate(eroded, dilated, element); 
cv::imshow("original", img); 
cv::imshow("eroded", eroded); 
cv::imshow("dilated", dilated); 

enter image description here

+0

问题是,对象大小可能会有所不同,所以我不能修复'max'。你是否有任何关于如何选择'max'的假设取决于一些可提取的轮廓的属性,如boundind矩形,轮廓区域或类似的? – Micka

+0

您可以不使用当前正在测试的blob最大维度的百分比吗?只是一个想法。 – zeFrenchy

+0

你也可以尝试增加侵蚀/扩张的量,直到你找到你正在寻找的东西或没有任何东西被侵蚀。 – zeFrenchy

2

我想出了下述方法,用于检测该矩形的边界/平方。它的工作原理基于一些假设:形状是矩形或正方形,它在图像中居中,不倾斜。

  • 鸿沟掩蔽(填充的)在沿着x轴的一半图像,这样得到的两个区域(上半部和下半部)
  • 采取的每个区域的投影上与x轴
  • 采取这些预测的所有非零条目,并采取他们的中位数。这些中值给出了y边界
  • 类似地,将图像沿y轴分成两半,将投影置于y轴上,然后计算中值以获得x边界
  • 使用边界裁剪区域

中线和样本图像上半部分的投影如下所示。 proj-n-med-line

结果限值和裁剪区域为两个样品: s1 s2

的代码在八度/ Matlab的,并且我测试此上八度(你需要的图像包来运行此)。

clear all 
close all 

im = double(imread('kTouF.png')); 
[r, c] = size(im); 
% top half 
p = sum(im(1:int32(end/2), :), 1); 
y1 = -median(p(find(p > 0))) + int32(r/2); 
% bottom half 
p = sum(im(int32(end/2):end, :), 1); 
y2 = median(p(find(p > 0))) + int32(r/2); 
% left half 
p = sum(im(:, 1:int32(end/2)), 2); 
x1 = -median(p(find(p > 0))) + int32(c/2); 
% right half 
p = sum(im(:, int32(end/2):end), 2); 
x2 = median(p(find(p > 0))) + int32(c/2); 

% crop the image using the bounds 
rect = [x1 y1 x2-x1 y2-y1]; 
cr = imcrop(im, rect); 
im2 = zeros(size(im)); 
im2(y1:y2, x1:x2) = cr; 

figure, 
axis equal 
subplot(1, 2, 1) 
imagesc(im) 
hold on 
plot([x1 x2 x2 x1 x1], [y1 y1 y2 y2 y1], 'g-') 
hold off 
subplot(1, 2, 2) 
imagesc(im2)