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

Я успешно реализовал пример определения квадрата 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 ]

152 голосов
/ 14 января 2012

Это повторяющаяся тема в 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

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

38 голосов
/ 17 января 2013

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

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

enter image description here enter image description here

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

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

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

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

11 голосов
/ 20 декабря 2017

Ну, я опоздал.


На вашем изображении бумага white, а фон colored.Таким образом, лучше обнаружить бумагу с Saturation(饱和度) каналом в HSV color space.Сначала обратитесь к вики 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: обнаружение листаof Paper / Square Detection
  4. Как использовать `cv2.findContours` в различных версиях OpenCV?
2 голосов
/ 04 августа 2013

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

В основном необходимо сделать следующее:

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

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

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

0 голосов
/ 13 апреля 2016

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

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

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

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

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