motiondetect.c 18.4 KB
Newer Older
1
/*****************************************************************************
Pere Orga's avatar
Pere Orga committed
2
 * motiondetect.c : Second version of a motion detection plugin.
3
 *****************************************************************************
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
4
 * Copyright (C) 2000-2008 VLC authors and VideoLAN
5
 * $Id$
6
 *
7
 * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
9 10 11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
12 13 14 15
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
16 17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
Jean-Baptiste Kempf's avatar
LGPL  
Jean-Baptiste Kempf committed
19 20 21
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25 26 27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

28 29 30 31
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

32
#include <vlc_common.h>
33
#include <vlc_plugin.h>
34
#include <vlc_sout.h>
Rémi Duraffort's avatar
Rémi Duraffort committed
35
#include <vlc_filter.h>
36
#include <vlc_picture.h>
37
#include "filter_picture.h"
38 39

/*****************************************************************************
40
 * Module descriptor
41 42 43 44
 *****************************************************************************/
static int  Create    ( vlc_object_t * );
static void Destroy   ( vlc_object_t * );

45
#define FILTER_PREFIX "motiondetect-"
46

47 48 49 50 51
vlc_module_begin ()
    set_description( N_("Motion detect video filter") )
    set_shortname( N_( "Motion Detect" ))
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_VFILTER )
52
    set_capability( "video filter", 0 )
53

54 55 56
    add_shortcut( "motion" )
    set_callbacks( Create, Destroy )
vlc_module_end ()
57

58 59 60 61 62 63 64 65 66 67 68

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static picture_t *Filter( filter_t *, picture_t * );
static void GaussianConvolution( uint32_t *, uint32_t *, int, int, int );
static int FindShapes( uint32_t *, uint32_t *, int, int, int,
                       int *, int *, int *, int *, int *);
static void Draw( filter_t *p_filter, uint8_t *p_pix, int i_pix_pitch, int i_pix_size );
#define NUM_COLORS (5000)

69
struct filter_sys_t
70
{
71
    bool is_yuv_planar;
72 73
    bool b_old;
    picture_t *p_old;
74 75
    uint32_t *p_buf;
    uint32_t *p_buf2;
76 77 78 79 80 81 82 83

    /* */
    int i_colors;
    int colors[NUM_COLORS];
    int color_x_min[NUM_COLORS];
    int color_x_max[NUM_COLORS];
    int color_y_min[NUM_COLORS];
    int color_y_max[NUM_COLORS];
84
};
85 86

/*****************************************************************************
87
 * Create
88 89 90
 *****************************************************************************/
static int Create( vlc_object_t *p_this )
{
91
    filter_t *p_filter = (filter_t *)p_this;
92 93
    const video_format_t *p_fmt = &p_filter->fmt_in.video;
    filter_sys_t *p_sys;
94
    bool is_yuv_planar;
95

96
    switch( p_fmt->i_chroma )
97 98
    {
        CASE_PLANAR_YUV
99
            is_yuv_planar = true;
100 101 102
            break;

        CASE_PACKED_YUV_422
103
            is_yuv_planar = false;
104 105
            break;

106
        default:
107
            msg_Err( p_filter, "Unsupported input chroma (%4.4s)",
108
                     (char*)&(p_fmt->i_chroma) );
109 110
            return VLC_EGENERIC;
    }
111
    p_filter->pf_video_filter = Filter;
112

113
    /* Allocate structure */
114
    p_filter->p_sys = p_sys = malloc( sizeof( filter_sys_t ) );
115
    if( p_filter->p_sys == NULL )
116 117
        return VLC_ENOMEM;

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 122
    p_sys->p_buf  = calloc( p_fmt->i_width * p_fmt->i_height, sizeof(*p_sys->p_buf) );
    p_sys->p_buf2 = calloc( p_fmt->i_width * p_fmt->i_height, sizeof(*p_sys->p_buf) );
123

124 125 126 127 128 129 130 131
    if( !p_sys->p_old || !p_sys->p_buf || !p_sys->p_buf2 )
    {
        free( p_sys->p_buf2 );
        free( p_sys->p_buf );
        if( p_sys->p_old )
            picture_Release( p_sys->p_old );
        return VLC_ENOMEM;
    }
132 133 134 135 136

    return VLC_SUCCESS;
}

