Я даю свой ответ в 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() );
}
}
Это окончательный результат, который вы получите: