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 ответа
Вы можете попробовать использовать 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, чтобы значительно сократить количество строк, обнаруженных после определения порога.
Хау трансформация
Затем преобразование Хафа используется для определения сторон квадрата.
Пересечения
Наконец, мы вычисляем пересечения всех пар линий.
Надеюсь, это поможет!
Я пытался использовать 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()
Как указалАбид в своем ответе, это использовать метод выпуклых. Обратитесь к руководству по C++ для OpenCV по этому методу.
- преобразовать в лабораторное пространство
- использовать Kmeans для 2 кластеров
- обнаружить suqares один внутренний кластер, это решит много вещей в пространстве RGB