mirror of
https://github.com/opencv/opencv.git
synced 2024-11-24 11:10:21 +08:00
Merge pull request #25304 from gursimarsingh:geometry_sample_cpp
Geometry C++ sample combining other shape detection samples #25304 Clean Samples #25006 This PR removes adds a new cpp sample (geometry) which combines different methods of finding and drawing shapes in an image. It makes separate samples for convexHull, fitellipse, minAreaRect, minAreaCircle redudant. Shapes can be changed using hotkeys after running the program ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
869016d8b1
commit
3e561d8353
@ -4173,8 +4173,6 @@ The function finds the minimal enclosing circle of a 2D point set using an itera
|
||||
CV_EXPORTS_W void minEnclosingCircle( InputArray points,
|
||||
CV_OUT Point2f& center, CV_OUT float& radius );
|
||||
|
||||
/** @example samples/cpp/minarea.cpp
|
||||
*/
|
||||
|
||||
/** @brief Finds a triangle of minimum area enclosing a 2D point set and returns its area.
|
||||
|
||||
@ -4209,8 +4207,8 @@ The function compares two shapes. All three implemented methods use the Hu invar
|
||||
CV_EXPORTS_W double matchShapes( InputArray contour1, InputArray contour2,
|
||||
int method, double parameter );
|
||||
|
||||
/** @example samples/cpp/convexhull.cpp
|
||||
An example using the convexHull functionality
|
||||
/** @example samples/cpp/geometry.cpp
|
||||
An example program illustrates the use of cv::convexHull, cv::fitEllipse, cv::minEnclosingTriangle, cv::minEnclosingCircle and cv::minAreaRect.
|
||||
*/
|
||||
|
||||
/** @brief Finds the convex hull of a point set.
|
||||
@ -4291,9 +4289,6 @@ of the other, they are not considered nested and an intersection will be found r
|
||||
CV_EXPORTS_W float intersectConvexConvex( InputArray p1, InputArray p2,
|
||||
OutputArray p12, bool handleNested = true );
|
||||
|
||||
/** @example samples/cpp/fitellipse.cpp
|
||||
An example using the fitEllipse technique
|
||||
*/
|
||||
|
||||
/** @brief Fits an ellipse around a set of 2D points.
|
||||
|
||||
@ -4750,10 +4745,6 @@ CV_EXPORTS void polylines(InputOutputArray img, const Point* const* pts, const i
|
||||
int ncontours, bool isClosed, const Scalar& color,
|
||||
int thickness = 1, int lineType = LINE_8, int shift = 0 );
|
||||
|
||||
/** @example samples/cpp/contours2.cpp
|
||||
An example program illustrates the use of cv::findContours and cv::drawContours
|
||||
\image html WindowsQtContoursOutput.png "Screenshot of the program"
|
||||
*/
|
||||
|
||||
/** @example samples/cpp/segment_objects.cpp
|
||||
An example using drawContours to clean up a background segmentation result
|
||||
|
@ -1,96 +0,0 @@
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
static void help(char** argv)
|
||||
{
|
||||
cout
|
||||
<< "\nThis program illustrates the use of findContours and drawContours\n"
|
||||
<< "The original image is put up along with the image of drawn contours\n"
|
||||
<< "Usage:\n";
|
||||
cout
|
||||
<< argv[0]
|
||||
<< "\nA trackbar is put up which controls the contour level from -3 to 3\n"
|
||||
<< endl;
|
||||
}
|
||||
|
||||
const int w = 500;
|
||||
int levels = 3;
|
||||
|
||||
vector<vector<Point> > contours;
|
||||
vector<Vec4i> hierarchy;
|
||||
|
||||
static void on_trackbar(int, void*)
|
||||
{
|
||||
Mat cnt_img = Mat::zeros(w, w, CV_8UC3);
|
||||
int _levels = levels - 3;
|
||||
drawContours( cnt_img, contours, _levels <= 0 ? 3 : -1, Scalar(128,255,255),
|
||||
3, LINE_AA, hierarchy, std::abs(_levels) );
|
||||
|
||||
imshow("contours", cnt_img);
|
||||
}
|
||||
|
||||
int main( int argc, char** argv)
|
||||
{
|
||||
cv::CommandLineParser parser(argc, argv, "{help h||}");
|
||||
if (parser.has("help"))
|
||||
{
|
||||
help(argv);
|
||||
return 0;
|
||||
}
|
||||
Mat img = Mat::zeros(w, w, CV_8UC1);
|
||||
//Draw 6 faces
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
int dx = (i%2)*250 - 30;
|
||||
int dy = (i/2)*150;
|
||||
const Scalar white = Scalar(255);
|
||||
const Scalar black = Scalar(0);
|
||||
|
||||
if( i == 0 )
|
||||
{
|
||||
for( int j = 0; j <= 10; j++ )
|
||||
{
|
||||
double angle = (j+5)*CV_PI/21;
|
||||
line(img, Point(cvRound(dx+100+j*10-80*cos(angle)),
|
||||
cvRound(dy+100-90*sin(angle))),
|
||||
Point(cvRound(dx+100+j*10-30*cos(angle)),
|
||||
cvRound(dy+100-30*sin(angle))), white, 1, 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ellipse( img, Point(dx+150, dy+100), Size(100,70), 0, 0, 360, white, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+115, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+185, dy+70), Size(30,20), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+115, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+185, dy+70), Size(15,15), 0, 0, 360, white, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+115, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+185, dy+70), Size(5,5), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+150, dy+100), Size(10,5), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+150, dy+150), Size(40,10), 0, 0, 360, black, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+27, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 );
|
||||
ellipse( img, Point(dx+273, dy+100), Size(20,35), 0, 0, 360, white, -1, 8, 0 );
|
||||
}
|
||||
//show the faces
|
||||
namedWindow( "image", 1 );
|
||||
imshow( "image", img );
|
||||
//Extract the contours so that
|
||||
vector<vector<Point> > contours0;
|
||||
findContours( img, contours0, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
|
||||
|
||||
contours.resize(contours0.size());
|
||||
for( size_t k = 0; k < contours0.size(); k++ )
|
||||
approxPolyDP(Mat(contours0[k]), contours[k], 3, true);
|
||||
|
||||
namedWindow( "contours", 1 );
|
||||
createTrackbar( "levels+3", "contours", &levels, 7, on_trackbar );
|
||||
|
||||
on_trackbar(0,0);
|
||||
waitKey();
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
static void help(char** argv)
|
||||
{
|
||||
cout << "\nThis sample program demonstrates the use of the convexHull() function\n"
|
||||
<< "Call:\n"
|
||||
<< argv[0] << endl;
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
CommandLineParser parser(argc, argv, "{help h||}");
|
||||
if (parser.has("help"))
|
||||
{
|
||||
help(argv);
|
||||
return 0;
|
||||
}
|
||||
Mat img(500, 500, CV_8UC3);
|
||||
RNG& rng = theRNG();
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int i, count = (unsigned)rng%100 + 1;
|
||||
|
||||
vector<Point> points;
|
||||
|
||||
for( i = 0; i < count; i++ )
|
||||
{
|
||||
Point pt;
|
||||
pt.x = rng.uniform(img.cols/4, img.cols*3/4);
|
||||
pt.y = rng.uniform(img.rows/4, img.rows*3/4);
|
||||
|
||||
points.push_back(pt);
|
||||
}
|
||||
|
||||
vector<Point> hull;
|
||||
convexHull(points, hull, true);
|
||||
|
||||
img = Scalar::all(0);
|
||||
for( i = 0; i < count; i++ )
|
||||
circle(img, points[i], 3, Scalar(0, 0, 255), FILLED, LINE_AA);
|
||||
|
||||
polylines(img, hull, true, Scalar(0, 255, 0), 1, LINE_AA);
|
||||
imshow("hull", img);
|
||||
|
||||
char key = (char)waitKey();
|
||||
if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,311 +0,0 @@
|
||||
/********************************************************************************
|
||||
*
|
||||
*
|
||||
* This program is demonstration for ellipse fitting. Program finds
|
||||
* contours and approximate it by ellipses using three methods.
|
||||
* 1: OpenCV's original method fitEllipse which implements Fitzgibbon 1995 method.
|
||||
* 2: The Approximate Mean Square (AMS) method fitEllipseAMS proposed by Taubin 1991
|
||||
* 3: The Direct least square (Direct) method fitEllipseDirect proposed by Fitzgibbon1999.
|
||||
*
|
||||
* Trackbar specify threshold parameter.
|
||||
*
|
||||
* White lines is contours/input points and the true ellipse used to generate the data.
|
||||
* 1: Blue lines is fitting ellipses using openCV's original method.
|
||||
* 2: Green lines is fitting ellipses using the AMS method.
|
||||
* 3: Red lines is fitting ellipses using the Direct method.
|
||||
*
|
||||
*
|
||||
* Original Author: Denis Burenkov
|
||||
* AMS and Direct Methods Author: Jasper Shemilt
|
||||
*
|
||||
*
|
||||
********************************************************************************/
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
class canvas{
|
||||
public:
|
||||
bool setupQ;
|
||||
cv::Point origin;
|
||||
cv::Point corner;
|
||||
int minDims,maxDims;
|
||||
double scale;
|
||||
int rows, cols;
|
||||
cv::Mat img;
|
||||
|
||||
void init(int minD, int maxD){
|
||||
// Initialise the canvas with minimum and maximum rows and column sizes.
|
||||
minDims = minD; maxDims = maxD;
|
||||
origin = cv::Point(0,0);
|
||||
corner = cv::Point(0,0);
|
||||
scale = 1.0;
|
||||
rows = 0;
|
||||
cols = 0;
|
||||
setupQ = false;
|
||||
}
|
||||
|
||||
void stretch(cv::Point2f min, cv::Point2f max){
|
||||
// Stretch the canvas to include the points min and max.
|
||||
if(setupQ){
|
||||
if(corner.x < max.x){corner.x = (int)(max.x + 1.0);};
|
||||
if(corner.y < max.y){corner.y = (int)(max.y + 1.0);};
|
||||
if(origin.x > min.x){origin.x = (int) min.x;};
|
||||
if(origin.y > min.y){origin.y = (int) min.y;};
|
||||
} else {
|
||||
origin = cv::Point((int)min.x, (int)min.y);
|
||||
corner = cv::Point((int)(max.x + 1.0), (int)(max.y + 1.0));
|
||||
}
|
||||
|
||||
int c = (int)(scale*((corner.x + 1.0) - origin.x));
|
||||
if(c<minDims){
|
||||
scale = scale * (double)minDims/(double)c;
|
||||
} else {
|
||||
if(c>maxDims){
|
||||
scale = scale * (double)maxDims/(double)c;
|
||||
}
|
||||
}
|
||||
int r = (int)(scale*((corner.y + 1.0) - origin.y));
|
||||
if(r<minDims){
|
||||
scale = scale * (double)minDims/(double)r;
|
||||
} else {
|
||||
if(r>maxDims){
|
||||
scale = scale * (double)maxDims/(double)r;
|
||||
}
|
||||
}
|
||||
cols = (int)(scale*((corner.x + 1.0) - origin.x));
|
||||
rows = (int)(scale*((corner.y + 1.0) - origin.y));
|
||||
setupQ = true;
|
||||
}
|
||||
|
||||
void stretch(vector<Point2f> pts)
|
||||
{ // Stretch the canvas so all the points pts are on the canvas.
|
||||
cv::Point2f min = pts[0];
|
||||
cv::Point2f max = pts[0];
|
||||
for(size_t i=1; i < pts.size(); i++){
|
||||
Point2f pnt = pts[i];
|
||||
if(max.x < pnt.x){max.x = pnt.x;};
|
||||
if(max.y < pnt.y){max.y = pnt.y;};
|
||||
if(min.x > pnt.x){min.x = pnt.x;};
|
||||
if(min.y > pnt.y){min.y = pnt.y;};
|
||||
};
|
||||
stretch(min, max);
|
||||
}
|
||||
|
||||
void stretch(cv::RotatedRect box)
|
||||
{ // Stretch the canvas so that the rectangle box is on the canvas.
|
||||
cv::Point2f min = box.center;
|
||||
cv::Point2f max = box.center;
|
||||
cv::Point2f vtx[4];
|
||||
box.points(vtx);
|
||||
for( int i = 0; i < 4; i++ ){
|
||||
cv::Point2f pnt = vtx[i];
|
||||
if(max.x < pnt.x){max.x = pnt.x;};
|
||||
if(max.y < pnt.y){max.y = pnt.y;};
|
||||
if(min.x > pnt.x){min.x = pnt.x;};
|
||||
if(min.y > pnt.y){min.y = pnt.y;};
|
||||
}
|
||||
stretch(min, max);
|
||||
}
|
||||
|
||||
void drawEllipseWithBox(cv::RotatedRect box, cv::Scalar color, int lineThickness)
|
||||
{
|
||||
if(img.empty()){
|
||||
stretch(box);
|
||||
img = cv::Mat::zeros(rows,cols,CV_8UC3);
|
||||
}
|
||||
|
||||
box.center = scale * cv::Point2f(box.center.x - origin.x, box.center.y - origin.y);
|
||||
box.size.width = (float)(scale * box.size.width);
|
||||
box.size.height = (float)(scale * box.size.height);
|
||||
|
||||
ellipse(img, box, color, lineThickness, LINE_AA);
|
||||
|
||||
Point2f vtx[4];
|
||||
box.points(vtx);
|
||||
for( int j = 0; j < 4; j++ ){
|
||||
line(img, vtx[j], vtx[(j+1)%4], color, lineThickness, LINE_AA);
|
||||
}
|
||||
}
|
||||
|
||||
void drawPoints(vector<Point2f> pts, cv::Scalar color)
|
||||
{
|
||||
if(img.empty()){
|
||||
stretch(pts);
|
||||
img = cv::Mat::zeros(rows,cols,CV_8UC3);
|
||||
}
|
||||
for(size_t i=0; i < pts.size(); i++){
|
||||
Point2f pnt = scale * cv::Point2f(pts[i].x - origin.x, pts[i].y - origin.y);
|
||||
img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[0] = (uchar)color[0];
|
||||
img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[1] = (uchar)color[1];
|
||||
img.at<cv::Vec3b>(int(pnt.y), int(pnt.x))[2] = (uchar)color[2];
|
||||
};
|
||||
}
|
||||
|
||||
void drawLabels( std::vector<std::string> text, std::vector<cv::Scalar> colors)
|
||||
{
|
||||
if(img.empty()){
|
||||
img = cv::Mat::zeros(rows,cols,CV_8UC3);
|
||||
}
|
||||
int vPos = 0;
|
||||
for (size_t i=0; i < text.size(); i++) {
|
||||
cv::Scalar color = colors[i];
|
||||
std::string txt = text[i];
|
||||
Size textsize = getTextSize(txt, FONT_HERSHEY_COMPLEX, 1, 1, 0);
|
||||
vPos += (int)(1.3 * textsize.height);
|
||||
Point org((img.cols - textsize.width), vPos);
|
||||
cv::putText(img, txt, org, FONT_HERSHEY_COMPLEX, 1, color, 1, LINE_8);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static void help(char** argv)
|
||||
{
|
||||
cout << "\nThis program is demonstration for ellipse fitting. The program finds\n"
|
||||
"contours and approximate it by ellipses. Three methods are used to find the \n"
|
||||
"elliptical fits: fitEllipse, fitEllipseAMS and fitEllipseDirect.\n"
|
||||
"Call:\n"
|
||||
<< argv[0] << " [image_name -- Default ellipses.jpg]\n" << endl;
|
||||
}
|
||||
|
||||
int sliderPos = 70;
|
||||
|
||||
Mat image;
|
||||
|
||||
bool fitEllipseQ, fitEllipseAMSQ, fitEllipseDirectQ;
|
||||
cv::Scalar fitEllipseColor = Scalar(255, 0, 0);
|
||||
cv::Scalar fitEllipseAMSColor = Scalar( 0,255, 0);
|
||||
cv::Scalar fitEllipseDirectColor = Scalar( 0, 0,255);
|
||||
cv::Scalar fitEllipseTrueColor = Scalar(255,255,255);
|
||||
|
||||
void processImage(int, void*);
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
fitEllipseQ = true;
|
||||
fitEllipseAMSQ = true;
|
||||
fitEllipseDirectQ = true;
|
||||
|
||||
cv::CommandLineParser parser(argc, argv,"{help h||}{@image|ellipses.jpg|}");
|
||||
if (parser.has("help"))
|
||||
{
|
||||
help(argv);
|
||||
return 0;
|
||||
}
|
||||
string filename = parser.get<string>("@image");
|
||||
image = imread(samples::findFile(filename), 0);
|
||||
if( image.empty() )
|
||||
{
|
||||
cout << "Couldn't open image " << filename << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
imshow("source", image);
|
||||
namedWindow("result", WINDOW_NORMAL );
|
||||
|
||||
// Create toolbars. HighGUI use.
|
||||
createTrackbar( "threshold", "result", &sliderPos, 255, processImage );
|
||||
|
||||
processImage(0, 0);
|
||||
|
||||
// Wait for a key stroke; the same function arranges events processing
|
||||
waitKey();
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static bool isGoodBox(const RotatedRect& box) {
|
||||
//size.height >= size.width awalys,only if the pts are on a line or at the same point,size.width=0
|
||||
return (box.size.height <= box.size.width * 30) && (box.size.width > 0);
|
||||
}
|
||||
|
||||
// Define trackbar callback function. This function finds contours,
|
||||
// draws them, and approximates by ellipses.
|
||||
void processImage(int /*h*/, void*)
|
||||
{
|
||||
RotatedRect box, boxAMS, boxDirect;
|
||||
vector<vector<Point> > contours;
|
||||
Mat bimage = image >= sliderPos;
|
||||
|
||||
findContours(bimage, contours, RETR_LIST, CHAIN_APPROX_NONE);
|
||||
|
||||
canvas paper;
|
||||
paper.init(int(0.8*MIN(bimage.rows, bimage.cols)), int(1.2*MAX(bimage.rows, bimage.cols)));
|
||||
paper.stretch(cv::Point2f(0.0f, 0.0f), cv::Point2f((float)(bimage.cols+2.0), (float)(bimage.rows+2.0)));
|
||||
|
||||
std::vector<std::string> text;
|
||||
std::vector<cv::Scalar> color;
|
||||
|
||||
if (fitEllipseQ) {
|
||||
text.push_back("OpenCV");
|
||||
color.push_back(fitEllipseColor);
|
||||
}
|
||||
if (fitEllipseAMSQ) {
|
||||
text.push_back("AMS");
|
||||
color.push_back(fitEllipseAMSColor);
|
||||
}
|
||||
if (fitEllipseDirectQ) {
|
||||
text.push_back("Direct");
|
||||
color.push_back(fitEllipseDirectColor);
|
||||
}
|
||||
paper.drawLabels(text, color);
|
||||
|
||||
int margin = 2;
|
||||
vector< vector<Point2f> > points;
|
||||
for(size_t i = 0; i < contours.size(); i++)
|
||||
{
|
||||
size_t count = contours[i].size();
|
||||
if( count < 6 )
|
||||
continue;
|
||||
|
||||
Mat pointsf;
|
||||
Mat(contours[i]).convertTo(pointsf, CV_32F);
|
||||
|
||||
vector<Point2f>pts;
|
||||
for (int j = 0; j < pointsf.rows; j++) {
|
||||
Point2f pnt = Point2f(pointsf.at<float>(j,0), pointsf.at<float>(j,1));
|
||||
if ((pnt.x > margin && pnt.y > margin && pnt.x < bimage.cols-margin && pnt.y < bimage.rows-margin)) {
|
||||
if(j%20==0){
|
||||
pts.push_back(pnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
points.push_back(pts);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < points.size(); i++)
|
||||
{
|
||||
vector<Point2f> pts = points[i];
|
||||
|
||||
//At least 5 points can fit an ellipse
|
||||
if (pts.size()<5) {
|
||||
continue;
|
||||
}
|
||||
if (fitEllipseQ) {
|
||||
box = fitEllipse(pts);
|
||||
if (isGoodBox(box)) {
|
||||
paper.drawEllipseWithBox(box, fitEllipseColor, 3);
|
||||
}
|
||||
}
|
||||
if (fitEllipseAMSQ) {
|
||||
boxAMS = fitEllipseAMS(pts);
|
||||
if (isGoodBox(boxAMS)) {
|
||||
paper.drawEllipseWithBox(boxAMS, fitEllipseAMSColor, 2);
|
||||
}
|
||||
}
|
||||
if (fitEllipseDirectQ) {
|
||||
boxDirect = fitEllipseDirect(pts);
|
||||
if (isGoodBox(boxDirect)){
|
||||
paper.drawEllipseWithBox(boxDirect, fitEllipseDirectColor, 1);
|
||||
}
|
||||
}
|
||||
|
||||
paper.drawPoints(pts, fitEllipseTrueColor);
|
||||
}
|
||||
|
||||
imshow("result", paper.img);
|
||||
}
|
268
samples/cpp/geometry.cpp
Normal file
268
samples/cpp/geometry.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* This program demonstrates various shape fitting techniques using OpenCV.
|
||||
* It reads an image, applies binary thresholding, and then detects contours.
|
||||
*
|
||||
* For each contour, it fits and draws several geometric shapes including
|
||||
* convex hulls, minimum enclosing circles, rectangles, triangles, and ellipses
|
||||
* using different fitting methods:
|
||||
* 1: OpenCV's original method fitEllipse which implements Fitzgibbon 1995 method.
|
||||
* 2: The Approximate Mean Square (AMS) method fitEllipseAMS proposed by Taubin 1991
|
||||
* 3: The Direct least square (Direct) method fitEllipseDirect proposed by Fitzgibbon1999
|
||||
*
|
||||
* The results are displayed with unique colors
|
||||
* for each shape and fitting method for clear differentiation.
|
||||
*
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
const string hot_keys =
|
||||
"\n\nHot keys: \n"
|
||||
"\tESC - quit the program\n"
|
||||
"\tq - quit the program\n"
|
||||
"\tc - make the circle\n"
|
||||
"\tr - make the rectangle\n"
|
||||
"\th - make the convexhull\n"
|
||||
"\tt - make the triangle\n"
|
||||
"\te - make the ellipse\n"
|
||||
"\ta - make all shapes\n"
|
||||
"\t0 - use OpenCV's method for ellipse fitting\n"
|
||||
"\t1 - use Approximate Mean Square (AMS) method for ellipse fitting \n"
|
||||
"\t2 - use Direct least square (Direct) method for ellipse fitting\n";
|
||||
|
||||
static void help(char** argv)
|
||||
{
|
||||
cout << "\nThis program demonstrates various shape fitting techniques on a set of points using functions: \n"
|
||||
<< "minAreaRect(), minEnclosingTriangle(), minEnclosingCircle(), convexHull(), and ellipse().\n\n"
|
||||
<< "Usage: " << argv[0] << " [--image_name=<image_path> Default: ellipses.jpg]\n\n";
|
||||
cout << hot_keys << endl;
|
||||
}
|
||||
|
||||
void processImage(int, void*);
|
||||
void drawShapes(Mat &img, const vector<Point> &points, int ellipseMethod, string shape);
|
||||
void drawConvexHull(Mat &img, const vector<Point> &points);
|
||||
void drawMinAreaRect(Mat &img, const vector<Point> &points);
|
||||
void drawFittedEllipses(Mat &img, const vector<Point> &points, int ellipseMethod);
|
||||
void drawMinEnclosingCircle(Mat &img, const vector<Point> &points);
|
||||
void drawMinEnclosingTriangle(Mat &img, const vector<Point> &points);
|
||||
|
||||
// Shape fitting options
|
||||
Mat image;
|
||||
enum EllipseFittingMethod {
|
||||
OpenCV_Method,
|
||||
AMS_Method,
|
||||
Direct_Method
|
||||
};
|
||||
|
||||
struct UserData {
|
||||
int sliderPos = 70;
|
||||
string shape = "--all";
|
||||
int ellipseMethod = OpenCV_Method;
|
||||
};
|
||||
|
||||
const char* keys =
|
||||
"{help h | | Show help message }"
|
||||
"{@image |ellipses.jpg| Path to input image file }";
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
cv::CommandLineParser parser(argc, argv, keys);
|
||||
help(argv);
|
||||
|
||||
if (parser.has("help"))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
UserData userData; // variable to pass all the necessary values to trackbar callback
|
||||
|
||||
string filename = parser.get<string>("@image");
|
||||
image = imread(samples::findFile(filename), IMREAD_COLOR); // Read the image from the specified path
|
||||
|
||||
if (image.empty()) {
|
||||
cout << "Could not open or find the image" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
namedWindow("Shapes", WINDOW_AUTOSIZE); // Create a window to display the results
|
||||
createTrackbar("Threshold", "Shapes", NULL, 255, processImage, &userData); // Create a threshold trackbar
|
||||
setTrackbarPos("Threshold", "Shapes", userData.sliderPos);
|
||||
|
||||
for(;;) {
|
||||
char key = (char)waitKey(0); // Listen for a key press
|
||||
if (key == 'q' || key == 27) break; // Exit the loop if 'q' or ESC is pressed
|
||||
|
||||
switch (key) {
|
||||
case 'h': userData.shape = "--convexhull"; break;
|
||||
case 'a': userData.shape = "--all"; break;
|
||||
case 't': userData.shape = "--triangle"; break;
|
||||
case 'c': userData.shape = "--circle"; break;
|
||||
case 'e': userData.shape = "--ellipse"; break;
|
||||
case 'r': userData.shape = "--rectangle"; break;
|
||||
case '0': userData.ellipseMethod = OpenCV_Method; break;
|
||||
case '1': userData.ellipseMethod = AMS_Method; break;
|
||||
case '2': userData.ellipseMethod = Direct_Method; break;
|
||||
default: break; // Do nothing for other keys
|
||||
}
|
||||
|
||||
processImage(userData.sliderPos, &userData); // Process the image with the current settings
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Function to draw the minimum enclosing circle around given points
|
||||
void drawMinEnclosingCircle(Mat &img, const vector<Point> &points) {
|
||||
Point2f center;
|
||||
float radius = 0;
|
||||
minEnclosingCircle(points, center, radius); // Find the enclosing circle
|
||||
// Draw the circle
|
||||
circle(img, center, cvRound(radius), Scalar(0, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
|
||||
// Function to draw the minimum enclosing triangle around given points
|
||||
void drawMinEnclosingTriangle(Mat &img, const vector<Point> &points) {
|
||||
vector<Point> triangle;
|
||||
minEnclosingTriangle(points, triangle); // Find the enclosing triangle
|
||||
|
||||
if (triangle.size() != 3) {
|
||||
return;
|
||||
}
|
||||
// Use polylines to draw the triangle. The 'true' argument closes the triangle.
|
||||
polylines(img, triangle, true, Scalar(255, 0, 0), 2, LINE_AA);
|
||||
|
||||
}
|
||||
|
||||
// Function to draw the minimum area rectangle around given points
|
||||
void drawMinAreaRect(Mat &img, const vector<Point> &points) {
|
||||
RotatedRect box = minAreaRect(points); // Find the minimum area rectangle
|
||||
Point2f vtx[4];
|
||||
box.points(vtx);
|
||||
// Convert Point2f to Point because polylines expects a vector of Point
|
||||
vector<Point> rectPoints;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rectPoints.push_back(Point(cvRound(vtx[i].x), cvRound(vtx[i].y)));
|
||||
}
|
||||
|
||||
// Use polylines to draw the rectangle. The 'true' argument closes the loop, drawing a rectangle.
|
||||
polylines(img, rectPoints, true, Scalar(0, 255, 0), 2, LINE_AA);
|
||||
}
|
||||
|
||||
// Function to draw the convex hull of given points
|
||||
void drawConvexHull(Mat &img, const vector<Point> &points) {
|
||||
vector<Point> hull;
|
||||
convexHull(points, hull, false); // Find the convex hull
|
||||
// Draw the convex hull
|
||||
polylines(img, hull, true, Scalar(255, 255, 0), 2, LINE_AA);
|
||||
}
|
||||
|
||||
inline static bool isGoodBox(const RotatedRect& box) {
|
||||
//size.height >= size.width awalys,only if the pts are on a line or at the same point,size.width=0
|
||||
return (box.size.height <= box.size.width * 30) && (box.size.width > 0);
|
||||
}
|
||||
|
||||
// Function to draw fitted ellipses using different methods
|
||||
void drawFittedEllipses(Mat &img, const vector<Point> &points, int ellipseMethod) {
|
||||
switch (ellipseMethod) {
|
||||
case OpenCV_Method: // Standard ellipse fitting
|
||||
{
|
||||
RotatedRect fittedEllipse = fitEllipse(points);
|
||||
if (isGoodBox(fittedEllipse)) {
|
||||
ellipse(img, fittedEllipse, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
putText(img, "OpenCV", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
break;
|
||||
case AMS_Method: // AMS ellipse fitting
|
||||
{
|
||||
RotatedRect fittedEllipseAMS = fitEllipseAMS(points);
|
||||
if (isGoodBox(fittedEllipseAMS)) {
|
||||
ellipse(img, fittedEllipseAMS, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
putText(img, "AMS", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
break;
|
||||
case Direct_Method: // Direct ellipse fitting
|
||||
{
|
||||
RotatedRect fittedEllipseDirect = fitEllipseDirect(points);
|
||||
if (isGoodBox(fittedEllipseDirect)) {
|
||||
ellipse(img, fittedEllipseDirect, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
putText(img, "Direct", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
break;
|
||||
default: // Default case falls back to OpenCV method
|
||||
{
|
||||
RotatedRect fittedEllipse = fitEllipse(points);
|
||||
if (isGoodBox(fittedEllipse)) {
|
||||
ellipse(img, fittedEllipse, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
}
|
||||
putText(img, "OpenCV (default)", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
|
||||
cout << "Warning: Invalid ellipseMethod value. Falling back to default OpenCV method." << endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to draw shapes
|
||||
void drawShapes(Mat &img, const vector<Point> &points, int ellipseMethod, string shape) {
|
||||
if (shape == "--circle") {
|
||||
drawMinEnclosingCircle(img, points);
|
||||
} else if (shape == "--triangle") {
|
||||
drawMinEnclosingTriangle(img, points);
|
||||
} else if (shape == "--rectangle") {
|
||||
drawMinAreaRect(img, points);
|
||||
} else if (shape == "--convexhull") {
|
||||
drawConvexHull(img, points);
|
||||
} else if (shape == "--ellipse"){
|
||||
drawFittedEllipses(img, points, ellipseMethod);
|
||||
}
|
||||
else if (shape == "--all") {
|
||||
drawMinEnclosingCircle(img, points);
|
||||
drawMinEnclosingTriangle(img, points);
|
||||
drawMinAreaRect(img, points);
|
||||
drawConvexHull(img, points);
|
||||
drawFittedEllipses(img, points, ellipseMethod);
|
||||
}
|
||||
}
|
||||
|
||||
// Main function to process the image based on the current trackbar position
|
||||
void processImage(int position, void* userData){
|
||||
|
||||
UserData* data = static_cast<UserData*>(userData);
|
||||
|
||||
data->sliderPos = position;
|
||||
|
||||
Mat processedImg = image.clone(); // Clone the original image for processing
|
||||
Mat gray;
|
||||
cvtColor(processedImg, gray, COLOR_BGR2GRAY); // Convert to grayscale
|
||||
threshold(gray, gray, data->sliderPos, 255, THRESH_BINARY); // Apply binary threshold
|
||||
|
||||
Mat filteredImg;
|
||||
medianBlur(gray, filteredImg, 3);
|
||||
|
||||
vector<vector<Point>> contours;
|
||||
findContours(filteredImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); // Find contours
|
||||
|
||||
if (contours.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
imshow("Mask", filteredImg); // Show the mask
|
||||
for (size_t i = 0; i < contours.size(); ++i) {
|
||||
if (contours[i].size() < 5) { // Check if the contour has enough points
|
||||
continue;
|
||||
}
|
||||
drawShapes(processedImg, contours[i], data->ellipseMethod, data->shape);
|
||||
}
|
||||
|
||||
imshow("Shapes", processedImg); // Display the processed image with fitted shapes
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
#include "opencv2/highgui.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace cv;
|
||||
using namespace std;
|
||||
|
||||
static void help()
|
||||
{
|
||||
cout << "This program demonstrates finding the minimum enclosing box, triangle or circle of a set\n"
|
||||
<< "of points using functions: minAreaRect() minEnclosingTriangle() minEnclosingCircle().\n"
|
||||
<< "Random points are generated and then enclosed.\n\n"
|
||||
<< "Press ESC, 'q' or 'Q' to exit and any other key to regenerate the set of points.\n\n";
|
||||
}
|
||||
|
||||
int main( int /*argc*/, char** /*argv*/ )
|
||||
{
|
||||
help();
|
||||
|
||||
Mat img(500, 500, CV_8UC3, Scalar::all(0));
|
||||
RNG& rng = theRNG();
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int i, count = rng.uniform(1, 101);
|
||||
vector<Point> points;
|
||||
|
||||
// Generate a random set of points
|
||||
for( i = 0; i < count; i++ )
|
||||
{
|
||||
Point pt;
|
||||
pt.x = rng.uniform(img.cols/4, img.cols*3/4);
|
||||
pt.y = rng.uniform(img.rows/4, img.rows*3/4);
|
||||
|
||||
points.push_back(pt);
|
||||
}
|
||||
|
||||
// Find the minimum area enclosing bounding box
|
||||
Point2f vtx[4];
|
||||
RotatedRect box = minAreaRect(points);
|
||||
box.points(vtx);
|
||||
|
||||
// Find the minimum area enclosing triangle
|
||||
vector<Point2f> triangle;
|
||||
minEnclosingTriangle(points, triangle);
|
||||
|
||||
// Find the minimum area enclosing circle
|
||||
Point2f center;
|
||||
float radius = 0;
|
||||
minEnclosingCircle(points, center, radius);
|
||||
|
||||
img = Scalar::all(0);
|
||||
|
||||
// Draw the points
|
||||
for( i = 0; i < count; i++ )
|
||||
circle( img, points[i], 3, Scalar(0, 0, 255), FILLED, LINE_AA );
|
||||
|
||||
// Draw the bounding box
|
||||
for( i = 0; i < 4; i++ )
|
||||
line(img, vtx[i], vtx[(i+1)%4], Scalar(0, 255, 0), 1, LINE_AA);
|
||||
|
||||
// Draw the triangle
|
||||
for( i = 0; i < 3; i++ )
|
||||
line(img, triangle[i], triangle[(i+1)%3], Scalar(255, 255, 0), 1, LINE_AA);
|
||||
|
||||
// Draw the circle
|
||||
circle(img, center, cvRound(radius), Scalar(0, 255, 255), 1, LINE_AA);
|
||||
|
||||
imshow( "Rectangle, triangle & circle", img );
|
||||
|
||||
char key = (char)waitKey();
|
||||
if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user