13

我正在尝试从轮廓中检测并精确定位图像中的某些对象。我得到的轮廓经常包含一些噪音(可能形成背景,我不知道)。对象应类似于矩形或正方形,例如:

在此处输入图像描述

我通过形状匹配cv::matchShapes

噪音看起来像:

在此处输入图像描述在此处输入图像描述例如。

我的想法是找到凸面缺陷,如果它们变得太强,以某种方式剪掉导致凹面的部分。检测缺陷是可以的,通常每个“不需要的结构”都会有两个缺陷,但我被困在如何决定应该从轮廓中删除哪些点以及在哪里删除点。

以下是一些轮廓、它们的掩码(因此您可以轻松提取轮廓)和凸包,包括阈值凸度缺陷:

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

在此处输入图像描述在此处输入图像描述在此处输入图像描述

我可以只穿过轮廓并在本地决定轮廓是否执行“左转”(如果顺时针行走),如果是这样,删除轮廓点直到下一次左转?也许从凸面缺陷开始?

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

4

4 回答 4

14

这种方法只适用于点。您无需为此创建蒙版。

主要思想是:

  1. 在轮廓上查找缺陷
  2. 如果我发现至少两个缺陷,找到两个最接近的缺陷
  3. 从轮廓中删除两个最近缺陷之间的点
  4. 在新轮廓上从 1 重新开始

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

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

#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;
}

更新

在近似轮廓上工作(例如使用CHAIN_APPROX_SIMPLEin 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);
    }
}
于 2016-02-07T09:59:39.043 回答
2

我想出了以下方法来检测矩形/正方形的边界。它的工作基于几个假设:形状是矩形或正方形,它在图像中居中,它不倾斜。

  • 蒙版(填充)图像沿 x 轴分成两半,以便获得两个区域(上半部分和下半部分)
  • 将每个区域在 x 轴上的投影
  • 取这些预测的所有非零条目并取它们的中位数。这些中位数为您提供 y 界限
  • 类似地,沿 y 轴将图像分成两半,在 y 轴上进行投影,然后计算中值以获得 x 边界
  • 使用边界裁剪区域

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

两个样本的结果边界和裁剪区域: s1 s2

代码在 Octave/Matlab 中,我在 Octave 上进行了测试(您需要图像包来运行它)。

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)
于 2016-02-06T07:26:56.310 回答
1

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

 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);

在此处输入图像描述

于 2016-02-05T15:43:06.523 回答
1

这是一个遵循 Miki 代码的 Python 实现。

import numpy as np
import cv2

def ed2(lhs, rhs):
    return(lhs[0] - rhs[0])*(lhs[0] - rhs[0]) + (lhs[1] - rhs[1])*(lhs[1] - rhs[1])


def remove_from_contour(contour, defectsIdx, tmp):
    minDist = sys.maxsize
    startIdx, endIdx = 0, 0

    for i in range(0,len(defectsIdx)):
        for j in range(i+1, len(defectsIdx)):
            dist = ed2(contour[defectsIdx[i]][0], contour[defectsIdx[j]][0])
            if minDist > dist:
                minDist = dist
                startIdx = defectsIdx[i]
                endIdx = defectsIdx[j]

    if startIdx <= endIdx:
        inside = contour[startIdx:endIdx]
        len1 = 0 if inside.size == 0 else cv2.arcLength(inside, False)
        outside1 = contour[0:startIdx]
        outside2 = contour[endIdx:len(contour)]
        len2 = (0 if outside1.size == 0 else cv2.arcLength(outside1, False)) + (0 if outside2.size == 0 else cv2.arcLength(outside2, False))
        if len2 < len1:
            startIdx,endIdx = endIdx,startIdx     
    else:
        inside = contour[endIdx:startIdx]
        len1 = 0 if inside.size == 0 else cv2.arcLength(inside, False)
        outside1 = contour[0:endIdx]
        outside2 = contour[startIdx:len(contour)]
        len2 = (0 if outside1.size == 0 else cv2.arcLength(outside1, False)) + (0 if outside2.size == 0 else cv2.arcLength(outside2, False))
        if len1 < len2:
            startIdx,endIdx = endIdx,startIdx

    if startIdx <= endIdx:
        out = np.concatenate((contour[0:startIdx], contour[endIdx:len(contour)]), axis=0)
    else:
        out = contour[endIdx:startIdx]
    return out


def remove_defects(mask, debug=False):
    tmp = mask.copy()
    mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)

    # get contour
    contours, _ = cv2.findContours(
        mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    assert len(contours) > 0, "No contours found"
    contour = sorted(contours, key=cv2.contourArea)[-1] #largest contour
    if debug:
        init = cv2.drawContours(tmp.copy(), [contour], 0, (255, 0, 255), 1, cv2.LINE_AA)
        figure, ax = plt.subplots(1)
        ax.imshow(init)
        ax.set_title("Initital Contour")

    hull = cv2.convexHull(contour, returnPoints=False)
    defects = cv2.convexityDefects(contour, hull)

    while True:
        defectsIdx = []
        
        for i in range(defects.shape[0]):
            s, e, f, d = defects[i, 0]
            start = tuple(contour[s][0])
            end = tuple(contour[e][0])
            far = tuple(contour[f][0])
            
            depth = d / 256
            if depth > 2:
                defectsIdx.append(f)

        if len(defectsIdx) < 2:
            break

        contour = remove_from_contour(contour, defectsIdx, tmp)
        hull = cv2.convexHull(contour, returnPoints=False)
        defects = cv2.convexityDefects(contour, hull)

    if debug:
      rslt = cv2.drawContours(tmp.copy(), [contour], 0, (0, 255, 255), 1)
      figure, ax = plt.subplots(1)
      ax.imshow(rslt)
      ax.set_title("Corrected Contour")

mask = cv2.imread("a.png")
remove_defects(mask, True)
于 2021-12-11T20:08:37.107 回答