Определите кадр с квадратами в углах с помощью OpenCV. js - PullRequest
1 голос
/ 17 января 2020

Я играл с созданием сканера заполненной формы с Javascript и OpenCV. js. Что я в основном хочу сделать, так это сфотографировать лист бумаги с заполненной формой на нем и уметь сканировать фотографию и анализировать ответы в форме. Первый шаг - найти форму на картинке и применить перспективное преобразование, чтобы получить «вид сверху» на бумаге. Что я сделал, так это то, что мне удалось получить сценарий, чтобы обнаружить лист бумаги и применить преобразование, чтобы получить его хорошо отсканированное Я сделал это, применив оттенки серого, затем обнаружение краев Канни, итерировал по найденным краям и нашел самый большой с четырьмя углами и предположил, что это моя статья.

enter image description here

Это работает относительно хорошо, но время от времени сценарий запутывается в том, что на самом деле представляет из себя бумага - иногда есть другие прямоугольники, которые обнаруженная и предполагаемая как бумага, иногда фон, на котором сфотографирована бумага, очень светлый, а края нечеткие (недостаточно контрастный). Это действительно разрушает мой поток позже, когда сценарий думает, что нашел документ, но на самом деле это что-то другое. Я хотел бы улучшить эту часть обнаружения бумаги, чтобы всегда быть уверенным, что правильная вещь была обнаружена. Я подумал - давайте добавим пользовательскую рамку вокруг формы, которую будет легче обнаружить, и добавим несколько квадратов в углах (чтобы дважды проверить, соответствует ли найденная рамка 100% той, которую я ищу).

Итак, я создал что-то вроде этого:

enter image description here

Теперь я хотел бы иметь возможность обнаруживать углы рамки и убедиться, что «заполнено» «В углах есть квадраты, чтобы быть уверенными, что это 100% кадр, который я ищу. Подскажите, пожалуйста, как этого добиться с помощью openCV? Это правильный путь к go? Спасибо!

1 Ответ

1 голос
/ 20 января 2020

Я работал над подобной проблемой раньше. Я работаю с C ++ реализацией OpenCV, но у меня есть несколько советов для вас.

Сегментирование статьи

Чтобы добиться лучшей сегментации, попробуйте Изображение Квантование . Этот метод сегментирует изображение в N кластеров , то есть группирует пиксели схожих цветов в группу. Затем эта группа представлена ​​одним цветом.

Преимущество этого метода перед другими, скажем, чистым двоичным порогом 1014 *, состоит в том, что он может идентифицировать несколько распределений цветов - те, которые будут сгруппированы в N кластеров . Проверьте это (извините за ссылки, мне не разрешено размещать прямые изображения):

Результат сегментации изображения

Это поможет вам получить лучшая сегментация вашей бумаги. В реализации используется алгоритм кластеризации, известный как «K-means» (подробнее об этом позже). В моем примере я попробовал 3 кластера и 5 алгоритмов «запуска» (или попыток, поскольку K-means часто выполняется более одного раза).

cv::Mat imageQuantization( cv::Mat inputImage, int numberOfClusters = 3, int iterations = 5 ){

        //step 1 : map the src to the samples
        cv::Mat samples(inputImage.total(), 3, CV_32F);
        auto samples_ptr = samples.ptr<float>(0);
        for( int row = 0; row != inputImage.rows; ++row){
            auto src_begin = inputImage.ptr<uchar>(row);
            auto src_end = src_begin + inputImage.cols * inputImage.channels();
            //auto samples_ptr = samples.ptr<float>(row * src.cols);
            while(src_begin != src_end){
                samples_ptr[0] = src_begin[0];
                samples_ptr[1] = src_begin[1];
                samples_ptr[2] = src_begin[2];
                samples_ptr += 3; src_begin +=3;
            }
        }

        //step 2 : apply kmeans to find labels and centers
        int clusterCount = numberOfClusters; //Number of clusters to split the set by
        cv::Mat labels;
        int attempts = iterations; //Number of times the algorithm is executed using different initial labels
        cv::Mat centers;
        int flags = cv::KMEANS_PP_CENTERS;
        cv::TermCriteria criteria = cv::TermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS,
                                                      10, 0.01 );

        //the call to kmeans:
        cv::kmeans( samples, clusterCount, labels, criteria, attempts, flags, centers );

        //step 3 : map the centers to the output
        cv::Mat clusteredImage( inputImage.size(), inputImage.type() );
        for( int row = 0; row != inputImage.rows; ++row ){
            auto clusteredImageBegin = clusteredImage.ptr<uchar>(row);
            auto clusteredImageEnd = clusteredImageBegin + clusteredImage.cols * 3;
            auto labels_ptr = labels.ptr<int>(row * inputImage.cols);

            while( clusteredImageBegin != clusteredImageEnd ){
                int const cluster_idx = *labels_ptr;
                auto centers_ptr = centers.ptr<float>(cluster_idx);
                clusteredImageBegin[0] = centers_ptr[0];
                clusteredImageBegin[1] = centers_ptr[1];
                clusteredImageBegin[2] = centers_ptr[2];
                clusteredImageBegin += 3; ++labels_ptr;
            }
        }   

        //return the output:
        return clusteredImage;
}

