В формате OpenCV на C++и OBJ-с: обнаруживать лист бумаги / Определение площади



Я успешно реализовал пример квадратного обнаружения 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;
}
859   5  

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 каналах, контраст уже слишком высок). Кроме того, если нет какой-то конкретной проблемы с изменением размера, я бы работал с уменьшенной версией ваших изображений, поскольку они относительно большие, и размер ничего не добавляет к решаемой проблеме. Затем, наконец, ваша проблема решается с помощью медианного фильтра, некоторого базового морфологические инструменты и статистика (в основном для порогового значения Otsu, которое уже сделано для вас).

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

enter image description hereenter 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}]]

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

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


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


основные этапы:

  1. читать BGR
  2. преобразование изображения из bgr to 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)
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. обнаружьте покрашенный этап в изображение
  2. обнаружение края в opencv android
  3. OpenCV C++ / Obj - C: обнаружение листа бумаги / обнаружение квадрата

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.