OpenCV锐化边缘(没有孔洞的边缘)

3
我正在尝试检测最大/较大的矩形形状并绘制边界框到检测区域。在我的用例中,表示矩形形状的对象往往(但并不总是)是白色的,背景也是非常相似的白色。
在检测轮廓之前,我对图像进行了预处理,以便检测完美的边缘。我的问题是,即使在模糊和使用“自适应阈值”或“阈值”之后,我也无法完美地检测到边缘,并且有很多噪声。 我用于轮廓检测的原始图像 我尝试了不同的方法来检测不同光照条件下的完美边缘,但没有成功。
如何处理图像以检测轮廓检测的完美边缘(没有孔洞的边缘)?
下面是我正在使用的代码
public static Mat findRectangleX(Mat original) {
  Mat src = original.clone();
  Mat gray = new Mat();
  Mat binary = new Mat();
  MatOfPoint2f approxCurve;
  List<MatOfPoint> contours = new ArrayList<MatOfPoint>();

    if (original.type() != CvType.CV_8U) {
        Imgproc.cvtColor(original, gray, Imgproc.COLOR_BGR2GRAY);
    } else {
        original.copyTo(gray);
    }

    Imgproc.GaussianBlur(gray, gray, new Size(5,5),0);
    Imgproc.adaptiveThreshold(gray, binary, 255,Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,Imgproc.THRESH_BINARY_INV,11, 1);

    //Imgproc.threshold(gray, binary,0,255,Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);


    double maxArea = 0;
    Imgproc.findContours(binary, contours, new Mat(),Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);

    for (int i = 0; i<contours.size();i++) {
        MatOfPoint contour = contours.get(i);
        MatOfPoint2f temp = new MatOfPoint2f(contour.toArray());
        double area = Imgproc.contourArea(contour);
        approxCurve = new MatOfPoint2f();
        Imgproc.approxPolyDP(temp, approxCurve, Imgproc.arcLength(temp, true) * 0.03, true);

        if (approxCurve.total() == 4 ) {
            Rect rect = Imgproc.boundingRect(contours.get(i));
            Imgproc.rectangle(src, rect.tl(), rect.br(), new Scalar(255, 0, 0, .8), 4);
            if(maxArea < area)
                maxArea = area;
        }
    }

    Log.v(TAG, "Total contours found : " + contours.size());
    Log.v(TAG, "Max area :" + maxArea);

    return src;

}

我在stackoverflow上搜索了类似的问题并尝试了代码示例,但它们都没有对我起作用。我认为困难在于白色物体和白色背景之间的区别。
如何处理图像以锐化轮廓检测边缘?
如何检测最大/最大的矩形形状并绘制矩形线到检测到的形状中?
//更新于:2017年2月20日
我已经尝试了@ Nejc在下面帖子中提出的解决方案。分割更好了,但我仍然有轮廓中的空洞,并且findcontours无法检测到更大的轮廓。 以下是@ Nejc提供的代码,并转换为java。
public static Mat process(Mat original){
    Mat src = original.clone();
    Mat hsvMat = new Mat();
    Mat saturation = new Mat();
    Mat sobx = new Mat();
    Mat soby = new Mat();
    Mat grad_abs_val_approx = new Mat();

    Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
    List<Mat> hsv_channels = new ArrayList<Mat>(3);
    Core.split(hsvMat, hsv_channels);
    Mat hue = hsv_channels.get( 0 );
    Mat sat = hsv_channels.get( 1 );
    Mat val = hsv_channels.get( 2 );

    Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
    Mat imf = new Mat();
    saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);

    Imgproc.Sobel(imf, sobx, -1, 1, 0);
    Imgproc.Sobel(imf, soby, -1, 0, 1);

    sobx = sobx.mul(sobx);
    soby = soby.mul(soby);

    Mat abs_x = new Mat();
    Core.convertScaleAbs(sobx,abs_x);
    Mat abs_y = new Mat();
    Core.convertScaleAbs(soby,abs_y);
    Core.addWeighted(abs_x, 1, abs_y, 1, 0, grad_abs_val_approx);

    sobx.release();
    soby.release();


    Mat filtered = new Mat();
    Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);

    final MatOfDouble mean = new MatOfDouble();
    final MatOfDouble stdev = new MatOfDouble();
    Core.meanStdDev(filtered, mean, stdev);

    Mat thresholded = new Mat();
    Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);

    /*
    Mat thresholded_bin = new Mat();
    Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY);
    Mat converted = new Mat();
    thresholded_bin.convertTo(converted, CV_8UC1);
    */

    return thresholded;
}

