Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.

Commit 55f29e4

Browse files
committed
#43. Use threshold technique against difference image. Add onStopDetect callback. Use TimeSpan in MotionConfig. Removed RGB16 support from ConvolutionBase and FrameDiffAnalyser due to exceptions being thrown by GDI+.
1 parent fadaaeb commit 55f29e4

File tree

6 files changed

+201
-56
lines changed

6 files changed

+201
-56
lines changed

src/MMALSharp.Processing/Handlers/CircularBufferCaptureHandler.cs

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public sealed class CircularBufferCaptureHandler : VideoStreamCaptureHandler, IM
2525
private Stopwatch _recordingElapsed;
2626
private IFrameAnalyser _analyser;
2727
private MotionConfig _config;
28+
private Action _onStopDetect;
2829

2930
/// <summary>
3031
/// The circular buffer object responsible for storing image data.
@@ -65,8 +66,6 @@ public override void Process(ImageContext context)
6566
{
6667
this.Buffer.PushBack(context.Data[i]);
6768
}
68-
69-
this.CheckRecordingProgress();
7069
}
7170
else
7271
{
@@ -107,12 +106,14 @@ public override void Process(ImageContext context)
107106
this.Processed += context.Data.Length;
108107
}
109108
}
110-
109+
111110
if (_shouldDetectMotion && !_recordToFileStream)
112111
{
113-
_analyser.Apply(context);
112+
_analyser?.Apply(context);
114113
}
115114

115+
this.CheckRecordingProgress();
116+
116117
// Not calling base method to stop data being written to the stream when not recording.
117118
this.ImageContext = context;
118119
}
@@ -122,11 +123,12 @@ public override void Process(ImageContext context)
122123
/// </summary>
123124
/// <param name="config">The motion configuration.</param>
124125
/// <param name="onDetect">A callback for when motion is detected.</param>
125-
public void DetectMotion(MotionConfig config, Action onDetect)
126+
/// <param name="onStopDetect">An optional callback for when the record duration has passed.</param>
127+
public void ConfigureMotionDetection(MotionConfig config, Action onDetect, Action onStopDetect = null)
126128
{
127129
_config = config;
128-
_shouldDetectMotion = true;
129-
130+
_onStopDetect = onStopDetect;
131+
130132
if (this.MotionType == MotionType.FrameDiff)
131133
{
132134
_analyser = new FrameDiffAnalyser(config, onDetect);
@@ -135,6 +137,28 @@ public void DetectMotion(MotionConfig config, Action onDetect)
135137
{
136138
// TODO: Motion vector analyser
137139
}
140+
141+
this.EnableMotionDetection();
142+
}
143+
144+
/// <summary>
145+
/// Enables motion detection. When configured, this will instruct the capture handler to detect motion.
146+
/// </summary>
147+
public void EnableMotionDetection()
148+
{
149+
_shouldDetectMotion = true;
150+
151+
MMALLog.Logger.LogInformation("Enabling motion detection.");
152+
}
153+
154+
/// <summary>
155+
/// Disables motion detection. When configured, this will instruct the capture handler not to detect motion.
156+
/// </summary>
157+
public void DisableMotionDetection()
158+
{
159+
_shouldDetectMotion = false;
160+
161+
MMALLog.Logger.LogInformation("Disabling motion detection.");
138162
}
139163

140164
/// <summary>
@@ -165,10 +189,7 @@ public void StopRecording()
165189
/// <inheritdoc />
166190
public override void Dispose()
167191
{
168-
if (_shouldDetectMotion)
169-
{
170-
this.CurrentStream?.Dispose();
171-
}
192+
this.CurrentStream?.Dispose();
172193
}
173194

174195
/// <inheritdoc />
@@ -181,9 +202,22 @@ private void CheckRecordingProgress()
181202
{
182203
if (_recordingElapsed != null && _config != null)
183204
{
184-
if (_recordingElapsed.Elapsed >= _config.RecordDuration.TimeOfDay)
205+
if (_recordingElapsed.Elapsed >= _config.RecordDuration)
185206
{
186-
this.StopRecording();
207+
if (_onStopDetect != null)
208+
{
209+
_onStopDetect();
210+
}
211+
else
212+
{
213+
this.StopRecording();
214+
}
215+
216+
if (_analyser is FrameDiffAnalyser)
217+
{
218+
var fdAnalyser = _analyser as FrameDiffAnalyser;
219+
fdAnalyser?.ResetAnalyser();
220+
}
187221
}
188222
}
189223
}

src/MMALSharp.Processing/Handlers/IMotionCaptureHandler.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
using MMALSharp.Processors.Motion;
77
using System;
8-
using MMALSharp.Common;
98

109
namespace MMALSharp.Handlers
1110
{
@@ -20,10 +19,21 @@ public interface IMotionCaptureHandler
2019
MotionType MotionType { get; set; }
2120

2221
/// <summary>
23-
/// Call to enable motion detection.
22+
/// Call to configure motion detection.
2423
/// </summary>
2524
/// <param name="config">The motion configuration.</param>
2625
/// <param name="onDetect">A callback for when motion is detected.</param>
27-
void DetectMotion(MotionConfig config, Action onDetect);
26+
/// <param name="onStopDetect">An optional callback for when the record duration has passed.</param>
27+
void ConfigureMotionDetection(MotionConfig config, Action onDetect, Action onStopDetect = null);
28+
29+
/// <summary>
30+
/// Enables motion detection. When configured, this will instruct the capture handler to detect motion.
31+
/// </summary>
32+
void EnableMotionDetection();
33+
34+
/// <summary>
35+
/// Disables motion detection. When configured, this will instruct the capture handler not to detect motion.
36+
/// </summary>
37+
void DisableMotionDetection();
2838
}
2939
}