Обратите внимание, что алгоритм также создает две дополнительные матрицы. «Метки» - фактические пиксели, помеченные целым числом, которое идентифицирует их кластер. «Центры» - это средние значения для каждого кластера.

Определение краев

Теперь это тривиально запустите Edge Detector для этого сегментированного изображения. Давайте попробуем Кэнни. Параметры, конечно же, могут быть отрегулированы вами. Здесь я попробовал нижний порог 0f 30 и верхний порог из 90. Довольно стандартный, просто убедитесь, что верхний порог соответствует условию = 3 * LowerThreshold, согласно Canny предложения. Вот результат:

Результат обнаружения края

    cv::Mat testEdges;
    float lowerThreshold = 30;
    float upperThreshold = 3 * lowerThreshold;
    cv::Canny( testSegmented, testEdges, lowerThreshold, upperThreshold );

Обнаружение линий

Хорошо. Хотите обнаружить линии, произведенные детектором края? Здесь есть как минимум 2 варианта. Первое и самое простое: используйте детектор линии Хафа . Однако, как вы наверняка видели, настройка Hough для поиска линий, которые вы действительно ищете, может быть сложной.

Одним из возможных решений для фильтрации строк, возвращаемых Hough, является запуск «углового фильтра». , так как мы ищем только (близко к) вертикальные и горизонтальные линии. Вы также можете фильтровать строки по длине.

Этот фрагмент кода дает представление о том, что вам нужно реализовать фильтр: // Запустите детектор строк Hough: cv :: HoughLinesP (grad, linesP, 1, CV_PI / 180, minVotes, minLineLength, maxLineGap);

    // Process the points (lines)
    for( size_t i = 0; i < linesP.size(); i++ ) //points are stored in linesP
    {
        //get the line
        cv::Vec4i l = linesP[i]; //get the line

        //get the points:
        cv::Point startPoint = cv::Point( l[0], l[1] );
        cv::Point endPoint = cv::Point( l[2], l[3] );

        //filter horizontal & vertical:
        float dx = abs(startPoint.x - endPoint.x);
        float dy = abs(startPoint.y - endPoint.y);

        //angle filtering, delta y and delta x
        if ( (dy < maxDy) || (dx < maxDx) ){
          //got my target lines!
        }
    }

В приведенном выше коде я фактически работаю с линейными компонентами, а не с углами. Итак, мои «угловые» ограничения определяются 2 минимальной длиной компонента : maxDy - максимальной длиной «дельты» по оси y, а также maxDx для оси х.

Другое решение для обнаружения линий состоит в том, чтобы использовать тот факт, что вы просматриваете только линии, у которых УГЛОВ или около 90 градусов между ними. Вы можете запустить морфологический фильтр для обнаружения этих «паттернов» с помощью операции попадания или пропуска :)

В любом случае, возвращаясь к Хау, это обнаружение я получаю без слишком большой настройки параметров и после применения линейного фильтра угла / длины:

Обнаружение линии после фильтрации угла

Охлаждение. Зеленые точки представляют начальную и конечную точки линий. Как видите, их куча. Как мы можем «объединить» их? Что если мы вычислим среднее из этих точек? Хорошо, но мы должны получить среднее значение линий PER «квадрант». Как вы видите на следующем рисунке, я разделил входное изображение на 4 квадранта (желтые линии):

Определение квадранта (желтые линии)

Каждый из Квадранты, к счастью, будут содержать точки, описывающие угол статьи. Для каждого из этих квадрантов проверьте, какие точки попадают в данный квадрант, и вычислите их среднее значение. Это общая идея.

Это достаточно код для написания. К счастью, если мы немного изучим проблему, то увидим, что все зеленые точки имеют тенденцию к CLUSTER в некоторых очень определенных областях (или, как мы говорили ранее, в «квадрантах»). Введите K- означает снова.

K-средства будут группировать данные одинакового значения независимо от того, что. Это могут быть пиксели, это могут быть пространственные точки, это может быть что угодно, просто дайте ему набор данных и количество кластеров, которые вы хотите, и он будет выплевывать найденные кластеры и СРЕДСТВА СКАЗАННЫХ КЛАСТЕРОВ - Ницца!

Если я запускаю K-means с точками линий, возвращенными Hough, я получаю результат, показанный на последнем изображении. Я также отбрасывал очки, которые слишком далеки от среднего. Средство точек возвращается через матрицу "центров", и здесь они отображаются оранжевым цветом - это довольно близко!

Надеюсь, что это вам поможет! :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...