Как сохранить содержимое в пределах квадратного контура и удалить контуры другой формы? - PullRequest
1 голос
/ 12 июля 2020

Мой проект состоит в том, чтобы отсканировать бумагу из изображения, которое выглядит enter image description here

After a lot of processes, the quality of images decreased. I run some code to find all the contours

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

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret,bw = cv2.threshold(gray,220,255,cv2.THRESH_BINARY_INV) contours,hierarchy = cv2.findContours(bw, cv2.RETR_CCOMP,1) cntLen = 10 ct = 0 #number of contours for cnt in contours:
    if len(cnt) > cntLen: #eliminate the noises
        ct += 1
        newimg = img.copy()
        cv2.drawContours(newimg,[cnt],0,(0,0,255),2)
        cv2.imshow('Win', newimg)
        cv2.waitKey(0) print('Total contours: ',ct)

Obviously, there are a lot of result, but I only want to sort out all the checkbox contours and check whether people tick on it or not.

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

Ответы [ 2 ]

1 голос
/ 13 июля 2020

Может быть несколько решений, один из способов go об этом:

  1. Извлечь контуры / соединенные компоненты (как вы это делаете)

  2. Выполните сопоставление квадратной маски на основе корреляции с шаблоном, которое вы можете определить, как показано ниже: маска из образца изображения

  3. Далее с помощью пороговой обработки, чтобы получить координаты пика, к которому вы применили маску (и, следовательно, вы можете вычислить свои 4 координаты прямоугольника)

  4. После получения 4 координат прямоугольника вы можете добавьте дополнительный фильтр, основанный на соотношении сторон / моменте обнаруженного окна (как предлагается eldesgraciado )

Примечание:

  • Вам придется провести множество экспериментов, чтобы получить правильное значение порога, и вам потребуется +/- запас порога
  • Вы получите несколько пиков совпадения, и в этом случае вам придется взять самый сильный
  • когда форма заполнена, может возникнуть ситуация, когда штрих пера будет продолжаться за пределами поля, поэтому соотношение сторон должно применяться после извлечения координат поля
  • Этот метод очень чувствителен к . Шум б. Перекос формы c. масштаб изображения или изменения рамки, et c, рекомендовал бы выполнить некоторые шаги предварительной обработки, такие как исправление перекоса, на основе ваших образцов изображений.

Template Matching Wiki

Соответствие шаблонов OpenCV

Аналогичный вопрос

1 голос
/ 13 июля 2020

Я даю свой ответ в C++, но те же операции доступны в Python.

Давайте рассмотрим два возможных решения. Первый предполагает применение предложенного мной решения непосредственно к предоставленному вами входному изображению. Я фильтрую контуры на основе пороговых значений aspect ratio и minimum width/height.

Сначала прочтите входное изображение и преобразуйте его в шкалу серого:

  std::string imageName = "C://opencvImages//survey.jpg";
  cv::Mat imageInput = cv::imread( imageName );

  //compute gray scale image:
  cv::cvtColor(imageInput, grayImage, cv::COLOR_RGB2GRAY );

Затем получите двоичное изображение через Otsu thresholding. Очень простой материал:

  //get binary image via Otsu:
  cv::Mat binImage;
  cv::threshold( grayImage, binImage, 0, 255, cv::THRESH_OTSU );
  
  //Invert the image:
  binImage = 255 - binImage;

Теперь просто l oop через каждое contour в двоичном изображении и примените соответствующий «Контурный фильтр» . Я буду искать контуры с minimum width/height и aspect ratio между 0.9 и 1.1. Эти параметры в значительной степени устанавливаются вручную. Посмотрим на код:

  //contour filter:
  for( int i = 0; i< contours.size(); i++ ){

    //get the bounding box for each parent countour found:
    cv::Rect bBox = cv::boundingRect( contours[i] );

    //compute aspect ratio:
    float aspectRatio = bBox.height / bBox.width;

    //set the aspect ratio thresholds:
    float lowerAspectRatio = 0.9;
    float upperAspectRatio = 1.1;

    //set the width/height thresholds:
    float minWidth = 8;
    float minHeight = 8;

    if ( (bBox.height > minHeight) && (bBox.width > minWidth) &&
         (aspectRatio >= lowerAspectRatio) && (aspectRatio <= upperAspectRatio) ) {
          cv::Scalar color = cv::Scalar( 0, 255, 0 );
          cv::drawContours( imageInput, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
    }

  }

Это результат:

Как видите, в фильтре отсутствуют некоторые флажки. В частности, спецификация фильтрации может быть слишком строгой, и некоторые флажки могут быть объединены другими символами.

Давайте посмотрим, сможем ли мы улучшить результат, применив сначала некоторую морфологию, чтобы избавиться от контуров, которые не являются частью флажков. Я использую тот факт, что целевые контуры состоят из горизонтальных и вертикальных линий.

Давайте создадим «вертикальные линии» маска, содержащая только вертикальные линии в двоичном изображении.

  //create a vertical structuring element of size 8:
  cv::Mat verticalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(1, 8) );
  
  //apply the morphology operations to isolate the vertical lines:
  cv::Mat verticalMask = binImage.clone();
  cv::erode( verticalMask, verticalMask, verticalStructure, cv::Point(-1, -1) );
  cv::dilate( verticalMask, verticalMask, verticalStructure, cv::Point(-1, -1) );

Я просто применяю morphological opening с вертикальной линией с 8, вот результат:

Я использую те же операции для создания «горизонтальной маски» . На этот раз структурирующий элемент выглядит следующим образом:

  cv::Mat horizontalStructure = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(8, 1) );

Те же морфологические операции создают эту маску:

Мы просто OR две маски для создания окончательной двоичной маски:

Обратите внимание, как все флажки пережили морфологические фильтры. Очень здорово, теперь вычислите контуры и отфильтруйте их соответствующим образом. Я изменил параметр фильтра, давайте воспользуемся blob area и посмотрим, какие результаты мы получим. Я буду искать капли выше и ниже определенного диапазона областей.

  //contour filter:
  for( int i = 0; i< contours.size(); i++ ){

    //get the bounding box for each parent countour found:
    cv::Rect bBox = cv::boundingRect( contours[i] );

    //compute blob area:
    float blobArea = bBox.area();

    //set the area thresholds:
    float minBlobArea = 25;
    float maxBlobArea = 300;

    //set the width/height thresholds:
    float minWidth = 5;
    float minHeight = 5;

    if ( (bBox.height > minHeight) && (bBox.width > minWidth) &&
         (blobArea > minBlobArea ) && (blobArea < maxBlobArea) ) {
          cv::Scalar color = cv::Scalar( 0, 0, 255 );
          cv::drawContours( imageInput, contours, i, color, 2, 8, hierarchy, 0, cv::Point() );
    }

  }

Это окончательный результат, который вы получите:

...