src/MMALSharp.Processing/Processors/Effects/ConvolutionBase.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,7 @@ private Bitmap LoadBitmap(ImageContext imageContext, MemoryStream stream)
110110
{
111111
PixelFormat format = default;
112112

113-
if (imageContext.PixelFormat == MMALEncoding.RGB16)
114-
{
115-
format = PixelFormat.Format16bppRgb565;
116-
}
117-
113+
// RGB16 doesn't appear to be supported by GDI?
118114
if (imageContext.PixelFormat == MMALEncoding.RGB24)
119115
{
120116
format = PixelFormat.Format24bppRgb;

src/MMALSharp.Processing/Processors/Motion/FrameDiffAnalyser.cs

Lines changed: 134 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using Microsoft.Extensions.Logging;
1414
using MMALSharp.Common;
1515
using MMALSharp.Common.Utility;
16-
using MMALSharp.Processors.Effects;
1716

1817
namespace MMALSharp.Processors.Motion
1918
{
@@ -75,12 +74,7 @@ public override void Apply(ImageContext context)
7574
if (context.Eos)
7675
{
7776
this.FullTestFrame = true;
78-
MMALLog.Logger.LogDebug("EOS reached for test frame. Applying edge detection.");
79-
80-
// We want to apply Edge Detection to the test frame to make it easier to detect changes.
81-
var edgeDetection = new EdgeDetection(this.MotionConfig.Sensitivity);
82-
this.ImageContext.Data = this.TestFrame.ToArray();
83-
edgeDetection.ApplyConvolution(edgeDetection.Kernel, EdgeDetection.KernelWidth, EdgeDetection.KernelHeight, this.ImageContext);
77+
MMALLog.Logger.LogDebug("EOS reached for test frame.");
8478
}
8579
}
8680

@@ -92,19 +86,27 @@ public override void Apply(ImageContext context)
9286
this.CheckForChanges(this.OnDetect);
9387
}
9488
}
95-
96-
private void CheckForChanges(Action onDetect)
89+
90+
/// <summary>
91+
/// Resets the test and working frames this analyser is using.
92+
/// </summary>
93+
public void ResetAnalyser()
9794
{
98-
var edgeDetection = new EdgeDetection(EDStrength.Medium);
99-
this.ImageContext.Data = this.WorkingData.ToArray();
100-
edgeDetection.ApplyConvolution(EdgeDetection.MediumStrengthKernel, 3, 3, this.ImageContext);
101-
var diff = this.Analyse();
95+
this.TestFrame = new List<byte>();
96+
this.WorkingData = new List<byte>();
97+
this.FullFrame = false;
98+
this.FullTestFrame = false;
99+
}
102100

103-
MMALLog.Logger.LogDebug($"Diff size: {diff}");
101+
private void CheckForChanges(Action onDetect)
102+
{
103+
this.PrepareDifferenceImage(this.ImageContext, this.MotionConfig.Threshold);
104104

105+
var diff = this.Analyse();
106+
105107
if (diff >= this.MotionConfig.Threshold)
106108
{
107-
MMALLog.Logger.LogInformation("Motion detected!");
109+
MMALLog.Logger.LogInformation($"Motion detected! Frame difference {diff}.");
108110
onDetect();
109111
}
110112
}
@@ -115,11 +117,7 @@ private Bitmap LoadBitmap(MemoryStream stream)
115117
{
116118
PixelFormat format = default;
117119

118-
if (this.ImageContext.PixelFormat == MMALEncoding.RGB16)
119-
{
120-
format = PixelFormat.Format16bppRgb565;
121-
}
122-
120+
// RGB16 doesn't appear to be supported by GDI?
123121
if (this.ImageContext.PixelFormat == MMALEncoding.RGB24)
124122
{
125123
format = PixelFormat.Format24bppRgb;
@@ -198,11 +196,114 @@ private int Analyse()
198196

199197
testBmp.UnlockBits(testBmpData);
200198
currentBmp.UnlockBits(currentBmpData);
201-
199+
202200
return diff;
203201
}
204202
}
205203

