using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

//Emgu
using Emgu.CV;
using Emgu.CV.Features2D;
using Emgu.CV.Structure;
using Emgu.CV.UI;

namespace MyImageProcessingClasses
{
    /// <summary>
    /// Class which contains constants and static objects necessary for used image processing operations.
    /// </summary>
    public static class ImageProcessingTools
    {
        #region Constants

        /// <summary>
        /// Hysteresis of hue used for thresholding image.
        /// </summary>
        public static readonly double HUE_HYSTERESIS = 10.0;

        /// <summary>
        /// Hysteresis of saturation used for thresholding image.
        /// </summary>
        public static readonly double SATURATION_HYSTERESIS = 100.0;

        /// <summary>
        /// Index of first jpeg encoder parameter.
        /// </summary>
        public static readonly Byte ENCODER_PARAMETER_FIRST = 0;

        /// <summary>
        /// Size of rectangle used to visualize center of detected mark.
        /// </summary>
        private static readonly Int32 CENTER_RESTANGLE_SIZE = 15;

        #endregion

        #region Public objects

        /// <summary>
        /// Font for graphic labels.
        /// </summary>
        public static Font myFont = new Font(FontFamily.GenericSansSerif, 8f);

        /// <summary>
        /// Brushes to paint detected center of marks.
        /// </summary>
        public static Brush[] myBrushes = { new SolidBrush(Color.GreenYellow), 
                                            new SolidBrush(Color.Red), 
                                            new SolidBrush(Color.Blue),
                                            new SolidBrush(Color.Yellow)};

        /// <summary>
        /// Defualt point (left upper corner of the image).
        /// </summary>
        public static Point defaultPoint = new Point(0, 0);

        /// <summary>
        /// Structuring element for morphological operations.
        /// </summary>
        public static StructuringElementEx StructEl_circle_5_5 = new StructuringElementEx(5, 5, 2, 2, Emgu.CV.CvEnum.CV_ELEMENT_SHAPE.CV_SHAPE_ELLIPSE);

        /// <summary>
        /// Jpeg encoder.
        /// </summary>
        public static ImageCodecInfo jpegCodec = GetEncoder(ImageFormat.Jpeg);

        /// <summary>
        /// Encoder to process camera frame before it is sent.
        /// </summary>
        public static System.Drawing.Imaging.Encoder cameraFrameEncoder = System.Drawing.Imaging.Encoder.Quality;

        /// <summary>
        /// Container of encoder parameters.
        /// </summary>
        public static EncoderParameters cameraFrameEncoderParameters = new EncoderParameters();

        /// <summary>
        /// Array of 100 elements representing jpeg encoder with compression level corresponding with its index.
        /// </summary>
        public static EncoderParameter[] cameraFrameEncoderParameter = CreateJpegEncoderParametersArray();

        #endregion

        #region Methods

        /// <summary>
        /// Return array of 100 elements representing jpeg encoder with compression level corresponding with its index.
        /// </summary>
        /// <returns>Encoder parameters array.</returns>
        private static EncoderParameter[] CreateJpegEncoderParametersArray()
        {
            const Byte PARAMETERS_COUNT = 101;

            EncoderParameter[] eParams = new EncoderParameter[PARAMETERS_COUNT];

            for (Byte i = 0; i < PARAMETERS_COUNT; i++)
                eParams[i] = new EncoderParameter(cameraFrameEncoder, (Int64)i);

            return eParams; 
        }

        /// <summary>
        /// Get encoder for specified image format.
        /// </summary>
        /// <param name="format">Image format to get encoder for.</param>
        /// <returns>Image codec info.</returns>
        private static ImageCodecInfo GetEncoder(ImageFormat format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
            
            //find right encoder
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }

            return null;
        }

