/// <summary> /// Highlights a motion detection cell, typically to indicate a threshold was tripped. /// </summary> /// <param name="r">Red channel of the highlight RGB color</param> /// <param name="g">Green channel of the highlight RGB color</param> /// <param name="b">Blue channel of the highlight RGB color</param> /// <param name="driver">The <see cref="FrameDiffDriver"/> containing the buffer</param> /// <param name="metadata">The <see cref="FrameAnalysisMetadata"/> structure with frame properties</param> /// <param name="index">The array index of the cell to highlight</param> /// <param name="buffer">The frame buffer to draw into</param> protected void HighlightCell(byte r, byte g, byte b, FrameDiffDriver driver, FrameAnalysisMetadata metadata, int index, byte[] buffer) { for (int x = driver.CellRect[index].X; x < driver.CellRect[index].X + driver.CellRect[index].Width; x++) { var y = driver.CellRect[index].Y; var i = (x * metadata.Bpp) + (y * metadata.Stride); buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; y += driver.CellRect[index].Height - 1; i = (x * metadata.Bpp) + (y * metadata.Stride); buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; } for (int y = driver.CellRect[index].Y; y < driver.CellRect[index].Y + driver.CellRect[index].Height; y++) { var x = driver.CellRect[index].X; var i = (x * metadata.Bpp) + (y * metadata.Stride); buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; x += driver.CellRect[index].Width - 1; i = (x * metadata.Bpp) + (y * metadata.Stride); buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; } }
} // not necessary for this algorithm /// <inheritdoc /> public bool DetectMotion(FrameDiffDriver driver, FrameAnalysisMetadata metadata) { Parallel.ForEach(driver.CellDiff, (cell, loopState, loopIndex) => CheckDiff(loopIndex, driver, metadata, _parameters)); int diff = 0; for (int i = 0; i < driver.CellDiff.Length; i++) { diff += driver.CellDiff[i]; if (_parameters.AnalysisMode && driver.CellDiff[i] == 1) { HighlightCell(255, 0, 255, driver, metadata, i, _analysisBuffer); } } var detected = diff >= _cellCountThreshold; // Draw a bar across the frame; red indicates motion, green indicates no motion if (_parameters.AnalysisMode && diff > 0) { int x2 = (int)(((diff * 2f) / (driver.CellDiff.Length / 2f)) * (metadata.Width / 2f)); (byte r, byte g) = detected ? ((byte)255, (byte)0) : ((byte)0, (byte)255); DrawIndicatorBlock(r, g, 0, 0, x2, 0, 10, _analysisBuffer, metadata); } if (_parameters.AnalysisMode) { _outputHandler?.Process(_fullRawFrameImageContext); } return(detected); }
/// <inheritdoc /> public void FirstFrameCompleted(FrameDiffDriver driver, FrameAnalysisMetadata metadata, ImageContext contextTemplate) { _fullRawFrameImageContext = contextTemplate; _parameters.CellPixelThreshold = (int)(metadata.CellWidth * metadata.CellHeight * (_cellPixelPercentage / 100f)); _analysisBuffer = new byte[driver.TestFrame.Length]; // Not necessary for this analysis, CheckDiff overwrites the buffer completely // Array.Copy(driver.TestFrame, _analysisBuffer, _analysisBuffer.Length); _fullRawFrameImageContext.Data = driver.TestFrame; _outputHandler?.Process(_fullRawFrameImageContext); _fullRawFrameImageContext.Data = _analysisBuffer; }
private void ProcessCell(Rectangle rect, byte[] image, double[,] kernel, int kernelWidth, int kernelHeight, FrameAnalysisMetadata metadata, bool storeFromRaw) { // Rectangle and FrameAnalysisMetadata are structures; they are by-value copies and all fields are value-types which makes them thread safe int x2 = rect.X + rect.Width; int y2 = rect.Y + rect.Height; int index; // Indicates RGB needs to be swapped to BGR so that Bitmap.Save works correctly. if (storeFromRaw) { for (var x = rect.X; x < x2; x++) { for (var y = rect.Y; y < y2; y++) { index = (x * metadata.Bpp) + (y * metadata.Stride); byte swap = image[index]; image[index] = image[index + 2]; image[index + 2] = swap; } } } for (var x = rect.X; x < x2; x++) { for (var y = rect.Y; y < y2; y++) { double r = 0; double g = 0; double b = 0; if (x > kernelWidth && y > kernelHeight) { for (var t = 0; t < kernelWidth; t++) { for (var u = 0; u < kernelHeight; u++) { double k = kernel[t, u]; index = (Clamp(y + u, y2) * metadata.Stride) + (Clamp(x + t, x2) * metadata.Bpp); r += image[index] * k; g += image[index + 1] * k; b += image[index + 2] * k; } } r = (r < 0) ? 0 : r; g = (g < 0) ? 0 : g; b = (b < 0) ? 0 : b; } index = (x * metadata.Bpp) + (y * metadata.Stride); image[index] = (byte)r; image[index + 1] = (byte)g; image[index + 2] = (byte)b; } } }
/// <summary> /// Draws a filled block into the frame buffer. Can be used as a visual indicator of internal app state. /// </summary> /// <param name="r">Red channel of the highlight RGB color</param> /// <param name="g">Green channel of the highlight RGB color</param> /// <param name="b">Blue channel of the highlight RGB color</param> /// <param name="x1">Left column of the block</param> /// <param name="x2">Right column of the block</param> /// <param name="y1">Top row of the block</param> /// <param name="y2">Bottom row of the block</param> /// <param name="buffer">The frame buffer to draw into</param> /// <param name="metrics">The <see cref="FrameAnalysisMetadata"/> structure with frame properties</param> protected void DrawIndicatorBlock(byte r, byte g, byte b, int x1, int x2, int y1, int y2, byte[] buffer, FrameAnalysisMetadata metrics) { for (int x = x1; x <= x2; x++) { for (int y = y1; y <= y2; y++) { var i = (x * metrics.Bpp) + (y * metrics.Stride); buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; } } }
/// <inheritdoc /> public void ResetAnalyser(FrameDiffDriver driver, FrameAnalysisMetadata metadata) { } // not necessary for this algorithm
private void CheckDiff(long cellIndex, FrameDiffDriver driver, FrameAnalysisMetadata metadata, ThreadSafeParameters parameters) { // FrameAnalysisMetadata and ThreadSafeParameters are structures; they are by-value copies and all fields are value-types which makes them thread safe int diff = 0; var rect = driver.CellRect[cellIndex]; int x2 = rect.X + rect.Width; int y2 = rect.Y + rect.Height; for (var col = rect.X; col < x2; col++) { for (var row = rect.Y; row < y2; row++) { var index = (col * metadata.Bpp) + (row * metadata.Stride); // Disregard full-black cells in the mask bitmap if (driver.FrameMask != null) { var rgbMask = driver.FrameMask[index] + driver.FrameMask[index + 1] + driver.FrameMask[index + 2]; if (rgbMask == 0) { continue; } } byte r = driver.TestFrame[index]; byte g = driver.TestFrame[index + 1]; byte b = driver.TestFrame[index + 2]; int rgb1 = r + g + b; r = driver.CurrentFrame[index]; g = driver.CurrentFrame[index + 1]; b = driver.CurrentFrame[index + 2]; int rgb2 = r + g + b; int rgbDiff = Math.Abs(rgb2 - rgb1); if (rgbDiff > parameters.RGBThreshold) { diff++; } if (!parameters.AnalysisMode) { // Check for early exit opportunity if (diff >= parameters.CellPixelThreshold) { continue; } } else { // No early exit for analysis purposes // Output in grayscale based on strength of the diff (765 = 255 x 3) r = Math.Min((byte)255, (byte)((rgbDiff / 765f) * 255.999f)); g = r; b = r; // Highlight cell corners if ((col == rect.X || col == x2 - 1) && (row == rect.Y || row == y2 - 1)) { r = 128; g = 0; b = 128; } _analysisBuffer[index] = r; _analysisBuffer[index + 1] = g; _analysisBuffer[index + 2] = b; } } } driver.CellDiff[cellIndex] = (diff >= parameters.CellPixelThreshold) ? 1 : 0; }