这是我运行上面代码后得到的图像:

使用 @Nejc 的解决方案后的图像

1)为什么我的翻译代码不能输出与 @Nejc 相同的图像? 应用于相同图像的相同代码应该产生相同的输出吗?

2)在翻译时,我有遗漏了什么吗?

3)为了我理解,为什么我们要在 sobx.mul(sobx) 这条指令中将图像乘以自己?


“Perfect edge” 是什么意思?指完美的直边吗? - Rethunk
通过增强图像边缘以更好地检测轮廓。 - ctb corp
2个回答

1
我通过计算输入图像的梯度绝对值的近似值,成功获得了边缘的相当不错的图像。
编辑:在开始工作之前,我将输入图像调整为5倍小的大小。 点击这里查看! 如果您在该图像上使用我的代码,则结果将很好。如果您想让我的代码与原始大小的图像配合良好,则可以执行以下操作之一:
- 将高斯核的大小和标准差乘以5,或者 - 将图像下采样5倍,执行算法,然后将结果上采样5倍(这比第一个选项要快得多)
这是我得到的结果:

enter image description here

enter image description here

我的过程依赖于两个关键特征。第一个是转换为适当的颜色空间。正如Jeru Luke在他的答案中所述, 在HSV颜色空间中,饱和度通道是一个好选择。第二个重要的事情是估计梯度的绝对值。我使用了Sobel算子和一些算术运算来实现这个目的。如果有人需要,我可以提供额外的解释。
这是我用来获得第一张图片的代码。
using namespace std;
using namespace cv;

Mat img_rgb = imread("letter.jpg");

Mat img_hsv;
cvtColor(img_rgb, img_hsv, CV_BGR2HSV);
vector<Mat> channels_hsv;
split(img_hsv, channels_hsv);

Mat channel_s = channels_hsv[1];
GaussianBlur(channel_s, channel_s, Size(9, 9), 2, 2);

Mat imf;
channel_s.convertTo(imf, CV_32FC1, 0.5f, 0.5f);

Mat sobx, soby;
Sobel(imf, sobx, -1, 1, 0);
Sobel(imf, soby, -1, 0, 1);

sobx = sobx.mul(sobx);
soby = soby.mul(soby);

Mat grad_abs_val_approx;
cv::pow(sobx + soby, 0.5, grad_abs_val_approx);

Mat filtered;
GaussianBlur(grad_abs_val_approx, filtered, Size(9, 9), 2, 2);

Scalar mean, stdev;
meanStdDev(filtered, mean, stdev);

Mat thresholded;
cv::threshold(filtered, thresholded, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_TOZERO);

// I scale the image at this point so that it is displayed properly 
imshow("image", thresholded/50);

这就是我计算第二张图片的方法:

Mat thresholded_bin;
cv::threshold(filtered, thresholded_bin, mean.val[0] + stdev.val[0], 1.0, CV_THRESH_BINARY);

Mat converted;
thresholded_bin.convertTo(converted, CV_8UC1);

vector<vector<Point>> contours;
findContours(converted, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);

Mat contour_img = Mat::zeros(converted.size(), CV_8UC1);
drawContours(contour_img, contours, -1, 255);

imshow("contours", contour_img);

