opencv - 如何过滤掉边缘检测圆中噪声极大的点?

标签 opencv image-processing hough-transform

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

在某些区域中,孔径边缘轮廓清晰,但在其他区域中,噪声非常大。我目前正在尝试隔离“好的”点来进行 RANSAC 拟合,但我在此过程中还采取了其他步骤。以下是两张原图供引用:

testImage1 testImage2

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

然后我决定在将图像发送到 HoughCircle 之前尝试对其进行更多处理。我从原始图像开始,进行中值模糊、高斯模糊、阈值化、膨胀,进行 Canny 边缘检测,然后将 Canny 图像输入到函数中。

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

  1. 没有膨胀的 Canny 输出
  2. 带膨胀的 Canny 输出
  3. 在原始图像上绘制的扩张 Canny 图像的霍夫输出。

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

最佳答案

谢谢@jalconvolvon - 这是一个有趣的问题。这是我的结果: enter image description here 我发现重要的是在原型(prototype)设计时使用动态参数调整,因此我包含了用于调整 Canny 检测的函数。该代码还使用 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

关于opencv - 如何过滤掉边缘检测圆中噪声极大的点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42641331/

相关文章:

Android camera2 输出到 ImageReader 格式 YUV_420_888 仍然很慢

python - 使用opencv编写pgms时如何更改最大灰度的默认值?

c# - EmguCV 64 位构建运行时错误

python - 在二进制边缘图像中查找闭合循环数

java - 使用 hough 进行瞳孔检测的最佳参数? java opencv

c++ - 如何查找两条线的点积 (Opencv)

image-processing - 烟花PNG格式,有什么见解吗?任何库?

python-3.x - 在 Matplotlib 中使用百分比累积计数增强对比度

javascript - 使用 JavaScript 进行运动检测和简单分析?

python - opencv检测虚线