From 1f33ad08aa5d7844c0cd7131c8b1325daacfcb59 Mon Sep 17 00:00:00 2001 From: Jon McGuire Date: Sun, 23 Aug 2020 09:08:04 -0400 Subject: [PATCH] Improved motion perf (#171) * Parallel processing improvements * prefer atomic array write vs mutable struct * increase cell divisor to 32 (1024 cells) * Styling changes. Co-authored-by: techyian --- .../Processors/Motion/FrameDiffAnalyser.cs | 99 ++++++++++++------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/src/MMALSharp.Processing/Processors/Motion/FrameDiffAnalyser.cs b/src/MMALSharp.Processing/Processors/Motion/FrameDiffAnalyser.cs index 99959e24..1c4b20db 100644 --- a/src/MMALSharp.Processing/Processors/Motion/FrameDiffAnalyser.cs +++ b/src/MMALSharp.Processing/Processors/Motion/FrameDiffAnalyser.cs @@ -34,6 +34,17 @@ public class FrameDiffAnalyser : FrameAnalyser private byte[] _mask; private Stopwatch _testFrameAge; + private int[] _cellDiff; + private Rectangle[] _cellRect; + private byte[] _workingData; + + /// + /// Controls how many cells the frames are divided into. The result is a power of two of this + /// value (so the default of 32 yields 1024 cells). These cells are processed in parallel. This + /// should be a value that divides evenly into the X and Y resolutions of the motion stream. + /// + public int CellDivisor { get; set; } = 32; + internal Action OnDetect { get; set; } /// @@ -121,9 +132,30 @@ private void PrepareTestFrame() _frameBpp = this.GetBpp() / 8; _frameStride = this.ImageContext.Stride; + // one-time setup of the diff cell parameters and arrays + int indices = (int)Math.Pow(CellDivisor, 2); + int cellWidth = _frameWidth / CellDivisor; + int cellHeight = _frameHeight / CellDivisor; + int i = 0; + + _cellRect = new Rectangle[indices]; + _cellDiff = new int[indices]; + + for (int row = 0; row < CellDivisor; row++) + { + int y = row * cellHeight; + + for (int col = 0; col < CellDivisor; col++) + { + int x = col * cellWidth; + _cellRect[i] = new Rectangle(x, y, cellWidth, cellHeight); + i++; + } + } + this.TestFrame = this.WorkingData.ToArray(); - if(!string.IsNullOrWhiteSpace(this.MotionConfig.MotionMaskPathname)) + if (!string.IsNullOrWhiteSpace(this.MotionConfig.MotionMaskPathname)) { this.PrepareMask(); } @@ -218,48 +250,40 @@ private void CheckForChanges(Action onDetect) private int Analyse() { - var quadA = new Rectangle(0, 0, _frameWidth / 2, _frameHeight / 2); - var quadB = new Rectangle(_frameWidth / 2, 0, _frameWidth / 2, _frameHeight / 2); - var quadC = new Rectangle(0, _frameHeight / 2, _frameWidth / 2, _frameHeight / 2); - var quadD = new Rectangle(_frameWidth / 2, _frameHeight / 2, _frameWidth / 2, _frameHeight / 2); + _workingData = this.WorkingData.ToArray(); - var currentBytes = this.WorkingData.ToArray(); + var result = Parallel.ForEach(_cellDiff, (cell, loopState, loopIndex) => CheckDiff(loopIndex, loopState)); - int diff = 0; - - var t1 = Task.Run(() => - { - diff += this.CheckDiff(quadA, currentBytes); - }); - var t2 = Task.Run(() => + // How Parallel Stop works: https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff963552(v=pandp.10)#parallel-stop + if (!result.IsCompleted && !result.LowestBreakIteration.HasValue) { - diff += this.CheckDiff(quadB, currentBytes); - }); - var t3 = Task.Run(() => - { - diff += this.CheckDiff(quadC, currentBytes); - }); - var t4 = Task.Run(() => + return int.MaxValue; // loop was stopped, so return a large diff + } + else { - diff += this.CheckDiff(quadD, currentBytes); - }); + int diff = 0; - Task.WaitAll(t1, t2, t3, t4); - - return diff; + foreach (var cellDiff in _cellDiff) + { + diff += cellDiff; + } + + return diff; + } } - private int CheckDiff(Rectangle quad, byte[] currentFrame) + private void CheckDiff(long cellIndex, ParallelLoopState loopState) { int diff = 0; + var rect = _cellRect[cellIndex]; - for (int column = quad.X; column < quad.X + quad.Width; column++) + for (int col = rect.X; col < rect.X + rect.Width; col++) { - for (int row = quad.Y; row < quad.Y + quad.Height; row++) + for (int row = rect.Y; row < rect.Y + rect.Height; row++) { - var index = (column * _frameBpp) + (row * _frameStride); + var index = (col * _frameBpp) + (row * _frameStride); - if(_mask != null) + if (_mask != null) { var rgbMask = _mask[index] + _mask[index + 1] + _mask[index + 2]; @@ -270,28 +294,31 @@ private int CheckDiff(Rectangle quad, byte[] currentFrame) } var rgb1 = TestFrame[index] + TestFrame[index + 1] + TestFrame[index + 2]; - - var rgb2 = currentFrame[index] + currentFrame[index + 1] + currentFrame[index + 2]; + var rgb2 = _workingData[index] + _workingData[index + 1] + _workingData[index + 2]; if (rgb2 - rgb1 > MotionConfig.Threshold) { diff++; } - // If the threshold has been exceeded, we want to exit from this method immediately for performance reasons. + // If the threshold has been exceeded, exit immediately and preempt any CheckDiff calls not yet started. if (diff > MotionConfig.Threshold) { - return diff; + _cellDiff[cellIndex] = diff; + loopState.Stop(); + return; } } if (diff > MotionConfig.Threshold) { - return diff; + _cellDiff[cellIndex] = diff; + loopState.Stop(); + return; } } - return diff; + _cellDiff[cellIndex] = diff; } } }