/*****************************************************************************
137
 * Destroy
138
 *****************************************************************************/
139
static void Destroy( vlc_object_t *p_this )
140
{
141
    filter_t *p_filter = (filter_t *)p_this;
142
    filter_sys_t *p_sys = p_filter->p_sys;
143

144 145 146 147
    free( p_sys->p_buf2 );
    free( p_sys->p_buf );
    picture_Release( p_sys->p_old );
    free( p_sys );
148
}
149

150

151
/*****************************************************************************
152
 * Filter YUV Planar/Packed
153
 *****************************************************************************/
154
static void PreparePlanar( filter_t *p_filter, picture_t *p_inpic )
155 156
{
    filter_sys_t *p_sys = p_filter->p_sys;
157 158 159 160
    const video_format_t *p_fmt = &p_filter->fmt_in.video;

    uint8_t *p_oldpix   = p_sys->p_old->p[Y_PLANE].p_pixels;
    const int i_old_pitch = p_sys->p_old->p[Y_PLANE].i_pitch;
161

162 163 164
    const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
    const int i_src_pitch = p_inpic->p[Y_PLANE].i_pitch;

165 166 167
    /**
     * Substract Y planes
     */
168
    for( unsigned y = 0; y < p_fmt->i_height; y++ )
169
    {
170 171
        for( unsigned x = 0; x < p_fmt->i_width; x++ )
            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] );
172
    }
173 174 175

    int i_chroma_dx;
    int i_chroma_dy;
176 177
    switch( p_inpic->format.i_chroma )
    {
178 179 180
        case VLC_CODEC_I420:
        case VLC_CODEC_J420:
        case VLC_CODEC_YV12:
181 182
            i_chroma_dx = 2;
            i_chroma_dy = 2;
183
            break;
184

185 186
        case VLC_CODEC_I422:
        case VLC_CODEC_J422:
187 188
            i_chroma_dx = 2;
            i_chroma_dy = 1;
189
            break;
190

191 192
        default:
            msg_Warn( p_filter, "Not taking chroma into account" );
193
            return;
194
    }
195

196 197 198 199
    const uint8_t *p_inpix_u = p_inpic->p[U_PLANE].p_pixels;
    const uint8_t *p_inpix_v = p_inpic->p[V_PLANE].p_pixels;
    const int i_src_pitch_u = p_inpic->p[U_PLANE].i_pitch;
    const int i_src_pitch_v = p_inpic->p[V_PLANE].i_pitch;
200

201 202 203 204
    const uint8_t *p_oldpix_u = p_sys->p_old->p[U_PLANE].p_pixels;
    const uint8_t *p_oldpix_v = p_sys->p_old->p[V_PLANE].p_pixels;
    const int i_old_pitch_u = p_sys->p_old->p[U_PLANE].i_pitch;
    const int i_old_pitch_v = p_sys->p_old->p[V_PLANE].i_pitch;
205

206 207 208
    for( unsigned y = 0; y < p_fmt->i_height/i_chroma_dy; y++ )
    {
        for( unsigned x = 0; x < p_fmt->i_width/i_chroma_dx; x ++ )
209
        {
210 211 212
            const int d = abs( p_inpix_u[y*i_src_pitch_u+x] - p_oldpix_u[y*i_old_pitch_u+x] ) +
                          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++ )
214
            {
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;
217
            }
218 219
        }
    }
220 221
}

