In my hand tracking project I've used color segmentation to get the hand contour.
The problem is that usually the contour is not perfect, there are parts missing and/or there is some noise, for that reason I coded this general purpose contour refining function that fits the contour on a feature map moving each point along its normal.
Download file here
The problem is that usually the contour is not perfect, there are parts missing and/or there is some noise, for that reason I coded this general purpose contour refining function that fits the contour on a feature map moving each point along its normal.
Download file here
/// <summary>
/// Find better positions for each better at contour by searching on the edge normals on
/// the feature map
/// </summary>
/// <param name="_objectContour">The contour to be refined</param>
/// <param name="_featureMap">The feature map to be refined unto</param>
/// <param name="_normalOffset">The maximum number of pixels to offset</param>
/// <param name="_featureThreshold">The minimum feature value acceptable</param>
/// <returns>Refined Contour</returns>
public static Seq<Point> ContourRefine(
Seq<Point> _objectContour,
Image<Gray, float> _featureMap,
int _normalOffset = 5,
float _featureThreshold = float.MaxValue,
float _inertiaCoeff = 1.0f,
float _multiplierCoeff = -1.0f)
{
List<Point> pointsFitted = new List<Point>();
Point[] pointsArray = _objectContour.ToArray();
for (int i = 0; i < pointsArray.Length; i++)
{
int noPoints = pointsArray.Length,
ki = (i + 1) % noPoints, ik = (i >= 1) ? (i - 1) : (noPoints - 1 + i) % noPoints;
Point pointCurrent = pointsArray[i],
pointNext = pointsArray[ki],
pointPrev = pointsArray[ik];
// get normals pointing in and out
PointF pointNormalOut = NormalAtPoint(pointPrev, pointCurrent, pointNext, false),
pointNormalIn = NormalAtPoint(pointPrev, pointCurrent, pointNext, true);
// get points away from normal
Point pointOut = new Point(
(int)Math.Round(pointNormalOut.X * _normalOffset) + pointCurrent.X,
(int)Math.Round(pointNormalOut.Y * _normalOffset) + pointCurrent.Y),
pointIn = new Point(
(int)Math.Round(pointNormalIn.X * _normalOffset) + pointCurrent.X,
(int)Math.Round(pointNormalIn.Y * _normalOffset) + pointCurrent.Y);
LineSegment2D lineOut = new LineSegment2D(pointCurrent, pointOut),
lineIn = new LineSegment2D(pointCurrent, pointIn);
// sample along the normals
float[,] sampleIn = _featureMap.Sample(lineIn);
float[,] sampleOut = _featureMap.Sample(lineOut);
float maxByte = 0.0f, sample = 0.0f;
int j = 0;
bool inOut = false;
// run through the normal pointing out to find the best fit
for (int k = 0; k < sampleOut.Length; k++)
{
sample = sampleOut[k, 0] + _multiplierCoeff * (float)Math.Pow(_inertiaCoeff, k);
if (sample > maxByte)
{
maxByte = sample;
j = k;
inOut = false;
}
}
// run through the normal pointing in to find the best fit
for (int k = 0; k < sampleIn.Length; k++)
{
sample = sampleIn[k, 0] + _multiplierCoeff * (float)Math.Pow(_inertiaCoeff, k);
if (sample > maxByte)
{
maxByte = sample;
j = k;
inOut = true;
}
}
// if feature on point found exceeds a threshold add it to the contour
if (maxByte >= _featureThreshold)
{
int x, y;
double length, xLength, yLength;
if (!inOut)
{
xLength = lineOut.P1.X - lineOut.P2.X;
yLength = lineOut.P1.Y - lineOut.P2.Y;
length = lineOut.Length;
x = (int)Math.Round((float)j / (float)sampleOut.Length * pointNormalOut.X * _normalOffset);
y = (int)Math.Round((float)j / (float)sampleOut.Length * pointNormalOut.Y * _normalOffset);
}
else
{
xLength = lineIn.P1.X - lineIn.P2.X;
yLength = lineIn.P1.Y - lineIn.P2.Y;
length = lineIn.Length;
x = (int)Math.Round((float)j / (float)sampleIn.Length * pointNormalIn.X * _normalOffset);
y = (int)Math.Round((float)j / (float)sampleIn.Length * pointNormalIn.Y * _normalOffset);
}
pointsFitted.Add(new Point(pointCurrent.X + x, pointCurrent.Y + y));
}
}
_objectContour.Clear();
_objectContour.PushMulti(pointsFitted.ToArray(), BACK_OR_FRONT.BACK);
return _objectContour;
}
/// <summary>
/// Calulcate the normal at given point
/// </summary>
/// <param name="_prevPoint">Previous point</param>
/// <param name="_currentPoint">Current point</param>
/// <param name="_nextPoint">Next point</param>
/// <param name="_inOut">In or out flag</param>
/// <returns>Normal at point</returns>
public static PointF NormalAtPoint(
Point _prevPoint,
Point _currentPoint,
Point _nextPoint,
bool _inOut = true)
{
PointF normal;
float dx1 = _currentPoint.X - _prevPoint.X,
dx2 = _nextPoint.X - _currentPoint.X,
dy1 = _currentPoint.Y - _prevPoint.Y,
dy2 = _nextPoint.Y - _currentPoint.Y;
if (_inOut)
normal = new PointF((dy1 + dy2) * 0.5f, -(dx1 + dx2) * 0.5f);
else
normal = new PointF(-(dy1 + dy2) * 0.5f, (dx1 + dx2) * 0.5f);
return NormalizePoint(normal);
}
/// <summary>
/// Normalize a given point so its _noBinsAngle equals to one
/// </summary>
/// <param name="_point">Point to normalize</param>
/// <returns>Normalized point</returns>
public static PointF NormalizePoint(PointF _point)
{
float length = (float)Math.Sqrt(_point.X * _point.X + _point.Y * _point.Y);
if (length > 0.0f)
return new PointF(_point.X / length, _point.Y / length);
return new PointF(0.0f, 0.0f);
}