检测图像的最外边缘并根据其绘制

8

我正在开发一个能够通过图像计算肘关节角度的项目,但目前遇到了图像处理方面的困难。

我现在使用Python和Intel RealSense R200来完成这个项目(尽管可以认为我是使用图像输入)。

我试图检测左图像的边缘,以便获得中心图像,然后提取外轮廓(右图):

知道两个管子的侧面是平行的(两个橙色侧面和两个绿色侧面与同一颜色平行)...

...我试图构建两个点的轨迹,使其与两对颜色相距相等,然后“向中间外推”以计算角度:

我已经做到了第二张图片,并且不太可靠地做到了第三张图片。我非常愿意听取建议,并将非常感谢任何帮助。


1
你说“不可靠,至少到第三张图片”,但是你给出的示例图片非常容易分割,以获取外部轮廓。你能给出一个更难的例子吗? - alkasm
6个回答

14
我会采用以下方法来尝试找到问题中提供的四行内容。
1. 阅读图像,并将其转换为灰度图像。
import cv2
import numpy as np
rgb_img = cv2.imread('pipe.jpg')
gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)
height, width = gray_img.shape

2. 在图片顶部添加一些白色的内边距(只是为了增加一些额外的背景)
white_padding = np.zeros((50, width, 3))
white_padding[:, :] = [255, 255, 255]
rgb_img = np.row_stack((white_padding, rgb_img))

结果图像 - 白色垫子图像 3. 反转灰度图像并在顶部应用黑色填充
gray_img = 255 - gray_img
gray_img[gray_img > 100] = 255
gray_img[gray_img <= 100] = 0
black_padding = np.zeros((50, width))
gray_img = np.row_stack((black_padding, gray_img))

Black padded image

4. 使用形态学闭运算来填补图像中的空洞 -
kernel = np.ones((30, 30), np.uint8)
closing = cv2.morphologyEx(gray_img, cv2.MORPH_CLOSE, kernel)

关闭图片 5. 使用Canny边缘检测在图像中找到边缘-

edges = cv2.Canny(closing, 100, 200)

管道边缘图像 6. 现在,我们可以使用openCV的HoughLinesP函数在给定的图像中找到线条-

minLineLength = 500
maxLineGap = 10
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 50, None, 50, 100)
all_lines = lines[0]
for x1,y1,x2,y2 in lines[0]:
    cv2.line(rgb_img,(x1,y1),(x2,y2),(0,0,255),2)

在这里输入图像描述 7.现在,我们需要找到最右边的两条水平线和最底部的两条垂直线。对于水平线,我们将使用(x2,x1)进行排序,按降序排列。在这个排序列表中,第一条线将是最右边的垂直线。跳过这条线,如果我们选择下面的两条线,它们将是最右边的水平线。

all_lines_x_sorted = sorted(all_lines, key=lambda k: (-k[2], -k[0]))
for x1,y1,x2,y2 in all_lines_x_sorted[1:3]:
    cv2.line(rgb_img,(x1,y1),(x2,y2),(0,0,255),2)

水平线图片 8. 同样地,可以使用y1坐标按降序对线进行排序,排序列表中的前两条线将是最底部的垂直线。

all_lines_y_sorted = sorted(all_lines, key=lambda k: (-k[1]))
for x1,y1,x2,y2 in all_lines_y_sorted[:2]:
    cv2.line(rgb_img,(x1,y1),(x2,y2),(0,0,255),2)

垂直线条图片 9. 同时包含两条线的图片 -

final_lines = all_lines_x_sorted[1:3] + all_lines_y_sorted[:2]

final lines

因此,获取这4行可以帮助您完成剩下的任务。

如果你调整一下图片的显示格式(它们现在太大了),我会给这个答案点赞。这将极大地改善你的回答。我编辑了问题并调整了图片大小,这样你就可以看看并学习如何做了。以下是相关代码:<img src="https://i.stack.imgur.com/GmHqQ.png" width="200" height="150"> - karlphillip
当然,@karlphillip。我会在有空的时候尽快完成这个任务。感谢您宝贵的建议。 - GaneshTata
据我所知,“Canny边缘检测”可以应用于灰度图像。但是,您将其应用于二进制图像。此外,我尝试了完全相同的代码,但它并没有起作用。您是如何运行这段代码的?@GaneshTata - Yoko Bolt
我能否根据第5步指定的线条裁剪图像? - Chandra Kanth
1
@ChandraKanth 在第四步中,背景为黑色,管道由所有白色像素组成。因此,您应该能够获取包含白色像素的所有图像坐标。这些坐标将代表管道。 - GaneshTata
@GaneshTata 但是我该如何将这些像素作为单独的图像获取并进行进一步的分析呢?我有一个需要从原始图像中剥离出来并制作成新图像的矩形部分。我正在使用您的技术,但我只能得到边界框,如何从该边界框中提取原始图像并保存它呢? - Chandra Kanth