222
static int PreparePacked( filter_t *p_filter, picture_t *p_inpic, int *pi_pix_offset )
223 224
{
    filter_sys_t *p_sys = p_filter->p_sys;
225
    const video_format_t *p_fmt = &p_filter->fmt_in.video;
226

227 228 229 230 231 232 233 234 235 236 237
    int i_y_offset, i_u_offset, i_v_offset;
    if( GetPackedYuvOffsets( p_fmt->i_chroma,
                             &i_y_offset, &i_u_offset, &i_v_offset ) )
    {
        msg_Warn( p_filter, "Unsupported input chroma (%4.4s)",
                  (char*)&p_fmt->i_chroma );
        return VLC_EGENERIC;
    }
    *pi_pix_offset = i_y_offset;

    /* Substract all planes at once */
238 239
    uint8_t *p_oldpix   = p_sys->p_old->p[Y_PLANE].p_pixels;
    const int i_old_pitch = p_sys->p_old->p[Y_PLANE].i_pitch;
240

241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
    const uint8_t *p_inpix = p_inpic->p[Y_PLANE].p_pixels;
    const int i_src_pitch = p_inpic->p[Y_PLANE].i_pitch;

    for( unsigned y = 0; y < p_fmt->i_height; y++ )
    {
        for( unsigned x = 0; x < p_fmt->i_width; x+=2 )
        {
            int d;
            d = abs( p_inpix[y*i_src_pitch+2*x+i_u_offset] - p_oldpix[y*i_old_pitch+2*x+i_u_offset] ) +
                abs( p_inpix[y*i_src_pitch+2*x+i_v_offset] - p_oldpix[y*i_old_pitch+2*x+i_v_offset] );

            for( int i = 0; i < 2; i++ )
                p_sys->p_buf2[y*p_fmt->i_width+x+i] =
                    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;
        }
    }
    return VLC_SUCCESS;
}

static picture_t *Filter( filter_t *p_filter, picture_t *p_inpic )
{
    filter_sys_t *p_sys = p_filter->p_sys;
263

264 265 266
    if( !p_inpic )
        return NULL;

267
    picture_t *p_outpic = filter_NewPicture( p_filter );
268
    if( !p_outpic )
269
    {
270 271
        picture_Release( p_inpic );
        return NULL;
272
    }
273
    picture_Copy( p_outpic, p_inpic );
274

275
    if( !p_sys->b_old )
276
    {
277 278
        picture_Copy( p_sys->p_old, p_inpic );
        p_sys->b_old = true;
279
        goto exit;
280 281
    }

282 283 284
    int i_pix_offset;
    int i_pix_size;
    if( p_sys->is_yuv_planar )
285
    {
286 287 288
        PreparePlanar( p_filter, p_inpic );
        i_pix_offset = 0;
        i_pix_size = 1;
289
    }
290
    else
291
    {
292 293 294
        if( PreparePacked( p_filter, p_inpic, &i_pix_offset ) )
            goto exit;
        i_pix_size = 2;
295
    }
296 297 298 299

    /**
     * Get the areas where movement was detected
     */
300 301
    const video_format_t *p_fmt = &p_filter->fmt_in.video;
    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 );
303

304 305 306 307
    /**
     * Count final number of shapes
     * 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 );
309 310

    /**
311
     * We're done. Lets keep a copy of the picture
312 313 314
     * TODO we may just picture_Release with a latency of 1 if the filters/vout
     * handle it correctly */
    picture_Copy( p_sys->p_old, p_inpic );
315

316
exit:
317 318
    picture_Release( p_inpic );
    return p_outpic;
319 320 321 322
}


/*****************************************************************************
323 324 325 326 327 328 329 330 331
 * Gaussian Convolution
 *****************************************************************************
 *    Gaussian convolution ( sigma == 1.4 )
 *
 *    |  2  4  5  4  2  |   |  2  4  4  4  2 |
 *    |  4  9 12  9  4  |   |  4  8 12  8  4 |
 *    |  5 12 15 12  5  | ~ |  4 12 16 12  4 |
 *    |  4  9 12  9  4  |   |  4  8 12  8  4 |
 *    |  2  4  5  4  2  |   |  2  4  4  4  2 |
332
 *****************************************************************************/
333 334 335
static void GaussianConvolution( uint32_t *p_inpix, uint32_t *p_smooth,
                                 int i_src_pitch, int i_num_lines,
                                 int i_src_visible )
