OpenCV C ++ / Obj-C:检测一张纸/方形检测

我在testing应用程序中成功实现了OpenCV方块检测示例,但现在需要过滤输出,因为它很安静 – 或者是我的代码错误?

我对纸张的四个angular落感兴趣,以减less偏斜(像那样 )和进一步的处理…

input输出: 输入输出

原始图像:

点击

码:

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) { double dx1 = pt1.x - pt0.x; double dy1 = pt1.y - pt0.y; double dx2 = pt2.x - pt0.x; double dy2 = pt2.y - pt0.y; return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } - (std::vector<std::vector<cv::Point> >)findSquaresInImage:(cv::Mat)_image { std::vector<std::vector<cv::Point> > squares; cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray; int thresh = 50, N = 11; cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2)); cv::pyrUp(pyr, timg, _image.size()); std::vector<std::vector<cv::Point> > contours; for( int c = 0; c < 3; c++ ) { int ch[] = {c, 0}; mixChannels(&timg, 1, &gray0, 1, ch, 1); for( int l = 0; l < N; l++ ) { if( l == 0 ) { cv::Canny(gray0, gray, 0, thresh, 5); cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1)); } else { gray = gray0 >= (l+1)*255/N; } cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); std::vector<cv::Point> approx; for( size_t i = 0; i < contours.size(); i++ ) { cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true); if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::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); } } } } } return squares; } 

编辑17/08/2012:

要在图像上绘制检测到的正方形,请使用以下代码:

 cv::Mat debugSquares( std::vector<std::vector<cv::Point> > squares, cv::Mat image ) { for ( int i = 0; i< squares.size(); i++ ) { // draw contour cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector<cv::Vec4i>(), 0, cv::Point()); // draw bounding rect cv::Rect rect = boundingRect(cv::Mat(squares[i])); cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0); // draw rotated rect cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i])); cv::Point2f rect_points[4]; minRect.points( rect_points ); for ( int j = 0; j < 4; j++ ) { cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue } } return image; } 

这是在Stackoverflow反复出现的主题,因为我无法find相关的实现,我决定接受挑战。

我对OpenCV中的正方形演示进行了一些修改,下面的C ++代码能够检测到图像中的一张纸:

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

执行此过程后,纸张将成为vector<vector<Point> >的最大正方形:

opencv纸张检测

我正在让你写functionfind最大的广场。 ;)

除非有其他要求没有说明,否则我只是简单地将你的彩色图像转换成灰度图并且只能用于这个(不需要在三个通道上工作,目前的对比度已经太高了)。 另外,除非有关于resize的一些具体问题,否则我会使用缩小版本的图像,因为它们相对较大,大小并没有增加解决问题的程度。 然后,最后,用中值滤波器,一些基本的形态学工具和统计(主要用于大津阈值,已经为您完成)来解决您的问题。

下面是我用你的样本图像和其他一些图片,以及我发现的一张纸:

在这里输入图像描述在这里输入图像描述

中值filter用于从当前灰度图像中移除较小的细节。 它可能会删除白色纸张内的细线,这是很好的,因为那么你将以很小的连接组件结束,这很容易丢弃。 中位数后,应用形态学梯度(简单地dilationerosion )并用Otsu对结果进行二值化。 形态梯度是保持强边的好方法,应该多用。 那么,由于这个梯度会增加轮廓宽度,所以应用形态变薄。 现在,您可以放弃小部件。

在这一点上,这里是我们对上面正确的图像(绘制蓝色多边形之前),左边没有显示,因为唯一剩下的组件是描述纸的那个:

在这里输入图像描述

考虑到这些例子,现在剩下的唯一问题就是区分看起来像矩形的组件和没有的组件。 这是确定包含形状的凸包的面积与其边界框的面积之间的比率的问题; 比例0.7对这些例子很好。 也可能出现这样的情况:您也需要丢弃纸张内部的组件,但不要使用这种方法(但是,这一步应该非常容易,尤其是因为可以通过OpenCV直接完成)。

作为参考,这里是Mathematica中的示例代码:

 f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"] f = ImageResize[f, ImageDimensions[f][[1]]/4] g = MedianFilter[ColorConvert[f, "Grayscale"], 2] h = DeleteSmallComponents[Thinning[ Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]] convexvert = ComponentMeasurements[SelectComponents[ h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], "ConvexVertices"][[All, 2]] (* To visualize the blue polygons above: *) Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], Polygon @@ convexvert}]] 

如果纸张的矩形不是很清晰,或者其他形状混淆了这种情况,这种情况可能会由于各种原因而发生,但是常见的原因是图像采集不良,处理步骤与在论文“基于窗口霍夫变换的矩形检测”中描述的工作相同。

你需要的是一个四边形,而不是一个旋转的矩形。 RotatedRect会给你不正确的结果。 你也需要一个透视投影。

基本上必须做的是:

  • 循环遍历所有的多边形分段,并连接那些几乎是平等的。
  • 对它们进行sorting,以便拥有最大的4条线段。
  • 相交这些线,你有4个最有可能的angular点。
  • 在从angular点聚集的angular度和已知物体的纵横比上变换matrix。

我实现了一个类Quadrangle ,它将轮廓转换为四边形转换,并将其转换为正确的视angular。

在这里看到一个工作实现: Java OpenCV去除一个轮廓

检测纸张是有点老派。 如果你想解决歪斜检测问题,那么最好是直接瞄准文本行检测。 有了这个,你会得到左,右,上,下的极值。 如果你不想要的话,丢弃图像中的任何graphics,然后对文本线段进行一些统计,找出最出现的angular度范围或angular度。 这是如何缩小到一个良好的倾斜angular度。 在此之后,您将这些参数设置为倾斜angular度,并将极值进行去偏移,并将图像切分为所需的值。

至于当前的图像要求,最好是尝试CV_RETR_EXTERNAL而不是CV_RETR_LIST。

另一种检测边缘的方法是在纸边上训练随机森林分类器,然后使用分类器来获得边缘地图。 这是一个强有力的方法,但需要训练和时间。

随机森林将在低对比度差异的情况下工作,例如在大致白色背景上的白皮书。