blob: 33f314a64d18852954127b5c6b2c7e1f1cfd8c67 [file] [log] [blame]
/*
* GStreamer MotionCells detect areas of motion
* Copyright (C) 2011 Robert Jobbagy <jobbagy.robert@gmail.com>
* Copyright (C) 2011 Nicola Murino <nicola.murino@gmail.com>
*
* 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.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-1.0 videotestsrc pattern=18 ! videorate ! videoscale ! video/x-raw,width=320,height=240,framerate=5/1 ! videoconvert ! motioncells ! videoconvert ! xvimagesink
* ]|
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#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 <jobbagy dot robert at gmail dot com>, Nicola Murino <nicola dot murino at gmail.com>");
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);
}