Detecting the center and angle of rectangles in an image using Opencv

I have an image as shown below:

An example of an image containing many rectangle outlines

I need to find out the number of rectangles, the center of each rectangle, and measure the angle between the axis parallel to the long edge of the rectangle passing through the center, and measure the angle counterclockwise from the horizontal. I recognized the number of rectangles in the image. I hit the center detection and angle of reflection. A sense of center through moments does not give me the right answer.

My code is:

import cv2 import numpy as np import sys img = cv2.imread(str(sys.argv[1]),0) ret,thresh = cv2.threshold(img,127,255,0) contours,hierarchy = cv2.findContours(thresh,1,2) for contour in contours: area = cv2.contourArea(contour) if area>100000: contours.remove(contour) cnt = contours[0] epsilon = 0.02*cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,epsilon,True) print 'No of rectangles',len(approx) #finding the centre of the contour M = cv2.moments(cnt) cx = int(M['m10']/M['m00']) cy = int(M['m01']/M['m00']) print cx,cy 
+6
source share
3 answers

So you can do this with the minAreaRect openCV function. It is written in C ++, but you can probably adapt it easily, since almost all OpenCV functions were used.

  cv::Mat input = cv::imread("../inputData/rectangles.png"); cv::Mat gray; cv::cvtColor(input,gray,CV_BGR2GRAY); // since your image has compression artifacts, we have to threshold the image int threshold = 200; cv::Mat mask = gray > threshold; cv::imshow("mask", mask); // extract contours std::vector<std::vector<cv::Point> > contours; cv::findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); for(int i=0; i<contours.size(); ++i) { // fit bounding rectangle around contour cv::RotatedRect rotatedRect = cv::minAreaRect(contours[i]); // read points and angle cv::Point2f rect_points[4]; rotatedRect.points( rect_points ); float angle = rotatedRect.angle; // angle // read center of rotated rect cv::Point2f center = rotatedRect.center; // center // draw rotated rect for(unsigned int j=0; j<4; ++j) cv::line(input, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,255,0)); // draw center and print text std::stringstream ss; ss << angle; // convert float to string cv::circle(input, center, 5, cv::Scalar(0,255,0)); // draw center cv::putText(input, ss.str(), center + cv::Point2f(-25,25), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(255,0,255)); // print angle } 

which leads to this image:

enter image description here

as you can see, the corners are probably not what you want (because they randomly use a longer or smaller line as a reference). Instead, you can extract the longer sides of the rectangles and calculate the angle manually.

If you select the longer edge of the rotating rectangles and calculate the angle from it, it will look like this:

 // choose the longer edge of the rotated rect to compute the angle cv::Point2f edge1 = cv::Vec2f(rect_points[1].x, rect_points[1].y) - cv::Vec2f(rect_points[0].x, rect_points[0].y); cv::Point2f edge2 = cv::Vec2f(rect_points[2].x, rect_points[2].y) - cv::Vec2f(rect_points[1].x, rect_points[1].y); cv::Point2f usedEdge = edge1; if(cv::norm(edge2) > cv::norm(edge1)) usedEdge = edge2; cv::Point2f reference = cv::Vec2f(1,0); // horizontal edge angle = 180.0f/CV_PI * acos((reference.x*usedEdge.x + reference.y*usedEdge.y) / (cv::norm(reference) *cv::norm(usedEdge))); 

giving this result, which should be what you are looking for!

enter image description here

EDIT: It looks like op is not using the input image that it placed, since the supporting rectangular centers lie outside the image.

Using this input (manually scaled, but probably still not optimal):

enter image description here

I get these results (blue dots are the centers of the support rectangles provided by the operator):

enter image description here

Comparison of link with detection:

 reference (x,y,angle) detection (x,y,angle) (320,240,0) (320, 240, 180) // angle 180 is equal to angle 0 for lines (75,175,90) (73.5, 174.5, 90) (279,401,170) (279.002, 401.824, 169.992) (507,379,61) (507.842, 379.75, 61.1443) (545,95,135) (545.75, 94.25, 135) (307,79,37) (306.756, 77.8384, 37.1042) 

I would really like to see the REAL input image, perhaps the result will be even better.

+8
source

Here's how you can do it:

  • Marking connected components to detect each pattern (in your case, rectangles)
  • Separate templates in different images
  • (optional) if the template does not have all the rectangles, then use shape indices to distinguish between them.
  • Calculate the main axis using the main component analysis (PCA), this will give you the angle you are looking for.
+2
source

approx = cv2.approxPolyDP (cnt, epsilon, True) creates an approximated polygon of a given closed loop. The line segments in the polygon are of variable length, which leads to an incorrect calculation of the moment, since it expects the points to be selected from a regular grid to give you the correct center.

There are three solutions to your problem:

  • Use the moments of the original contours before calling the method to approximate the polygon.
  • Use drawContours to create a mask of areas within each closed path, and then use the moments of the generated mask to calculate the center.
  • Example points at a unit distance along each line segment of your closed polygon and use the resulting point collections to calculate the moments themselves. That should give you the same focus.
+1
source

All Articles