OpenCV C++/Obj-C:高级正方形检测
Posted
技术标签:
【中文标题】OpenCV C++/Obj-C:高级正方形检测【英文标题】:OpenCV C++/Obj-C: Advanced square detection 【发布时间】:2012-05-18 23:31:36 【问题描述】:前段时间我问a question about square detection 和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);
【问题讨论】:
您尝试了哪些修改?至少让我们知道您教授的一些值得探索的方向......这也有助于用几句话概述代码段中使用的过程。 【参考方案1】:您可以尝试使用HoughLines 来检测正方形的四个边。接下来,定位四个生成的线交叉点以检测角点。 Hough transform 对噪声和遮挡相当稳健,因此在这里它可能很有用。此外,here 是一个交互式演示,展示了霍夫变换的工作原理(我认为至少它很酷:)。 Here 是我之前的答案之一,它检测到激光交叉中心显示大部分相同的数学(除了它只找到一个角)。
每边可能会有多条线,但定位交叉点应该有助于确定内点与异常点。找到候选角后,您还可以按面积或多边形的“方形”程度过滤这些候选角。
编辑:所有这些带有代码和图像的答案让我觉得我的答案有点缺乏:) 所以,这里是你如何做到这一点的实现:
#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 边缘检测来帮助大大减少阈值处理后检测到的行数。
霍夫变换
然后使用霍夫变换来检测正方形的边。
十字路口
最后,我们计算所有线对的交点。
希望有帮助!
【讨论】:
+1 可靠的方法,我明天会测试它。我需要一个强大的解决方案,它可以抗噪。 现在代码依靠阈值来隔离正方形。如果您更愿意隔离方形后交叉点检测,则需要开始测试交叉点,可能使用前面提到的 cv::approxPoly。 嗨@mevatron,如何在iOS中做到这一点(目标c)? 感谢您的想法。有多个矩形的场景呢?交叉路口不会造成很多误报吗? 如何在设备坐标中绘制这些角点。目前我能够找到这些不在设备坐标中的 cv::point【参考方案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()
(这里,我还没有在所有平面上找到正方形。如果你愿意,自己做吧。)
下面是我得到的结果:
我希望这是你需要的。
【讨论】:
+1 我也必须赞成这个,因为它很简单。干得好! @abidrahmank 我试图用 C++ 重写这段代码,但一直失败,有什么想法吗? ***.com/questions/13599695/… 如果瓶子是白色的,那么轮廓会延伸到瓶子的轮廓,所以凸包不起作用。 当有多个正方形时,此解决方案可能会失败【参考方案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);
但还有其他替代方案可以提供更好的结果。一种是investigateinRange()
,另一种是通过将图像转换为HSV色彩空间来实现detect through color。
This thread 还提供了关于该主题的兴趣讨论。
第 2 次strong>:在您执行此过程之一后,您可以尝试将结果直接输入到find_squares()
:
find_squares()
的替代方法是实现the bounding box technique,它有可能提供更准确的矩形区域检测(前提是您有完美的阈值结果)。我用过here 和here。值得注意的是,OpenCV 有自己的bounding box tutorial。
Abid 在他的回答中指出,除了find_squares()
之外的另一种方法是使用convexHull 方法。检查 OpenCV 的 C++ tutorial on this method 的代码。
【讨论】:
【参考方案4】:-
转换为实验室空间
对 2 个集群使用 kmeans
检测 suqares 一个内部集群,它将解决 rgb 空间中的许多问题
【讨论】:
这会有什么帮助? 使用命令:cv::cvtColor(image, LABImage, CV_RGB2Lab);在实验室颜色中,您有更多的分离,并且对光线条件不太敏感。以上是关于OpenCV C++/Obj-C:高级正方形检测的主要内容,如果未能解决你的问题,请参考以下文章
有没有像“cvHoughCircles()”这样的opencv函数来进行正方形检测?