/// <summary>
        /// Collects the labeling objects.
        /// </summary>
        /// <param name="count">The objects count.</param>
        /// <param name="map">The objects labels map.</param>
        /// <returns>The list of objects segments.</returns>
        private SegmentInfo[] CollectObjects(int count, byte[] map)
        {
            int label    = 0;
            int position = 0;

            int[]  x1   = new int[count + 1];
            int[]  y1   = new int[count + 1];
            int[]  x2   = new int[count + 1];
            int[]  y2   = new int[count + 1];
            long[] cx   = new long[count + 1];
            long[] cy   = new long[count + 1];
            long[] area = new long[count + 1];

            for (int j = 1; j <= count; ++j)
            {
                x1[j] = this.width;
                y1[j] = this.height;
            }

            for (int y = 0; y < this.height; ++y)
            {
                for (int x = 0; x < this.width; ++x, ++position)
                {
                    label = map[position];

                    if (label == 0)
                    {
                        continue;
                    }

                    x1[label] = x < x1[label] ? x : x1[label];
                    x2[label] = x > x2[label] ? x : x2[label];
                    y1[label] = y < y1[label] ? y : y1[label];
                    y2[label] = y > y2[label] ? y : y2[label];

                    cx[label] += x;
                    cy[label] += y;
                    area[label]++;
                }
            }

            double absoluteAreaThrehold   = 0;
            double absoluteWidthThrehold  = 0;
            double absoluteHeightThrehold = 0;

            for (int i = 1; i <= count; ++i)
            {
                absoluteAreaThrehold   = Math.Max(absoluteAreaThrehold, area[i]);
                absoluteWidthThrehold  = Math.Max(absoluteWidthThrehold, x2[i] - x1[i]);
                absoluteHeightThrehold = Math.Max(absoluteHeightThrehold, y2[i] - y1[i]);

                cx[i] /= area[i];
                cy[i] /= area[i];
            }

            absoluteAreaThrehold   = this.areaThreshold * absoluteAreaThrehold;
            absoluteWidthThrehold  = this.sizeThreshold * absoluteWidthThrehold;
            absoluteHeightThrehold = this.sizeThreshold * absoluteHeightThrehold;

            List <SegmentInfo> result = new List <SegmentInfo>();

            for (int j = 1; j <= count; ++j)
            {
                if (area[j] < absoluteAreaThrehold || (x2[j] - x1[j]) < (absoluteWidthThrehold) || (y2[j] - y1[j]) < absoluteHeightThrehold)
                {
                    continue;
                }

                SegmentInfo info = ComputeSegmentInfo(j, map, x1[j], x2[j], y1[j], y2[j], (int)cx[j], (int)cy[j]);

                if (info != null)
                {
                    result.Add(info);
                }
            }

            return(result.ToArray());
        }
        /// <summary>
        /// Extracts the objects from the specified image.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="segments">The fingerprint segments.</param>
        /// <param name="fingerImages">The cropped and rotated segments images.</param>
        /// <returns><c>true</c> if extractions completes successfully; otherwise, <c>false</c>.</returns>
        public bool Extract(Bitmap image, out SegmentInfo[] segments, out Image[] fingerImages)
        {
            if (!Extract(image, out segments))
            {
                fingerImages = new Image[segments.Length];
                return(false);
            }

            fingerImages = new Image[segments.Length];

            PointF[] vertices       = new PointF[4];
            Matrix   transformation = new Matrix();

            int[] limits = new int[4];

            for (int i = 0; i < segments.Length; ++i)
            {
                SegmentInfo info       = segments[i];
                int         halfWidth  = info.Size.Width / 2;
                int         halfHeight = info.Size.Height / 2;

                transformation.Reset();
                transformation.Translate(info.Centroid.X, info.Centroid.Y);
                transformation.Rotate(-info.Rotation);

                vertices[0] = new PointF(-halfWidth, -halfHeight);
                vertices[1] = new PointF(halfWidth, -halfHeight);
                vertices[2] = new PointF(-halfWidth, halfHeight);
                vertices[3] = new PointF(halfWidth, halfHeight);
                limits[0]   = int.MaxValue;
                limits[1]   = 0;
                limits[2]   = int.MaxValue;
                limits[3]   = 0;

                transformation.TransformPoints(vertices);

                for (int j = 0; j < 4; ++j)
                {
                    limits[0] = (int)Math.Min(limits[0], vertices[j].X);
                    limits[1] = (int)Math.Max(limits[1], vertices[j].X);
                    limits[2] = (int)Math.Min(limits[2], vertices[j].Y);
                    limits[3] = (int)Math.Max(limits[3], vertices[j].Y);
                }

                int    cropWidth    = limits[1] - limits[0];
                int    cropHeight   = limits[3] - limits[2];
                Bitmap segmentImage = new Bitmap(info.Size.Width, info.Size.Height);

                transformation.Reset();
                transformation.Translate(-cropWidth / 2, -cropHeight / 2);
                transformation.Rotate(info.Rotation, MatrixOrder.Append);
                transformation.Translate(halfWidth, halfHeight, MatrixOrder.Append);

                using (Graphics gfx = Graphics.FromImage(segmentImage))
                {
                    gfx.Transform         = transformation;
                    gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    gfx.SmoothingMode     = SmoothingMode.HighQuality;
                    gfx.DrawImage(image, 0.0f, 0.0f, new Rectangle(limits[0], limits[2], cropWidth, cropHeight), GraphicsUnit.Pixel);
                }

                fingerImages[i] = segmentImage;
            }

            return(true);
        }