【opencv450-samples】轮廓点拟合椭圆fitellipse.cpp

46 篇文章 4 订阅

测试示例

/********************************************************************************
 *
 *
 *  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
 *  这个程序是椭圆拟合的演示。 程序使用三种方法找到轮廓并通过椭圆对其进行近似。
* 1:OpenCV 的原始方法 fitEllipse 实现了 Fitzgibbon 1995 方法。
* 2:Taubin 1991 提出的近似均方 (AMS) 方法 fitEllipseAMS
* 3:Fitzgibbon1999提出的直接最小二乘(Direct)方法 fitEllipseDirect。
*
* Trackbar  指定阈值参数.//
*
* 白线是轮廓/输入点和用于生成数据的真实椭圆。
* 1:蓝线是使用openCV的原始方法拟合椭圆。
* 2:绿线是使用 AMS 方法拟合椭圆。
* 3:红线是使用 Direct 方法拟合椭圆。
*
*
* 原作者:丹尼斯布伦科夫
* AMS 和直接方法作者: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.
        //拉伸画布以包括点 min 和 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.
        //拉伸画布,使所有点 pts 都在画布上。

        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++) {//遍历四个角点,找出最大(x,y)点,最小(x,y)点
            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*);//先声明,main之后实现

int main(int argc, char** argv)
{
    //三种计算方式
    fitEllipseQ = true;
    fitEllipseAMSQ = true;
    fitEllipseDirectQ = true;
    //读取图片
    cv::CommandLineParser parser(argc, argv, "{help h||}{@image|ok0015.bmp|}");//ellipses.jpg    fruits.jpg  detect_blob.png ok0008.bmp
    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;
    }
    if (image.cols > 1920)
    {
        resize(image, image, Size(750, 615));
    }

    if (image.channels() == 3)
    {
        cvtColor(image, image, COLOR_BGR2GRAY);
    }

    imshow("source", image);
    namedWindow("result", WINDOW_NORMAL);

    // 创建滑动条  :设置二值图像的白色阈值
    createTrackbar("threshold", "result", &sliderPos, 255, processImage);

    processImage(0, 0);

    // Wait for a key stroke; the same function arranges events processing
    waitKey();
    return 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)));//图像窄边的0.8倍  图像宽边的1.2倍
    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();//轮廓i的 轮廓点数
        if (count < 6)
            continue;//轮廓点少于6不考虑

        Mat pointsf;
        Mat(contours[i]).convertTo(pointsf, CV_32F);//轮廓点坐标转换为Mat

        vector<Point2f>pts;//采样点
        for (int j = 0; j < pointsf.rows; j++) {//遍历单个轮廓的所有行
            Point2f pnt = Point2f(pointsf.at<float>(j, 0), pointsf.at<float>(j, 1));//点的x,y坐标
            if ((pnt.x > margin && pnt.y > margin && pnt.x < bimage.cols - margin && pnt.y < bimage.rows - margin)) {//点在画布上
                if (j % 20 == 0) {//取20分之一的点
                    pts.push_back(pnt);
                }
            }
        }
        points.push_back(pts);//采样的轮廓点 集合
    }
    //拟合椭圆和绘制椭圆
    for (size_t i = 0; i < points.size(); i++)//遍历采样的轮廓点集
    {
        vector<Point2f> pts = points[i];

        if (pts.size() <= 5) {//单个轮廓点数少于5 不考虑
            continue;
        }
        if (fitEllipseQ) {
            box = fitEllipse(pts);//拟合椭圆,得到旋转矩形
            if (MAX(box.size.width, box.size.height) > MIN(box.size.width, box.size.height) * 30 ||  //长边比短边的三倍还要长,不考虑跳过
                MAX(box.size.width, box.size.height) <= 0 ||
                MIN(box.size.width, box.size.height) <= 0) {
                continue;//旋转矩形不合法,跳过
            };
        }
        if (fitEllipseAMSQ) {
            boxAMS = fitEllipseAMS(pts);//AMS法拟合椭圆
            if (MAX(boxAMS.size.width, boxAMS.size.height) > MIN(boxAMS.size.width, boxAMS.size.height) * 30 ||
                MAX(box.size.width, box.size.height) <= 0 ||
                MIN(box.size.width, box.size.height) <= 0) {
                continue;
            };
        }
        if (fitEllipseDirectQ) {
            boxDirect = fitEllipseDirect(pts);//直接拟合
            if (MAX(boxDirect.size.width, boxDirect.size.height) > MIN(boxDirect.size.width, boxDirect.size.height) * 30 ||
                MAX(box.size.width, box.size.height) <= 0 ||
                MIN(box.size.width, box.size.height) <= 0) {
                continue;
            };
        }
        //绘制椭圆
        if (fitEllipseQ) {
            paper.drawEllipseWithBox(box, fitEllipseColor, 3);//在画布上绘制椭圆  线宽3
        }
        if (fitEllipseAMSQ) {
            paper.drawEllipseWithBox(boxAMS, fitEllipseAMSColor, 2);//
        }
        if (fitEllipseDirectQ) {
            paper.drawEllipseWithBox(boxDirect, fitEllipseDirectColor, 1);
        }

        paper.drawPoints(pts, cv::Scalar(255, 255, 255));//绘制采样的点为 白点
    }

    imshow("result", paper.img);//显示最终结果
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值