336
{
337 338 339
    /* A bit overkill but ... simpler */
    memset( p_smooth, 0, sizeof(*p_smooth) * i_src_pitch * i_num_lines );

340
    for( int y = 2; y < i_num_lines - 2; y++ )
341
    {
342
        for( int x = 2; x < i_src_visible - 2; x++ )
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
        {
            p_smooth[y*i_src_visible+x] = (uint32_t)(
              /* 2 rows up */
                ( p_inpix[(y-2)*i_src_pitch+x-2] )
              + ((p_inpix[(y-2)*i_src_pitch+x-1]
              +   p_inpix[(y-2)*i_src_pitch+x]
              +   p_inpix[(y-2)*i_src_pitch+x+1])<<1 )
              + ( p_inpix[(y-2)*i_src_pitch+x+2] )
              /* 1 row up */
              + ((p_inpix[(y-1)*i_src_pitch+x-2]
              + ( p_inpix[(y-1)*i_src_pitch+x-1]<<1 )
              + ( p_inpix[(y-1)*i_src_pitch+x]*3 )
              + ( p_inpix[(y-1)*i_src_pitch+x+1]<<1 )
              +   p_inpix[(y-1)*i_src_pitch+x+2]
              /* */
              +   p_inpix[y*i_src_pitch+x-2]
              + ( p_inpix[y*i_src_pitch+x-1]*3 )
              + ( p_inpix[y*i_src_pitch+x]<<2 )
              + ( p_inpix[y*i_src_pitch+x+1]*3 )
              +   p_inpix[y*i_src_pitch+x+2]
              /* 1 row down */
              +   p_inpix[(y+1)*i_src_pitch+x-2]
              + ( p_inpix[(y+1)*i_src_pitch+x-1]<<1 )
              + ( p_inpix[(y+1)*i_src_pitch+x]*3 )
              + ( p_inpix[(y+1)*i_src_pitch+x+1]<<1 )
              +   p_inpix[(y+1)*i_src_pitch+x+2] )<<1 )
              /* 2 rows down */
              + ( p_inpix[(y+2)*i_src_pitch+x-2] )
              + ((p_inpix[(y+2)*i_src_pitch+x-1]
              +   p_inpix[(y+2)*i_src_pitch+x]
              +   p_inpix[(y+2)*i_src_pitch+x+1])<<1 )
              + ( p_inpix[(y+2)*i_src_pitch+x+2] )
              ) >> 6 /* 115 */;
        }
    }
378
}
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393

/*****************************************************************************
 *
 *****************************************************************************/
