如何过滤出边缘检测到的极其嘈杂的圆点?

时间:2017-03-07 06:08:08

标签: opencv image-processing hough-transform

我正在研究检测由激光束照射的圆形孔径的中心和半径。该算法将从我无法进行物理控制的系统(即调暗光源或调整激光位置)中提供图像。我需要使用C ++完成此操作,并选择使用openCV。

在某些地区,光圈边缘定义明确,但在其他地区则非常嘈杂。我目前正试图孤立“好”点以做RANSAC适合,但我已经采取了其他步骤。以下是两张原始图片供参考:

testImage1 testImage2

我首先尝试做一个Hough fit。我执行了中值模糊以消除盐和胡椒噪声,然后是高斯模糊,然后将图像输入openCV中的HoughCircle函数,滑块控制霍夫参数1和2定义here。结果是灾难性的:bad results

然后我决定在将图像发送到HoughCircle之前尝试再处理图像。我从原始图像开始,中值模糊,高斯模糊,阈值化,扩张,做了Canny边缘检测,然后将Canny图像输入到函数中。

我最终能够对我的圆圈进行合理的估计,但是当手动减少霍夫参数时,它显示的是第15个圆圈。我手动绘制了紫色轮廓,绿色圆圈表示霍夫输出接近我的手动估计。以下图片是:

  1. 没有扩张的Canny输出
  2. Canny输出扩张
  3. 在原始图像上绘制的扩张Canny图像的霍夫输出。
  4. canny image test

    正如你所看到的,无效圆圈的数量远远超过正确的圆圈,并且我不太确定如何隔离好圆圈,因为霍夫变换返回了许多其他无效圆圈,参数更严格。

    我目前有一些我实现的代码,对于我给出的所有测试图像都可以正常工作,但代码是一个令人费解的混乱,许多可调参数似乎非常脆弱。我所做的驱动逻辑是注意到激光照射良好的光圈边缘区域在几个阈值水平上相对恒定(如下图所示)。

    image

    我在两个阈值水平进行边缘检测,并在两个图像中重叠存储点。目前,结果也存在一些不准确性,因为孔径边缘仍然会随着不同的阈值水平而略微偏移。如果有必要,我可以发布很长的代码,但它背后的伪代码是:

    1. Perform a median blur, followed by a Gaussian blur. Kernels are 9x9.
    2. Threshold the image until 35% of the image is white. (~intensities > 30)
    3. Take the Canny edges of this thresholded image and store (Canny1)
    4. Take the original image, perform the same median and Gaussian blurs, but threshold with a 50% larger value, giving a smaller spot (~intensities > 45)
    5. Perform the "Closing" morphology operation to further erode the spot and remove any smaller contours.
    6. Perform another Canny to get the edges, and store this image (Canny2)
    7. Blur both the Canny images with a 7x7 Gaussian blur.
    8. Take the regions where the two Canny images overlap and say that these points are likely to be good points.
    9. Do a RANSAC circle fit with these points.
    

    我注意到有一些检测到边缘的区域,人眼可以将其区分为最佳圆圈的一部分。有没有办法隔离这些区域以适应RANSAC?

    霍夫代码:

    int houghParam1 = 100;
    int houghParam2 = 100;
    int dp = 10; //divided by 10 later
    int x=616;
    int y=444;
    int radius = 398;
    int iterations = 0;
    
    int main()
    {
    namedWindow("Circled Orig");
    namedWindow("Processed", 1);
    namedWindow("Circles");
    namedWindow("Parameters");
    namedWindow("Canny");
    createTrackbar("Param1", "Parameters", &houghParam1, 200);
    createTrackbar("Param2", "Parameters", &houghParam2, 200);
    createTrackbar("dp", "Parameters", &dp, 20);
    createTrackbar("x", "Parameters", &x, 1200);
    createTrackbar("y", "Parameters", &y, 1200);
    createTrackbar("radius", "Parameters", &radius, 900);
    createTrackbar("dilate #", "Parameters", &iterations, 20);
    std::string directory = "Secret";
    std::string suffix = ".pgm";
    Mat processedImage;
    Mat origImg;
    for (int fileCounter = 2; fileCounter < 3; fileCounter++) //1, 12
    {
        std::string numString = std::to_string(static_cast<long long>(fileCounter));
        std::string imageFile = directory + numString + suffix;
        testImage = imread(imageFile);
        Mat bwImage;
        cvtColor(testImage, bwImage, CV_BGR2GRAY);
        GaussianBlur(bwImage, processedImage, Size(9, 9), 9);
        threshold(processedImage, processedImage, 25, 255, THRESH_BINARY); //THRESH_OTSU
        int numberContours = -1;
        int iterations = 1;
        imshow("Processed", processedImage);
    }
    
    vector<Vec3f> circles;
    Mat element = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
    float dp2 = dp;
    while (true)
    {
        float dp2 = dp;
        Mat circleImage = processedImage.clone();
        origImg = testImage.clone();
        if (iterations > 0) dilate(circleImage, circleImage, element, Point(-1, -1), iterations);
        Mat cannyImage;
        Canny(circleImage, cannyImage, 100, 20);
        imshow("Canny", cannyImage);
        HoughCircles(circleImage, circles, HOUGH_GRADIENT, dp2/10, 5, houghParam1, houghParam2, 300, 5000);
        cvtColor(circleImage, circleImage, CV_GRAY2BGR);
        for (size_t i = 0; i < circles.size(); i++)
        {
            Scalar color = Scalar(0, 0, 255);
            Point center2(cvRound(circles[i][0]), cvRound(circles[i][1]));
            int radius2 = cvRound(circles[i][2]);
            if (abs(center2.x - x) < 10 && abs((center2.y - y) < 10) && abs(radius - radius2) < 20)  color = Scalar(0, 255, 0);
            circle(circleImage, center2, 3, color, -1, 8, 0);
            circle(circleImage, center2, radius2, color, 3, 8, 0);
            circle(origImg, center2, 3, color, -1, 8, 0);
            circle(origImg, center2, radius2,color, 3, 8, 0);
        }
        //Manual circles
        circle(circleImage, Point(x, y), 3, Scalar(128, 0, 128), -1, 8, 0);
        circle(circleImage, Point(x, y), radius, Scalar(128, 0, 128), 3, 8, 0);
        circle(origImg, Point(x, y), 3, Scalar(128, 0, 128), -1, 8, 0);
        circle(origImg, Point(x, y), radius, Scalar(128, 0, 128), 3, 8, 0);
        imshow("Circles", circleImage);
        imshow("Circled Orig", origImg);
        int x = waitKey(50);
    }
    Mat drawnImage;
    cvtColor(processedImage, drawnImage, CV_GRAY2BGR);
    return 1;
    }
    

