检测图像的最外边缘并基于它进行绘图

Detecting outer most-edge of image and plotting based on it

我正在做一个可以通过图像计算肘关节角度的项目。我努力的部分是图像处理。

目前正在 Python 使用 Intel RealSense R200 执行此操作(尽管可以认为我正在使用图像输入)。

我正在尝试检测左图的边缘,这样我就可以获得中心图,目的是提取外轮廓(右图):

知道从角出来的两根管子的边会平行(橙色的两条边和绿色的两条边平行是同一种颜色)...

...我正在尝试构建与两对颜色等距的点的 2 个轨迹,然后 'extrapolate to the middle' 以计算角度:

我已经看到了第二张图片,不可靠的是,我看到了第三张图片。我非常乐于接受建议,如果有任何帮助,我将不胜感激。

遗憾的是,您的方法不起作用,因为如果相机与关节平面完全垂直,则通过此方法计算出的角度只是实际角度。您需要在图像中有一个参考方块,以便能够计算出相机的握持角度,从而能够校正相机角度。并且参考方块必须放置在与管接头相同的平面上。

第二张图片的 Hough transform 似乎应该给出两个强垂直(在 Theta-Rho space 中)集群,对应于平行线束。这样您就可以确定主要方向。

这是我使用第二张图片和 OpenCV 函数 HoughLines

进行快速测试的结果

然后我计算了所有方向(四舍五入到整数度)在 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

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

不清楚这个几何图形是固定的还是其他布局也是可能的。

由于物体与背景的对比度非常好,因此您可以通过沿着探测线找到第一个和最后一个过渡来检测几个点。

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

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

大家可以看到,二值图中的线并不是那么直,而且还有很多相似的线。所以直接在这样的图片上做HoughLinea bad choice,不是责任


我尝试对图像进行二值化,删除左上区域 (3*w/4, h*2/3),然后我得到两个单独的区域:

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)

然后您可以根据需要执行其他 post 个步骤。

我会使用以下方法尝试找到问题中提供的四行。

1.读取图像,并将其转换为灰度

import cv2
import numpy as np
rgb_img = cv2.imread('pipe.jpg')
height, width = gray_img.shape
gray_img = cv2.cvtColor(rgb_img, cv2.COLOR_BGR2GRAY)

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

4.Use 形态学闭合以填充图像中的空洞 -

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.Now,我们要找到最右边的两条横线,和最下面的两条竖线。对于水平线,我们将使用 (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]

因此,获得这4条线可以帮助您完成剩下的任务。

这已经有很多好的答案了,不过 none 接受了。我尝试了一些不同的东西,所以即使问题很老也想发布它。至少其他人可能会觉得这很有用。仅当示例图像中有漂亮的统一背景时,此方法才有效。

  • 检测兴趣点(尝试不同的兴趣点检测器。我用的是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;
}