// Detects objects on given Bitmap. Only 32bppPArgb, 32bppArgb, 24bppRgb and 8bppIndexed formats are supported for now. public DResults Detect(Bitmap Bitmap, DetectionParams Parameters) { int Width = Bitmap.Width; int Height = Bitmap.Height; int[,] CumSum = new int[Width, Height]; // Cumulative sums of every pixel. long[,] CumSum2 = new long[Width, Height]; // Squares of sums of every pixel. These will be used for standart deviation calculations. BitmapData BitmapData = Bitmap.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Bitmap.PixelFormat); if (Bitmap.PixelFormat == PixelFormat.Format24bppRgb) { CalculateCumSums24bpp(ref CumSum, ref CumSum2, ref BitmapData, ref Width, ref Height); } else if (Bitmap.PixelFormat == PixelFormat.Format8bppIndexed) { CalculateCumSums8bpp(ref CumSum, ref CumSum2, ref BitmapData, ref Width, ref Height); Parameters.Pen = null; // Can't draw anything on an 8 bit indexed image. } else if (Bitmap.PixelFormat == PixelFormat.Format32bppPArgb || Bitmap.PixelFormat == PixelFormat.Format32bppArgb) { CalculateCumSums32bpp(ref CumSum, ref CumSum2, ref BitmapData, ref Width, ref Height); } else { throw new Exception(Bitmap.PixelFormat.ToString() + " is not supported."); //Bitmap.UnlockBits(BitmapData); //return null; } Bitmap.UnlockBits(BitmapData); List<Rectangle> DetectedOLocs = new List<Rectangle>(); // Passed regions will be stored here. int NOfObjects = 0; // Number of detected objects int SearchedSubRegionCount = 0; // Searched subregion count float Scaler = Parameters.FirstScale; // For all scales between first scale and max scale. while (Scaler < Parameters.MaxScale) { int WinWidth = Convert.ToInt32(HCascade.WindowSize.Width * Scaler); // Scaled searching window width int WinHeight = Convert.ToInt32(HCascade.WindowSize.Height * Scaler); // Scaled searching window height float InvArea = Convert.ToSingle(((double)1) / (WinWidth * WinHeight)); // Inverse of the area int StepSize = Convert.ToInt32(WinWidth * Parameters.SlidingRatio); // Current step size for (int i = 0; i <= Width - WinWidth - 1; i += StepSize) { for (int j = 0; j <= Height - WinHeight - 1; j += StepSize) { SearchedSubRegionCount = SearchedSubRegionCount + 1; // Integral image of current region: int IImg = CumSum[i + WinWidth, j + WinHeight] - CumSum[i, j + WinHeight] - CumSum[i + WinWidth, j] + CumSum[i, j]; long IImg2 = CumSum2[i + WinWidth, j + WinHeight] - CumSum2[i, j + WinHeight] - CumSum2[i + WinWidth, j] + CumSum2[i, j]; float Mean = IImg * InvArea; float Variance = IImg2 * InvArea - Mean * Mean; float Normalizer = 0; // Will normalize thresholds. if (Variance > 1) { Normalizer = Convert.ToSingle(Math.Sqrt(Variance)); // Standart deviation } else { Normalizer = 1; } bool Passed = true; foreach (HaarCascade.Stage Stage in HCascade.Stages) { float StageVal = 0; foreach (HaarCascade.Tree Tree in Stage.Trees) { HaarCascade.Node CurNode = Tree.Nodes[0]; while (true) { int RectSum = 0; foreach (HaarCascade.FeatureRect FeatureRect in CurNode.FeatureRects) { // Resize current feature rectangle to fit it in scaled searching window: int Rx1 = Convert.ToInt32(i + Math.Floor(FeatureRect.Rectangle.X * Scaler)); int Ry1 = Convert.ToInt32(j + Math.Floor(FeatureRect.Rectangle.Y * Scaler)); int Rx2 = Convert.ToInt32(Rx1 + Math.Floor(FeatureRect.Rectangle.Width * Scaler)); int Ry2 = Convert.ToInt32(Ry1 + Math.Floor(FeatureRect.Rectangle.Height * Scaler)); // Integral image of the region bordered by the current feature ractangle (sum of all pixels in it): RectSum = Convert.ToInt32(RectSum + (CumSum[Rx2, Ry2] - CumSum[Rx1, Ry2] - CumSum[Rx2, Ry1] + CumSum[Rx1, Ry1]) * FeatureRect.Weight); } float AvgRectSum = RectSum * InvArea; if (AvgRectSum < CurNode.Threshold * Normalizer) { if (CurNode.HasLNode) { CurNode = Tree.Nodes[CurNode.LeftNode]; // Go to the left node continue; } else { StageVal = StageVal + CurNode.LeftVal; break; // TODO: might not be correct. Was : Exit While // It is a leaf, exit. } } else { if (CurNode.HasRNode) { CurNode = Tree.Nodes[CurNode.RightNode]; // Go to the right node continue; } else { StageVal = StageVal + CurNode.RightVal; break; // TODO: might not be correct. Was : Exit While // It is a leaf, exit. } } } } if (StageVal < Stage.Threshold) { Passed = false; break; // TODO: might not be correct. Was : Exit For // Don't waste time with trying to pass it from other stages. } } // If current region was passed from all stages if (Passed) { DetectedOLocs.Add(new Rectangle(i, j, WinWidth, WinHeight)); NOfObjects += 1; // Are they enough? (note that, nested rectangles are not eliminated yet) if (NOfObjects == Parameters.MaxDetCount) { break; // TODO: might not be correct. Was : Exit While } } } } Scaler *= Parameters.ScaleMult; } DResults Results = default(DResults); if (DetectedOLocs.Count > 0) { Results = EliminateNestedRects(DetectedOLocs.ToArray(), NOfObjects, Parameters.MinNRectCount + 1, ref Parameters.SizeMultForNesRectCon); // If a pen was given, mark objects using given pen if (Parameters.Pen != null) { Graphics G = Graphics.FromImage(Bitmap); G.DrawRectangles(Parameters.Pen, Results.DetectedOLocs); G.Dispose(); } } else { Results = new DResults(0, 0, null); } Results.SearchedSubRegionCount = SearchedSubRegionCount; return Results; }
// Every detected object must be marked only with one rectangle. Others must be eliminated: private DResults EliminateNestedRects(Rectangle[] DetectedOLocs, int NOfObjects, int MinNRectCount, ref float SizeMultForNesRectCon) { int[] NestedRectsCount = new int[NOfObjects]; Rectangle[] AvgRects = new Rectangle[NOfObjects]; for (int i = 0; i <= NOfObjects - 1; i++) { Rectangle Current = DetectedOLocs[i]; AvgRects[i] = Current; for (int j = 0; j <= NOfObjects - 1; j++) { // Check if these 2 rectangles are nested if (i != j && DetectedOLocs[j].Width > 0 && AreTheyNested(ref Current, ref DetectedOLocs[j], ref SizeMultForNesRectCon)) { NestedRectsCount[i] += 1; AvgRects[i].X += DetectedOLocs[j].X; AvgRects[i].Y += DetectedOLocs[j].Y; AvgRects[i].Width += DetectedOLocs[j].Width; AvgRects[i].Height += DetectedOLocs[j].Height; DetectedOLocs[j].Width = 0; // Zero it to eliminate. } } } int k = 0; Rectangle[] NewRects = new Rectangle[NOfObjects]; for (int i = 0; i <= NOfObjects - 1; i++) { // Rectangles that are not eliminated if (DetectedOLocs[i].Width > 0) { int NOfNRects = NestedRectsCount[i] + 1; //+1 is itself. It is required, becuse we will calculate average of them. if (NOfNRects >= MinNRectCount) { // Average rectangle: NewRects[k] = new Rectangle(Convert.ToInt32(((double)AvgRects[i].X) / NOfNRects), Convert.ToInt32(((double)AvgRects[i].Y) / NOfNRects), Convert.ToInt32(((double)AvgRects[i].Width) / NOfNRects), Convert.ToInt32(((double)AvgRects[i].Height) / NOfNRects)); } k = k + 1; } } DResults Results = new DResults(); Results.DetectedOLocs = new Rectangle[k]; Array.Copy(NewRects, Results.DetectedOLocs, k); Results.NOfObjects = k; return Results; }