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