Skip to content

Instantly share code, notes, and snippets.

@akosnikhazy
Created June 21, 2019 08:31
Show Gist options
  • Save akosnikhazy/b7c75e3d86d826b5ecf35f7d1386ab40 to your computer and use it in GitHub Desktop.
Save akosnikhazy/b7c75e3d86d826b5ecf35f7d1386ab40 to your computer and use it in GitHub Desktop.
/*
* For this to work you need a form named "Form1"
* with a PictureBox named "camera".
*
* Next to the exe you need a "images" folder
*
* You need to install MjpegProcessor. It should work with
* other streams too, when I wrote this, happened that I
* had a mjpeg stream on my hand.
*/
using MjpegProcessor;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace motion_detector
{
public partial class MainWindow : Form
{
//shouldn't touch these
MjpegDecoder m_mjpeg;
Image lastImage; //image just seen
Image beforeLastImage; //image before that
Image lastImageChanged; //we modify both images smallSize * smallSize pixels, and greyscale
Image beforeLastImageChanged;
int steps = 0; //images saved for comparing
int hammeringDistance; //the difference between two images colors
int hammeringDistanceInTime = 0; //we add up the hammering distances "steps" times
List<int> lastImageMeanBits; //list of color mean values
List<int> beforeLastImageMeanBits;
//might touch these with care
int smallSize = 64; //the size of the modified image, darker cameras might do better with bigger value
int maxSteps = 10; //max images saved
Bitmap[] imageSteps = new Bitmap[10];//We save maxSteps number of images. If there is a movement we save them to file. Should be te size of max steps.
//settings
int itIsConsideredMovement = 15; //the value we consider movement on the images. The noisier the camera the higer this should be
string feedURI = "http://your-camera-ip/mjpg/video.mjpg";
public MainWindow()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
m_mjpeg = new MjpegDecoder();
m_mjpeg.ParseStream(new Uri(feedURI));
m_mjpeg.FrameReady += mjpeg_FrameReady;
}
private void mjpeg_FrameReady(object sender, FrameReadyEventArgs e)
{
hammeringDistance = 0;
//this needed if you have GUI with PictureBox. Good for testing.
camera.Image = e.Bitmap;
//camera.Image = Crop(e.Bitmap);
camera.Width = e.Bitmap.Width;
camera.Height = e.Bitmap.Height;
if (lastImage == null)
{//we don't care about the first image bceause there is no way to compare anything to it but save it
lastImage = e.Bitmap;
}
else
{//now we are talking! We have two images so we start to capture and compare them
beforeLastImage = lastImage;
lastImage = e.Bitmap;
//make last and one before that small, greyscale and cropped (lot of camera feeds has a clock on it that can be seen as movement, so we crop it)
//modify if cropping is not needed. Also cropping could be useful if you know movement will be seen only part of the image. In that case modify
//the Crop method to that extent.
lastImageChanged = MakeGrayscale3(new Bitmap(Crop(lastImage), new Size(smallSize, smallSize)));
beforeLastImageChanged = MakeGrayscale3(new Bitmap(Crop(beforeLastImage), new Size(smallSize, smallSize)));
//make bits from the two images
lastImageMeanBits = bits(colorMeanValue(lastImageChanged,smallSize));
beforeLastImageMeanBits = bits(colorMeanValue(beforeLastImageChanged,smallSize));
//calculationg hammering distance between the two images
for (int a = 0; a < smallSize* smallSize; a++)
{
if (lastImageMeanBits[a] != beforeLastImageMeanBits[a])
{
hammeringDistance++;
}
}
//add up hammering distances maxSteps times. If hammering distance in maxSteps times is bigger or equal with
//itIsConsideredMovement then it is a movement and we save the images from those steps.
if (steps < maxSteps)
{
hammeringDistanceInTime += hammeringDistance;
imageSteps[steps] = e.Bitmap; //we collect the step images so if there is movement we can save images
steps++;
}
else
{
if (hammeringDistanceInTime >= itIsConsideredMovement)
{
var count = 0;
foreach(Bitmap img in imageSteps)
{
try
{//here I don't really care anymore about this script so if it fails it fails. It saves enough images anyway.
//that is why it is not a repositoriy just a gist
img.Save("images/" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss") + "-" + count + ".jpg", ImageFormat.Jpeg);
}
catch { };
count++;
}
}
steps = hammeringDistanceInTime= 0;
}
}
}
public static Bitmap Crop(Image myImage)
{
Bitmap croppedBitmap = new Bitmap(myImage);
croppedBitmap = croppedBitmap.Clone(
new Rectangle(0, 0, myImage.Width, myImage.Height - 20),
System.Drawing.Imaging.PixelFormat.DontCare);
return croppedBitmap;
}
private static Bitmap MakeGrayscale3(Image original)
{//from here https://stackoverflow.com/questions/2265910/convert-an-image-to-grayscale
//the whole thing could make different resoult with different grayscale method. If you
//find a better: use that.
//create a blank bitmap the same size as original
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
//get a graphics object from the new image
Graphics g = Graphics.FromImage(newBitmap);
//create the grayscale ColorMatrix
ColorMatrix colorMatrix = new ColorMatrix(
new float[][]
{
new float[] {.3f, .3f, .3f, 0, 0},
new float[] {.59f, .59f, .59f, 0, 0},
new float[] {.11f, .11f, .11f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
});
//create some image attributes
ImageAttributes attributes = new ImageAttributes();
//set the color matrix attribute
attributes.SetColorMatrix(colorMatrix);
//draw the original image on the new image
//using the grayscale color matrix
g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
//dispose the Graphics object
g.Dispose();
return newBitmap;
}
private static MeanValues colorMeanValue(Image i,int smallSize)
{
//returns the mean value of the colors and the list of all pixel's colors
List<int> colorList = new List<int>();
int colorSum = 0;
MeanValues returnObject = new MeanValues();
int a, b;
Bitmap c = new Bitmap(i);
for (a = 0; a < smallSize; a++)
{
for (b = 0; b < smallSize; b++)
{
Color pixelColor = c.GetPixel(a, b);
colorList.Add(pixelColor.ToArgb());
colorSum += pixelColor.ToArgb();
}
}
returnObject.colorList = colorList;
returnObject.colorMean = colorSum / (smallSize* smallSize);
return returnObject;
}
private List<int> bits(MeanValues colorMean)
{
//returns an array of 1s and 0s. If a color is bigger than the mean value of colors it is a 1
List<int> bits = new List<int>();
List<int> colorList = colorMean.colorList;
foreach (int color in colorList)
{
if (color >= colorMean.colorMean)
{
bits.Add(1);
}
else
{
bits.Add(0);
}
}
return bits;
}
}
public class MeanValues{
//use this object to collect these values. Easier down the line
public List<int> colorList = new List<int>();
public int colorMean = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment