OpenCV С++/Obj-C: расширенное определение квадратов

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

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

example

Есть идеи? Я работаю с кодом 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);
                    }
            }
        }
    }
}

Ответ 1

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

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

РЕДАКТИРОВАТЬ: Все эти ответы с кодом и изображениями заставили меня думать, что мой ответ немного не хватает :) Итак, вот реализация того, как вы могли бы сделать это:

#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, которое помогает значительно сократить количество линий, обнаруженных после определения порога.

enter image description here

Хау трансформация

Затем преобразование Хафа используется для определения сторон квадрата. enter image description here

Пересечения

Наконец, мы вычисляем пересечения всех пар линий. enter image description here

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

Ответ 2

Я попытался использовать 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()

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

Ниже приведен результат:

enter image description here

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

Ответ 3

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

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);

JzgbB.jpg

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

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

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

8tAwu.jpg

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

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

Ответ 4

есть ли где-нибудь API-служба, которая предоставляет такую же функциональность?

Ответ 5

  • конвертировать в лабораторное пространство
  • использовать kmeans для 2 кластеров
  • обнаруживает suqares один внутренний кластер, он решит много вещей в пространстве rgb