Emgucv обрезка обнаруживает форму автоматически - PullRequest
0 голосов
/ 12 июля 2020

У меня есть приложение, которое будет использоваться для обрезки пустых мест в отсканированных документах, например это изображение . Я хочу извлечь только карту и удалить все белые / пустые области. Я использую Emgucv FindContours для этого, и на данный момент я могу найти контур карты и некоторый шум, зафиксированный сканером на изображении, как вы можете видеть ниже.

введите описание изображения здесь

Мой вопрос: как я могу обрезать самый большой найденный контур или как извлечь его, удалив другие контуры и пробелы / пробелы? Или, может быть, это возможно с индексом контура?

Изменить: Возможно, другое возможное решение - если можно нарисовать контур на другом PictureBox.

Вот код что я использую:

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

1 Ответ

0 голосов
/ 13 июля 2020

Я дам вам свой ответ в C++, но те же операции должны быть доступны в Emgu CV.

Я предлагаю следующий подход: Сегментируйте (то есть - отдельно) целевой объект, используя цветовое пространство HSV . Вычислите двоичную маску для интересующего объекта. Получите самый большой blob в двоичной маске, это должна быть карта. Вычислите ограничивающую рамку карты. Обрезать карту из входного изображения

Хорошо, сначала получите (или прочтите) входное изображение. Примените фильтр median blur, он поможет избавиться от высокочастотного шума (маленькие серые капли), который вы видите на входе. Основным параметром для настройки является size от kernel (или апертуры фильтра), однако будьте осторожны - высокое значение приведет к агрессивному эффекту и, вероятно, разрушит ваше изображение:

  //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. Его преимущество перед другими цветовыми моделями в значительной степени зависит от приложения, но в целом он более надежен при работе с градиентами оттенка . Я попытаюсь получить двоичную маску на основе 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 изображение, а затем dilate, используя 3 iterations. structuring element - это rectangle размера 3 x 3:

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

Вы получаете этот результат. Посмотрите, как в основном исчезли шумные капли:

А теперь самое интересное. Вы можете пропустить oop через все contours на этом изображении и получить самый большой из них всех. Это типичная операция, которую я выполняю постоянно, поэтому я написал функцию, которая ее выполняет. Он называется findBiggestBlob. Я представлю функцию позже. Проверьте результат, который вы получите после поиска и извлечения самого большого двоичного объекта:

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

Вы получите это:

Теперь вы можете получить bounding box самого большого двоичного объекта, используя boundingRect:

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