首先定义了一个椭圆类用来存放一个一般化的椭圆的参数,然后构建一个椭圆方程,至于为什么需要这些参数可以参考上一篇博客:https://blog.csdn.net/qq_41685265/article/details/104267256
class Ellipse {
public:
double a;
double b;
double angle;
Point center;
Ellipse():a(0), b(0), angle(0), center(Point(0,0)) {}
Ellipse(double a_, double b_, double angle_, Point center_) :
a(a_), b(b_), angle(angle_), center(center_) {}
};
迭代的主要思想是:
- 从一个初始点开始
- 向正斜率和负斜率方向分别以一定步长进行逼近
- 一但到达了椭圆外,就改变步长反复迭代
- 终止条件是步长小于指定值
1、初始点选择
初始点选为椭圆中心与直线的垂足,这个垂足大概率是在椭圆内的,要注意的是,如果垂足在椭圆外,这个算法是不成立的。
2、判断点是否在椭圆内
可以带入椭圆方程看坐标是否小于1:,上代码:
bool fun(Ellipse& e, double x,double y) {
double a = e.a;
double b = e.b;
double angle = e.angle;
double m = e.center.x;
double n = e.center.y;
double t1 = pow((x - m) * cos(angle) + (y - n) * sin(angle), 2);
double t2 = pow((m - x) * sin(angle) + (y - n) * cos(angle), 2);
return t1 / (a * a) + t2 / (b * b) < 1;
}
3、求解交点
因为要求两个交点,所以要迭代两次,flag为1是正斜率,flag为0是负斜率,k和b是直线的两个参数,看代码:
Point solve_edge_pionts(Ellipse& e, double &k, double &b,int flag) {
//初始点
double x = (e.center.x / k + e.center.y - b) / (1 / k + k);
double y = k * x + b;
double step = 5;
if (!flag)step = -step;
double minstep = 0.01;
while (abs(step)>minstep) {
while (fun(e, x + step, k * (x + step) + b)) {
x = x + step;
}
step = step / 2;
}
Point p;
p.x = x;
p.y = k * x + b;
return p;
}
4、主函数调用
因为这是我做的项目的一部分,所以把主函数中相关的摆在下边了:
int main() {
string img = "test1.jpg";
Mat src = imread(img);
//椭圆参数自己获取,我这里省略了
vector<Ellipse>Ellipses;
//直线参数自己获取,我这里也省略了
double k, b;
//获取交点
vector<Point>keyPoints;
Point t;
for (auto c : Ellipses) {
t = solve_edge_pionts(c, k, b, 1);
keyPoints.push_back(t);
t = solve_edge_pionts(c, k, b, 0);
keyPoints.push_back(t);
}
//画出交点
for (auto c : keyPoints) {
circle(src, c, 2, Scalar(0, 255, 0), 2);
}
imshow("src", src);
waitKey(0);
return 0;
}
最后可以放一个示意图,是做项目中的中间结果: