/// <summary>
        /// This will adjust the histograms throughout the entire data, ensuring that the gradient angle bin with the largest magnitude is set to one and that all other magnitudes
        /// are adjust proportionally
        /// </summary>
        public static DataRectangle <HistogramOfGradient> Normalise(DataRectangle <HistogramOfGradient> hogs)
        {
            if (hogs == null)
            {
                throw new ArgumentNullException(nameof(hogs));
            }

            var maxMagnitude = hogs.Enumerate().Select(pointAndHistogram => pointAndHistogram.Item2).Max(histogram => histogram.GreatestMagnitude);

            return((maxMagnitude == 0)             // TODO: Need tests, particularly around this sort of thing
                                ? hogs.Transform(hog => hog.Normalise())
                                : hogs.Transform(hog => hog.Multiply(1 / maxMagnitude)));
        }
예제 #2
0
        public static DataRectangle <HistogramOfGradient> Get(DataRectangle <double> source, int blockSize)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if ((blockSize <= 0) || (blockSize > source.Width) || (blockSize > source.Height))
            {
                throw new ArgumentOutOfRangeException(nameof(blockSize));
            }

            return(source
                   .Transform((value, point) =>
            {
                // Don't calculate any values for the edge points. We could try to approximate by saying pretend that the content that would outside the available
                // space is the same as the current value but that would introduce angles that are not present in the actual image. It also makes the code below
                // simple since we don't have to worry about under or over flowing the array bounds.
                if ((point.X == 0) || (point.Y == 0) || (point.X == (source.Width - 1)) || (point.Y == (source.Height - 1)))
                {
                    return new Vector(magnitude: 0, angle: 0);
                }

                // Get an angle and magnitude for how the intensity changes as we move right and down. It doesn't really matter if we go right-and-down or up-and-
                // left (or if we do dark-to-light or light-to-dark) so long as we're consistent - all we need is the direction in which the intensity is changing
                // (and how quickly it's changing)
                var dx = source[point.X + 1, point.Y] - source[point.X - 1, point.Y];
                var dy = source[point.X, point.Y + 1] - source[point.X, point.Y - 1];
                return new Vector(
                    magnitude: Math.Sqrt((dx * dx) + (dy * dy)),
                    angle: GetAngle0To180(dx, dy)
                    );
            })
                   .BlockOut(blockSize, GenerateHistogram));
        }
        /// <summary>
        /// This will adjust the histograms in the data, ensuring that no gradient angle bin has a value greater than one. The histograms will be adjusted based upon the largest magnitude
        /// with a block of histograms - all magnitudes will be divided by the greatest magnitude of any angle within that block, such that no value anywhere will be greater than one
        /// </summary>
        public DataRectangle <HistogramOfGradient> Normalise(DataRectangle <HistogramOfGradient> hogs)
        {
            if (hogs == null)
            {
                throw new ArgumentNullException(nameof(hogs));
            }

            if ((hogs.Width < _blockSize) || (hogs.Height < _blockSize))
            {
                throw new ArgumentException($"too little data ({hogs.Width}x{hogs.Height}) for specified block size ({_blockSize})");
            }

            return(hogs.Transform((hog, point) =>
            {
                var x2 = Math.Min(point.X + _blockSize, hogs.Width);
                var x1 = x2 - _blockSize;
                var y2 = Math.Min(point.Y + _blockSize, hogs.Height);
                var y1 = y2 - _blockSize;
                var maxMagnitudeWithinBlock = hogs
                                              .Enumerate((p, h) => (p.X >= x1) && (p.X <= x2) && (p.Y >= y1) && (p.Y <= y2))
                                              .Max(pointAndHog => pointAndHog.Item2.GreatestMagnitude);
                return (maxMagnitudeWithinBlock == 0)                 // TODO: Need tests, particularly around this sort of thing
                                        ? hog.Normalise()
                                        : hog.Multiply(1 / maxMagnitudeWithinBlock);
            }));
        }
예제 #4
0
        public static DataRectangle <HistogramOfGradient> Get(DataRectangle <RGB> source, int blockSize)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            return(Get(
                       source.Transform(colour => Math.Round(colour.ToGreyScale())),
                       blockSize
                       ));
        }
예제 #5
0
        private static DataRectangle <RGB> CorrectZeroResponse(DataRectangle <RGB> values)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            // Get the smallest value of any RGB component
            var smallestValue = values
                                .Enumerate()
                                .Select(point => point.Item2)
                                .SelectMany(colour => new[] { colour.R, colour.G, colour.B })
                                .Min();

            // Subtract this from every RGB component
            return(values.Transform(value => new RGB((byte)(value.R - smallestValue), (byte)(value.G - smallestValue), (byte)(value.B - smallestValue))));
        }
예제 #6
0
        /// <summary>
        /// This reduces variance in data by replacing each value with the median value from a block drawn around it (it is helpful in reducing noise in an image)
        /// </summary>
        public static DataRectangle <double> MedianFilter <TSource>(this DataRectangle <TSource> source, Func <TSource, double> valueExtractor, int blockSize)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (valueExtractor == null)
            {
                throw new ArgumentNullException(nameof(valueExtractor));
            }
            if (blockSize <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(blockSize));
            }

            return(source.Transform(currentValue => valueExtractor(currentValue)).MedianFilter(blockSize));
        }
예제 #7
0
        public DataRectangle <IRgBy> IRgByCalculator(DataRectangle <RGB> values)
        {
            if (values == null)
            {
                throw new ArgumentNullException(nameof(values));
            }

            // See http://web.archive.org/web/20090723024922/http:/geocities.com/jaykapur/face.html
            Func <byte, double> L = x => (105 * Math.Log10(x + 1));

            return(values.Transform(
                       value => new IRgBy(
                           rg: L(value.R) - L(value.G),
                           by: L(value.B) - ((L(value.G) + L(value.R)) / 2),
                           i: (L(value.R) + L(value.B) + L(value.G)) / 3
                           )
                       ));
        }
예제 #8
0
        private IEnumerable <Rectangle> IdentifyFacesFromSkinMask(DataRectangle <bool> skinMask)
        {
            if (skinMask == null)
            {
                throw new ArgumentNullException(nameof(skinMask));
            }

            // Identify potential objects from positive image (build a list of all skin points, take the first one and flood fill from it - recording the results as one object
            // and remove all points from the list, then do the same for the next skin point until there are none left)
            var skinPoints = new HashSet <Point>(
                skinMask.Enumerate((point, isMasked) => isMasked).Select(point => point.Item1)
                );
            var scale       = _config.CalculateScale(skinMask.Width, skinMask.Height);
            var skinObjects = new List <Point[]>();

            while (skinPoints.Any())
            {
                var currentPoint   = skinPoints.First();
                var pointsInObject = TryToGetPointsInObject(skinMask, currentPoint, new Rectangle(0, 0, skinMask.Width, skinMask.Height)).ToArray();
                foreach (var point in pointsInObject)
                {
                    skinPoints.Remove(point);
                }
                skinObjects.Add(pointsInObject);
            }
            skinObjects = skinObjects.Where(skinObject => skinObject.Length >= (64 * scale)).ToList();             // Ignore any very small regions

            if (_config.SaveProgressImages)
            {
                var skinMaskPreviewImage     = new Bitmap(skinMask.Width, skinMask.Height);
                var skinObjectPreviewColours = new[] { new RGB(255, 0, 0), new RGB(0, 255, 0), new RGB(0, 0, 255), new RGB(128, 128, 0), new RGB(0, 128, 128), new RGB(128, 0, 128) };
                var allSkinObjectPoints      = skinObjects.Select((o, i) => new { Points = new HashSet <Point>(o), Colour = skinObjectPreviewColours[i % skinObjectPreviewColours.Length] }).ToArray();
                skinMaskPreviewImage.SetRGB(
                    skinMask.Transform((isSkin, point) =>
                {
                    var firstObject = allSkinObjectPoints.FirstOrDefault(o => o.Points.Contains(point));
                    return((firstObject == null) ? new RGB(0, 0, 0) : firstObject.Colour);
                })
                    );
                skinMaskPreviewImage.Save("SkinObjects.png");
            }

            // Look for any fully enclosed holes in each skin object (do this by flood filling from negative points and ignoring any where the fill gets to the edges of object)
            var boundsForSkinObjects = new List <Rectangle>();

            foreach (var skinObject in skinObjects)
            {
                var xValues                = skinObject.Select(p => p.X).ToArray();
                var yValues                = skinObject.Select(p => p.Y).ToArray();
                var left                   = xValues.Min();
                var top                    = yValues.Min();
                var skinObjectBounds       = new Rectangle(left, top, width: (xValues.Max() - left) + 1, height: (yValues.Max() - top) + 1);
                var negativePointsInObject = new HashSet <Point>(
                    skinMask.Enumerate((point, isMasked) => !isMasked && skinObjectBounds.Contains(point)).Select(point => point.Item1)
                    );
                while (negativePointsInObject.Any())
                {
                    var currentPoint = negativePointsInObject.First();
                    var pointsInFilledNegativeSpace = TryToGetPointsInObject(skinMask, currentPoint, skinObjectBounds).ToArray();
                    foreach (var point in pointsInFilledNegativeSpace)
                    {
                        negativePointsInObject.Remove(point);
                    }

                    if (pointsInFilledNegativeSpace.Any(p => (p.X == skinObjectBounds.Left) || (p.X == (skinObjectBounds.Right - 1)) || (p.Y == skinObjectBounds.Top) || (p.Y == (skinObjectBounds.Bottom - 1))))
                    {
                        continue;                         // Ignore any negative regions that are not fully enclosed within the skin mask
                    }
                    if (pointsInFilledNegativeSpace.Length <= scale)
                    {
                        continue;                               // Ignore any very small regions (likely anomalies)
                    }
                    boundsForSkinObjects.Add(skinObjectBounds); // Found a non-negligible fully-enclosed hole
                    break;
                }
            }
            return(boundsForSkinObjects);
        }