OpenCV С++/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;
}

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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}]]

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

Ответ 3

Ну, я опаздываю.


На вашем изображении бумага white, а colored фона. Таким образом, лучше обнаружить бумагу в канале Saturation(饱和度) в HSV color space. Сначала обратитесь к wiki HSL_and_HSV. Затем я скопирую большинство идей из моего ответа в этом Обнаружении цветного сегмента в изображении.


Основные шаги:

  1. Чтение в BGR
  2. Преобразование изображения из bgr в hsv пространстве
  3. Порог канала S
  4. Затем найдите максимальный внешний контур (или сделайте Canny, или HoughLines как вам нравится, я выбираю findContours), примерно для того, чтобы получить углы.

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

enter image description here


Код Python (Python 3.5 + OpenCV 3.3):

#!/usr/bin/python3
# 2017.12.20 10:47:28 CST
# 2017.12.20 11:29:30 CST

import cv2
import numpy as np

##(1) read into  bgr-space
img = cv2.imread("test2.jpg")

##(2) convert to hsv-space, then split the channels
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)

##(3) threshold the S channel using adaptive method('THRESH_OTSU') or fixed thresh
th, threshed = cv2.threshold(s, 50, 255, cv2.THRESH_BINARY_INV)

##(4) find all the external contours on the threshed S
#_, cnts, _ = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

canvas  = img.copy()
#cv2.drawContours(canvas, cnts, -1, (0,255,0), 1)

## sort and choose the largest contour
cnts = sorted(cnts, key = cv2.contourArea)
cnt = cnts[-1]

## approx the contour, so the get the corner points
arclen = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02* arclen, True)
cv2.drawContours(canvas, [cnt], -1, (255,0,0), 1, cv2.LINE_AA)
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA)

## Ok, you can see the result as tag(6)
cv2.imwrite("detected.png", canvas)

Похожие ответы:

  1. Как обнаружить цветные пятна на изображении с помощью OpenCV?
  2. Обнаружение краев на цветном фоне с использованием OpenCV
  3. OpenCV C++/Obj-C: Обнаружение листа бумаги/Квадратное обнаружение
  4. Как использовать 'cv2.findContours' в разных версиях OpenCV?

Ответ 4

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

В основном, что нужно сделать:

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

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

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

Ответ 5

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

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

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

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