OpenCV C++ / Obj - C: обнаружение листа бумаги / обнаружение квадрата

Я успешно реализовал пример обнаружения квадрата OpenCV в моем тестовом приложении, но теперь нужно отфильтровать вывод, потому что это довольно грязно - или мой код неверен?

меня интересует четыре угловые точки бумаги для уменьшения перекоса (например,это) и дальнейшей обработке ...

Вход И Выход: Input & Output

Оригинал изображение:

клик

код:

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

- (std::vector<std::vector<cv::Point> >)findSquaresInImage:(cv::Mat)_image
{
    std::vector<std::vector<cv::Point> > squares;
    cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray;
    int thresh = 50, N = 11;
    cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2));
    cv::pyrUp(pyr, timg, _image.size());
    std::vector<std::vector<cv::Point> > contours;
    for( int c = 0; c < 3; c++ ) {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);
        for( int l = 0; l < N; l++ ) {
            if( l == 0 ) {
                cv::Canny(gray0, gray, 0, thresh, 5);
                cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else {
                gray = gray0 >= (l+1)*255/N;
            }
            cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
            std::vector<cv::Point> approx;
            for( size_t i = 0; i < contours.size(); i++ )
            {
                cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
                if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::Mat(approx))) {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    if( maxCosine < 0.3 ) {
                        squares.push_back(approx);
                    }
                }
            }
        }
    }
    return squares;
}

изменить 17/08/2012:

чтобы нарисовать обнаруженные квадраты на изображении, используйте этот код:

cv::Mat debugSquares( std::vector<std::vector<cv::Point> > squares, cv::Mat image )
{
    for ( int i = 0; i< squares.size(); i++ ) {
        // draw contour
        cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector<cv::Vec4i>(), 0, cv::Point());

        // draw bounding rect
        cv::Rect rect = boundingRect(cv::Mat(squares[i]));
        cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0);

        // draw rotated rect
        cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i]));
        cv::Point2f rect_points[4];
        minRect.points( rect_points );
        for ( int j = 0; j < 4; j++ ) {
            cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue
        }
    }

    return image;
}

5 ответов


это повторяющаяся тема в StackOverflow и поскольку я не смог найти соответствующую реализацию, я решил принять вызов.

я внес некоторые изменения в демонстрацию квадратов, присутствующую в OpenCV, и полученный код C++ ниже способен обнаружить лист бумаги на изображении:

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > contours;

    // find squares in every color plane of the image
    for (int c = 0; c < 3; c++)
    {
        int ch[] = {c, 0};
        mixChannels(&blurred, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Use Canny instead of zero threshold level!
            // Canny helps to catch squares with gradient shading
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); // 

                // Dilate helps to remove potential holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                    gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Find contours and store them in a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Test contours
            vector<Point> approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                    // approximate contour with accuracy proportional
                    // to the contour perimeter
                    approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                    // Note: absolute value of an area is used because
                    // area may be positive or negative - in accordance with the
                    // contour orientation
                    if (approx.size() == 4 &&
                            fabs(contourArea(Mat(approx))) > 1000 &&
                            isContourConvex(Mat(approx)))
                    {
                            double maxCosine = 0;

                            for (int j = 2; j < 5; j++)
                            {
                                    double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                                    maxCosine = MAX(maxCosine, cosine);
                            }

                            if (maxCosine < 0.3)
                                    squares.push_back(approx);
                    }
            }
        }
    }
}

после выполнения этой процедуры лист бумаги будет самым большим квадратом в vector<vector<Point> >:

opencv paper sheet detection

Я позволяю вам написать функция Найти самый большой квадрат. ;)


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

вот что я получаю с вашим образцом изображения и некоторые другие изображения с листом бумаги я нашел вокруг:

enter image description hereenter image description here

медианный фильтр используется для удаления мелких деталей из, теперь серого, изображения. Он, возможно, удалит тонкие линии внутри беловатой бумаги, что хорошо, потому что тогда вы закончите с крошечными подключенными компонентами, которые легко выбросить. После медианы примените морфологический градиент (просто dilation -erosion) и бинаризовать результат с помощью Otsu. Морфологический градиент-хороший метод для сохранения сильных краев, его следует использовать больше. Затем, поскольку этот градиент увеличит ширину контура, примените морфологическое истончение. Теперь вы можете отбросить небольшие компоненты.

на данный момент, вот что мы имеем с правым изображением выше (перед рисованием синего многоугольника), левый не отображается, потому что единственным оставшимся компонентом является тот, который описывает статью:

enter image description here

учитывая примеры, теперь единственной проблемой остается различие между компонентами, которые выглядят как прямоугольники, и другими, которые этого не делают. Это вопрос определения отношения между площадью выпуклой оболочки, содержащей форму, и площадью ее ограничивающего ящика; отношение 0.7 отлично работает для этих примеров. Возможно, Вам также нужно отбросить компоненты, которые находятся внутри бумага, но не в этих примерах с помощью этого метода (тем не менее, сделать этот шаг должно быть очень легко, особенно потому, что это можно сделать через OpenCV напрямую).

Для справки, вот пример кода в Mathematica:

f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"]
f = ImageResize[f, ImageDimensions[f][[1]]/4]
g = MedianFilter[ColorConvert[f, "Grayscale"], 2]
h = DeleteSmallComponents[Thinning[
     Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]]
convexvert = ComponentMeasurements[SelectComponents[
     h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], 
     "ConvexVertices"][[All, 2]]
(* To visualize the blue polygons above: *)
Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], 
     Polygon @@ convexvert}]]

Если есть более разнообразные ситуации, когда прямоугольник бумаги не так хорошо определен, или подход путает его с другими формами - эти ситуации могут произойти по различным причинам, но общей причиной является плохое получение изображения -- затем попробуйте объединить этапы предварительной обработки с работой, описанной в статье "обнаружение прямоугольника на основе оконного преобразования Хоу".


то, что вам нужно-это четырехугольник вместо повернутого прямоугольника. RotatedRect даст вам неправильные результаты. Также вам понадобится перспективная проекция.

в основном то, что должно быть сделано:

  • петля через все сегменты полигона и соединить те, которые почти равны.
  • сортируйте их так, чтобы у вас было 4 самых больших сегмента линии.
  • пересеките эти линии, и у вас есть 4 наиболее вероятных угла точки.
  • преобразуйте матрицу над перспективой, собранной из угловых точек, и соотношением сторон известного объекта.

я реализовал класс Quadrangle который заботится о преобразовании контура в четырехугольник, а также преобразует его в правильную перспективу.

см. рабочую реализацию здесь: Java с использованием OpenCV выравнивания контура


обнаружение листа бумаги-это своего рода старая школа. Если вы хотите заняться обнаружением перекоса, то лучше сразу стремиться к обнаружению текстовой строки. При этом вы получите extremas слева, справа, сверху и снизу. Отбросьте любую графику в изображении, если вы не хотите, а затем сделайте некоторую статистику по сегментам текстовой строки, чтобы найти наиболее встречающийся диапазон углов или, скорее, угол. Вот как вы сузите до хорошего угла наклона. Теперь после этого вы ставите эти параметры под углом наклона и крайности, чтобы deskew и рубить изображение к тому, что требуется.

Что касается текущего требования к изображению, лучше попробовать CV_RETR_EXTERNAL вместо CV_RETR_LIST.

другой метод обнаружения ребер-обучить классификатор случайных лесов на краях бумаги, а затем использовать классификатор для получения карты ребер. Это безусловно надежный метод, но требует подготовки и времени.

случайные леса будут работать с сценариями с низким контрастом для пример белой бумаги на примерно белом фоне.