正如其他人已经提到的那样,您的数据包含噪声和幅度失真。但是,您的数据很可能还包含时间失真(正弦波的角度参数不是非线性的,也不是精确/恒定的相移)这将使atan,acos,asin 的使用非常不准确且有问题。
我愿意:
-
FIR 过滤数据
所以使用以值为中心的滑动平均值,这样输出就不会发生相移。这是O(n),只有一次。不要忘记使用临时数组...
-
检测单调区间
只需检测信号仅减少或仅增加的间隔...这也是O(n),并且只执行一次。
-
使用二分搜索
只需遍历所有间隔,如果您测量的电压在其范围内(间隔的第一个和最后一个值),则对间隔使用二进制搜索。间隔的选择是 O(1),因为间隔的数量始终是 4 或 5,具体取决于起始阶段。
二分搜索本身是O(log(n))
这里有一个小的 C++/VCL 示例:
//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
// LUT data
const int n=1317;
double atan2_u0[n]; // u0 [V] red
double atan2_u1[n]; // u1 [V] blue
// ix = ang*183/50 // index -> [deg]
// ang = ix*50/183 // [deg] -> index
// monotonic intervals
const int m=10; // max interval count
int atan2_n=0; // intervals count
int atan2_ix[m]; // interval start index
int atan2_s0[m]; // direction of atan2_u0
int atan2_s1[m]; // direction of atan2_u1
//---------------------------------------------------------------------------
double LUT_atan2(double _u0,double _u1)
{
int i,i0,i1,m;
double u,u0,u1,U0,U1,s0,s1;
// process interals
for (i=0;i<atan2_n;i++)
{
i0=atan2_ix[i];
i1=atan2_ix[i+1]-1;
// test BBOX
u0=atan2_u0[i0]; U0=atan2_u0[i1]; s0=atan2_s0[i];
u1=atan2_u1[i0]; U1=atan2_u1[i1]; s1=atan2_s1[i];
if (s0<0.0){ u=u0; u0=U0; U0=u; }
if (s1<0.0){ u=u1; u1=U1; U1=u; }
if ((_u0<u0)||(_u0>U0)) continue;
if ((_u1<u1)||(_u1>U1)) continue;
// binary search
for (m=1;m<=(i1-i0);m<<=1); m>>=1; if (!m) m=1; // m = exp2(log2(i1-i0))
for (;m;m>>=1)
{
i0+=m;
if (i0>i1){ i0-=m; continue; }
u0=atan2_u0[i0];
u1=atan2_u1[i0];
if (((_u0-u0)*s0<0.0)||((_u1-u1)*s1<0.0)) i0-=m;
}
return double(i0)*50.0/183.0;
}
return 0.0;
}
//---------------------------------------------------------------------------
void LUT_atan2_init() // filter u0,u1 and compute intervals
{
int i,j,k,r;
double u0,u1,du0,du1,U0,U1,tmp0[n],tmp1[n];
// centered sliding avg, window is <-r,+r>
r=25; du0=1.0/double(r+r+1);
for (i=0;i<n;i++) // all samples
{
u0=0.0; u1=0.0;
for (j=i-r;j<=i+r;j++)
{
k=j;
if (k< 0) k+=n;
if (k>=n) k-=n;
u0+=atan2_u0[k];
u1+=atan2_u1[k];
}
tmp0[i]=u0*du0;
tmp1[i]=u1*du0;
}
for (i=0;i<n;i++)
{
atan2_u0[i]=tmp0[i];
atan2_u1[i]=tmp1[i];
}
// find monotonic intervals
atan2_n=0; atan2_ix[0]=0;
u0=atan2_u0[0]; U0=atan2_u0[1]; atan2_s0[0]=0.0;
u1=atan2_u1[0]; U1=atan2_u1[1]; atan2_s1[0]=0.0;
for (i=2;i<n;i++)
{
// actual delta du0,du1
u0=U0; U0=atan2_u0[i]; du0=U0-u0; if (du0<0.0) du0=-1.0; else if (du0>0.0) du0=+1.0;
u1=U1; U1=atan2_u1[i]; du1=U1-u1; if (du1<0.0) du1=-1.0; else if (du1>0.0) du1=+1.0;
if (fabs(atan2_s0[atan2_n])<1e-3) atan2_s0[atan2_n]=du0;
if (fabs(atan2_s1[atan2_n])<1e-3) atan2_s1[atan2_n]=du1;
// if sign changed add new interval
if ((du0*atan2_s0[atan2_n]<-0.1)||(du1*atan2_s1[atan2_n]<-0.1))
{
if (atan2_n>=m-1) break;
atan2_n++;
atan2_ix[atan2_n]=i;
atan2_s0[atan2_n]=du0;
atan2_s1[atan2_n]=du1;
}
}
// add end of table as end of intervals
atan2_n++;
atan2_ix[atan2_n]=n;
atan2_s0[atan2_n]=0.0;
atan2_s1[atan2_n]=0.0;
}
//---------------------------------------------------------------------------
void data_generate() // extract mesurede values from image
{
int x,y,x0=44,y0=478,dx=227-x0,dy=y0-424,r,g,b,i;
double u0,u1,du=1.0/double(dy);
BYTE *c; DWORD *q;
// convert bmp -> pnt[]
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->LoadFromFile("in.bmp");
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
for (x=x0;x<bmp->Width;x++)
{
u0=u1=-1.0;
for (y=0;y<=y0;y++)
{
q=(DWORD*)bmp->ScanLine[y];
c=(BYTE*)(q+x);
b=c[0]; g=c[1]; r=c[2]; i=r+g+b;
if ((r>200)&&(i<500)){ u0=y0-y; u0*=du; break; } // [V]
}
for (y=0;y<=y0;y++)
{
q=(DWORD*)bmp->ScanLine[y];
c=(BYTE*)(q+x);
b=c[0]; g=c[1]; r=c[2]; i=r+g+b;
if ((b>200)&&(i<500)){ u1=y0-y; u1*=du; break; } // [V]
}
if (u0<-0.5) u0=u1;
if (u1<-0.5) u1=u0;
i=x-x0;
atan2_u0[i]=u0+1.0;
atan2_u1[i]=u1+1.0;
}
delete bmp;
}
//---------------------------------------------------------------------------
void TMain::draw()
{
if (!_redraw) return;
// clear buffer
bmp->Canvas->Brush->Color=clBlack;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
int i;
double x,y,dy=ys/10.0,r=5.0;
// LUT
bmp->Canvas->Pen->Color=clRed;
for (i=0;i<n;i++)
{
x=(i*xs)/n;
y=ys-(atan2_u0[i]*dy);
if (!i) bmp->Canvas->MoveTo(x,y);
else bmp->Canvas->LineTo(x,y);
}
bmp->Canvas->Pen->Color=clBlue;
for (i=0;i<n;i++)
{
x=(i*xs)/n;
y=ys-(atan2_u1[i]*dy);
if (!i) bmp->Canvas->MoveTo(x,y);
else bmp->Canvas->LineTo(x,y);
}
// intervals
bmp->Canvas->Pen->Color=clDkGray;
for (i=0;i<=atan2_n;i++)
{
x=(atan2_ix[i]*xs)/n;
bmp->Canvas->MoveTo(x,0);
bmp->Canvas->LineTo(x,ys);
}
// atan2
double u0=8.0,u1=1.7,a;
a=LUT_atan2(u0,u1); // [V,V] -> [deg]
x=(183.0*a*xs)/(50.0*n); // [deg] -> [pixel]
bmp->Canvas->Pen->Color=clWhite;
bmp->Canvas->MoveTo(x,0);
bmp->Canvas->LineTo(x,ys);
y=ys-(u0*dy);
bmp->Canvas->Pen->Color=clWhite;
bmp->Canvas->Brush->Color=clRed;
bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
y=ys-(u1*dy);
bmp->Canvas->Pen->Color=clWhite;
bmp->Canvas->Brush->Color=clBlue;
bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
// render backbuffer
Main->Canvas->Draw(0,0,bmp);
_redraw=false;
}
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
{
data_generate();
LUT_atan2_init();
bmp=new Graphics::TBitmap;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
pyx=NULL;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
{
if (pyx) delete[] pyx;
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
{
xs=ClientWidth; xs2=xs>>1;
ys=ClientHeight; ys2=ys>>1;
bmp->Width=xs;
bmp->Height=ys;
if (pyx) delete[] pyx;
pyx=new int*[ys];
for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
{
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
我从您的图像中提取输入数据并转换为[V] 和[deg]。你可以忽略 VCL 渲染和窗口的东西。这里唯一重要的是这些函数:
void LUT_atan2_init();
double LUT_atan2(double _u0,double _u1);
第一个预先计算所有需要的东西(只调用一次),第二个在[deg] 中返回角度u0,u1。
但是,如果您的 u0,u1 接近区间边缘,其中一个电压在一个区间内,而另一个电压在下一个区间内,这可能会出现轻微的准确性问题,如果添加噪声,可能会发生这种情况。在这种情况下,您可以对u0 和u1 进行二元搜索,分别记住这两个位置的两个位置,然后检查4 个组合,哪一个是正确的。之后,只需使用找到的两个索引之间的中间位置。为此,您也可以将间隔分开(但每个信号只有 2 或 3 个)。
您还可以通过在二分搜索之后添加线性插值来添加 “子像素”精度(我太懒了,所以不在我的示例中)
这里预览:
红、蓝曲线是您的u0,u1 信号,灰线是找到的区间边缘,点是测试(测量)电压,白色是找到的角度