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


Некоторое время назад я задал вопрос о квадратном обнаружении и karlphillip пришел с приличным результатом.

Теперь я хочу сделать еще один шаг вперед и найти квадраты, края которых не полностью видны. Взгляните на этот пример:

образец

Есть идеи? Я работаю с кодом karlphillips:

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);
                    }
            }
        }
    }
}
4 31

4 ответа:

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

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

EDIT: Все эти ответы с кодом и изображениями заставляли меня думать, что мой ответ был немного недостающим :) Итак, вот реализация того, как вы могли бы сделать это:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

Point2f computeIntersect(Vec2f line1, Vec2f line2);
vector<Point2f> lineToPointPair(Vec2f line);
bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta);

int main(int argc, char* argv[])
{
    Mat occludedSquare = imread("Square.jpg");

    resize(occludedSquare, occludedSquare, Size(0, 0), 0.25, 0.25);

    Mat occludedSquare8u;
    cvtColor(occludedSquare, occludedSquare8u, CV_BGR2GRAY);

    Mat thresh;
    threshold(occludedSquare8u, thresh, 200.0, 255.0, THRESH_BINARY);

    GaussianBlur(thresh, thresh, Size(7, 7), 2.0, 2.0);

    Mat edges;
    Canny(thresh, edges, 66.0, 133.0, 3);

    vector<Vec2f> lines;
    HoughLines( edges, lines, 1, CV_PI/180, 50, 0, 0 );

    cout << "Detected " << lines.size() << " lines." << endl;

    // compute the intersection from the lines detected...
    vector<Point2f> intersections;
    for( size_t i = 0; i < lines.size(); i++ )
    {
        for(size_t j = 0; j < lines.size(); j++)
        {
            Vec2f line1 = lines[i];
            Vec2f line2 = lines[j];
            if(acceptLinePair(line1, line2, CV_PI / 32))
            {
                Point2f intersection = computeIntersect(line1, line2);
                intersections.push_back(intersection);
            }
        }

    }

    if(intersections.size() > 0)
    {
        vector<Point2f>::iterator i;
        for(i = intersections.begin(); i != intersections.end(); ++i)
        {
            cout << "Intersection is " << i->x << ", " << i->y << endl;
            circle(occludedSquare, *i, 1, Scalar(0, 255, 0), 3);
        }
    }

    imshow("intersect", occludedSquare);
    waitKey();

    return 0;
}

bool acceptLinePair(Vec2f line1, Vec2f line2, float minTheta)
{
    float theta1 = line1[1], theta2 = line2[1];

    if(theta1 < minTheta)
    {
        theta1 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    if(theta2 < minTheta)
    {
        theta2 += CV_PI; // dealing with 0 and 180 ambiguities...
    }

    return abs(theta1 - theta2) > minTheta;
}

// the long nasty wikipedia line-intersection equation...bleh...
Point2f computeIntersect(Vec2f line1, Vec2f line2)
{
    vector<Point2f> p1 = lineToPointPair(line1);
    vector<Point2f> p2 = lineToPointPair(line2);

    float denom = (p1[0].x - p1[1].x)*(p2[0].y - p2[1].y) - (p1[0].y - p1[1].y)*(p2[0].x - p2[1].x);
    Point2f intersect(((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].x - p2[1].x) -
                       (p1[0].x - p1[1].x)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom,
                      ((p1[0].x*p1[1].y - p1[0].y*p1[1].x)*(p2[0].y - p2[1].y) -
                       (p1[0].y - p1[1].y)*(p2[0].x*p2[1].y - p2[0].y*p2[1].x)) / denom);

    return intersect;
}

vector<Point2f> lineToPointPair(Vec2f line)
{
    vector<Point2f> points;

    float r = line[0], t = line[1];
    double cos_t = cos(t), sin_t = sin(t);
    double x0 = r*cos_t, y0 = r*sin_t;
    double alpha = 1000;

    points.push_back(Point2f(x0 + alpha*(-sin_t), y0 + alpha*cos_t));
    points.push_back(Point2f(x0 - alpha*(-sin_t), y0 - alpha*cos_t));

    return points;
}

Примечание: основная причина, по которой я изменил размер изображения, заключалась в том, что я мог видеть его на своем экране и ускорить обработку.

Кэнни

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

Введите описание изображения здесь

Преобразование Хоу

Затем преобразование Хоу используется для определения сторон квадрата. Введите описание изображения здесь

Пересечения

Наконец, мы вычисляем пересечения всех пары линий. Введите описание изображения здесь

Надеюсь, это поможет!

Я попытался использовать convex hull method, что довольно просто.

Здесь вы найдете выпуклую оболочку обнаруженного контура. Он удаляет дефекты выпуклости в нижней части бумаги.

Ниже приведен код (в OpenCV-Python):

import cv2
import numpy as np

img = cv2.imread('sof.jpg')
img = cv2.resize(img,(500,500))
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

ret,thresh = cv2.threshold(gray,127,255,0)
contours,hier = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    if cv2.contourArea(cnt)>5000:  # remove small areas like noise etc
        hull = cv2.convexHull(cnt)    # find the convex hull of contour
        hull = cv2.approxPolyDP(hull,0.1*cv2.arcLength(hull,True),True)
        if len(hull)==4:
            cv2.drawContours(img,[hull],0,(0,255,0),2)

cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

(Здесь я не нашел квадрат во всех плоскостях. Сделай это сам, если хочешь.)

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

Введите описание изображения здесь

Я надеюсь, что это то, что вам нужно.

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

Mat new_img = imread(argv[1]);

double thres = 200;
double color = 255;
threshold(new_img, new_img, thres, color, CV_THRESH_BINARY);

imwrite("thres.png", new_img);

Но есть и другие альтернативы, которые могут обеспечить лучший результат. Один из них состоит в том, чтобы исследовать inRange(), и еще один способ - обнаружение через цвет путем преобразования изображения в цветовое пространство HSV.

Этот поток также представляет интерес дискуссия на эту тему.

2-й : после выполнения одной из этих процедур можно попытаться ввести результат непосредственно в find_squares():

Альтернативой find_squares() является реализацияметода ограничивающего прямоугольника , который потенциально может обеспечить более точное обнаружение прямоугольной области (при условии, что у вас есть идеальный результат порога). Я использовал его Здесь и здесь. Стоит отметить, что OpenCV имеет свой собственный учебник по ограничительной коробке .

Другой подход, помимо find_squares(), как указываетAbid на его ответ, заключается в использовании методаconvexHull . Проверьте учебник OpenCV C++ по этому методу для кода.

  1. преобразование в лабораторное пространство
  2. Используйте KME для 2 кластеров
  3. обнаружьте suqares один внутренний кластер, это решит многие проблемы в пространстве rgb