1 个答案:

答案 0 :(得分:2)

谢谢@jalconvolvon - 这是一个有趣的问题。这是我的结果: enter image description here 我发现重要的是在原型设计时使用动态参数调整,因此我包含了用于调整Canny检测的函数。该代码还对Ransac部分使用this答案。

import cv2
import numpy as np
import auxcv as aux
from skimage import measure, draw

def empty_function(*arg):
    pass

# tune canny edge detection. accept with pressing "C"
def CannyTrackbar(img, win_name):
    trackbar_name = win_name + "Trackbar"

    cv2.namedWindow(win_name)
    cv2.resizeWindow(win_name, 500,100)
    cv2.createTrackbar("canny_th1", win_name, 0, 255, empty_function)
    cv2.createTrackbar("canny_th2", win_name, 0, 255, empty_function)
    cv2.createTrackbar("blur_size", win_name, 0, 255, empty_function)
    cv2.createTrackbar("blur_amp", win_name, 0, 255, empty_function)

    while True:
        trackbar_pos1 = cv2.getTrackbarPos("canny_th1", win_name)
        trackbar_pos2 = cv2.getTrackbarPos("canny_th2", win_name)
        trackbar_pos3 = cv2.getTrackbarPos("blur_size", win_name)
        trackbar_pos4 = cv2.getTrackbarPos("blur_amp", win_name)
        img_blurred = cv2.GaussianBlur(img.copy(), (trackbar_pos3 * 2 + 1, trackbar_pos3 * 2 + 1), trackbar_pos4)
        canny = cv2.Canny(img_blurred, trackbar_pos1, trackbar_pos2)
        cv2.imshow(win_name, canny)

        key = cv2.waitKey(1) & 0xFF
        if key == ord("c"):
            break

    cv2.destroyAllWindows()
    return canny

img = cv2.imread("sphere.jpg")

#resize for convenience
img = cv2.resize(img, None, fx = 0.2, fy = 0.2)

#closing
kernel = np.ones((11,11), np.uint8)
img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

#sharpening
kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
img = cv2.filter2D(img, -1, kernel)

#test if you use different scale img than 0.2 of the original that I used
#remember that the actual kernel size for GaussianBlur is trackbar_pos3*2+1
#you want to get as full circle as possible here
#canny = CannyTrackbar(img, "canny_trakbar")

#additional blurring to reduce the offset toward brighter region
img_blurred = cv2.GaussianBlur(img.copy(), (8*2+1,8*2+1), 1)

#detect edge. important: make sure this works well with CannyTrackbar()
canny = cv2.Canny(img_blurred, 160, 78)

coords = np.column_stack(np.nonzero(canny))

model, inliers = measure.ransac(coords, measure.CircleModel,
                                min_samples=3, residual_threshold=1,
                                max_trials=1000)

rr, cc = draw.circle_perimeter(int(model.params[0]),
                               int(model.params[1]),
                               int(model.params[2]),
                               shape=img.shape)

img[rr, cc] = 1

import matplotlib.pyplot as plt
plt.imshow(img, cmap='gray')
plt.scatter(model.params[1], model.params[0], s=50, c='red')
plt.axis('off')
plt.savefig('sphere_center.png', bbox_inches='tight')
plt.show()

现在我可能会尝试计算像素统计数据的位置以及调整激光位置的位置(如果我理解你正在尝试做什么)

如果Ransac还不够。我尝试调整Canny只检测圆圈顶部的完美弧线(它的位置很好)并尝试使用以下依赖项(我怀疑这应该是可能的):

enter image description here