static int FindShapes( uint32_t *p_diff, uint32_t *p_smooth,
                       int i_pitch, int i_visible, int i_lines,
                       int *colors,
                       int *color_x_min, int *color_x_max,
                       int *color_y_min, int *color_y_max )
{
    int last = 1;

    /**
     * Apply some smoothing to remove noise
     */
394
    GaussianConvolution( p_diff, p_smooth, i_pitch, i_lines, i_visible );
395 396 397 398

    /**
     * Label the shapes and build the labels dependencies list
     */
399
    for( int j = 0; j < i_pitch; j++ )
400 401 402 403
    {
        p_smooth[j] = 0;
        p_smooth[(i_lines-1)*i_pitch+j] = 0;
    }
404
    for( int i = 1; i < i_lines-1; i++ )
405
    {
406
        int j;
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
        p_smooth[i*i_pitch] = 0;
        for( j = 1; j < i_pitch-1; j++ )
        {
            if( p_smooth[i*i_pitch+j] > 15 )
            {
                if( p_smooth[(i-1)*i_pitch+j-1] )
                {
                    p_smooth[i*i_pitch+j] = p_smooth[(i-1)*i_pitch+j-1];
                }
                else if( p_smooth[(i-1)*i_pitch+j] )
                    p_smooth[i*i_pitch+j] = p_smooth[(i-1)*i_pitch+j];
                else if( p_smooth[i*i_pitch+j-1] )
                    p_smooth[i*i_pitch+j] = p_smooth[i*i_pitch+j-1];
                else
                {
422
                    if( last < NUM_COLORS )
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
                    {
                        p_smooth[i*i_pitch+j] = last;
                        colors[last] = last;
                        last++;
                    }
                }
                #define CHECK( A ) \
                if( p_smooth[A] && p_smooth[A] != p_smooth[i*i_pitch+j] ) \
                { \
                    if( p_smooth[A] < p_smooth[i*i_pitch+j] ) \
                        colors[p_smooth[i*i_pitch+j]] = p_smooth[A]; \
                    else \
                        colors[p_smooth[A]] = p_smooth[i*i_pitch+j]; \
                }
                CHECK( i*i_pitch+j-1 );
                CHECK( (i-1)*i_pitch+j-1 );
                CHECK( (i-1)*i_pitch+j );
                CHECK( (i-1)*i_pitch+j+1 );
                #undef CHECK
            }
            else
            {
                p_smooth[i*i_pitch+j] = 0;
            }
        }
        p_smooth[i*i_pitch+j] = 0;
    }

    /**
     * Initialise empty rectangle list
     */
454
    for( int i = 1; i < last; i++ )
455 456 457 458 459 460 461 462 463 464
    {
        color_x_min[i] = -1;
        color_x_max[i] = -1;
        color_y_min[i] = -1;
        color_y_max[i] = -1;
    }

    /**
     * Compute rectangle coordinates
     */
465
    for( int i = 0; i < i_pitch * i_lines; i++ )
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
    {
        if( p_smooth[i] )
        {
            while( colors[p_smooth[i]] != (int)p_smooth[i] )
                p_smooth[i] = colors[p_smooth[i]];
            if( color_x_min[p_smooth[i]] == -1 )
            {
                color_x_min[p_smooth[i]] =
                color_x_max[p_smooth[i]] = i % i_pitch;
                color_y_min[p_smooth[i]] =
                color_y_max[p_smooth[i]] = i / i_pitch;
            }
            else
            {
                int x = i % i_pitch, y = i / i_pitch;
                if( x < color_x_min[p_smooth[i]] )
                    color_x_min[p_smooth[i]] = x;
                if( x > color_x_max[p_smooth[i]] )
                    color_x_max[p_smooth[i]] = x;
                if( y < color_y_min[p_smooth[i]] )
                    color_y_min[p_smooth[i]] = y;
                if( y > color_y_max[p_smooth[i]] )
                    color_y_max[p_smooth[i]] = y;
            }
        }
    }

    /**
     * Merge overlaping rectangles
     */
496
    for( int i = 1; i < last; i++ )
497 498 499
    {
        if( colors[i] != i ) continue;
        if( color_x_min[i] == -1 ) continue;
500
        for( int j = i+1; j < last; j++ )
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
        {
            if( colors[j] != j ) continue;
            if( color_x_min[j] == -1 ) continue;
            if( __MAX( color_x_min[i], color_x_min[j] ) < __MIN( color_x_max[i], color_x_max[j] ) &&
                __MAX( color_y_min[i], color_y_min[j] ) < __MIN( color_y_max[i], color_y_max[j] ) )
            {
                color_x_min[i] = __MIN( color_x_min[i], color_x_min[j] );
                color_x_max[i] = __MAX( color_x_max[i], color_x_max[j] );
                color_y_min[i] = __MIN( color_y_min[i], color_y_min[j] );
                color_y_max[i] = __MAX( color_y_max[i], color_y_max[j] );
                color_x_min[j] = -1;
                j = 0;
            }
        }
    }

    return last;
}
519 520 521 522 523

static void Draw( filter_t *p_filter, uint8_t *p_pix, int i_pix_pitch, int i_pix_size )
{
    filter_sys_t *p_sys = p_filter->p_sys;

524 525 526
    int j = 0;

    for( int i = 1; i < p_sys->i_colors; i++ )
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
    {
        int x, y;

        if( p_sys->colors[i] != i )
            continue;

        const int color_x_min = p_sys->color_x_min[i];
        const int color_x_max = p_sys->color_x_max[i];
        const int color_y_min = p_sys->color_y_min[i];
        const int color_y_max = p_sys->color_y_max[i];

        if( color_x_min == -1 )
            continue;
        if( ( color_y_max - color_y_min ) * ( color_x_max - color_x_min ) < 16 )
            continue;

        j++;

        y = color_y_min;
        for( x = color_x_min; x <= color_x_max; x++ )
            p_pix[y*i_pix_pitch+x*i_pix_size] = 0xff;

        y = color_y_max;
        for( x = color_x_min; x <= color_x_max; x++ )
            p_pix[y*i_pix_pitch+x*i_pix_size] = 0xff;

        x = color_x_min;
        for( y = color_y_min; y <= color_y_max; y++ )
            p_pix[y*i_pix_pitch+x*i_pix_size] = 0xff;

        x = color_x_max;
        for( y = color_y_min; y <= color_y_max; y++ )
            p_pix[y*i_pix_pitch+x*i_pix_size] = 0xff;
    }
    msg_Dbg( p_filter, "Counted %d moving shapes.", j );
}