谢谢您提供这个方案,这正是我要尝试的。我已经添加了上面的注释。 - ctb corp
@ctbcorp 关于问题1:我已在帖子开头添加了“编辑”部分。关于问题2:我注意到了两个错误-您在调用函数meanStdDev和threshold时将grad_abs_val_approx而不是filtered作为参数传递。关于问题3:当我将两个数组自乘并计算它们的平方根时,我做了这个:sqrt(a²+b²)。如果a和b是x和y方向梯度的近似值,则结果是梯度大小。 - Nejc
谢谢您提供这些信息。我在更新我的帖子后才看到了您的评论。我会根据您提供的新信息再次尝试。 - ctb corp
谢谢,你的笔记帮助我让它在大多数情况下工作。我发布了代码的一部分,可以工作,并且还有一个不起作用的图像示例。如果你有什么想法,这可能会有所帮助。 - ctb corp
@Nejc,有人正在尝试解决相同的问题。我正在尝试帮忙。你有任何想法为什么你提出的解决方案对以下两个图像无效:https://i.stack.imgur.com/2PeyG.jpg 和 https://i.stack.imgur.com/qEFMj.jpg? - ctb corp

0

感谢您的评论和建议。

@NEJC提供的代码完美运行,并且覆盖了我80%的使用情况。

然而,它不能处理类似这样的情况: 当前代码无法解决的问题 我不知道为什么。

也许有人有想法/线索/解决方案吗?

我会继续改进代码,尝试找到一个更通用的解决方案,以涵盖更多情况。如果我找到了,我会发布出来。

无论如何,以下是基于@NEJC的解决方案和注释的可工作代码。

public static Mat process(Mat original){
    Mat src = original.clone();
    Mat hsvMat = new Mat();
    Mat saturation = new Mat();
    Mat sobx = new Mat();
    Mat soby = new Mat();
    Mat grad_abs_val_approx = new Mat();

    Imgproc.cvtColor(src, hsvMat, Imgproc.COLOR_BGR2HSV);
    List<Mat> hsv_channels = new ArrayList<Mat>(3);
    Core.split(hsvMat, hsv_channels);
    Mat hue = hsv_channels.get( 0 );
    Mat sat = hsv_channels.get( 1 );
    Mat val = hsv_channels.get( 2 );

    Imgproc.GaussianBlur(sat, saturation, new Size(9, 9), 2, 2);
    Mat imf = new Mat();
    saturation.convertTo(imf, CV_32FC1, 0.5f, 0.5f);

    Imgproc.Sobel(imf, sobx, -1, 1, 0);
    Imgproc.Sobel(imf, soby, -1, 0, 1);

    sobx = sobx.mul(sobx);
    soby = soby.mul(soby);

    Mat sumxy = new Mat();
    Core.add(sobx,soby, sumxy);
    Core.pow(sumxy, 0.5, grad_abs_val_approx);

    sobx.release();
    soby.release();
    sumxy.release();;


    Mat filtered = new Mat();
    Imgproc.GaussianBlur(grad_abs_val_approx, filtered, new Size(9, 9), 2, 2);

    final MatOfDouble mean = new MatOfDouble();
    final MatOfDouble stdev = new MatOfDouble();
    Core.meanStdDev(filtered, mean, stdev);

    Mat thresholded = new Mat();
    Imgproc.threshold(filtered, thresholded, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_TOZERO);


    /*
    Mat thresholded_bin = new Mat();
    Imgproc.threshold(filtered, thresholded_bin, mean.toArray()[0] + stdev.toArray()[0], 1.0, Imgproc.THRESH_BINARY_INV);
    Mat converted = new Mat();
    thresholded_bin.convertTo(converted, CV_8UC1);
    */

    Mat converted = new Mat();
    thresholded.convertTo(converted, CV_8UC1);
    return converted;
}

在这种情况下,饱和度通道不是一个好的选择。当我使用值通道或灰度转换图像时,我得到了可以接受的结果,并且我还必须降低阈值(仅使用平均值而不是平均值+标准差)。在运行此代码之前,您可能需要某种算法来自动确定要使用哪个图像通道... - Nejc

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