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)