        /// <summary>
        /// Threshold image in HSV space by specifeied HSV values.
        /// </summary>
        /// <param name="image">BGR Image to be thresholded.</param>
        /// <param name="thresholdHSV">HSV values to be used for thresholding.</param>
        /// <returns>Thresholded Gray Image.</returns>
        public static Image<Gray, Byte> ThresholdImageInHSV(Image<Bgr, Byte> image, Hsv thresholdHSV)
        {
            Image<Gray, Byte> imageOut;

            //Threshold image in HSV color space
            try
            {
                imageOut = image.Convert<Hsv, Byte>().InRange(
                       new Hsv(thresholdHSV.Hue - ImageProcessingTools.HUE_HYSTERESIS, thresholdHSV.Satuation - ImageProcessingTools.SATURATION_HYSTERESIS, 0),  //Lower level
                       new Hsv(thresholdHSV.Hue + ImageProcessingTools.HUE_HYSTERESIS, thresholdHSV.Satuation + ImageProcessingTools.SATURATION_HYSTERESIS, 255) //Higher level
                       );
            }
            catch (Exception)
            {
                //Create empty image
                imageOut = new Image<Gray, Byte>(image.Size);
            }

            //Morpohological operations to supress noise
            imageOut = imageOut.MorphologyEx(ImageProcessingTools.StructEl_circle_5_5, Emgu.CV.CvEnum.CV_MORPH_OP.CV_MOP_OPEN, 2);

            return imageOut;
        }

        /// <summary>
        /// Find center of mass of whole input image.
        /// </summary>
        /// <param name="image">Input image.</param>
        /// <returns>Point of center of mass.</returns>
        public static Point FindCenter(Image<Gray, Byte> image)
        {
            Point center = new Point();

            try
            {
                //Get all moments of the image, consider image to be binary
                MCvMoments moments = image.GetMoments(true);

                //Spatial moment 10 / area (moment 00) = x coordinate of center of mass
                center.X = Convert.ToInt32(moments.m10 / moments.m00);

                //Spatial moment 01 / area (moment 00) = y coordinate of center of mass
                center.Y = Convert.ToInt32(moments.m01 / moments.m00);
            }
            catch (OverflowException)
            {
                //Reset the coordinates
                center.X = 0;
                center.Y = 0;
            }

            return center;
        }

        /// <summary>
        /// Gets rectangle to draw ellipse.
        /// </summary>
        /// <param name="image">Thresholded image.</param>
        /// <returns>Rectangle surrounding centre of geometric moments of the image.</returns>
        public static Rectangle GetCenterRectangle(Image<Gray, Byte> image)
        {
            Rectangle rect;
            Size size = new Size(CENTER_RESTANGLE_SIZE, CENTER_RESTANGLE_SIZE);

            //Find center of moment in specified image
            Point center = FindCenter(image);

            //Try to create the new rectangle
            try
            {
                rect = new Rectangle(center.X - CENTER_RESTANGLE_SIZE / 2, center.Y - CENTER_RESTANGLE_SIZE / 2, CENTER_RESTANGLE_SIZE, CENTER_RESTANGLE_SIZE);
            }
            catch
            {
                rect = new Rectangle(new Point(0, 0), size);
            }

            return rect;
        }

        /// <summary>
        /// Gets rectangle to draw ellipse.
        /// </summary>
        /// <param name="point">Point to surround.</param>
        /// <returns>Rectangle surrounding specified point.</returns>
        public static Rectangle GetCenterRectangle(Point point)
        {
            Rectangle rect;
            Size size = new Size(CENTER_RESTANGLE_SIZE, CENTER_RESTANGLE_SIZE);

            //Try to create the new rectangle
            try
            {
                rect = new Rectangle(point.X - CENTER_RESTANGLE_SIZE / 2, point.Y - CENTER_RESTANGLE_SIZE / 2, CENTER_RESTANGLE_SIZE, CENTER_RESTANGLE_SIZE);
            }
            catch
            {
                rect = new Rectangle(new Point(0, 0), size);
            }

            return rect;
        }

        /// <summary>
        /// Extension method to calculate center of specified rectangle.
        /// </summary>
        /// <param name="rect">Rectangle.</param>
        /// <returns>Center coordinates.</returns>
        public static Point Center(this Rectangle rect)
        {
            return new Point(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2);
        }

