/* * GStreamer MotionCells detect areas of motion * Copyright (C) 2011 Robert Jobbagy * Copyright (C) 2011 Nicola Murino * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Alternatively, the contents of this file may be used under the * GNU Lesser General Public License Version 2.1 (the "LGPL"), in * which case the following provisions apply instead of the ones * mentioned above: * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:element-motioncells * * Performs motion detection on videos. * * * Example launch line * |[ * gst-launch-1.0 videotestsrc pattern=18 ! videorate ! videoscale ! video/x-raw,width=320,height=240,framerate=5/1 ! videoconvert ! motioncells ! videoconvert ! xvimagesink * ]| * */ #ifdef HAVE_CONFIG_H # include #endif #include "gstmotioncells.h" GST_DEBUG_CATEGORY_STATIC (gst_motion_cells_debug); #define GST_CAT_DEFAULT gst_motion_cells_debug #define GRID_DEF 10 #define GRID_MIN 8 #define GRID_MAX 32 #define SENSITIVITY_DEFAULT 0.5 #define SENSITIVITY_MIN 0 #define SENSITIVITY_MAX 1 #define THRESHOLD_MIN 0 #define THRESHOLD_DEFAULT 0.01 #define THRESHOLD_MAX 1.0 #define GAP_MIN 1 #define GAP_DEF 5 #define GAP_MAX 60 #define POST_NO_MOTION_MIN 0 #define POST_NO_MOTION_DEF 0 #define POST_NO_MOTION_MAX 180 #define MINIMUM_MOTION_FRAMES_MIN 1 #define MINIMUM_MOTION_FRAMES_DEF 1 #define MINIMUM_MOTION_FRAMES_MAX 60 #define THICKNESS_MIN -1 #define THICKNESS_DEF 1 #define THICKNESS_MAX 5 #define DATE_MIN 0 #define DATE_DEF 1 #define DATE_MAX LONG_MAX #define DEF_DATAFILEEXT "vamc" #define MSGLEN 6 #define BUSMSGLEN 20 #define GFREE(POINTER)\ {\ g_free(POINTER);\ POINTER = NULL;\ } /* Filter signals and args */ enum { /* FILL ME */ LAST_SIGNAL }; enum { PROP_0, PROP_GRID_X, PROP_GRID_Y, PROP_SENSITIVITY, PROP_THRESHOLD, PROP_DISPLAY, PROP_DATE, PROP_DATAFILE, PROP_DATAFILE_EXT, PROP_MOTIONMASKCOORD, PROP_MOTIONMASKCELLSPOS, PROP_CELLSCOLOR, PROP_MOTIONCELLSIDX, PROP_GAP, PROP_POSTNOMOTION, PROP_MINIMUNMOTIONFRAMES, PROP_CALCULATEMOTION, PROP_POSTALLMOTION, PROP_USEALPHA, PROP_MOTIONCELLTHICKNESS }; /* the capabilities of the inputs and outputs. */ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB"))); static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB"))); G_DEFINE_TYPE (GstMotioncells, gst_motion_cells, GST_TYPE_OPENCV_VIDEO_FILTER); static void gst_motion_cells_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_motion_cells_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_motion_cells_handle_sink_event (GstPad * pad, GstObject * parent, GstEvent * event); static GstFlowReturn gst_motion_cells_transform_ip (GstOpencvVideoFilter * filter, GstBuffer * buf, IplImage * img); static void gst_motioncells_update_motion_cells (GstMotioncells * filter); static void gst_motioncells_update_motion_masks (GstMotioncells * filter); /* Clean up */ static void gst_motion_cells_finalize (GObject * obj) { GstMotioncells *filter = gst_motion_cells (obj); motion_cells_free (filter->id); //freeing previously allocated dynamic array if (filter->motionmaskcoord_count > 0) { GFREE (filter->motionmaskcoords); } if (filter->motionmaskcells_count > 0) { GFREE (filter->motionmaskcellsidx); } if (filter->motioncells_count > 0) { GFREE (filter->motioncellsidx); } GFREE (filter->motioncellscolor); GFREE (filter->prev_datafile); GFREE (filter->cur_datafile); GFREE (filter->basename_datafile); GFREE (filter->datafile_extension); G_OBJECT_CLASS (gst_motion_cells_parent_class)->finalize (obj); } /* initialize the motioncells's class */ static void gst_motion_cells_class_init (GstMotioncellsClass * klass) { GObjectClass *gobject_class; GstOpencvVideoFilterClass *gstopencvbasefilter_class; GstElementClass *element_class = GST_ELEMENT_CLASS (klass); gobject_class = (GObjectClass *) klass; gstopencvbasefilter_class = (GstOpencvVideoFilterClass *) klass; gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_motion_cells_finalize); gobject_class->set_property = gst_motion_cells_set_property; gobject_class->get_property = gst_motion_cells_get_property; gstopencvbasefilter_class->cv_trans_ip_func = gst_motion_cells_transform_ip; g_object_class_install_property (gobject_class, PROP_GRID_X, g_param_spec_int ("gridx", "Number of Horizontal Grids", "Number of horizontal grid cells.", GRID_MIN, GRID_MAX, GRID_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_GRID_Y, g_param_spec_int ("gridy", "Number of Vertical Grids", "Number of vertical grid cells.", GRID_MIN, GRID_MAX, GRID_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_SENSITIVITY, g_param_spec_double ("sensitivity", "Motion Sensitivity", "Motion detection sensitivity.", SENSITIVITY_MIN, SENSITIVITY_MAX, SENSITIVITY_DEFAULT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_THRESHOLD, g_param_spec_double ("threshold", "Lower bound of motion cells number", "Threshold value for motion. Filter detects motion when at least " "this fraction of the cells have moved", THRESHOLD_MIN, THRESHOLD_MAX, THRESHOLD_DEFAULT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_GAP, g_param_spec_int ("gap", "Motion-finished Threshold", "Interval in seconds after which motion is considered finished " "and a motion finished bus message is posted.", GAP_MIN, GAP_MAX, GAP_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_POSTNOMOTION, g_param_spec_int ("postnomotion", "No-motion Threshold", "If non 0, post a no_motion event on the bus if no motion is " "detected for the given number of seconds", POST_NO_MOTION_MIN, POST_NO_MOTION_MAX, POST_NO_MOTION_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_MINIMUNMOTIONFRAMES, g_param_spec_int ("minimummotionframes", "Minimum Motion Frames", "Minimum number of motion frames triggering a motion event", MINIMUM_MOTION_FRAMES_MIN, MINIMUM_MOTION_FRAMES_MAX, MINIMUM_MOTION_FRAMES_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_DISPLAY, g_param_spec_boolean ("display", "Display", "Toggle display of motion cells on current frame", FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_POSTALLMOTION, g_param_spec_boolean ("postallmotion", "Post All Motion", "Post bus messages for every motion frame or just motion start and " "motion stop", FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_USEALPHA, g_param_spec_boolean ("usealpha", "Use alpha", "Toggle usage of alpha blending on frames with motion cells", TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); #if 0 /* FIXME: should not be a long property, make it either gint or gint64 * (is this property actually used or useful for anything?) */ g_object_class_install_property (gobject_class, PROP_DATE, g_param_spec_long ("date", "Motion Cell Date", "Current Date in milliseconds", DATE_MIN, DATE_MAX, DATE_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); #endif g_object_class_install_property (gobject_class, PROP_DATAFILE, g_param_spec_string ("datafile", "DataFile", "Location of motioncells data file (empty string means no saving)", NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_DATAFILE_EXT, g_param_spec_string ("datafileextension", "DataFile Extension", "Extension of datafile", DEF_DATAFILEEXT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_MOTIONMASKCOORD, g_param_spec_string ("motionmaskcoords", "Motion Mask with Coordinates", "Describe a region with its upper left and lower right x, y " "coordinates separated with \":\". Pass multiple regions as a " "comma-separated list", NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_MOTIONMASKCELLSPOS, g_param_spec_string ("motionmaskcellspos", "Motion Mask with Cells Position", "Describe a cell with its line and column idx separated with \":\". " "Pass multiple cells as a comma-separated list", NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_CELLSCOLOR, g_param_spec_string ("cellscolor", "Color of Motion Cells", "Color for motion cells in R,G,B format. Max per channel is 255", "255,255,0", (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_MOTIONCELLSIDX, g_param_spec_string ("motioncellsidx", "Motion Cells Of Interest(MOCI)", "Describe a cell with its line and column idx separated with \":\". " "Pass multiple cells as a comma-separated list", NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_CALCULATEMOTION, g_param_spec_boolean ("calculatemotion", "Calculate Motion", "Toggles motion calculation. If FALSE, this filter does nothing", TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); g_object_class_install_property (gobject_class, PROP_MOTIONCELLTHICKNESS, g_param_spec_int ("motioncellthickness", "Motion Cell Thickness", "Motion Cell Border Thickness. Set to -1 to fill motion cell", THICKNESS_MIN, THICKNESS_MAX, THICKNESS_DEF, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); gst_element_class_set_static_metadata (element_class, "motioncells", "Filter/Effect/Video", "Performs motion detection on videos and images, providing detected motion cells index via bus messages", "Robert Jobbagy , Nicola Murino "); gst_element_class_add_static_pad_template (element_class, &src_factory); gst_element_class_add_static_pad_template (element_class, &sink_factory); } /* initialize the new element * instantiate pads and add them to element * set pad callback functions * initialize instance structure */ static void gst_motion_cells_init (GstMotioncells * filter) { gst_pad_set_event_function (GST_BASE_TRANSFORM_SINK_PAD (filter), GST_DEBUG_FUNCPTR (gst_motion_cells_handle_sink_event)); filter->display = TRUE; filter->calculate_motion = TRUE; filter->prevgridx = 0; filter->prevgridy = 0; filter->gridx = GRID_DEF; filter->gridy = GRID_DEF; filter->gap = GAP_DEF; filter->postnomotion = POST_NO_MOTION_DEF; filter->minimum_motion_frames = MINIMUM_MOTION_FRAMES_DEF; filter->prev_datafile = NULL; filter->cur_datafile = NULL; filter->basename_datafile = NULL; filter->datafile_extension = g_strdup (DEF_DATAFILEEXT); filter->sensitivity = SENSITIVITY_DEFAULT; filter->threshold = THRESHOLD_DEFAULT; filter->motionmaskcoord_count = 0; filter->motionmaskcoords = NULL; filter->motionmaskcells_count = 0; filter->motionmaskcellsidx = NULL; filter->motioncellscolor = g_new0 (cellscolor, 1); filter->motioncellscolor->R_channel_value = 255; filter->motioncellscolor->G_channel_value = 255; filter->motioncellscolor->B_channel_value = 0; filter->motioncellsidx = NULL; filter->motioncells_count = 0; filter->motion_begin_timestamp = 0; filter->last_motion_timestamp = 0; filter->last_nomotion_notified = 0; filter->consecutive_motion = 0; filter->motion_timestamp = 0; filter->prev_buff_timestamp = 0; filter->cur_buff_timestamp = 0; filter->diff_timestamp = -1; g_get_current_time (&filter->tv); filter->starttime = 1000 * filter->tv.tv_sec; filter->previous_motion = FALSE; filter->changed_datafile = FALSE; filter->postallmotion = FALSE; filter->usealpha = TRUE; filter->firstdatafile = FALSE; filter->firstgridx = TRUE; filter->firstgridy = TRUE; filter->changed_gridx = FALSE; filter->changed_gridy = FALSE; filter->firstframe = TRUE; filter->changed_startime = FALSE; filter->sent_init_error_msg = FALSE; filter->sent_save_error_msg = FALSE; filter->thickness = THICKNESS_DEF; filter->datafileidx = 0; filter->id = motion_cells_init (); gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter), TRUE); } static void gst_motion_cells_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstMotioncells *filter = gst_motion_cells (object); //variables for overlay regions setup gchar **strs, **colorstr, **motioncellsstr, **motionmaskcellsstr; int i, ux, uy, lx, ly; int r, g, b; int cellscolorscnt = 0; int linidx, colidx, masklinidx, maskcolidx; int tmpux = -1; int tmpuy = -1; int tmplx = -1; int tmply = -1; GST_OBJECT_LOCK (filter); switch (prop_id) { case PROP_GRID_X: filter->gridx = g_value_get_int (value); if (filter->prevgridx != filter->gridx && !filter->firstframe) { filter->changed_gridx = TRUE; } filter->prevgridx = filter->gridx; break; case PROP_GRID_Y: filter->gridy = g_value_get_int (value); if (filter->prevgridy != filter->gridy && !filter->firstframe) { filter->changed_gridy = TRUE; } filter->prevgridy = filter->gridy; break; case PROP_GAP: filter->gap = g_value_get_int (value); break; case PROP_POSTNOMOTION: filter->postnomotion = g_value_get_int (value); break; case PROP_MINIMUNMOTIONFRAMES: filter->minimum_motion_frames = g_value_get_int (value); break; case PROP_SENSITIVITY: filter->sensitivity = g_value_get_double (value); break; case PROP_THRESHOLD: filter->threshold = g_value_get_double (value); break; case PROP_DISPLAY: filter->display = g_value_get_boolean (value); break; case PROP_POSTALLMOTION: filter->postallmotion = g_value_get_boolean (value); break; case PROP_USEALPHA: filter->usealpha = g_value_get_boolean (value); break; case PROP_CALCULATEMOTION: filter->calculate_motion = g_value_get_boolean (value); break; case PROP_DATE: if (!filter->firstframe) { filter->changed_startime = TRUE; } filter->starttime = g_value_get_long (value); break; case PROP_DATAFILE: GFREE (filter->cur_datafile); GFREE (filter->basename_datafile); filter->basename_datafile = g_value_dup_string (value); if (strlen (filter->basename_datafile) == 0) { filter->cur_datafile = NULL; break; } filter->cur_datafile = g_strdup_printf ("%s-0.%s", filter->basename_datafile, filter->datafile_extension); if (g_strcmp0 (filter->prev_datafile, filter->basename_datafile) != 0) { filter->changed_datafile = TRUE; filter->sent_init_error_msg = FALSE; filter->sent_save_error_msg = FALSE; filter->datafileidx = 0; motion_cells_free_resources (filter->id); } else { filter->changed_datafile = FALSE; } GFREE (filter->prev_datafile); filter->prev_datafile = g_strdup (filter->basename_datafile); break; case PROP_DATAFILE_EXT: GFREE (filter->datafile_extension); filter->datafile_extension = g_value_dup_string (value); break; case PROP_MOTIONMASKCOORD: strs = g_strsplit (g_value_get_string (value), ",", 255); GFREE (filter->motionmaskcoords); //setting number of regions for (filter->motionmaskcoord_count = 0; strs[filter->motionmaskcoord_count] != NULL; ++filter->motionmaskcoord_count); if (filter->motionmaskcoord_count > 0) { sscanf (strs[0], "%d:%d:%d:%d", &tmpux, &tmpuy, &tmplx, &tmply); if (tmpux > -1 && tmpuy > -1 && tmplx > -1 && tmply > -1) { filter->motionmaskcoords = g_new0 (motionmaskcoordrect, filter->motionmaskcoord_count); for (i = 0; i < filter->motionmaskcoord_count; ++i) { sscanf (strs[i], "%d:%d:%d:%d", &ux, &uy, &lx, &ly); ux = CLAMP (ux, 0, filter->width - 1); uy = CLAMP (uy, 0, filter->height - 1); lx = CLAMP (lx, 0, filter->width - 1); ly = CLAMP (ly, 0, filter->height - 1); filter->motionmaskcoords[i].upper_left_x = ux; filter->motionmaskcoords[i].upper_left_y = uy; filter->motionmaskcoords[i].lower_right_x = lx; filter->motionmaskcoords[i].lower_right_y = ly; } } else { filter->motionmaskcoord_count = 0; } } g_strfreev (strs); tmpux = -1; tmpuy = -1; tmplx = -1; tmply = -1; break; case PROP_MOTIONMASKCELLSPOS: motionmaskcellsstr = g_strsplit (g_value_get_string (value), ",", 255); GFREE (filter->motionmaskcellsidx); //setting number of regions for (filter->motionmaskcells_count = 0; motionmaskcellsstr[filter->motionmaskcells_count] != NULL; ++filter->motionmaskcells_count); if (filter->motionmaskcells_count > 0) { sscanf (motionmaskcellsstr[0], "%d:%d", &tmpux, &tmpuy); if (tmpux > -1 && tmpuy > -1) { filter->motionmaskcellsidx = g_new0 (motioncellidx, filter->motionmaskcells_count); for (i = 0; i < filter->motionmaskcells_count; ++i) { sscanf (motionmaskcellsstr[i], "%d:%d", &masklinidx, &maskcolidx); filter->motionmaskcellsidx[i].lineidx = masklinidx; filter->motionmaskcellsidx[i].columnidx = maskcolidx; } } else { filter->motionmaskcells_count = 0; } } g_strfreev (motionmaskcellsstr); tmpux = -1; tmpuy = -1; tmplx = -1; tmply = -1; break; case PROP_CELLSCOLOR: colorstr = g_strsplit (g_value_get_string (value), ",", 4); for (cellscolorscnt = 0; colorstr[cellscolorscnt] != NULL; ++cellscolorscnt); if (cellscolorscnt != 3) { GST_WARNING_OBJECT (filter, "Ignoring badly-formatted cellscolor RGB " "string"); } else { sscanf (colorstr[0], "%d", &r); sscanf (colorstr[1], "%d", &g); sscanf (colorstr[2], "%d", &b); //check right RGB color format r = CLAMP (r, 1, 255); g = CLAMP (g, 1, 255); b = CLAMP (b, 1, 255); filter->motioncellscolor->R_channel_value = r; filter->motioncellscolor->G_channel_value = g; filter->motioncellscolor->B_channel_value = b; } g_strfreev (colorstr); break; case PROP_MOTIONCELLSIDX: motioncellsstr = g_strsplit (g_value_get_string (value), ",", 255); //setting number of regions for (filter->motioncells_count = 0; motioncellsstr[filter->motioncells_count] != NULL; ++filter->motioncells_count); if (filter->motioncells_count > 0) { sscanf (motioncellsstr[0], "%d:%d", &tmpux, &tmpuy); if (tmpux > -1 && tmpuy > -1) { GFREE (filter->motioncellsidx); filter->motioncellsidx = g_new0 (motioncellidx, filter->motioncells_count); for (i = 0; i < filter->motioncells_count; ++i) { sscanf (motioncellsstr[i], "%d:%d", &linidx, &colidx); filter->motioncellsidx[i].lineidx = linidx; filter->motioncellsidx[i].columnidx = colidx; } } else { filter->motioncells_count = 0; } } g_strfreev (motioncellsstr); tmpux = -1; tmpuy = -1; tmplx = -1; tmply = -1; break; case PROP_MOTIONCELLTHICKNESS: filter->thickness = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (filter); } static void gst_motion_cells_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstMotioncells *filter = gst_motion_cells (object); GString *str; int i; GST_OBJECT_LOCK (filter); switch (prop_id) { case PROP_GRID_X: g_value_set_int (value, filter->gridx); break; case PROP_GRID_Y: g_value_set_int (value, filter->gridy); break; case PROP_GAP: g_value_set_int (value, filter->gap); break; case PROP_POSTNOMOTION: g_value_set_int (value, filter->postnomotion); break; case PROP_MINIMUNMOTIONFRAMES: g_value_set_int (value, filter->minimum_motion_frames); break; case PROP_SENSITIVITY: g_value_set_double (value, filter->sensitivity); break; case PROP_THRESHOLD: g_value_set_double (value, filter->threshold); break; case PROP_DISPLAY: g_value_set_boolean (value, filter->display); break; case PROP_POSTALLMOTION: g_value_set_boolean (value, filter->postallmotion); break; case PROP_USEALPHA: g_value_set_boolean (value, filter->usealpha); break; case PROP_CALCULATEMOTION: g_value_set_boolean (value, filter->calculate_motion); break; case PROP_DATE: g_value_set_long (value, filter->starttime); break; case PROP_DATAFILE: g_value_set_string (value, filter->basename_datafile); break; case PROP_DATAFILE_EXT: g_value_set_string (value, filter->datafile_extension); break; case PROP_MOTIONMASKCOORD: str = g_string_new (""); for (i = 0; i < filter->motionmaskcoord_count; ++i) { if (i < filter->motionmaskcoord_count - 1) g_string_append_printf (str, "%d:%d:%d:%d,", filter->motionmaskcoords[i].upper_left_x, filter->motionmaskcoords[i].upper_left_y, filter->motionmaskcoords[i].lower_right_x, filter->motionmaskcoords[i].lower_right_y); else g_string_append_printf (str, "%d:%d:%d:%d", filter->motionmaskcoords[i].upper_left_x, filter->motionmaskcoords[i].upper_left_y, filter->motionmaskcoords[i].lower_right_x, filter->motionmaskcoords[i].lower_right_y); } g_value_set_string (value, str->str); g_string_free (str, TRUE); break; case PROP_MOTIONMASKCELLSPOS: str = g_string_new (""); for (i = 0; i < filter->motionmaskcells_count; ++i) { if (i < filter->motionmaskcells_count - 1) g_string_append_printf (str, "%d:%d,", filter->motionmaskcellsidx[i].lineidx, filter->motionmaskcellsidx[i].columnidx); else g_string_append_printf (str, "%d:%d", filter->motionmaskcellsidx[i].lineidx, filter->motionmaskcellsidx[i].columnidx); } g_value_set_string (value, str->str); g_string_free (str, TRUE); break; case PROP_CELLSCOLOR: str = g_string_new (""); g_string_printf (str, "%d,%d,%d", filter->motioncellscolor->R_channel_value, filter->motioncellscolor->G_channel_value, filter->motioncellscolor->B_channel_value); g_value_set_string (value, str->str); g_string_free (str, TRUE); break; case PROP_MOTIONCELLSIDX: str = g_string_new (""); for (i = 0; i < filter->motioncells_count; ++i) { if (i < filter->motioncells_count - 1) g_string_append_printf (str, "%d:%d,", filter->motioncellsidx[i].lineidx, filter->motioncellsidx[i].columnidx); else g_string_append_printf (str, "%d:%d", filter->motioncellsidx[i].lineidx, filter->motioncellsidx[i].columnidx); } g_value_set_string (value, str->str); g_string_free (str, TRUE); break; case PROP_MOTIONCELLTHICKNESS: g_value_set_int (value, filter->thickness); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (filter); } static void gst_motioncells_update_motion_cells (GstMotioncells * filter) { int i = 0; int cellscnt = 0; int j = 0; int newcellscnt; motioncellidx *motioncellsidx; for (i = 0; i < filter->motioncells_count; i++) { if ((filter->gridx <= filter->motioncellsidx[i].columnidx) || (filter->gridy <= filter->motioncellsidx[i].lineidx)) { cellscnt++; } } newcellscnt = filter->motioncells_count - cellscnt; motioncellsidx = g_new0 (motioncellidx, newcellscnt); for (i = 0; i < filter->motioncells_count; i++) { if ((filter->motioncellsidx[i].lineidx < filter->gridy) && (filter->motioncellsidx[i].columnidx < filter->gridx)) { motioncellsidx[j].lineidx = filter->motioncellsidx[i].lineidx; motioncellsidx[j].columnidx = filter->motioncellsidx[i].columnidx; j++; } } GFREE (filter->motioncellsidx); filter->motioncells_count = newcellscnt; filter->motioncellsidx = g_new0 (motioncellidx, filter->motioncells_count); j = 0; for (i = 0; i < filter->motioncells_count; i++) { filter->motioncellsidx[i].lineidx = motioncellsidx[j].lineidx; filter->motioncellsidx[i].columnidx = motioncellsidx[j].columnidx; j++; } GFREE (motioncellsidx); } static void gst_motioncells_update_motion_masks (GstMotioncells * filter) { int i = 0; int maskcnt = 0; int j = 0; int newmaskcnt; motioncellidx *motionmaskcellsidx; for (i = 0; i < filter->motionmaskcells_count; i++) { if ((filter->gridx <= filter->motionmaskcellsidx[i].columnidx) || (filter->gridy <= filter->motionmaskcellsidx[i].lineidx)) { maskcnt++; } } newmaskcnt = filter->motionmaskcells_count - maskcnt; motionmaskcellsidx = g_new0 (motioncellidx, newmaskcnt); for (i = 0; i < filter->motionmaskcells_count; i++) { if ((filter->motionmaskcellsidx[i].lineidx < filter->gridy) && (filter->motionmaskcellsidx[i].columnidx < filter->gridx)) { motionmaskcellsidx[j].lineidx = filter->motionmaskcellsidx[i].lineidx; motionmaskcellsidx[j].columnidx = filter->motionmaskcellsidx[i].columnidx; j++; } } GFREE (filter->motionmaskcellsidx); filter->motionmaskcells_count = newmaskcnt; filter->motionmaskcellsidx = g_new0 (motioncellidx, filter->motionmaskcells_count); j = 0; for (i = 0; i < filter->motionmaskcells_count; i++) { filter->motionmaskcellsidx[i].lineidx = motionmaskcellsidx[j].lineidx; filter->motionmaskcellsidx[i].columnidx = motionmaskcellsidx[j].columnidx; j++; } GFREE (motionmaskcellsidx); } /* GstElement vmethod implementations */ /* this function handles the link with other elements */ static gboolean gst_motion_cells_handle_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) { GstMotioncells *filter; GstVideoInfo info; gboolean res = TRUE; filter = gst_motion_cells (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_CAPS: { GstCaps *caps; gst_event_parse_caps (event, &caps); gst_video_info_from_caps (&info, caps); filter->width = info.width; filter->height = info.height; filter->framerate = (double) info.fps_n / (double) info.fps_d; break; } default: break; } res = gst_pad_event_default (pad, parent, event); return res; } /* chain function * this function does the actual processing */ static GstFlowReturn gst_motion_cells_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf, IplImage * img) { GstMotioncells *filter = gst_motion_cells (base); GST_OBJECT_LOCK (filter); if (filter->calculate_motion) { double sensitivity; int framerate, gridx, gridy, motionmaskcells_count, motionmaskcoord_count, motioncells_count, i; int thickness, success, motioncellsidxcnt, numberOfCells, motioncellsnumber, cellsOfInterestNumber; int mincellsOfInterestNumber, motiondetect; uint minimum_motion_frames, postnomotion; char *datafile; bool display, changed_datafile, useAlpha; gint64 starttime; motionmaskcoordrect *motionmaskcoords; motioncellidx *motionmaskcellsidx; cellscolor motioncellscolor; motioncellidx *motioncellsidx; buf = gst_buffer_make_writable (buf); if (filter->firstframe) { setPrevFrame (img, filter->id); filter->firstframe = FALSE; } minimum_motion_frames = filter->minimum_motion_frames; postnomotion = filter->postnomotion; sensitivity = filter->sensitivity; framerate = filter->framerate; gridx = filter->gridx; gridy = filter->gridy; display = filter->display; motionmaskcoord_count = filter->motionmaskcoord_count; motionmaskcoords = g_new0 (motionmaskcoordrect, filter->motionmaskcoord_count); for (i = 0; i < filter->motionmaskcoord_count; i++) { //we need divide 2 because we use gauss pyramid in C++ side motionmaskcoords[i].upper_left_x = filter->motionmaskcoords[i].upper_left_x / 2; motionmaskcoords[i].upper_left_y = filter->motionmaskcoords[i].upper_left_y / 2; motionmaskcoords[i].lower_right_x = filter->motionmaskcoords[i].lower_right_x / 2; motionmaskcoords[i].lower_right_y = filter->motionmaskcoords[i].lower_right_y / 2; } motioncellscolor.R_channel_value = filter->motioncellscolor->R_channel_value; motioncellscolor.G_channel_value = filter->motioncellscolor->G_channel_value; motioncellscolor.B_channel_value = filter->motioncellscolor->B_channel_value; if ((filter->changed_gridx || filter->changed_gridy || filter->changed_startime)) { if ((g_strcmp0 (filter->cur_datafile, NULL) != 0)) { GFREE (filter->cur_datafile); filter->datafileidx++; filter->cur_datafile = g_strdup_printf ("%s-%d.%s", filter->basename_datafile, filter->datafileidx, filter->datafile_extension); filter->changed_datafile = TRUE; motion_cells_free_resources (filter->id); } if (filter->motioncells_count > 0) gst_motioncells_update_motion_cells (filter); if (filter->motionmaskcells_count > 0) gst_motioncells_update_motion_masks (filter); filter->changed_gridx = FALSE; filter->changed_gridy = FALSE; filter->changed_startime = FALSE; } datafile = g_strdup (filter->cur_datafile); filter->cur_buff_timestamp = (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND); filter->starttime += (filter->cur_buff_timestamp - filter->prev_buff_timestamp); starttime = filter->starttime; if (filter->changed_datafile || filter->diff_timestamp < 0) filter->diff_timestamp = (gint64) (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND); changed_datafile = filter->changed_datafile; motionmaskcells_count = filter->motionmaskcells_count; motionmaskcellsidx = g_new0 (motioncellidx, filter->motionmaskcells_count); for (i = 0; i < filter->motionmaskcells_count; i++) { motionmaskcellsidx[i].lineidx = filter->motionmaskcellsidx[i].lineidx; motionmaskcellsidx[i].columnidx = filter->motionmaskcellsidx[i].columnidx; } motioncells_count = filter->motioncells_count; motioncellsidx = g_new0 (motioncellidx, filter->motioncells_count); for (i = 0; i < filter->motioncells_count; i++) { motioncellsidx[i].lineidx = filter->motioncellsidx[i].lineidx; motioncellsidx[i].columnidx = filter->motioncellsidx[i].columnidx; } useAlpha = filter->usealpha; thickness = filter->thickness; success = perform_detection_motion_cells (img, sensitivity, framerate, gridx, gridy, (gint64) (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND) - filter->diff_timestamp, display, useAlpha, motionmaskcoord_count, motionmaskcoords, motionmaskcells_count, motionmaskcellsidx, motioncellscolor, motioncells_count, motioncellsidx, starttime, datafile, changed_datafile, thickness, filter->id); if ((success == 1) && (filter->sent_init_error_msg == FALSE)) { char *initfailedreason; int initerrorcode; GstStructure *s; GstMessage *m; initfailedreason = getInitDataFileFailed (filter->id); initerrorcode = getInitErrorCode (filter->id); s = gst_structure_new ("motion", "init_error_code", G_TYPE_INT, initerrorcode, "details", G_TYPE_STRING, initfailedreason, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); filter->sent_init_error_msg = TRUE; } if ((success == -1) && (filter->sent_save_error_msg == FALSE)) { char *savefailedreason; int saveerrorcode; GstStructure *s; GstMessage *m; savefailedreason = getSaveDataFileFailed (filter->id); saveerrorcode = getSaveErrorCode (filter->id); s = gst_structure_new ("motion", "save_error_code", G_TYPE_INT, saveerrorcode, "details", G_TYPE_STRING, savefailedreason, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); filter->sent_save_error_msg = TRUE; } if (success == -2) { GST_LOG_OBJECT (filter, "frame dropped"); filter->prev_buff_timestamp = filter->cur_buff_timestamp; //free GFREE (datafile); GFREE (motionmaskcoords); GFREE (motionmaskcellsidx); GFREE (motioncellsidx); GST_OBJECT_UNLOCK (filter); return GST_FLOW_OK; } filter->changed_datafile = getChangedDataFile (filter->id); motioncellsidxcnt = getMotionCellsIdxCnt (filter->id); numberOfCells = filter->gridx * filter->gridy; motioncellsnumber = motioncellsidxcnt / MSGLEN; cellsOfInterestNumber = (filter->motioncells_count > 0) ? //how many cells interest for us (filter->motioncells_count) : (numberOfCells); mincellsOfInterestNumber = floor ((double) cellsOfInterestNumber * filter->threshold); GST_OBJECT_UNLOCK (filter); motiondetect = (motioncellsnumber >= mincellsOfInterestNumber) ? 1 : 0; if ((motioncellsidxcnt > 0) && (motiondetect == 1)) { char *detectedmotioncells; filter->last_motion_timestamp = GST_BUFFER_TIMESTAMP (buf); detectedmotioncells = getMotionCellsIdx (filter->id); if (detectedmotioncells) { filter->consecutive_motion++; if ((filter->previous_motion == FALSE) && (filter->consecutive_motion >= minimum_motion_frames)) { GstStructure *s; GstMessage *m; GST_DEBUG_OBJECT (filter, "motion started, post msg on the bus"); filter->previous_motion = TRUE; filter->motion_begin_timestamp = GST_BUFFER_TIMESTAMP (buf); s = gst_structure_new ("motion", "motion_cells_indices", G_TYPE_STRING, detectedmotioncells, "motion_begin", G_TYPE_UINT64, filter->motion_begin_timestamp, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); } else if (filter->postallmotion) { GstStructure *s; GstMessage *m; GST_DEBUG_OBJECT (filter, "motion, post msg on the bus"); filter->motion_timestamp = GST_BUFFER_TIMESTAMP (buf); s = gst_structure_new ("motion", "motion_cells_indices", G_TYPE_STRING, detectedmotioncells, "motion", G_TYPE_UINT64, filter->motion_timestamp, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); } } else { GstStructure *s; GstMessage *m; s = gst_structure_new ("motion", "motion_cells_indices", G_TYPE_STRING, "error", NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); } } else { filter->consecutive_motion = 0; if ((((GST_BUFFER_TIMESTAMP (buf) - filter->last_motion_timestamp) / 1000000000l) >= filter->gap) && (filter->last_motion_timestamp > 0)) { if (filter->previous_motion) { GstStructure *s; GstMessage *m; GST_DEBUG_OBJECT (filter, "motion finished, post msg on the bus"); filter->previous_motion = FALSE; s = gst_structure_new ("motion", "motion_finished", G_TYPE_UINT64, filter->last_motion_timestamp, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); } } } if (postnomotion > 0) { guint64 last_buf_timestamp = GST_BUFFER_TIMESTAMP (buf) / 1000000000l; if ((last_buf_timestamp - (filter->last_motion_timestamp / 1000000000l)) >= filter->postnomotion) { GST_DEBUG_OBJECT (filter, "post no motion msg on the bus"); if ((last_buf_timestamp - (filter->last_nomotion_notified / 1000000000l)) >= filter->postnomotion) { GstStructure *s; GstMessage *m; filter->last_nomotion_notified = GST_BUFFER_TIMESTAMP (buf); s = gst_structure_new ("motion", "no_motion", G_TYPE_UINT64, filter->last_motion_timestamp, NULL); m = gst_message_new_element (GST_OBJECT (filter), s); gst_element_post_message (GST_ELEMENT (filter), m); } } } filter->prev_buff_timestamp = filter->cur_buff_timestamp; //free GFREE (datafile); GFREE (motionmaskcoords); GFREE (motionmaskcellsidx); GFREE (motioncellsidx); } else { GST_WARNING_OBJECT (filter, "Motion detection disabled"); GST_OBJECT_UNLOCK (filter); } return GST_FLOW_OK; } /* entry point to initialize the plug-in * initialize the plug-in itself * register the element factories and other features */ gboolean gst_motion_cells_plugin_init (GstPlugin * plugin) { /* debug category for fltering log messages */ GST_DEBUG_CATEGORY_INIT (gst_motion_cells_debug, "motioncells", 0, "Performs motion detection on videos, providing detected positions via bus messages"); return gst_element_register (plugin, "motioncells", GST_RANK_NONE, GST_TYPE_MOTIONCELLS); }