I recommend to use FIR filter. The idea behind is to use weighted average of points arround filtered point... You just do something like
p(i) = 0.5*p(i) + 0.25*p(i-1) + 0.25*p(i+1)
where p(i)
is i-th
point. Its a good idea to remember original p(i)
as it is used for next iteration (to avoid shifting). You can do each axis separately. You can use any weights but their sum should be 1.0. You can use any number of neighbors not just 2 (but in such case you need to remember more points). Symmetric weights will reduce shifting. You can apply FIR multiple times ...
Here small 2D C++ example:
//---------------------------------------------------------------------------
const int n=50; // points
float pnt[n][2]; // points x,y ...
//---------------------------------------------------------------------------
void pnt_init()
{
int i;
float a,da=2.0*M_PI/float(n),r;
Randomize();
for (a=0.0,i=0;i<n;i++,a+=da)
{
r=0.75+(0.2*Random());
pnt[i][0]=r*cos(a);
pnt[i][1]=r*sin(a);
}
}
//---------------------------------------------------------------------------
void pnt_smooth()
{
int i,j;
float p0[2],*p1,*p2,tmp[2],aa0[2],aa1[2],bb0[2],bb1[2];
// bb = original BBOX
for (j=0;j<2;j++) { bb0[j]=pnt[0][j]; bb1[j]=pnt[0][j]; }
for (i=0;i<n;i++)
for (p1=pnt[i],j=0;j<2;j++)
{
if (bb0[j]>p1[j]) bb0[j]=p1[j];
if (bb1[j]<p1[j]) bb1[j]=p1[j];
}
// FIR filter
for (j=0;j<2;j++) p0[j]=pnt[n-1][j]; // remember p[i-1]
p1=pnt[0]; p2=pnt[1]; // pointers to p[i],p[i+1]
for (i=0;i<n;i++,p1=p2,p2=pnt[(i+1)%n])
{
for (j=0;j<2;j++)
{
tmp[j]=p1[j]; // store original p[i]
p1[j]=(0.1*p0[j]) + (0.8*p1[j]) + (0.1*p2[j]); // p[i] = FIR(p0,p1,p2)
p0[j]=tmp[j]; // remeber original p1 as p[i-1] for next iteration
}
}
// aa = new BBOX
for (j=0;j<2;j++) { aa0[j]=pnt[0][j]; aa1[j]=pnt[0][j]; }
for (i=0;i<n;i++)
for (p1=pnt[i],j=0;j<2;j++)
{
if (aa0[j]>p1[j]) aa0[j]=p1[j];
if (aa1[j]<p1[j]) aa1[j]=p1[j];
}
// compute scale transform aa -> bb
for (j=0;j<2;j++) tmp[j]=(bb1[j]-bb0[j])/(aa1[j]-aa0[j]); // scale
// convert aa -> bb
for (i=0;i<n;i++)
for (p1=pnt[i],j=0;j<2;j++)
p1[j]=bb0[0]+((p1[j]-aa0[j])*tmp[j]);
}
//---------------------------------------------------------------------------
I added also checking BBOX before and after smoothing so the shape does not change size and position. In some cases centroid is better than BBOX for the position correction.
Here preview of multiple application of FIR filter:
