【问题标题】:Emgucv crop detected shape automaticallyEmgucv 自动裁剪检测到的形状
【发布时间】:2020-11-02 01:53:02
【问题描述】:

我有一个应用程序将用于从扫描的文档中裁剪空格,例如this image。我想要做的是只提取卡片并删除所有白色/空白区域。我正在使用 Emgucv FindContours 来执行此操作,目前我能够在图像中找到卡片轮廓和扫描仪捕获的一些噪点,如下所示。

我的问题是如何裁剪找到的最大轮廓或如何通过删除其他轮廓和空白/空白来提取它?或者也许可以使用轮廓索引?

编辑:也许另一种可能的解决方案是将轮廓绘制到另一个图片框。

这是我正在使用的代码:

Image<Bgr, byte> imgInput;
Image<Bgr, byte> imgCrop;

private void abrirToolStripMenuItem_Click(object sender, EventArgs e)
{
    try
    {
        OpenFileDialog dialog = new OpenFileDialog();

        if (dialog.ShowDialog() ==DialogResult.OK)
        {
            imgInput = new Image<Bgr, byte>(dialog.FileName);
            pictureBox1.Image = imgInput.Bitmap;

            imgCrop = imgInput;
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);        
    }
}

private void shapeToolStripMenuItem_Click(object sender, EventArgs e)
{
    if (imgCrop == null)
    {
        return;
    }

    try
    {
        var temp = imgCrop.SmoothGaussian(5).Convert<Gray, byte>().ThresholdBinaryInv(new Gray(230), new Gray(255));
        VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
        Mat m = new Mat();

        CvInvoke.FindContours(temp, contours, m, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);

        for (int i = 0; i < contours.Size; i++)
        {
            double perimeter = CvInvoke.ArcLength(contours[i], true);
            VectorOfPoint approx = new VectorOfPoint();
            CvInvoke.ApproxPolyDP(contours[i], approx, 0.04 * perimeter, true);

            CvInvoke.DrawContours(imgCrop, contours, i, new MCvScalar(0, 0, 255), 2);
            pictureBox2.Image = imgCrop.Bitmap;
        }

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

【问题讨论】:

  • 如果您还计算轮廓area(即通过boundingRect 获取边界框,然后调用area())并过滤低于某个area threshold 的任何内容怎么办?
  • @eldesgraciado 说实话我对 emgucv 了解不多,我今天刚开始阅读文档并尝试使用代码,我该怎么做你说的?
  • 我的回答正是这样做的:stackoverflow.com/questions/62550517/…

标签: c# winforms opencv emgucv opencv-drawcontour


【解决方案1】:

我会在C++ 中给你答案,但同样的操作应该在Emgu CV 中可用。

我提出以下方法:使用 HSV 颜色空间分割(即分离)目标对象。计算感兴趣对象的二进制掩码。获取二进制掩码中的最大的blob,这应该是卡片。计算卡片的边界框裁剪输入图像中的卡片

好的,首先获取(或读取)输入图像。应用median blur 过滤器,它将有助于消除您在输入中看到的高频噪声(小灰色斑点)。要调整的主要参数是kernel(或滤镜孔径)的size,但要小心 - 高值会导致强烈的效果并可能破坏您的图像:

  //read input image:
  std::string imageName = "C://opencvImages//yoshiButNotYoshi.png";
  cv::Mat imageInput = cv::imread( imageName );

  //apply a median blur filter, the size of the kernel is 5 x 5:
  cv::Mat blurredImage;
  cv::medianBlur ( imageInput, blurredImage, 5 );

这是模糊滤镜的结果(嵌入的图像被调整大小)

接下来,分割图像。利用背景是白色的事实,而其他一切(主要是感兴趣的对象)都有一些颜色信息。您可以使用HSV 色彩空间。首先将BGR图片转换成HSV

  //BGR to HSV conversion:
  cv::Mat hsvImg;
  cv::cvtColor( blurredImage, hsvImg, CV_RGB2HSV );

HSV 颜色空间对颜色信息的编码不同于典型的BGR/RGB 颜色空间。它相对于其他颜色模型的优势在很大程度上取决于应用程序,但总的来说,它在使用 hue 渐变 时更加稳健。我将尝试为感兴趣的对象获取基于 HSV 的二进制掩码

在二进制掩码中,您对输入图像感兴趣的所有内容都以white 着色,其他所有内容以black 着色(反之亦然)。您可以使用inRange 函数获取此掩码。但是,您必须指定将在输出掩码中呈现为白色(或黑色)的颜色范围。对于您的图像,并使用 HSV 颜色模型,这些值是:

  cv::Scalar minColor( 0, 0, 100 ); //the lower range of colors
  cv::Scalar maxColor( 0, 0, 255 ); //the upper range of colors

现在,获取二进制掩码:

  //prepare the binary mask:
  cv::Mat binaryMask;
  //create the binary mask using the specified range of color
  cv::inRange( hsvImg, minColor, maxColor, binaryMask );
  //invert the mask:
  binaryMask = 255 - binaryMask;

你得到这张图片:

现在,您可以通过 morphological filtering 去除一些噪声(在模糊滤镜中幸存下来)。形态过滤器本质上是应用于二值(或灰色)图像的逻辑规则。它们在输入中采用 “邻域”像素,并应用逻辑函数来获得输出。它们在清理二进制图像时非常方便。我将应用一系列逻辑过滤器来实现这一点。

我将首先使用erode 图像,然后使用3 iterations dilate 它。 structuring element 是大小为 3 x 3rectangle

  //apply some morphology the clean the binary mask a little bit:
  cv::Mat SE = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(3, 3) );
  int morphIterations = 3;
  cv::morphologyEx( binaryMask, binaryMask, cv::MORPH_ERODE, SE, cv::Point(-1,-1), morphIterations );
  cv::morphologyEx( binaryMask, binaryMask, cv::MORPH_DILATE, SE, cv::Point(-1,-1), morphIterations );

你得到这个输出。看看嘈杂的斑点是如何消失的:

现在,很酷的部分来了。您可以遍历这张图片中的所有contours 并获得最大的。这是我经常执行的典型操作,因此,我编写了一个函数来执行此操作。它被称为findBiggestBlob。稍后我将介绍该功能。查看找到并提取最大 blob 后得到的结果:

  //find the biggest blob in the binary image:
  cv::Mat biggestBlob = findBiggestBlob( binaryMask );

你明白了:

现在,您可以使用 boundingRect 获取最大 blob 的 bounding box

  //Get the bounding box of the biggest blob:
  cv::Rect bBox = cv::boundingRect( biggestBlob );

让我们在输入图像上绘制bounding box

  cv::Mat imageClone = imageInput.clone();
  cv::rectangle( imageClone, bBox, cv::Scalar(255,0,0), 2 );

最后,让我们从输入图像中裁剪出卡片:

  cv::Mat croppedImage = imageInput( bBox );

这是裁剪后的输出:

这是findBiggestBlob 函数的代码。这个想法只是计算二进制输入中的所有轮廓,计算它们的面积并存储具有最大面积的轮廓:

//Function to get the largest blob in a binary image:
cv::Mat findBiggestBlob( cv::Mat &inputImage ){

    cv::Mat biggestBlob = inputImage.clone();

    int largest_area = 0;
    int largest_contour_index = 0;

    std::vector< std::vector<cv::Point> > contours; // Vector for storing contour
    std::vector< cv::Vec4i > hierarchy;

    // Find the contours in the image
    cv::findContours( biggestBlob, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE ); 

    for( int i = 0; i < (int)contours.size(); i++ ) {            

        //Find the area of the contour            
        double a = cv::contourArea( contours[i], false);
        //Store the index of largest contour:
        if( a > largest_area ){
            largest_area = a;                
            largest_contour_index = i;
        }

    }

    //Once you get the biggest blob, paint it black:
    cv::Mat tempMat = biggestBlob.clone();
    cv::drawContours( tempMat, contours, largest_contour_index, cv::Scalar(0),
                  CV_FILLED, 8, hierarchy );

    //Erase the smaller blobs:
    biggestBlob = biggestBlob - tempMat;
    tempMat.release();
    return biggestBlob;
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 2018-01-27
相关资源
最近更新 更多