OpenCV 学习笔记

OpenCV 笔记(17):轮廓的椭圆拟合、直线拟合

2024-01-13  本文已影响0人  fengzhizi715

1. 椭圆拟合

轮廓的椭圆拟合是指用椭圆来近似轮廓的形状。当这个椭圆的长轴和短轴相等时,它就是一个圆。

椭圆拟合的基本思路是:对于给定平面上的一组样本点,寻找一个椭圆,使其尽可能接近这些样本点。也就是说,将图像中的一组数据以椭圆方程为模型进行拟合,使某一椭圆方程尽量满足这些数据,并求出该椭圆方程的各个参数。

椭圆拟合有以下几种常用方法:

在 OpenCV 提供了三种 fitEllipse()、fitEllipseAMS()、fitEllipseDirect() 函数实现椭圆拟合。

RotatedRect fitEllipse( InputArray points );

RotatedRect fitEllipseAMS( InputArray points );

RotatedRect fitEllipseDirect( InputArray points );

其输出的 RotatedRect 包含了

OpenCV 提供的这三个函数还是有一定区别的:

函数 算法 优点 缺点
fitEllipse() 最小二乘法 简单易实现 计算量大,收敛问题
fitEllipseAMS() 改进的最小二乘法 收敛速度快,精度高 计算量略大
fitEllipseDirect() 直接求解 计算量最小 对初始值敏感

在实际应用中,可以根据具体情况选择合适的椭圆拟合函数。如果对拟合精度要求较高,可以使用 fitEllipseAMS() 函数。如果对计算速度要求较高,可以使用 fitEllipseDirect() 函数。

下面的例子展示了找到有效的轮廓后,对这些轮廓进行椭圆拟合。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

bool ascendSort(vector<Point> a,vector<Point> b)
{
    return contourArea(a) > contourArea(b);
}

int main(int argc, char **argv) {
    Mat src = imread(".../test.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    threshold(gray,thresh,0,255,THRESH_BINARY_INV | THRESH_OTSU);
    imshow("thresh", thresh);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    sort(contours.begin(), contours.end(), ascendSort);//ascending sort

    for (size_t i = 0; i< contours.size(); i++) {
        double area = contourArea(contours[i]);

        if (area < 1000) {
            continue;
        }

        RotatedRect rrt = fitEllipse(contours[i]);
        Point2f center = rrt.center;
        float w = rrt.size.width;
        float h = rrt.size.height;
        float angle = rrt.angle;
        printf("w = %f, h = %f , angle = %f\n",w,h,angle);
        ellipse(src,rrt, Scalar(0, 255, 255), 8, 8);
    }
    imshow("result", src);

    waitKey(0);
    return 0;
}

执行结果:

w = 172.647202, h = 569.465088 , angle = 76.293610
w = 174.775482, h = 544.108643 , angle = 134.146057
w = 172.592422, h = 589.588135 , angle = 175.138290
w = 178.092865, h = 536.354919 , angle = 154.063202
w = 170.954330, h = 539.261047 , angle = 113.472153
w = 166.067673, h = 571.457947 , angle = 45.668461
w = 162.812332, h = 559.349915 , angle = 97.494217
w = 149.240463, h = 615.341309 , angle = 20.167418
w = 152.298386, h = 594.066528 , angle = 11.754380
w = 144.079239, h = 591.841553 , angle = 69.640686
w = 154.095871, h = 518.927307 , angle = 42.097614
椭圆拟合.png

2. 直线拟合

轮廓的直线拟合是将一个轮廓近似表示为一条与该轮廓形状相近的直线。

直线拟合可以用于以下几个方面:

直线拟合有以下几种常用方法:

在 OpenCV 提供了 fitLine() 函数实现直线拟合。

void fitLine( InputArray points, OutputArray line, int distType,
                           double param, double reps, double aeps );

第一个参数 points:表示输入点集,可以是 Point 数组或 Mat 矩阵。
第二个参数 line:输出直线。

第三个参数 distType:表示距离类型,也就是在直线拟合时使用哪种算法。这里的算法基于 M-estimators 实现:

第四个参数 param:表示距离参数,跟所选的距离类型有关。如果为 0,则自动选择最佳值。
第五个参数 reps:表示拟合直线所需要的径向精度,通常该值被设定为 0.01。
第六个参数 aeps:表示拟合直线所需要的角度精度,通常该值被设定为 0.01。

下面的例子,将一些点拟合成一条直线,并找到直线的极值点。

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char **argv) {
    Mat image(800, 800, CV_8UC3, Scalar(0,0,0));

    vector<Point> points;
    points.push_back(Point(48, 58));
    points.push_back(Point(105, 98));
    points.push_back(Point(155, 160));
    points.push_back(Point(212, 220));
    points.push_back(Point(248, 260));
    points.push_back(Point(320, 300));
    points.push_back(Point(350, 360));
    points.push_back(Point(412, 400));

    //将拟合点绘制到空白图上
    for (int i = 0; i < points.size(); i++)
    {
        circle(image, points[i], 5, cv::Scalar(0, 0, 255), 2, 8);
    }
    imshow("src", image);

    cv::Vec4f line_para;
    cv::fitLine(points, line_para, cv::DIST_L2, 0, 1e-2, 1e-2);
    std::cout << "line_para = " << line_para << std::endl;

    //获取直线的斜率、截矩
    float vx = line_para[0];
    float vy = line_para[1];
    float x0 = line_para[2];
    float y0 = line_para[3];

    float k = vy / vx;
    float b = y0 - k*x0;

    // 寻找直线的极值点
    int minx = 0, miny = 10000;
    int maxx = 0, maxy = 0;
    for (int i = 0; i < points.size(); i++) {
        Point pt = points[i];
        if (miny > pt.y) {
            miny = pt.y;
        }
        if (maxy < pt.y) {
            maxy = pt.y;
        }
    }
    maxx = (maxy - b) / k;
    minx = (miny - b) / k;
    line(image, Point(maxx, maxy), Point(minx, miny), Scalar(255, 0, 0), 2, 8);

    imshow("result", image);
    waitKey(0);

    return 0;
}
点的直线拟合.png