4
这个问题已经有很多好的答案,尽管没有被接受。我尝试了一些不同的方法,所以即使这个问题很老,我也想发布它。至少其他人可能会觉得这很有用。这只适用于像示例图像中那样有漂亮统一背景的情况。
  • 检测兴趣点(尝试不同的兴趣点检测器。我使用了FAST)
  • 找到这些点的最小包围三角形
  • 找到这个三角形的最大(是吗?)角度
这将给你一个粗略的估计。
对于示例图像,代码给出:
90.868604
42.180990
46.950407

代码是用 c++ 编写的。如果您觉得有用,您可以轻松地将其移植。

三角形

// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
static double angle( Point2f pt1, Point2f pt2, Point2f pt0 )
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

int _tmain(int argc, _TCHAR* argv[])
{
    Mat rgb = imread("GmHqQ.jpg");

    Mat im;
    cvtColor(rgb, im, CV_BGR2GRAY);

    Ptr<FeatureDetector> detector = FastFeatureDetector::create();
    vector<KeyPoint> keypoints;
    detector->detect(im, keypoints);

    drawKeypoints(im, keypoints, rgb, Scalar(0, 0, 255));

    vector<Point2f> points;
    for (KeyPoint& kp: keypoints)
    {
        points.push_back(kp.pt);
    }

    vector<Point2f> triangle(3);
    minEnclosingTriangle(points, triangle);

    for (size_t i = 0; i < triangle.size(); i++)
    {
        line(rgb, triangle[i], triangle[(i + 1) % triangle.size()], Scalar(255, 0, 0), 2);
        printf("%f\n", acosf( angle(triangle[i], 
            triangle[(i + 1) % triangle.size()], 
            triangle[(i + 2) % triangle.size()]) ) * 180 / CV_PI);
    }

    return 0;
}

对我来说最好的解决方案是:检测基于HSV空间中色调值的目标对象区域,然后在此ROI上应用边界矩形以识别包含多个对象的情况,这样可以使解决方案更加健壮。 - flamelite
@flamelite OP说他正在使用RealSense,因此如果他得到深度图像或点云作为输出,也可以通过深度来分割对象。在点云的情况下,他可以将分割点投影到平面上并拟合最小外接三角形。 - dhanushka

2

目前不清楚这种几何形状是固定的还是可以有其他布局。

由于您拥有物体与背景的极佳对比度,因此可以通过在探针线上找到第一个和最后一个转换来检测一些点。

成对的点给出了一个方向。更多的点允许您进行直线拟合,并且您可以使用橙色和绿色区域中的所有点。甚至可以同时拟合两条平行线。

请注意,如果您只需要一个角度,则无需找到管道的轴。

enter image description here


2
似乎第二张图片使用Hough变换应该会得到两个在Theta-Rho空间中的强垂直聚类,对应于平行线束。因此,您可以确定主要方向。
这是我使用第二张图片和OpenCV函数HoughLines进行快速测试的结果。

enter image description here

然后我统计了在范围0到180度内所有方向(四舍五入为整数度)的线条数量,并打印出结果,其中count>1。显然我们可以看到在86-87度和175-176度有更大的计数(注意几乎是90度的差异)。
line 
angle : count
84: 3
85: 3
86: 8
87: 12
88: 3
102: 3
135: 3
140: 2
141: 2
165: 2
171: 4
172: 2
173: 2
175: 7
176: 17
177: 3

注意:我使用了 Delphi 的 HoughLines 函数的任意示例,并添加了方向计数。您可以获取此 Python 示例并构建 theta 值的直方图。

那看起来非常有前途。非常感谢。你能提供源代码吗? - George Hynes
很抱歉,我的代码可能不是很有用,请看我的注释。 - MBo

2
正如您所看到的,在二进制图像中,该线并不是那么直的,而且有很多类似的线条。因此,在这样的图像上直接进行HoughLine是一个错误的选择,不负责任。
我试图将图像二值化,去掉左上角的区域 (3*w/4, h*2/3),然后得到两个分离的区域:

enter image description here

img = cv2.imread("img04.jpg", 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
H,W = img.shape[:2]
threshed[:H*2//3,:W*3//4] = 0

cv2.imwrite("regions.png", threshed)

然后您可以按照您的喜好进行其他的后续步骤。

0
很遗憾,您的方法行不通,因为通过这种方法计算出的角度仅在摄像机被完全垂直于接头平面时才是实际角度。 您需要在图像中放置一个参考正方形,以便能够计算摄像机所持角度,并能够校正摄像机角度。 参考正方形必须放置在与管道接头相同的平面上。

我确实对此有一些担忧。该项目涉及一个支架,使肘部与下方的板和上方安装的相机保持“平坦”/正常。我会在平台上放置一个网格。 - George Hynes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接