        #endregion
    }

    /// <summary>
    /// Class to calibrate camera and transform coordinates using homography.
    /// </summary>
    public class CameraCalibrator
    {
        /// <summary>
        /// Enum to specify point location
        /// </summary>
        public enum PointLocation : byte
        {
            /// <summary>
            /// Upper left point.
            /// </summary>
            UPPER_LEFT = 0,

            /// <summary>
            /// Upper right point.
            /// </summary>
            UPPER_RIGHT = 1,

            /// <summary>
            /// Lower right point.
            /// </summary>
            LOWER_RIGHT = 2,

            /// <summary>
            /// Lower left point.
            /// </summary>
            LOWER_LEFT = 3
        };

        /// <summary>
        /// Calibration points coordinates.
        /// </summary>
        public static Dictionary<Byte, PointF> CalibrationPoints = new Dictionary<Byte, PointF>()
        {
            {(Byte)PointLocation.UPPER_LEFT, new PointF(-0.5f,1f)},
            {(Byte)PointLocation.UPPER_RIGHT, new PointF(0.5f,1f)},
            {(Byte)PointLocation.LOWER_RIGHT, new PointF(0.5f,0.5f)},
            {(Byte)PointLocation.LOWER_LEFT, new PointF(-0.5f,0.5f)}
        };

        /// <summary>
        /// Template calibration point.
        /// </summary>
        private List<PointF> templatePoints;

        /// <summary>
        /// Calibration point specified by user in camera image.
        /// </summary>
        private List<PointF> selectedPoints;

        /// <summary>
        /// Homography matrix.
        /// </summary>
        private HomographyMatrix homographyMatrix;

        /// <summary>
        /// Homography matrix getter.
        /// </summary>
        public HomographyMatrix HomographyMat
        {
            get
            {
                return homographyMatrix;
            }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public CameraCalibrator()
        {
            //Template points initialization
            templatePoints = new List<PointF> { CalibrationPoints[(Byte)PointLocation.UPPER_LEFT],
                                                CalibrationPoints[(Byte)PointLocation.UPPER_RIGHT],
                                                CalibrationPoints[(Byte)PointLocation.LOWER_RIGHT],
                                                CalibrationPoints[(Byte)PointLocation.LOWER_LEFT],
                                                };

            //Selected point are initalized as empty set
            selectedPoints = new List<PointF> { new PointF(-1f, -1f),
                                                new PointF(-1f, -1f),
                                                new PointF(-1f, -1f),
                                                new PointF(-1f, -1f)};

            homographyMatrix = new HomographyMatrix();
        }

        /// <summary>
        /// Adds point into the list.
        /// </summary>
        /// <param name="point">User defined point.</param>
        /// <param name="location">Location of point.</param>
        public void AddCalibrationPoint(PointF point, PointLocation location)
        {
            selectedPoints[(int)location] = point;
        }

        /// <summary>
        /// Calculates homography matrix.
        /// </summary>
        public void CalculateHomography()
        {
            homographyMatrix = CameraCalibration.FindHomography(selectedPoints.ToArray(),                                          
                                                                templatePoints.ToArray(),
                                                                Emgu.CV.CvEnum.HOMOGRAPHY_METHOD.DEFAULT, 1.0);
        }

        /// <summary>
        /// Transforms point coordinates using homography matrix
        /// </summary>
        /// <param name="point">Point to be transformed.</param>
        /// <returns>Transformed point.</returns>
        /// <seealso cref="http://en.wikipedia.org/wiki/Homography"/>
        public PointF TransformPoint(PointF point)
        {
            //Create data array
            Double[,] data = new Double[3, 1]{  {(Double)point.X},  //X
                                                {(Double)point.Y},  //Y
                                                {1.0}               //Scale
                                             };
            
            //Matrix from data
            Matrix<Double> matrixTemp = new Matrix<double>(data);

            //Transform vector using homography matrix
            matrixTemp = homographyMatrix.Mul(matrixTemp);

            /*Return transformed point. 
            Note that homographyMatrix is not generally the affine homography matrix,
            therefore x2 = x2'/w' and y2 = y2'/w', where w' is scale in the new plane*/
            return new PointF((float)(matrixTemp[0, 0] / matrixTemp[2, 0]), (float)(matrixTemp[1, 0] / matrixTemp[2, 0]));
        }

        /// <summary>
        /// Determines whether all calibration points have been specified.
        /// </summary>
        /// <returns>True on success.</returns>
        public Boolean AllCalibrationPointsSpecified
        {
            get
            {
                foreach (PointF point in selectedPoints)
                    if (point.X < 0 || point.Y < 0)
                        return false;

                return true;
            }
        }

        /// <summary>
        /// ToString() method override.
        /// </summary>
        /// <returns>Homography matrix elements in a string.</returns>
        public override string ToString()
        {
            StringBuilder outString = new StringBuilder();

            //Parse matrix elements into string
            for (byte i = 0; i < homographyMatrix.Rows; i++)
            {
                for (byte j = 0; j < homographyMatrix.Cols; j++)
                { 
                    outString.Append(homographyMatrix[i,j].ToString("F3") + " ");
                }

                outString.Append(Environment.NewLine);
            }

            return outString.ToString();
        }
    }

    /// <summary>
    /// Class used to determine FPS of the capture.
    /// </summary>
    public class FPS_Counter
    {
        /// <summary>
        /// Time when last frame was captured.
        /// </summary>
        private DateTime lastFrameTime;

        /// <summary>
        /// Framerate [FPS].
        /// </summary>
        private double fps;

        /// <summary>
        /// Framerate getter [fps].
        /// </summary>
        public double FPS
        {
            get
            { 
                return fps;
            }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public FPS_Counter()
        {
            //Set default values
            lastFrameTime = DateTime.Now;
            fps = -1.0;
        }

        /// <summary>
        /// Signal captured frame to calculate fps.
        /// </summary>
        public void FrameCaptured()
        {
            DateTime currentTime = DateTime.Now;

            //Calculate fps
            fps = Convert.ToDouble(1 / ((currentTime - lastFrameTime).Milliseconds / 1000.0));

            lastFrameTime = currentTime;
        }
    }

    /// <summary>
    /// Class used to detect marks by averaging previous measurement results.
    /// </summary>
    public class MarksDetector
    { 
        /// <summary>
        /// Length of buffer to store previous measurements.
        /// </summary>
        private const Byte MARK_BUFFER_LENGTH = 5;

        /// <summary>
        /// Maximal allowed deviation.
        /// </summary>
        private const float DEVIATION_THRESH = 10f;

        /// <summary>
        /// Queue to store last values.
        /// </summary>
        private Queue<PointF>  marksBuffer = new Queue<PointF>(MARK_BUFFER_LENGTH);

        /// <summary>
        /// Lock for buffer operations.
        /// </summary>
        private Object bufferAccessLock = new Object();

        /// <summary>
        /// Default constructor.
        /// </summary>
        public MarksDetector()
        { }

        /// <summary>
        /// Empty the query.
        /// </summary>
        public void Reset()
        {
            lock(bufferAccessLock)
                marksBuffer.Clear();
        }

        /// <summary>
        /// Adds point into the query.
        /// </summary>
        /// <param name="point">Point.</param>
        public void AddPoint(PointF point)
        {
            lock (bufferAccessLock)
            {
                if (marksBuffer.Count < MARK_BUFFER_LENGTH)
                {
                    //Buffer not full, just add point
                    marksBuffer.Enqueue(point);
                }
                else
                {
                    //Dequeue oldest point and and new one
                    marksBuffer.Dequeue();
                    marksBuffer.Enqueue(point);
                }
            }
        }

        /// <summary>
        /// Buffer full property getter.
        /// </summary>
        public Boolean IsBufferFull
        {
            get
            {
                lock(bufferAccessLock)
                    return (marksBuffer.Count == MARK_BUFFER_LENGTH);
            }
        }

        /// <summary>
        /// Detect point using values stored in buffer.
        /// </summary>
        /// <param name="detectedPoint">Detected point.</param>
        /// <returns>True if succeedes.</returns>
        public Boolean DetectMark(out Point detectedPoint)
        {
            List<float> xCoordList = new List<float>(MARK_BUFFER_LENGTH);
            List<float> yCoordList = new List<float>(MARK_BUFFER_LENGTH);

            Double averageX, averageY, squaresSum, deviation;  //Variables for statictics

            detectedPoint = new Point();

            //Lock buffer access to get consistent data
            lock(bufferAccessLock)
            {
                //Buffer not full, can not calculate average
                if (!IsBufferFull)
                    return false;

                foreach (PointF point in marksBuffer)
                {
                    xCoordList.Add(point.X);
                    yCoordList.Add(point.Y);
                }
            }
                
            //Calculate standard deviation of x coordinates measurements
            averageX = xCoordList.Average();
            squaresSum = xCoordList.Select(val => (val - averageX) * (val - averageX)).Sum();
            deviation = Math.Sqrt(squaresSum / xCoordList.Count);

            if (deviation > DEVIATION_THRESH)
                return false;

            //Calculate standard deviation of y coordinates measurements
            averageY = yCoordList.Average();
            squaresSum = yCoordList.Select(val => (val - averageY) * (val - averageY)).Sum();
            deviation = Math.Sqrt(squaresSum / yCoordList.Count);

            if (deviation > DEVIATION_THRESH)
                return false;

            //Deviation within allowed border, return point
            detectedPoint = new Point((Int32)averageX, (Int32)averageY);

            return true;
        }
    }

    /// <summary>
    /// Class to detecect marks using features comparison with model.
    /// </summary>
    /// <see cref="http://www.emgu.com/wiki/index.php/SURF_feature_detector_in_CSharp"/>
    public class FeatureDetector : Emgu.Util.DisposableObject
    {
        #region Private variables

        private Features2DTracker<float> _tracker;
        private SURFDetector _detector;
        private MemStorage _rectangleStorage;
        private Contour<Point> _rectangle;
        private Byte _referenceHue;

        #endregion

        /// <summary>
        /// enum to specify reference hue.
        /// </summary>
        public enum RefrenceHue : byte
        {
            /// <summary>
            /// Red color hue.
            /// </summary>
            Red = 5,

            /// <summary>
            /// Green color hue.
            /// </summary>
            Green = 70,

            /// <summary>
            /// Blue color hue.
            /// </summary>
            Blue = 160,

            /// <summary>
            /// Cyan hue.
            /// </summary>
            Cyan = 120,

            /// <summary>
            /// Magenta hue.
            /// </summary>
            Magenta = 200,

            /// <summary>
            /// Yellow hue.
            /// </summary>
            Yellow = 40
        };

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="featureModel">Model image.</param>
        /// <param name="referenceHue">Reference hue.</param>
        public FeatureDetector(Image<Bgr, Byte> featureModel, Byte referenceHue)
        {
            _detector = new SURFDetector(500, false);
            _referenceHue = referenceHue;

            using (Image<Gray, Byte> mask = GetPixelMask(featureModel, referenceHue))
            {
                try
                {
                    //Try to detect features
                    _tracker = new Features2DTracker<float>(_detector.DetectFeatures(mask, null));
                }
                catch (IndexOutOfRangeException)
                { 
                    //No features detected
                }
            }
            _rectangleStorage = new MemStorage();
            _rectangle = new Contour<Point>(_rectangleStorage);

            //Create new contour reference rectangle
            _rectangle.PushMulti(new Point[] { 
            new Point(0, 0),
            new Point(3, 0),
            new Point(3, 2),
            new Point(0, 2)},
               Emgu.CV.CvEnum.BACK_OR_FRONT.FRONT);
        }

        /// <summary>
        /// Compute the pixel mask for the given image.
        /// </summary>
        /// <param name="image">The color image to find mask from.</param>
        /// <param name="referenceHue">Hue of reference color.</param>
        /// <returns>Masked image.</returns>
        private static Image<Gray, Byte> GetPixelMask(Image<Bgr, byte> image, Byte referenceHue)
        {
            const Byte HUE_RANGE = 25;  //Allowed hue deviation
            const Double VALUE_LOWER = 30.0; //Lower threshold of value
            const Double VALUE_HIGHER = 230.0; //Higher threshold of value

            using (Image<Hsv, Byte> hsv = image.Convert<Hsv, Byte>())
            {
                Image<Gray, Byte>[] channels = hsv.Split();

                try
                {
                    //channels[0] is hue channel, select pixels within the specified range
                    CvInvoke.cvInRangeS(channels[0],
                        new MCvScalar((referenceHue - HUE_RANGE) < 1 ? 1.0 : (Double)(referenceHue - HUE_RANGE)),
                        new MCvScalar((referenceHue + HUE_RANGE) > 255 ? 255.0 : (Double)(referenceHue + HUE_RANGE)),
                        channels[0]);

                    //Threshold saturation
                    CvInvoke.cvThreshold(channels[1], channels[1], Byte.MaxValue / 2.0, (Double)Byte.MaxValue, Emgu.CV.CvEnum.THRESH.CV_THRESH_BINARY);

                    //channels[2] (value) -> filter value to eliminate black and white pixels
                    CvInvoke.cvInRangeS(channels[2], new MCvScalar(VALUE_LOWER), new MCvScalar(VALUE_HIGHER), channels[2]);
                    channels[2]._Not();

                    //Create mask
                    CvInvoke.cvAnd(channels[0], channels[2], channels[0], IntPtr.Zero);
                    CvInvoke.cvAnd(channels[0], channels[1], channels[0], IntPtr.Zero);
                }
                finally
                {
                    channels[1].Dispose();
                    channels[2].Dispose();
                }

                return channels[0];
            }
        }

        private void FindFeature(Image<Bgr, byte> img, List<Image<Gray, Byte>> featureList, List<Rectangle> boxList, Contour<Point> contours)
        {
            Int32 maxFeatures = 0;

            //Run through all detected contours
            for (; contours != null; contours = contours.HNext)
            {
                contours.ApproxPoly(contours.Perimeter * 0.02, 0, contours.Storage);
                if (contours.Area > 50)
                {
                    double ratio = CvInvoke.cvMatchShapes(_rectangle, contours, Emgu.CV.CvEnum.CONTOURS_MATCH_TYPE.CV_CONTOURS_MATCH_I3, 0);

                    if (ratio > 0.3) //Not a good match of contour shape, re-run
                    {
                        Contour<Point> child = contours.VNext;
                        if (child != null)
                            FindFeature(img, featureList, boxList, child);
                        continue;
                    }

                    Rectangle box = contours.BoundingRectangle;

                    Image<Gray, Byte> candidate;
                    using (Image<Bgr, Byte> tmp = img.Copy(box))
                        candidate = tmp.Convert<Gray, byte>();

                    //Set the value of pixels out of the contour region to zero
                    using (Image<Gray, Byte> mask = new Image<Gray, byte>(box.Size))
                    {
                        mask.Draw(contours, new Gray(255), new Gray(255), 0, -1, new Point(-box.X, -box.Y));

                        double mean = CvInvoke.cvAvg(candidate, mask).v0;
                        candidate._ThresholdBinary(new Gray(mean), new Gray(255.0));
                        candidate._Not();
                        mask._Not();
                        candidate.SetValue(0, mask);
                    }

                    //Detect features
                    ImageFeature<float>[] features = _detector.DetectFeatures(candidate, null);

                    //Match features of image and model
                    Features2DTracker<float>.MatchedImageFeature[] matchedFeatures = new Features2DTracker<float>.MatchedImageFeature[0];

                    //At least two features neccessary to find neighbours
                        if (features.Length >= 2)
                            matchedFeatures = _tracker.MatchFeature(features, 2);

                    int goodMatchCount = 0;

                    //Compare metric distance of detected features to find number of corresponding features
                    foreach (Features2DTracker<float>.MatchedImageFeature ms in matchedFeatures)
                        if (ms.SimilarFeatures[0].Distance < 0.7) goodMatchCount++;

                    if ((goodMatchCount >= 3) && (goodMatchCount > maxFeatures))
                    {
                        //Add only the item with highest count of matching features
                        maxFeatures = goodMatchCount;

                        boxList.Clear();
                        boxList.Add(box);

                        featureList.Clear();
                        featureList.Add(candidate);
                    }

                }
            }
        }

        /// <summary>
        /// Detect feature in given image.
        /// </summary>
        /// <param name="img">Image to be analyzed.</param>
        /// <param name="featureList">List where detected features are stored.</param>
        /// <param name="boxList">List of detected features' contour boxes.</param>
        public void DetectFeature(Image<Bgr, byte> img, List<Image<Gray, Byte>> featureList, List<Rectangle> boxList)
        {
            Image<Bgr, Byte> smoothImg = img.SmoothGaussian(5, 5, 1.5, 1.5);
            Image<Gray, Byte> smoothedMask = GetPixelMask(smoothImg, _referenceHue);

            //Use Dilate followed by Erode to eliminate small gaps in some countour.
            smoothedMask._Dilate(2);
            smoothedMask._Erode(1);

            using (Image<Gray, Byte> canny = smoothedMask.Canny(100.0, 50.0))
            using (MemStorage stor = new MemStorage())
            {
                Contour<Point> contours = canny.FindContours(
                   Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
                   Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_TREE,
                   stor);

                try
                { FindFeature(img, featureList, boxList, contours); }
                catch (NullReferenceException) { ; }
            }
        }

        /// <summary>
        /// Free memory resources.
        /// </summary>
        protected override void DisposeObject()
        {
            try
            {
                _tracker.Dispose();
                _rectangleStorage.Dispose();
            }
            catch (NullReferenceException)
            { /*Object instance not available, nothing to dipose*/ }
        }
    }
}