下面的例子,对轮廓进行直线拟合。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

bool ascendSort(vector<Point> a,vector<Point> b)
{
    return contourArea(a) > contourArea(b);
}

int main(int argc, char **argv) {
    Mat src = imread(".../test.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    threshold(gray,thresh,0,255,THRESH_BINARY_INV | THRESH_OTSU);
    imshow("thresh", thresh);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    sort(contours.begin(), contours.end(), ascendSort);//ascending sort

    for (size_t t = 0; t< contours.size(); t++) {
        double area = contourArea(contours[t]);

        if (area < 1000) {
            continue;
        }

        // 直线拟合
        Vec4f line_para;
        fitLine(contours[t], line_para, DIST_L2, 0, 0.01, 0.01);
        //获取直线的斜率、截矩
        float vx = line_para[0];
        float vy = line_para[1];
        float x0 = line_para[2];
        float y0 = line_para[3];

        float k = vy / vx;
        float b = y0 - k*x0;

        // 寻找直线的极值点
        int minx = 0, miny = 10000;
        int maxx = 0, maxy = 0;
        for (int i = 0; i < contours[t].size(); i++) {
            Point pt = contours[t][i];
            if (miny > pt.y) {
                miny = pt.y;
            }
            if (maxy < pt.y) {
                maxy = pt.y;
            }
        }
        maxx = (maxy - b) / k;
        minx = (miny - b) / k;
        line(src, Point(maxx, maxy), Point(minx, miny), Scalar(255, 0, 0), 8, 8);
    }
    imshow("result", src);

    waitKey(0);
    return 0;
}
轮廓的直线拟合.png

3. 总结

本文介绍了在 OpenCV 中如何对轮廓进行椭圆拟合和直线拟合。它们可以用于提取轮廓的特征,简化轮廓的表示,提高轮廓的处理效率。它们在图像分割、目标识别、目标跟踪等任务中有着广泛的应用。

上一篇下一篇

猜你喜欢

热点阅读