204+
private void PrepareDifferenceImage(ImageContext context, int threshold)
205+
{
206+
BitmapData bmpData = null;
207+
IntPtr pNative = IntPtr.Zero;
208+
int bytes;
209+
byte[] store = null;
210+
211+
using (var ms = new MemoryStream(context.Data))
212+
using (var bmp = this.LoadBitmap(ms))
213+
{
214+
bmpData = bmp.LockBits(new Rectangle(0, 0,
215+
bmp.Width,
216+
bmp.Height),
217+
ImageLockMode.ReadWrite,
218+
bmp.PixelFormat);
219+
220+
if (context.Raw)
221+
{
222+
this.InitBitmapData(bmpData, ms.ToArray());
223+
}
224+
225+
pNative = bmpData.Scan0;
226+
227+
// Split image into 4 quadrants and process individually.
228+
var quadA = new Rectangle(0, 0, bmpData.Width / 2, bmpData.Height / 2);
229+
var quadB = new Rectangle(bmpData.Width / 2, 0, bmpData.Width / 2, bmpData.Height / 2);
230+
var quadC = new Rectangle(0, bmpData.Height / 2, bmpData.Width / 2, bmpData.Height / 2);
231+
var quadD = new Rectangle(bmpData.Width / 2, bmpData.Height / 2, bmpData.Width / 2, bmpData.Height / 2);
232+
233+
bytes = bmpData.Stride * bmp.Height;
234+
235+
var rgbValues = new byte[bytes];
236+
237+
// Copy the RGB values into the array.
238+
Marshal.Copy(pNative, rgbValues, 0, bytes);
239+
240+
var bpp = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
241+
242+
var t1 = Task.Run(() =>
243+
{
244+
this.ApplyThreshold(quadA, bmpData, bpp, threshold);
245+
});
246+
var t2 = Task.Run(() =>
247+
{
248+
this.ApplyThreshold(quadB, bmpData, bpp, threshold);
249+
});
250+
var t3 = Task.Run(() =>
251+
{
252+
this.ApplyThreshold(quadC, bmpData, bpp, threshold);
253+
});
254+
var t4 = Task.Run(() =>
255+
{
256+
this.ApplyThreshold(quadD, bmpData, bpp, threshold);
257+
});
258+
259+
Task.WaitAll(t1, t2, t3, t4);
260+
261+
if (context.Raw)
262+
{
263+
store = new byte[bytes];
264+
Marshal.Copy(pNative, store, 0, bytes);
265+
}
266+
267+
bmp.UnlockBits(bmpData);
268+
}
269+
270+
context.Data = store;
271+
}
272+
273+
private void ApplyThreshold(Rectangle quad, BitmapData bmpData, int pixelDepth, int threshold)
274+
{
275+
unsafe
276+
{
277+
// Declare an array to hold the bytes of the bitmap.
278+
var stride = bmpData.Stride;
279+
280+
byte* ptr1 = (byte*)bmpData.Scan0;
281+
282+
for (int column = quad.X; column < quad.X + quad.Width; column++)
283+
{
284+
for (int row = quad.Y; row < quad.Y + quad.Height; row++)
285+
{
286+
var rgb1 = ptr1[(column * pixelDepth) + (row * stride)] +
287+
ptr1[(column * pixelDepth) + (row * stride) + 1] +
288+
ptr1[(column * pixelDepth) + (row * stride) + 2];
289+
290+
if (rgb1 > threshold)
291+
{
292+
ptr1[(column * pixelDepth) + (row * stride)] = 255;
293+
ptr1[(column * pixelDepth) + (row * stride) + 1] = 255;
294+
ptr1[(column * pixelDepth) + (row * stride) + 2] = 255;
295+
}
296+
else
297+
{
298+
ptr1[(column * pixelDepth) + (row * stride)] = 0;
299+
ptr1[(column * pixelDepth) + (row * stride) + 1] = 0;
300+
ptr1[(column * pixelDepth) + (row * stride) + 2] = 0;
301+
}
302+
}
303+
}
304+
}
305+
}
306+
206307
private int CheckDiff(Rectangle quad, BitmapData bmpData, BitmapData bmpData2, int pixelDepth, int threshold)
207308
{
208309
unsafe
@@ -228,7 +329,7 @@ private int CheckDiff(Rectangle quad, BitmapData bmpData, BitmapData bmpData2, i
228329
ptr2[(column * pixelDepth) + (row * stride2) + 1] +
229330
ptr2[(column * pixelDepth) + (row * stride2) + 2];
230331

231-
if (rgb2 > rgb1 + threshold)
332+
if (rgb2 - rgb1 > threshold)
232333
{
233334
diff++;
234335

@@ -252,6 +353,17 @@ private int CheckDiff(Rectangle quad, BitmapData bmpData, BitmapData bmpData2, i
252353
highestX = column;
253354
}
254355
}
356+
357+
// If the threshold has been exceeded, we want to exit from this method immediately for performance reasons.
358+
if (diff > threshold)
359+
{
360+
break;
361+
}
362+
}
363+
364+
if (diff > threshold)
365+
{
366+
break;
255367
}
256368
}
257369

0 commit comments

Comments
 (0)