如何裁剪凸面缺陷?
我试图从轮廓中检测并精确定位图像中的某些对象。 我得到的轮廓经常包括一些噪音(可能是背景,我不知道)。 对象看起来应该类似于矩形或正方形:
使用形状匹配( cv::matchShapes
)可以获得非常好的结果,可以检测带有和不带噪声的物体的轮廓,但是如果出现噪音,我会遇到精细定位问题。
噪音看起来像:
要么 例如。
我的想法是找到凸面缺陷,如果它们变得太强,就会以某种方式削弱导致凹面的部分。 检测缺陷是可以的,通常我会在每个“不需要的结构”上得到两个缺陷,但我一直在坚持如何确定我应该从轮廓中删除点的位置和位置。
这里有一些轮廓,他们的面具(所以你可以轻松地提取轮廓)以及包含阈值凸面缺陷的凸包:
我可以通过轮廓走路并在本地决定轮廓是否执行“左转”(如果顺时针方向走),如果是,则移除轮廓点直到下一个左转被取走? 也许从一个凸面缺陷开始?
我正在寻找算法或代码,编程语言不应该很重要,算法更重要。
这种方法只适用于点。 你不需要为此创建掩码。
主要想法是:
我得到以下结果。 正如你所看到的,它对于光滑的缺陷(例如第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;
}
UPDATE
使用近似轮廓(例如,在findContours
使用CHAIN_APPROX_SIMPLE
)可能会更快,但轮廓长度必须使用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);
}
}
我想出了以下方法来检测矩形/方块的边界。 它的工作原理基于一些假设:形状是矩形或正方形,它在图像中居中,不倾斜。
中线和样本图像上半部分的投影如下所示。
两个样本的结果范围和裁剪区域:
代码在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)
作为一个起点,假设相对于您尝试识别的对象来说缺陷不会太大,您可以在使用cv::matchShapes
之前尝试一个简单的erode + 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);
链接地址: http://www.djcxy.com/p/58729.html
上一篇: How to crop away convexity defects?
下一篇: Detect geometric object on video stream and reconstruct its contours