Showing posts with label Emgu. Show all posts
Showing posts with label Emgu. Show all posts

Friday, March 2, 2012

Contour Refinement

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

        /// <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);
        }

Monday, February 27, 2012

HAAR xml file

Because many people have asked for it, I believe that it will make your life easier I give you my trained hand HAAR cascade xml file.
It's trained on about 20k positives and 20k negatives and works on any orientation.
Watch for high false positive rates. It also works with the cuda version of OpenCV.

It will help you but it won't make you happy.

The xml download. In a later post I will show you how to make haar cascade perform even better.

Monday, January 30, 2012

Recognising Fingertips

Pulling an all nighter is always rewarding , so Friday night I made my first attempt at detecting the fingertips on the hand. Most people use the "convex irregularities" method. I really didn't like that method. It seems sloppy and doesn't detect all fingers. I prefer the kcosines method as described in "Vision-Based Finger Action Recognition by Angle Detection and Contour Analysis".

These are the results of my first attempt.

This week I'll concentrate on making tracking and contour extraction more robust because as you can see at some points the contours break up. I guess selecting my wooden office desk as a testing area proved to be quite a challenge.

Wednesday, January 25, 2012

Extracting and stabilizing contours


It's been a busy week. I'm now at the stage of contour extraction. Using my adaptive skin classifier and the samples gathered from the detector I build a histogram model for the hand and extract the contours around it. As you can see it is quite robust and works under different illumination. All these are possible with the assumption that the detector doesn't return a false positive. While HAAR cascades are fairly good at the job they don't have a 0% percent of false positives so I intend to add a Fourier hand validator.

Saturday, January 7, 2012

Well I'm getting closer...

 

I'm usually very critical of the stuff I make but today I feel quite satisfied with the results.

Tuesday, January 3, 2012

The constraints of histogram tracking

While in the last months I have implemented and tested a couple of histogram based tracking algorithms I only recently realized their inherent constraints. If you use it for hand tracking and you have your arms naked or your face exposed it is very easy for the tracker to get confused because of the coarse quantization of the histograms.
The worst part is that there is little you can do :
  • I tried using a different color space such as HSV. While it is far better that RGB for tracking skin and coping for small lighting variances it is still not enough and the tracker often gets confused especially when the hand goes out of view.
  • I tried incorporating different features such as edge magnitude (bad idea) and edge orientation (much better) and it had the effect of better localizing the detector. 
Integral histograms are still an option but there are far too slow for a real time application like the one I'm working on.

Thursday, December 29, 2011

Mixing Histograms

In the last few days I implemented an adaptive skin classifier in make it easier for the tracking algorithms to find the objects. Emgu already has an adaptive skin classifier but you cannot change any parameters and the results are horrible.

Anyway, mixing two histograms in Emgu is not as straightforward as one might think.
The internal representation of the histograms is the N-th dimensional matrix class , MatND and the OpenCV does not overload any operators.

Thankfully there is a function in the cvInvoke that does the job.
I wasted about half an hour trying to use the cvAdd function but it crashed the whole thing about a dozen times, so I ended up using the cvAddWeighted function.

Below is the code snippet for histogram mixing :

        /// <summary>
        /// Mixes two histograms , new histogram is _mixCoeff * _histTarget + (1 - _mixCoeff) * _histMix
        /// Old target histogram is destroyed
        /// </summary>
        /// <param name="_histTarget">Target histogram</param>
        /// <param name="_hist1">Histogram 1</param>
        /// <param name="_hist2">Histogram 2</param>
        /// <param name="_mixCoeff">Mixing Coefficient</param>
        public static void MixHistograms(
            DenseHistogram _histTarget,
            DenseHistogram _hist1,
            DenseHistogram _hist2,
            float _mixCoeff)
        {
            CvInvoke.cvAddWeighted(
                _hist1.MatND.Ptr,
                _mixCoeff,
                _hist2.MatND.Ptr,
                1.0d - _mixCoeff,
                0,
                _histTarget.MatND.Ptr);
        }

PS. On a following post I will post the adaptive skin classifier.

Monday, November 21, 2011

Particle Filter

Well, the particle filter is finished and it is great...it is like a real time genetic algorithm with a very short selection of features and a stupid evaluation process.

Nevertheless it is done and it is working.

Strangely I don't feel any satisfaction after finishing this. It's a bit weird.
Anyway , maybe I'll post some code later on. But (there is always a but) there are so many parameters to tweak that it is driving me crazy...I will now select the features to follow, I'm looking for illumination, rotation and position invariant features to use, unfortunately such features don't exist or are hugely expensive to compute.