算法思路:
假设原曲线没有高频振荡(如sin(1000t))
1. 首先使用直线段进行拟合(使用一定步长)
2. 在直线段拟合的基础上,尝试把若干段直线段组合起来使用圆弧拟合,看一下拟合效果是否
提高,如果是则用圆弧段代替该直线段组,否则保留直线段.
程序如下(有三个例子)
如果发现有哪个函数的效果不如理想,请把函数给我.
uses
Windows, SysUtils;
const
InitLineNum = 20; //直线拟合的初始数量
DeltaLineNum = 10; //直线拟合的递增数量
InitSegNum = 20; //多曲线段拟合的初始数量
DeltaSegNum = 10; //多曲线段拟合的递增数量
MinT = 0; //待拟合曲线的时间变化范围
MaxT = 6.28;
MaxLineEps = 5; //直线拟合平均误差的最大误差限
LineCheckCount = 4; //检测直线拟合效果的插值点数量 + 1
InvLineCheckCount = 1 / LineCheckCount;
ArcCheckCount = 10; //检测圆弧拟合效果的插值点数量 + 1
InvArcCheckCount = 1 / ArcCheckCount;
type
//实点结构
TRealPoint = packed record
x, y: Extended;
end;
//多曲线段拟合类型
TSegmentStyle = (ssLine, ssArc);
//多曲线段拟合结构
TSegment = packed record
case Style: TSegmentStyle of
//直线段记下两端点
ssLine: (Pt1, Pt2: TRealPoint);
//圆弧段记下圆心坐标,圆半径,起始角度和结束角度
ssArc: (Org: TRealPoint; Radius, StartAngle, EndAngle: Extended);
end;
var
Pts: array of TRealPoint; //直线拟合的点坐标
Times: array of Extended; //直线拟合的点对应t值
Segs: array of TSegment; //多曲线段拟合的结构数组
function Curve(t: Extended): TRealPoint;
procedure CalcLinePoints;
procedure CalcArcs;
implementation
uses
Math;
var
Pi: Extended;
//已知曲线函数,输入自变量t,返回质点坐标
//Eg.1. 螺旋线 0 < t < Pi, x(t) = 200 cos(t), y(t) = 100 sin(t);
// Pi < t < 2 Pi, x(t) = 140 cos(t) - 60, y(t) = 200 sin(t)
//Eg.2. 正弦曲线 0 < t < 2 Pi, x(t) = t, y(t) = sin(t)
//Eg.3. 星型线 0 < t < 2 Pi, x(t) = 200 cos(t)^3, y(t) = 200 * sin(t)^3
function Curve(t: Extended): TRealPoint;
var
c, s: Extended;
begin
SinCos(t, s, c);
if t > Pi then begin
Result.x := 140 * c - 60;
Result.y := 200 * s;
end else begin
Result.x := 200 * c;
Result.y := 100 * s;
end;
{Result.x := t * 60;
Result.y := Sin(t) * 200; }
{SinCos(t, s, c);
Result.x := 200 * Sqr(c) * c;
Result.y := 200 * Sqr(s) * s; }
end;
//两点距离函数
function Distance(const Pt1, Pt2: TRealPoint): Extended;
begin
Result := Sqrt(Sqr(Pt1.x - Pt2.x) + Sqr(Pt1.y - Pt2.y));
end;
//计算直线段与其上原曲线段的拟合误差估计
function CalcLineEps(const LastT: Extended; const LastPt: TRealPoint;
const ThisT: Extended; const ThisPt: TRealPoint): Extended;
var
DeltaT, t: Extended;
i: Integer;
Pt, LPt: TRealPoint;
begin
DeltaT := (ThisT - LastT) * InvLineCheckCount;
Result := 0;
//把直线分为 LineCheckCount 段进行分别计算其拟合误差取算术平均
for i := 1 to LineCheckCount - 1 do begin
t := LastT + i * DeltaT;
Pt := Curve(t);
LPt.x := (i * ThisPt.x + (LineCheckCount - i) * LastPt.x) * InvLineCheckCount;
LPt.y:= (i * ThisPt.y + (LineCheckCount - i) * LastPt.y) * InvLineCheckCount;
Result := Result + Distance(Pt, LPt);
end;
Result := Result * InvLineCheckCount;
end;
//直线段拟合
procedure CalcLinePoints;
var
DeltaT, t, LastT, ThisDeltaT: Extended;
CurCount, p: Integer;
Pt, LastPt: TRealPoint;
begin
CurCount := InitLineNum + 1;
SetLength(Pts, CurCount);
SetLength(Times, CurCount);
DeltaT := (MaxT - MinT) / InitLineNum; //直线拟合的初始步长
t := MinT;
Pt := Curve(t);
Pts[0] := Pt;
Times[0] := MinT;
p := 1;
LastT := t;
LastPt := Pt;
repeat
ThisDeltaT := DeltaT;
repeat
t := LastT + ThisDeltaT;
if t > MaxT then t := MaxT;
Pt := Curve(t);
//测试选定的直线段是否达到拟合要求,尚未达到则缩小步长重来
if CalcLineEps(LastT, LastPt, t, Pt) <= MaxLineEps then Break;
ThisDeltaT := ThisDeltaT * 0.5;
until t >= MaxT;
//动态分配存储空间
if p >= CurCount then begin
Inc(CurCount, DeltaLineNum);
SetLength(Pts, CurCount);
SetLength(Times, CurCount);
end;
Pts[p] := Pt;
Times[p] := t;
Inc(p);
LastT := t;
LastPt := Pt;
until t >= MaxT;
//保留实际用到的存储空间
SetLength(Pts, p);
SetLength(Times, p);
end;
//用圆弧去拟合原曲线上的一段,返回圆弧相关参数
procedure CalcArcParam(const t1, t2: Extended; var Org: TRealPoint;
var r, StAngle, EdAngle: Extended);
var
Pt1, Pt2, Pt3: TRealPoint;
divd, a, b: Extended;
begin
Pt1 := Curve(t1);
Pt2 := Curve((t1 + t2) * 0.5);
Pt3 := Curve(t2);
divd := 4 * ((Pt2.x - Pt1.x) * (Pt3.y - Pt1.y)
- (Pt2.y - Pt1.y) * (Pt3.x - Pt1.x));
a := Sqr(Pt2.y) - Sqr(Pt1.y) + Sqr(Pt2.x) - Sqr(Pt1.x);
b := Sqr(Pt3.y) - Sqr(Pt1.y) + Sqr(Pt3.x) - Sqr(Pt1.x);
Org.x := 2 * (a * (Pt3.y - Pt1.y) - b * (Pt2.y - Pt1.y)) / divd;
Org.y := 2 * (b * (Pt2.x - Pt1.x) - a * (Pt3.x - Pt1.x)) / divd;
r := Distance(Pt1, Org);
StAngle := ArcTan2(Pt1.y - Org.y, Pt1.x - Org.x);
EdAngle := ArcTan2(Pt3.y - Org.y, Pt3.x - Org.x);
if Abs(EdAngle - StAngle) > Pi then
EdAngle := EdAngle + 2 * Pi;
end;
//计算圆弧与原曲线的拟合误差估计
function CalcArcEps(const t1, t2: Extended; const Org: TRealPoint;
const r, StAngle, EdAngle: Extended): Extended;
var
DeltaT, t, c, s, Angle: Extended;
i: Integer;
Pt, APt: TRealPoint;
begin
DeltaT := (t2 - t1) * InvArcCheckCount;
Result := 0;
//把圆弧分成 ArcCheckCount 段分别计算误差,并取算术平均
for i := 1 to ArcCheckCount - 1 do begin
t := t1 + i * DeltaT;
Pt := Curve(t);
Angle := (i * EdAngle + (ArcCheckCount - i) * StAngle) * InvArcCheckCount;
SinCos(Angle, s, c);
APt.x := Org.x + r * c;
APt.y := Org.y + r * s;
Result := Result + Distance(Pt, APt);
end;
Result := Result * InvArcCheckCount;
end;
//通过直线段拟合的结果进行多曲线段拟合
procedure CalcArcs;
var
CurCount, p1, p2, PtCount, SegCnt, i: Integer;
r, StAngle, EdAngle, LineEps, ArcEps, LastR, LastStAngle, LastEdAngle: Extended;
OrgPt, LastOrgPt: TRealPoint;
//动态分配多曲线段的存储空间
procedure CheckMemory;
begin
if SegCnt >= CurCount then begin
Inc(CurCount, DeltaSegNum);
SetLength(Segs, CurCount);
end;
end;
//添加一条新直线段
procedure AddLine(Point1, Point2: TRealPoint);
begin
with Segs[SegCnt] do begin
Style := ssLine;
Pt1 := Point1;
Pt2 := Point2;
end;
Inc(SegCnt);
end;
//添加一条新圆弧段
procedure AddArc(OrgPt: TRealPoint; r, StAngle, EdAngle: Extended);
begin
with Segs[SegCnt] do begin
Style := ssArc;
Org := OrgPt;
Radius := r;
StartAngle := StAngle;
EndAngle := EdAngle;
end;
Inc(SegCnt);
end;
begin
CurCount := InitSegNum;
SetLength(Segs, CurCount);
p1 := 0;
p2 := 1;
PtCount := Length(Pts);
SegCnt := 0;
LastOrgPt.x := 0;
LastOrgPt.y := 0;
LastR := 0;
LastStAngle := 0;
LastEdAngle := 0;
while p2 < PtCount do begin
//计算使用直线段拟合的误差
LineEps := 0;
for i := p1 + 1 to p2 do
LineEps := LineEps + CalcLineEps(Times[i - 1], Pts[i - 1],
Times, Pts);
LineEps := LineEps / (p2 - p1);
//计算使用圆弧段拟合的误差
CalcArcParam(Times[p1], Times[p2], OrgPt, r, StAngle, EdAngle);
ArcEps := CalcArcEps(Times[p1], Times[p2], OrgPt, r, StAngle, EdAngle);
//测试使用直线拟合和圆弧拟合的拟合效果哪一个好
if LineEps < ArcEps then begin
CheckMemory;
if p2 - p1 = 1 then begin
AddLine(Pts[p1], Pts[p2]);
p1 := p2;
p2 := p1 + 1;
end else begin
AddArc(LastOrgPt, LastR, LastStAngle, LastEdAngle);
p1 := p2 - 1;
end;
end else if p2 = PtCount - 1 then begin
CheckMemory;
if p2 - p1 = 1 then
AddLine(Pts[p1], Pts[p2])
else
AddArc(OrgPt, r, StAngle, EdAngle);
Break;
end else begin
LastOrgPt := OrgPt;
LastStAngle := StAngle;
LastEdAngle := EdAngle;
LastR := r;
Inc(p2);
end;
end;
//保留实际需要的存储空间
SetLength(Segs, SegCnt);
end;
initialization
Pi := System.Pi;
end.