1 /*****************************************************************************
2 * motiondetect.c : Second version of a motion detection plugin.
3 *****************************************************************************
4 * Copyright (C) 2000-2008 VLC authors and VideoLAN
7 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
35 #include <vlc_filter.h>
36 #include <vlc_picture.h>
37 #include "filter_picture.h"
39 /*****************************************************************************
41 *****************************************************************************/
42 static int Create ( vlc_object_t
* );
43 static void Destroy ( vlc_object_t
* );
45 #define FILTER_PREFIX "motiondetect-"
48 set_description( N_("Motion detect video filter") )
49 set_shortname( N_( "Motion Detect" ))
50 set_category( CAT_VIDEO
)
51 set_subcategory( SUBCAT_VIDEO_VFILTER
)
52 set_capability( "video filter", 0 )
54 add_shortcut( "motion" )
55 set_callbacks( Create
, Destroy
)
59 /*****************************************************************************
61 *****************************************************************************/
62 static picture_t
*Filter( filter_t
*, picture_t
* );
63 static void GaussianConvolution( uint32_t *, uint32_t *, int, int, int );
64 static int FindShapes( uint32_t *, uint32_t *, int, int, int,
65 int *, int *, int *, int *, int *);
66 static void Draw( filter_t
*p_filter
, uint8_t *p_pix
, int i_pix_pitch
, int i_pix_size
);
67 #define NUM_COLORS (5000)
79 int colors
[NUM_COLORS
];
80 int color_x_min
[NUM_COLORS
];
81 int color_x_max
[NUM_COLORS
];
82 int color_y_min
[NUM_COLORS
];
83 int color_y_max
[NUM_COLORS
];
86 /*****************************************************************************
88 *****************************************************************************/
89 static int Create( vlc_object_t
*p_this
)
91 filter_t
*p_filter
= (filter_t
*)p_this
;
92 const video_format_t
*p_fmt
= &p_filter
->fmt_in
.video
;
96 switch( p_fmt
->i_chroma
)
103 is_yuv_planar
= false;
107 msg_Err( p_filter
, "Unsupported input chroma (%4.4s)",
108 (char*)&(p_fmt
->i_chroma
) );
111 p_filter
->pf_video_filter
= Filter
;
113 /* Allocate structure */
114 p_filter
->p_sys
= p_sys
= malloc( sizeof( filter_sys_t
) );
115 if( p_filter
->p_sys
== NULL
)
118 p_sys
->is_yuv_planar
= is_yuv_planar
;
119 p_sys
->b_old
= false;
120 p_sys
->p_old
= picture_NewFromFormat( p_fmt
);
121 p_sys
->p_buf
= calloc( p_fmt
->i_width
* p_fmt
->i_height
, sizeof(*p_sys
->p_buf
) );
122 p_sys
->p_buf2
= calloc( p_fmt
->i_width
* p_fmt
->i_height
, sizeof(*p_sys
->p_buf
) );
124 if( !p_sys
->p_old
|| !p_sys
->p_buf
|| !p_sys
->p_buf2
)
126 free( p_sys
->p_buf2
);
127 free( p_sys
->p_buf
);
129 picture_Release( p_sys
->p_old
);
136 /*****************************************************************************
138 *****************************************************************************/
139 static void Destroy( vlc_object_t
*p_this
)
141 filter_t
*p_filter
= (filter_t
*)p_this
;
142 filter_sys_t
*p_sys
= p_filter
->p_sys
;
144 free( p_sys
->p_buf2
);
145 free( p_sys
->p_buf
);
146 picture_Release( p_sys
->p_old
);
151 /*****************************************************************************
152 * Filter YUV Planar/Packed
153 *****************************************************************************/
154 static void PreparePlanar( filter_t
*p_filter
, picture_t
*p_inpic
)
156 filter_sys_t
*p_sys
= p_filter
->p_sys
;
157 const video_format_t
*p_fmt
= &p_filter
->fmt_in
.video
;
159 uint8_t *p_oldpix
= p_sys
->p_old
->p
[Y_PLANE
].p_pixels
;
160 const int i_old_pitch
= p_sys
->p_old
->p
[Y_PLANE
].i_pitch
;
162 const uint8_t *p_inpix
= p_inpic
->p
[Y_PLANE
].p_pixels
;
163 const int i_src_pitch
= p_inpic
->p
[Y_PLANE
].i_pitch
;
168 for( unsigned y
= 0; y
< p_fmt
->i_height
; y
++ )
170 for( unsigned x
= 0; x
< p_fmt
->i_width
; x
++ )
171 p_sys
->p_buf2
[y
*p_fmt
->i_width
+x
] = abs( p_inpix
[y
*i_src_pitch
+x
] - p_oldpix
[y
*i_old_pitch
+x
] );
176 switch( p_inpic
->format
.i_chroma
)
192 msg_Warn( p_filter
, "Not taking chroma into account" );
196 const uint8_t *p_inpix_u
= p_inpic
->p
[U_PLANE
].p_pixels
;
197 const uint8_t *p_inpix_v
= p_inpic
->p
[V_PLANE
].p_pixels
;
198 const int i_src_pitch_u
= p_inpic
->p
[U_PLANE
].i_pitch
;
199 const int i_src_pitch_v
= p_inpic
->p
[V_PLANE
].i_pitch
;
201 const uint8_t *p_oldpix_u
= p_sys
->p_old
->p
[U_PLANE
].p_pixels
;
202 const uint8_t *p_oldpix_v
= p_sys
->p_old
->p
[V_PLANE
].p_pixels
;
203 const int i_old_pitch_u
= p_sys
->p_old
->p
[U_PLANE
].i_pitch
;
204 const int i_old_pitch_v
= p_sys
->p_old
->p
[V_PLANE
].i_pitch
;
206 for( unsigned y
= 0; y
< p_fmt
->i_height
/i_chroma_dy
; y
++ )
208 for( unsigned x
= 0; x
< p_fmt
->i_width
/i_chroma_dx
; x
++ )
210 const int d
= abs( p_inpix_u
[y
*i_src_pitch_u
+x
] - p_oldpix_u
[y
*i_old_pitch_u
+x
] ) +
211 abs( p_inpix_v
[y
*i_src_pitch_v
+x
] - p_oldpix_v
[y
*i_old_pitch_v
+x
] );
213 for( int j
= 0; j
< i_chroma_dy
; j
++ )
215 for( int i
= 0; i
< i_chroma_dx
; i
++ )
216 p_sys
->p_buf2
[i_chroma_dy
*p_fmt
->i_width
*j
+ i_chroma_dx
*i
] = d
;
222 static int PreparePacked( filter_t
*p_filter
, picture_t
*p_inpic
, int *pi_pix_offset
)
224 filter_sys_t
*p_sys
= p_filter
->p_sys
;
225 const video_format_t
*p_fmt
= &p_filter
->fmt_in
.video
;
227 int i_y_offset
, i_u_offset
, i_v_offset
;
228 if( GetPackedYuvOffsets( p_fmt
->i_chroma
,
229 &i_y_offset
, &i_u_offset
, &i_v_offset
) )
231 msg_Warn( p_filter
, "Unsupported input chroma (%4.4s)",
232 (char*)&p_fmt
->i_chroma
);
235 *pi_pix_offset
= i_y_offset
;
237 /* Substract all planes at once */
238 uint8_t *p_oldpix
= p_sys
->p_old
->p
[Y_PLANE
].p_pixels
;
239 const int i_old_pitch
= p_sys
->p_old
->p
[Y_PLANE
].i_pitch
;
241 const uint8_t *p_inpix
= p_inpic
->p
[Y_PLANE
].p_pixels
;
242 const int i_src_pitch
= p_inpic
->p
[Y_PLANE
].i_pitch
;
244 for( unsigned y
= 0; y
< p_fmt
->i_height
; y
++ )
246 for( unsigned x
= 0; x
< p_fmt
->i_width
; x
+=2 )
249 d
= abs( p_inpix
[y
*i_src_pitch
+2*x
+i_u_offset
] - p_oldpix
[y
*i_old_pitch
+2*x
+i_u_offset
] ) +
250 abs( p_inpix
[y
*i_src_pitch
+2*x
+i_v_offset
] - p_oldpix
[y
*i_old_pitch
+2*x
+i_v_offset
] );
252 for( int i
= 0; i
< 2; i
++ )
253 p_sys
->p_buf2
[y
*p_fmt
->i_width
+x
+i
] =
254 abs( p_inpix
[y
*i_src_pitch
+2*(x
+i
)+i_y_offset
] - p_oldpix
[y
*i_old_pitch
+2*(x
+i
)+i_y_offset
] ) + d
;
260 static picture_t
*Filter( filter_t
*p_filter
, picture_t
*p_inpic
)
262 filter_sys_t
*p_sys
= p_filter
->p_sys
;
267 picture_t
*p_outpic
= filter_NewPicture( p_filter
);
270 picture_Release( p_inpic
);
273 picture_Copy( p_outpic
, p_inpic
);
277 picture_Copy( p_sys
->p_old
, p_inpic
);
284 if( p_sys
->is_yuv_planar
)
286 PreparePlanar( p_filter
, p_inpic
);
292 if( PreparePacked( p_filter
, p_inpic
, &i_pix_offset
) )
298 * Get the areas where movement was detected
300 const video_format_t
*p_fmt
= &p_filter
->fmt_in
.video
;
301 p_sys
->i_colors
= FindShapes( p_sys
->p_buf2
, p_sys
->p_buf
, p_fmt
->i_width
, p_fmt
->i_width
, p_fmt
->i_height
,
302 p_sys
->colors
, p_sys
->color_x_min
, p_sys
->color_x_max
, p_sys
->color_y_min
, p_sys
->color_y_max
);
305 * Count final number of shapes
306 * Draw rectangles (there can be more than 1 moving shape in 1 rectangle)
308 Draw( p_filter
, &p_outpic
->p
[Y_PLANE
].p_pixels
[i_pix_offset
], p_outpic
->p
[Y_PLANE
].i_pitch
, i_pix_size
);
311 * We're done. Lets keep a copy of the picture
312 * TODO we may just picture_Release with a latency of 1 if the filters/vout
313 * handle it correctly */
314 picture_Copy( p_sys
->p_old
, p_inpic
);
317 picture_Release( p_inpic
);
322 /*****************************************************************************
323 * Gaussian Convolution
324 *****************************************************************************
325 * Gaussian convolution ( sigma == 1.4 )
327 * | 2 4 5 4 2 | | 2 4 4 4 2 |
328 * | 4 9 12 9 4 | | 4 8 12 8 4 |
329 * | 5 12 15 12 5 | ~ | 4 12 16 12 4 |
330 * | 4 9 12 9 4 | | 4 8 12 8 4 |
331 * | 2 4 5 4 2 | | 2 4 4 4 2 |
332 *****************************************************************************/
333 static void GaussianConvolution( uint32_t *p_inpix
, uint32_t *p_smooth
,
334 int i_src_pitch
, int i_num_lines
,
337 /* A bit overkill but ... simpler */
338 memset( p_smooth
, 0, sizeof(*p_smooth
) * i_src_pitch
* i_num_lines
);
340 for( int y
= 2; y
< i_num_lines
- 2; y
++ )
342 for( int x
= 2; x
< i_src_visible
- 2; x
++ )
344 p_smooth
[y
*i_src_visible
+x
] = (uint32_t)(
346 ( p_inpix
[(y
-2)*i_src_pitch
+x
-2] )
347 + ((p_inpix
[(y
-2)*i_src_pitch
+x
-1]
348 + p_inpix
[(y
-2)*i_src_pitch
+x
]
349 + p_inpix
[(y
-2)*i_src_pitch
+x
+1])<<1 )
350 + ( p_inpix
[(y
-2)*i_src_pitch
+x
+2] )
352 + ((p_inpix
[(y
-1)*i_src_pitch
+x
-2]
353 + ( p_inpix
[(y
-1)*i_src_pitch
+x
-1]<<1 )
354 + ( p_inpix
[(y
-1)*i_src_pitch
+x
]*3 )
355 + ( p_inpix
[(y
-1)*i_src_pitch
+x
+1]<<1 )
356 + p_inpix
[(y
-1)*i_src_pitch
+x
+2]
358 + p_inpix
[y
*i_src_pitch
+x
-2]
359 + ( p_inpix
[y
*i_src_pitch
+x
-1]*3 )
360 + ( p_inpix
[y
*i_src_pitch
+x
]<<2 )
361 + ( p_inpix
[y
*i_src_pitch
+x
+1]*3 )
362 + p_inpix
[y
*i_src_pitch
+x
+2]
364 + p_inpix
[(y
+1)*i_src_pitch
+x
-2]
365 + ( p_inpix
[(y
+1)*i_src_pitch
+x
-1]<<1 )
366 + ( p_inpix
[(y
+1)*i_src_pitch
+x
]*3 )
367 + ( p_inpix
[(y
+1)*i_src_pitch
+x
+1]<<1 )
368 + p_inpix
[(y
+1)*i_src_pitch
+x
+2] )<<1 )
370 + ( p_inpix
[(y
+2)*i_src_pitch
+x
-2] )
371 + ((p_inpix
[(y
+2)*i_src_pitch
+x
-1]
372 + p_inpix
[(y
+2)*i_src_pitch
+x
]
373 + p_inpix
[(y
+2)*i_src_pitch
+x
+1])<<1 )
374 + ( p_inpix
[(y
+2)*i_src_pitch
+x
+2] )
380 /*****************************************************************************
382 *****************************************************************************/
383 static int FindShapes( uint32_t *p_diff
, uint32_t *p_smooth
,
384 int i_pitch
, int i_visible
, int i_lines
,
386 int *color_x_min
, int *color_x_max
,
387 int *color_y_min
, int *color_y_max
)
392 * Apply some smoothing to remove noise
394 GaussianConvolution( p_diff
, p_smooth
, i_pitch
, i_lines
, i_visible
);
397 * Label the shapes and build the labels dependencies list
399 for( int j
= 0; j
< i_pitch
; j
++ )
402 p_smooth
[(i_lines
-1)*i_pitch
+j
] = 0;
404 for( int i
= 1; i
< i_lines
-1; i
++ )
407 p_smooth
[i
*i_pitch
] = 0;
408 for( j
= 1; j
< i_pitch
-1; j
++ )
410 if( p_smooth
[i
*i_pitch
+j
] > 15 )
412 if( p_smooth
[(i
-1)*i_pitch
+j
-1] )
414 p_smooth
[i
*i_pitch
+j
] = p_smooth
[(i
-1)*i_pitch
+j
-1];
416 else if( p_smooth
[(i
-1)*i_pitch
+j
] )
417 p_smooth
[i
*i_pitch
+j
] = p_smooth
[(i
-1)*i_pitch
+j
];
418 else if( p_smooth
[i
*i_pitch
+j
-1] )
419 p_smooth
[i
*i_pitch
+j
] = p_smooth
[i
*i_pitch
+j
-1];
422 if( last
< NUM_COLORS
)
424 p_smooth
[i
*i_pitch
+j
] = last
;
430 if( p_smooth[A] && p_smooth[A] != p_smooth[i*i_pitch+j] ) \
432 if( p_smooth[A] < p_smooth[i*i_pitch+j] ) \
433 colors[p_smooth[i*i_pitch+j]] = p_smooth[A]; \
435 colors[p_smooth[A]] = p_smooth[i*i_pitch+j]; \
437 CHECK( i
*i_pitch
+j
-1 );
438 CHECK( (i
-1)*i_pitch
+j
-1 );
439 CHECK( (i
-1)*i_pitch
+j
);
440 CHECK( (i
-1)*i_pitch
+j
+1 );
445 p_smooth
[i
*i_pitch
+j
] = 0;
448 p_smooth
[i
*i_pitch
+j
] = 0;
452 * Initialise empty rectangle list
454 for( int i
= 1; i
< last
; i
++ )
463 * Compute rectangle coordinates
465 for( int i
= 0; i
< i_pitch
* i_lines
; i
++ )
469 while( colors
[p_smooth
[i
]] != (int)p_smooth
[i
] )
470 p_smooth
[i
] = colors
[p_smooth
[i
]];
471 if( color_x_min
[p_smooth
[i
]] == -1 )
473 color_x_min
[p_smooth
[i
]] =
474 color_x_max
[p_smooth
[i
]] = i
% i_pitch
;
475 color_y_min
[p_smooth
[i
]] =
476 color_y_max
[p_smooth
[i
]] = i
/ i_pitch
;
480 int x
= i
% i_pitch
, y
= i
/ i_pitch
;
481 if( x
< color_x_min
[p_smooth
[i
]] )
482 color_x_min
[p_smooth
[i
]] = x
;
483 if( x
> color_x_max
[p_smooth
[i
]] )
484 color_x_max
[p_smooth
[i
]] = x
;
485 if( y
< color_y_min
[p_smooth
[i
]] )
486 color_y_min
[p_smooth
[i
]] = y
;
487 if( y
> color_y_max
[p_smooth
[i
]] )
488 color_y_max
[p_smooth
[i
]] = y
;
494 * Merge overlaping rectangles
496 for( int i
= 1; i
< last
; i
++ )
498 if( colors
[i
] != i
) continue;
499 if( color_x_min
[i
] == -1 ) continue;
500 for( int j
= i
+1; j
< last
; j
++ )
502 if( colors
[j
] != j
) continue;
503 if( color_x_min
[j
] == -1 ) continue;
504 if( __MAX( color_x_min
[i
], color_x_min
[j
] ) < __MIN( color_x_max
[i
], color_x_max
[j
] ) &&
505 __MAX( color_y_min
[i
], color_y_min
[j
] ) < __MIN( color_y_max
[i
], color_y_max
[j
] ) )
507 color_x_min
[i
] = __MIN( color_x_min
[i
], color_x_min
[j
] );
508 color_x_max
[i
] = __MAX( color_x_max
[i
], color_x_max
[j
] );
509 color_y_min
[i
] = __MIN( color_y_min
[i
], color_y_min
[j
] );
510 color_y_max
[i
] = __MAX( color_y_max
[i
], color_y_max
[j
] );
520 static void Draw( filter_t
*p_filter
, uint8_t *p_pix
, int i_pix_pitch
, int i_pix_size
)
522 filter_sys_t
*p_sys
= p_filter
->p_sys
;
526 for( int i
= 1; i
< p_sys
->i_colors
; i
++ )
530 if( p_sys
->colors
[i
] != i
)
533 const int color_x_min
= p_sys
->color_x_min
[i
];
534 const int color_x_max
= p_sys
->color_x_max
[i
];
535 const int color_y_min
= p_sys
->color_y_min
[i
];
536 const int color_y_max
= p_sys
->color_y_max
[i
];
538 if( color_x_min
== -1 )
540 if( ( color_y_max
- color_y_min
) * ( color_x_max
- color_x_min
) < 16 )
546 for( x
= color_x_min
; x
<= color_x_max
; x
++ )
547 p_pix
[y
*i_pix_pitch
+x
*i_pix_size
] = 0xff;
550 for( x
= color_x_min
; x
<= color_x_max
; x
++ )
551 p_pix
[y
*i_pix_pitch
+x
*i_pix_size
] = 0xff;
554 for( y
= color_y_min
; y
<= color_y_max
; y
++ )
555 p_pix
[y
*i_pix_pitch
+x
*i_pix_size
] = 0xff;
558 for( y
= color_y_min
; y
<= color_y_max
; y
++ )
559 p_pix
[y
*i_pix_pitch
+x
*i_pix_size
] = 0xff;
561 msg_Dbg( p_filter
, "Counted %d moving shapes.", j
);