基于OpenCV&C++(ObjC++)的正方形检测:角点提取与验证求助
Hey there! You’re already halfway there by extracting the largest region—nice work. Let’s walk through how to grab those four corners and validate if the shape is a square, since you hit a snag at this stage.
Step 1: Refine the Contour & Get 4 Vertices
First, your extracted region might have minor irregularities or small protrusions. We’ll use a convex hull to smooth out the contour, then apply polygon approximation to narrow it down to exactly 4 vertices (the square’s corners):
// Assume you already have the largest contour stored in 'largestContour' std::vector<cv::Point> hull; cv::convexHull(largestContour, hull); // Approximate the convex hull to a simplified polygon std::vector<cv::Point> approx; double epsilon = 0.02 * cv::arcLength(hull, true); // Adjust based on your image scale cv::approxPolyDP(hull, approx, epsilon, true); // Ensure we get a quadrilateral (4 points) if (approx.size() != 4) { // Handle non-quadrilateral case: tweak epsilon or clean up the contour first return; }
Step 2: Sort the Corners (Left-Top, Right-Top, Left-Bottom, Right-Bottom)
Now we need to order the 4 points so we can consistently reference each corner. Here’s a straightforward sorting method based on coordinate values:
// Helper function to sort corners into standard order void sortCorners(std::vector<cv::Point>& corners) { // First sort by x-coordinate (left to right) std::sort(corners.begin(), corners.end(), [](const cv::Point& a, const cv::Point& b) { return a.x < b.x; }); // Split into left and right pairs, then sort each by y-coordinate (top to bottom) std::vector<cv::Point> leftPair = {corners[0], corners[1]}; std::vector<cv::Point> rightPair = {corners[2], corners[3]}; std::sort(leftPair.begin(), leftPair.end(), [](const cv::Point& a, const cv::Point& b) { return a.y < b.y; }); std::sort(rightPair.begin(), rightPair.end(), [](const cv::Point& a, const cv::Point& b) { return a.y < b.y; }); // Reassign to standard order: left-top, right-top, left-bottom, right-bottom corners[0] = leftPair[0]; corners[1] = rightPair[0]; corners[2] = leftPair[1]; corners[3] = rightPair[1]; } // Call the sorter on your approximated points sortCorners(approx);
Step 3: Validate if It’s a Square
You mentioned checking side lengths or angles—let’s cover both. Note: Squares have 90° angles, so I’ll assume the 45° mention was a typo, but I’ll include a note if you meant something else like a rhombus with 45° angles.
// Calculate all four side lengths using Euclidean distance std::vector<double> sideLengths; sideLengths.push_back(cv::norm(approx[0] - approx[1])); // Top side sideLengths.push_back(cv::norm(approx[1] - approx[3])); // Right side sideLengths.push_back(cv::norm(approx[3] - approx[2])); // Bottom side sideLengths.push_back(cv::norm(approx[2] - approx[0])); // Left side // Check if all sides are roughly equal (allow 10% tolerance—adjust for your use case) double avgLength = std::accumulate(sideLengths.begin(), sideLengths.end(), 0.0) / 4; bool sidesMatch = true; for (double len : sideLengths) { if (std::abs(len - avgLength) > 0.1 * avgLength) { sidesMatch = false; break; } } // Check if angles are ~90° using dot product cv::Point vecTop = approx[1] - approx[0]; cv::Point vecLeft = approx[2] - approx[0]; double dotProduct = vecTop.x * vecLeft.x + vecTop.y * vecLeft.y; double angle = std::acos(dotProduct / (cv::norm(vecTop) * cv::norm(vecLeft))) * 180 / CV_PI; bool anglesValid = (std::abs(angle - 90) < 5); // Allow 5° tolerance // Final check for square if (sidesMatch && anglesValid) { std::cout << "Success! This is a square." << std::endl; } else { std::cout << "Not a square—tweak tolerance or clean up the contour further." << std::endl; }
Quick Troubleshooting Tips
- Epsilon issues: If
approxPolyDPisn’t returning 4 points, adjust the epsilon value (try 0.01 to 0.05 * arcLength). - Noisy contours: Run a
cv::morphologyExwith a closing kernel first to fill small holes or smooth edges. - Tolerance tweaks: If your images are low-quality, loosen the side length tolerance (e.g., 15%) or angle tolerance (e.g., 8°).
内容的提问来源于stack exchange,提问作者user9590073

