mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
9.9 KiB
311 lines
9.9 KiB
/******************************************************************************** |
|
* |
|
* |
|
* 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); |
|
}
|
|
|