/*
* Motion detecting
*
* File: motion_detect.c
*
* Author: Hugui
* E-Mail: y_y_z_l @ 163.com
* Date: 2012.7.6
*
* Changing Log:
*
*/
#include <string.h>
#include <malloc.h>
#include "motion_detect.h"
extern int decode_y(unsigned char * jpeg, int jpeglen, int * y);
/*
* The last changing direction of Y.
* 00: grayscale no changed.
* 01: grayscale increased.
* 10: grayscale decreased.
*
* last_y_direction[0][]
* byte0 byte1 byte2
* 01 00 10 01 10 10 00 01 10 10 00 01 ...
* |
* |
* V
* Start from here,This is the first block.
* last_y_direction[1][]
* ...
*
*/
#define BLK_Y_NO_CHANGE 0
#define BLK_Y_INCREASED 1
#define BLK_Y_DECREASED 2
unsigned char last_y_direction[ALARM_SENSITY_LEVEL_MAX][ \
(IMG_WIDTH_MAX / JPEG_BLOCK_DIM) * \
(IMG_HEIGHT_MAX / JPEG_BLOCK_DIM) / 4];
/* Last grayscale */
int last_y[(IMG_WIDTH_MAX / JPEG_BLOCK_DIM) * (IMG_HEIGHT_MAX / JPEG_BLOCK_DIM)];
int last_y_len;
/* Current graysacle, this variable is for temperary using. */
int crt_y[(IMG_WIDTH_MAX / JPEG_BLOCK_DIM) * (IMG_HEIGHT_MAX / JPEG_BLOCK_DIM)];
/*
* Alarm table.
*
* 0: no alarm.
* 1: ALARM !!!
*
* alarm_tbl[0][]
* byte0 byte1 byte2
* 01001001 10100001 10100001 ...
* |
* |
* V
* Start from here,This is the first block.
* alarm_tbl[1][]
* ...
*
*/
unsigned char alarm_tbl[ALARM_SENSITY_LEVEL_MAX][ \
(IMG_WIDTH_MAX / JPEG_BLOCK_DIM) * \
(IMG_HEIGHT_MAX / JPEG_BLOCK_DIM) / 8];
/* The current width & height of image */
unsigned short img_width = 320;
unsigned short img_height = 240;
/* State of motion detecting process */
#define MD_STATE_FRAM1 1
#define MD_STATE_FRAM2 2
#define MD_STATE_FRAM3 3
unsigned char motion_detect_state = MD_STATE_FRAM1;
/* Registered alarm zone */
PAlarmZone alarm_zone_tbl[ALARM_ZONE_MAX];
/*
* Initial or reset the motion detecting system.
*
* @return: 0 for OK
*/
int reset_motion_detect(void)
{
motion_detect_state = MD_STATE_FRAM1;
memset(last_y_direction, 0x00, sizeof(last_y_direction));
return 0;
}
/*
* Set dimention of image
* When the dimention of image has changed, you should call this function.
* Default setting: 320 * 240
*
* @width: width of image, unit by pixel
* @height: height
*
* @return: 0 for OK
*/
int set_img_dim(unsigned short width, unsigned short height)
{
if (width > IMG_WIDTH_MAX ||
height > IMG_HEIGHT_MAX )
return EC_DIM;
if (width < IMG_WIDTH_MIN ||
height < IMG_HEIGHT_MIN )
return EC_DIM;
img_width = width;
img_height = height;
reset_motion_detect();
return 0;
}
/*
* Add alarm zone.
*
* @zone: selected alarm zone, a rectangle(check header file for details).
* @id: Registered id.
* @alarm_level: use macro defined in the header file.
*
* @return: 0 for OK.
*/
int add_alarm_zone(Rect* zone, int id, unsigned char alarm_level)
{
PAlarmZone p;
int i;
/* Check the zone id, no repeated */
for (i = 0; i < ALARM_ZONE_MAX; i++) {
if (alarm_zone_tbl[i]) {
if (alarm_zone_tbl[i]->id == id)
return EC_ID;
}
}
/* Find an empty slot */
for (i = 0; i < ALARM_ZONE_MAX; i++) {
if (!alarm_zone_tbl[i])
break;
}
/* No empty slot */
if (i == ALARM_ZONE_MAX)
return EC_NO_SLOT;
/* Get memory */
p = (PAlarmZone )malloc(sizeof(AlarmZone));
if (!p) {
return EC_MEM;
}
/* Check the range of rectangle */
if (zone->left > 999) zone->left = 999;
if (zone->right > 999) zone->right = 999;
if (zone->top > 999) zone->top = 999;
if (zone->bottom > 999) zone->bottom = 999;
/* Initialize */
p->id = id;
p->alarm_level = alarm_level;
memcpy(&p->zone, zone, sizeof(Rect));
/* Insert to one empty slot */
alarm_zone_tbl[i] = p;
return 0;
}
/*
* Remove alarm zone by id.
*
* @id: id of alarm zone.
*
* @return: 0 for OK
*/
int rm_alarm_zone(int id)
{
int i;
for (i = 0; i < ALARM_ZONE_MAX; i++) {
if (alarm_zone_tbl[i]) {
if (alarm_zone_tbl[i]->id == id) {
free(alarm_zone_tbl[i]);
alarm_zone_tbl[i] = NULL;
return 0;
}
}
}
return EC_ID;
}
/*
* Remove all alarm zone .
*
* @return: 0 for OK
*/
int rm_all_alarm(void)
{
memset(alarm_zone_tbl, 0x00, sizeof(alarm_zone_tbl));
return 0;
}
/*
* Invoke this at every image frame
*
* @img: An image object pointer. Contains buffer and length.
* @alarm_info: Alarm info, this is an array.
* @alarm_count: The valid length of @alarm_info array.
*
* @return: 0 for OK. The output parameters are valid only when return 0.
*/
int motion_detect(Image* __INPUT__ img,
AlarmInfoArray* __OUTPUT__ alarm_info, int* __OUTPUT__ alarm_count)
{
int blocks;
int i;
int delta_y;
/* Decode grayscale */
blocks = decode_y(img->pbuf, img->len, crt_y);
switch (motion_detect_state) {
/* Not ready, only one frame. */
case MD_STATE_FRAM1:
last_y_len = blocks;
memcpy(last_y, crt_y, sizeof(crt_y));
motion_detect_state = MD_STATE_FRAM2;
return EC_FRAM1;
/* Not ready, only two frames. */
case MD_STATE_FRAM2:
/* Dimention of image has changed but didn't invoke set_img_dim() */
if (blocks != last_y_len) {
reset_motion_detect();
return EC_DIM_CHANG;
}
for (i = 0; i < blocks; i++) {
delta_y = crt_y[i] - last_y[i];
update_y_direction(i, delta_y);
}
last_y_len = blocks;
memcpy(last_y, crt_y, sizeof(crt_y));
motion_detect_state = MD_STATE_FRAM3;
return EC_FRAM2;
/* YES! Loops in this case */
case MD_STATE_FRAM3:
/* Dimention of image has changed but didn't invoke set_img_dim() */
if (blocks != last_y_len) {
reset_motion_detect();
return EC_DIM_CHANG;
}
for (i = 0; i < blocks; i++) {
delta_y = crt_y[i] - last_y[i];
update_alarm_tbl(i, delta_y);
}
last_y_len = blocks;
memcpy(last_y, crt_y, sizeof(crt_y));
generate_alarm(alarm_info, alarm_count);
return 0;
/* NOT expected */
default:
return EC_STATE;
}
}
/*
* Caculate the alarm level by y.
*
* @delta_y: grayscale changed.
*
* @return: Alarm level. Check the header file for details.
*/
unsigned char whichlevel(int delta_y)
{
if (delta_y < 0)
delta_y = -delta_y;
#if ALARM_SENSITY_LEVEL_MAX >= 5
if (delta_y >= ALARM_SENSITY_VAL_LV5)
return ALARM_SENSITY_LV5;
else
#endif
#if ALARM_SENSITY_LEVEL_MAX >= 4
if (delta_y >= ALARM_SENSITY_VAL_LV4)
return ALARM_SENSITY_LV4;
else
#if ALARM_SENSITY_LEVEL_MAX >= 3
#endif
if (delta_y >= ALARM_SENSITY_VAL_LV3)
return ALARM_SENSITY_LV3;
else
#if ALARM_SENSITY_LEVEL_MAX >= 2
#endif
if (delta_y >= ALARM_SENSITY_VAL_LV2)
return ALARM_SENSITY_LV2;
else
#endif
#if ALARM_SENSITY_LEVEL_MAX >= 1
if (delta_y >= ALARM_SENSITY_VAL_LV1)
return ALARM_SENSITY_LV1;
else
#endif
return 0; /* NO changing */
}
/*
* Update the changed direction of y.
* There are 4 blocks in one byte,each block own 2 bits.
*
* @block_index: which jepg block?
* @delta_y: y changed.
*/
void update_y_direction(int block_index, int delta_y)
{
int i;
unsigned char level;
level = whichlevel(delta_y);
switch (level) {
case 0:
/* NO changing. Update all of the table */
for (i = 0; i < ALARM_SENSITY_LEVEL_MAX; i++) {
SET_BYTE_BITS(last_y_direction[i][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_NO_CHANGE);
}
break;
#ifdef USING_ALARM_LV5
case ALARM_SENSITY_LV5:
SET_BYTE_BITS(last_y_direction[ALARM_SENSITY_LV5 - 1][block_index / 4],
(block_index % 4) << 1, 2, delta_y > 0 ? BLK_Y_INCREASED : BLK_Y_DECREASED);
#endif
#ifdef USING_ALARM_LV4
case ALARM_SENSITY_LV4:
SET_BYTE_BITS(last_y_direction[ALARM_SENSITY_LV4 - 1][block_index / 4],
(block_index % 4) << 1, 2, delta_y > 0 ? BLK_Y_INCREASED : BLK_Y_DECREASED);
#endif
#ifdef USING_ALARM_LV3
case ALARM_SENSITY_LV3:
SET_BYTE_BITS(last_y_direction[ALARM_SENSITY_LV3 - 1][block_index / 4],
(block_index % 4) << 1, 2, delta_y > 0 ? BLK_Y_INCREASED : BLK_Y_DECREASED);
#endif
#ifdef USING_ALARM_LV2
case ALARM_SENSITY_LV2:
SET_BYTE_BITS(last_y_direction[ALARM_SENSITY_LV2 - 1][block_index / 4],
(block_index % 4) << 1, 2, delta_y > 0 ? BLK_Y_INCREASED : BLK_Y_DECREASED);
#endif
#ifdef USING_ALARM_LV1
case ALARM_SENSITY_LV1:
SET_BYTE_BITS(last_y_direction[ALARM_SENSITY_LV1 - 1][block_index / 4],
(block_index % 4) << 1, 2, delta_y > 0 ? BLK_Y_INCREASED : BLK_Y_DECREASED);
#endif
break;
default:
; /* WARNING! */
}
}
/*
* Update the alarm table.
*
* @block_index: which jepg block?
* @delta_y: y changed.
*/
void update_alarm_tbl(int block_index, int delta_y)
{
unsigned char level;
level = whichlevel(delta_y);
switch (level) {
#ifdef USING_ALARM_LV5
case ALARM_SENSITY_LV5:
if (
/* Bright -> Dark -> Bright */
(delta_y > 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV5 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_DECREASED)) ||
/* Dark -> Bright -> Dark */
(delta_y < 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV5 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_INCREASED))
) {
alarm_tbl[ALARM_SENSITY_LV5 - 1][block_index / 8] |= 0x01 << (block_index % 8);
}
#endif
#ifdef USING_ALARM_LV4
case ALARM_SENSITY_LV4:
if (
/* Bright -> Dark -> Bright */
(delta_y > 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV4 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_DECREASED)) ||
/* Dark -> Bright -> Dark */
(delta_y < 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV4 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_INCREASED))
) {
alarm_tbl[ALARM_SENSITY_LV4 - 1][block_index / 8] |= 0x01 << (block_index % 8);
}
#endif
#ifdef USING_ALARM_LV3
case ALARM_SENSITY_LV3:
if (
/* Bright -> Dark -> Bright */
(delta_y > 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV3 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_DECREASED)) ||
/* Dark -> Bright -> Dark */
(delta_y < 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV3 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_INCREASED))
) {
alarm_tbl[ALARM_SENSITY_LV3 - 1][block_index / 8] |= 0x01 << (block_index % 8);
}
#endif
#ifdef USING_ALARM_LV2
case ALARM_SENSITY_LV2:
if (
/* Bright -> Dark -> Bright */
(delta_y > 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV2 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_DECREASED)) ||
/* Dark -> Bright -> Dark */
(delta_y < 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV2 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_INCREASED))
) {
alarm_tbl[ALARM_SENSITY_LV2 - 1][block_index / 8] |= 0x01 << (block_index % 8);
}
#endif
#ifdef USING_ALARM_LV1
case ALARM_SENSITY_LV1:
if (
/* Bright -> Dark -> Bright */
(delta_y > 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV1 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_DECREASED)) ||
/* Dark -> Bright -> Dark */
(delta_y < 0 && TEST_BYTE_BITS(
last_y_direction[ALARM_SENSITY_LV1 - 1][block_index / 4],
(block_index % 4) << 1, 2, BLK_Y_INCREASED))
) {
alarm_tbl[ALARM_SENSITY_LV1 - 1][block_index / 8] |= 0x01 << (block_index % 8);
}
#endif
break;
default:
; /* WARNING! */
}
/* Cover the old table */
update_y_direction(block_index, delta_y);
}
/*
* Scan the alarm table, when valid and the corresponding area
* is registered, return alarm info.
*
* @alarm_info: Alarm info, this is an array.
* @alarm_count: The valid length of @alarm_info array.
*/
void generate_alarm(AlarmInfoArray* __OUTPUT__ alarm_info, int* __OUTPUT__ alarm_count)
{
int i;
Rect blk_zone;
int index = 0;
/* Reset the count */
*alarm_count = 0;
for (i = 0; i < ALARM_ZONE_MAX; i++) {
if (alarm_zone_tbl[i]) {
coordinate_transfer(&(alarm_zone_tbl[i]->zone), &blk_zone);
if (is_alarm(&blk_zone, alarm_zone_tbl[i]->alarm_level)) {
memcpy(&(*alarm_info)[index], alarm_zone_tbl[i], sizeof(AlarmZone));
index++;
}
}
}
*alarm_count = index;
/* Reset alarm_tbl. This is very IMPORTANT! */
memset(alarm_tbl, 0x00, sizeof(alarm_tbl));
}
/*
* Check whether the selected block zone contains valid alarm.
*
* @blk_zone: Rectangle descripted by jpeg block coordinate system.
* @alarm_level: which alarm level do you want to check?
*
* @return: true for ALARM and false for NO alarm.
*/
bool is_alarm(Rect* blk_zone, unsigned char alarm_level)
{
int row, col;
int blk_index;
for (row = blk_zone->top; row <= blk_zone->bottom; row++) {
for (col = blk_zone->left; col <= blk_zone->right; col++) {
blk_index = img_width / JPEG_BLOCK_DIM * row + col;
if (alarm_tbl[alarm_level - 1][blk_index / 8] & (0x01 << (blk_index % 8))) {
return true;
}
}
}
return false;
}
/*
* Transfer the rectangle from image view coordinate system to jpeg block coordinate system.
*
* @alarm_zone: Selected alarm zone.
* @blk_zone: jpeg block coordinate system. See header file for details.
*/
void coordinate_transfer(Rect* alarm_zone, Rect* __OUTPUT__ blk_zone)
{
blk_zone->left = img_width / JPEG_BLOCK_DIM * alarm_zone->left / 1000;
blk_zone->right = img_width / JPEG_BLOCK_DIM * alarm_zone->right / 1000;
blk_zone->top = img_height / JPEG_BLOCK_DIM * alarm_zone->top / 1000;
blk_zone->bottom = img_height / JPEG_BLOCK_DIM * alarm_zone->bottom / 1000;
}