zoneminder/src/zm_zone.cpp

1006 lines
36 KiB
C++
Raw Normal View History

//
// ZoneMinder Zone Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (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
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU 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.
//
#define __STDC_FORMAT_MACROS 1
#include <cinttypes>
#include "zm.h"
#include "zm_db.h"
#include "zm_zone.h"
#include "zm_image.h"
#include "zm_monitor.h"
#include "zm_fifo.h"
2017-05-20 21:41:13 +08:00
void Zone::Setup(
Monitor *p_monitor,
int p_id,
const char *p_label,
ZoneType p_type,
const Polygon &p_polygon,
const Rgb p_alarm_rgb,
CheckMethod p_check_method,
int p_min_pixel_threshold,
int p_max_pixel_threshold,
int p_min_alarm_pixels,
int p_max_alarm_pixels,
const Coord &p_filter_box,
int p_min_filter_pixels,
int p_max_filter_pixels,
int p_min_blob_pixels,
int p_max_blob_pixels,
int p_min_blobs,
int p_max_blobs,
int p_overload_frames,
int p_extend_alarm_frames
) {
monitor = p_monitor;
id = p_id;
label = new char[strlen(p_label)+1];
strcpy(label, p_label);
type = p_type;
polygon = p_polygon;
alarm_rgb = p_alarm_rgb;
check_method = p_check_method;
min_pixel_threshold = p_min_pixel_threshold;
max_pixel_threshold = p_max_pixel_threshold;
min_alarm_pixels = p_min_alarm_pixels;
max_alarm_pixels = p_max_alarm_pixels;
filter_box = p_filter_box;
min_filter_pixels = p_min_filter_pixels;
max_filter_pixels = p_max_filter_pixels;
min_blob_pixels = p_min_blob_pixels;
max_blob_pixels = p_max_blob_pixels;
min_blobs = p_min_blobs;
max_blobs = p_max_blobs;
overload_frames = p_overload_frames;
extend_alarm_frames = p_extend_alarm_frames;
2017-08-01 03:49:27 +08:00
//Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames );
alarmed = false;
was_alarmed = false;
pixel_diff = 0;
alarm_pixels = 0;
alarm_filter_pixels = 0;
alarm_blob_pixels = 0;
alarm_blobs = 0;
min_blob_size = 0;
max_blob_size = 0;
image = nullptr;
score = 0;
overload_count = 0;
extend_alarm_count = 0;
pg_image = new Image(monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE);
pg_image->Clear();
pg_image->Fill(0xff, polygon);
pg_image->Outline(0xff, polygon);
ranges = new Range[monitor->Height()];
2017-05-20 21:41:13 +08:00
for ( unsigned int y = 0; y < monitor->Height(); y++ ) {
ranges[y].lo_x = -1;
ranges[y].hi_x = 0;
ranges[y].off_x = 0;
const uint8_t *ppoly = pg_image->Buffer( 0, y );
2017-05-20 21:41:13 +08:00
for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) {
if ( *ppoly ) {
if ( ranges[y].lo_x == -1 ) {
ranges[y].lo_x = x;
}
2017-05-20 21:41:13 +08:00
if ( (unsigned int)ranges[y].hi_x < x ) {
ranges[y].hi_x = x;
}
}
}
}
2017-05-20 21:41:13 +08:00
if ( config.record_diag_images ) {
if ( config.record_diag_images_fifo ) {
snprintf(diag_path, sizeof(diag_path),
"%s/diagpipe-%d-poly.jpg",
staticConfig.PATH_SOCKS.c_str(), id);
FifoStream::fifo_create_if_missing(diag_path);
} else {
snprintf(diag_path, sizeof(diag_path), "%s/diag-%d-poly.jpg",
monitor->getStorage()->Path(), id);
}
pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
} else {
diag_path[0] = 0;
}
2020-07-23 05:30:39 +08:00
} // end Zone::Setup
2017-05-24 03:07:50 +08:00
Zone::~Zone() {
delete[] label;
2020-10-03 00:47:05 +08:00
if ( image )
delete image;
delete pg_image;
delete[] ranges;
}
2020-07-23 05:30:39 +08:00
void Zone::RecordStats(const Event *event) {
2017-05-24 03:07:50 +08:00
static char sql[ZM_SQL_MED_BUFSIZ];
2018-02-16 04:54:13 +08:00
db_mutex.lock();
snprintf(sql, sizeof(sql),
"INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d",
monitor->Id(), id, event->Id(), event->Frames(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score
);
int rc = mysql_query(&dbconn, sql);
db_mutex.unlock();
if ( rc ) {
Error("Can't insert event stats: %s", mysql_error(&dbconn));
}
2020-07-23 05:30:39 +08:00
} // end void Zone::RecordStats( const Event *event )
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
bool Zone::CheckOverloadCount() {
if ( overload_count ) {
Debug(4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames);
2017-05-24 03:07:50 +08:00
overload_count--;
return false;
2017-05-24 03:07:50 +08:00
}
return true;
2020-07-23 05:30:39 +08:00
} // end bool Zone::CheckOverloadCount()
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
void Zone::SetScore(unsigned int nScore) {
score = nScore;
2020-07-23 05:30:39 +08:00
} // end void Zone::SetScore(unsigned int nScore)
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
void Zone::SetAlarmImage(const Image* srcImage) {
2020-10-03 00:47:05 +08:00
if ( image )
delete image;
2017-05-24 03:07:50 +08:00
image = new Image(*srcImage);
2020-07-23 05:30:39 +08:00
} // end void Zone::SetAlarmImage( const Image* srcImage )
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
int Zone::GetOverloadCount() {
return overload_count;
2020-07-23 05:30:39 +08:00
} // end int Zone::GetOverloadCount()
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
void Zone::SetOverloadCount(int nOverCount) {
overload_count = nOverCount;
2020-07-23 05:30:39 +08:00
} // end void Zone::SetOverloadCount(int nOverCount )
2013-03-17 07:45:21 +08:00
2017-05-24 03:07:50 +08:00
int Zone::GetOverloadFrames() {
return overload_frames;
2020-07-23 05:30:39 +08:00
} // end int Zone::GetOverloadFrames
2017-05-24 03:07:50 +08:00
int Zone::GetExtendAlarmCount() {
return extend_alarm_count;
2020-07-23 05:30:39 +08:00
} // end int Zone::GetExtendAlarmCount()
2017-05-24 03:07:50 +08:00
void Zone::SetExtendAlarmCount(int nExtendAlarmCount) {
extend_alarm_count = nExtendAlarmCount;
2020-07-23 05:30:39 +08:00
} // end void Zone::SetExtendAlarmCount( int nExtendAlarmCount )
2017-05-24 03:07:50 +08:00
int Zone::GetExtendAlarmFrames() {
return extend_alarm_frames;
2020-07-23 05:30:39 +08:00
} // end int Zone::GetExtendAlarmFrames()
2017-05-24 03:07:50 +08:00
bool Zone::CheckExtendAlarmCount() {
Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames);
2017-05-24 03:07:50 +08:00
if ( extend_alarm_count ) {
Debug(3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames);
2017-05-24 03:07:50 +08:00
extend_alarm_count--;
return true;
2017-05-24 03:07:50 +08:00
}
return false;
2020-07-23 05:30:39 +08:00
} // end bool Zone::CheckExtendAlarmCount
bool Zone::CheckAlarms(const Image *delta_image) {
ResetStats();
2017-05-20 21:41:13 +08:00
if ( overload_count ) {
Info("In overload mode, %d frames of %d remaining", overload_count, overload_frames);
overload_count--;
return false;
}
2020-10-03 00:47:05 +08:00
if ( image )
delete image;
// Get the difference image
Image *diff_image = image = new Image(*delta_image);
int diff_width = diff_image->Width();
uint8_t* diff_buff = (uint8_t*)diff_image->Buffer();
uint8_t* pdiff;
unsigned int pixel_diff_count = 0;
int alarm_lo_x = 0;
int alarm_hi_x = 0;
int alarm_lo_y = 0;
int alarm_hi_y = 0;
int alarm_mid_x = -1;
int alarm_mid_y = -1;
2017-05-20 21:41:13 +08:00
unsigned int lo_y = polygon.LoY();
unsigned int lo_x = polygon.LoX();
unsigned int hi_x = polygon.HiX();
unsigned int hi_y = polygon.HiY();
Debug(4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y);
2017-05-20 21:41:13 +08:00
2020-07-20 05:50:47 +08:00
/* if(config.cpu_extensions && sse_version >= 20) {
2017-05-20 21:41:13 +08:00
sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
} else {
std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
} */
std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count);
2017-05-20 21:41:13 +08:00
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
if ( pixel_diff_count && alarm_pixels )
pixel_diff = pixel_diff_count/alarm_pixels;
2018-08-22 23:14:00 +08:00
Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d",
alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff);
if ( config.record_diag_images_fifo ) {
FifoDebug(5, "{\"zone\":%d,\"type\":\"ALRM\",\"pixels\":%d,\"avg_diff\":%d}",
id, alarm_pixels, pixel_diff);
}
if ( alarm_pixels ) {
if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) {
/* Not enough pixels alarmed */
return false;
} else if ( max_alarm_pixels && (alarm_pixels > (unsigned int)max_alarm_pixels) ) {
/* Too many pixels alarmed */
overload_count = overload_frames;
return false;
}
} else {
/* No alarmed pixels */
return false;
}
score = (100*alarm_pixels)/(max_alarm_pixels?max_alarm_pixels:polygon.Area());
2017-05-20 21:41:13 +08:00
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
2017-05-20 21:41:13 +08:00
if ( check_method >= FILTERED_PIXELS ) {
int bx = filter_box.X();
int by = filter_box.Y();
int bx1 = bx-1;
int by1 = by-1;
Debug(5, "Checking for filtered pixels");
2017-05-20 21:41:13 +08:00
if ( bx > 1 || by > 1 ) {
// Now remove any pixels smaller than our filter size
unsigned char *cpdiff;
int ldx, hdx, ldy, hdy;
bool block;
2017-05-20 21:41:13 +08:00
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
int lo_x = ranges[y].lo_x;
int hi_x = ranges[y].hi_x;
pdiff = (uint8_t*)diff_image->Buffer(lo_x, y);
2017-05-20 21:41:13 +08:00
for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) {
if ( *pdiff == WHITE ) {
// Check participation in an X block
ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x;
hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1);
ldy = (y>=(lo_y+by1))?-by1:lo_y-y;
hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1);
block = false;
2017-05-20 21:41:13 +08:00
for ( int dy = ldy; !block && dy <= hdy; dy++ ) {
for ( int dx = ldx; !block && dx <= hdx; dx++ ) {
block = true;
2017-05-20 21:41:13 +08:00
for ( int dy2 = 0; block && dy2 < by; dy2++ ) {
for ( int dx2 = 0; block && dx2 < bx; dx2++ ) {
cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2));
2017-05-20 21:41:13 +08:00
if ( !*cpdiff ) {
block = false;
}
}
}
}
}
2017-05-20 21:41:13 +08:00
if ( !block ) {
*pdiff = BLACK;
continue;
}
alarm_filter_pixels++;
} // end if white
} // end for x
} // end foreach y line
2017-05-20 21:41:13 +08:00
} else {
alarm_filter_pixels = alarm_pixels;
}
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
2018-08-22 23:14:00 +08:00
Debug(5, "Got %d filtered pixels, need %d -> %d",
alarm_filter_pixels, min_filter_pixels, max_filter_pixels);
2017-05-20 21:41:13 +08:00
if ( config.record_diag_images_fifo )
FifoDebug(5, "{\"zone\":%d,\"type\":\"FILT\",\"pixels\":%d}", id, alarm_filter_pixels);
if ( alarm_filter_pixels ) {
if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) {
/* Not enough pixels alarmed */
return false;
} else if ( max_filter_pixels && (alarm_filter_pixels > max_filter_pixels) ) {
/* Too many pixels alarmed */
overload_count = overload_frames;
return false;
}
} else {
/* No filtered pixels */
return false;
}
if ( max_filter_pixels != 0 )
score = (100*alarm_filter_pixels)/max_filter_pixels;
else
score = (100*alarm_filter_pixels)/polygon.Area();
2017-05-20 21:41:13 +08:00
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
2017-05-20 21:41:13 +08:00
if ( check_method >= BLOBS ) {
Debug(5, "Checking for blob pixels");
// ICON FIXME Would like to get rid of this memset
memset(blob_stats, 0, sizeof(BlobStats)*256);
uint8_t *spdiff;
uint8_t last_x, last_y;
BlobStats *bsx, *bsy;
BlobStats *bsm, *bss;
2017-05-20 21:41:13 +08:00
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
int lo_x = ranges[y].lo_x;
int hi_x = ranges[y].hi_x;
pdiff = (uint8_t*)diff_image->Buffer(lo_x, y);
2017-05-20 21:41:13 +08:00
for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) {
if ( *pdiff == WHITE ) {
Debug(9, "Got white pixel at %d,%d (%p)", x, y, pdiff);
2017-05-20 21:41:13 +08:00
last_x = ((x > 0) && ( (x-1) >= lo_x )) ? *(pdiff-1) : 0;
2017-05-20 21:41:13 +08:00
last_y = 0;
if ( y > 0 ) {
if ( (y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x ) {
last_y = *(pdiff-diff_width);
}
}
2017-05-20 21:41:13 +08:00
if ( last_x ) {
Debug(9, "Left neighbour is %d", last_x);
bsx = &blob_stats[last_x];
2017-05-20 21:41:13 +08:00
if ( last_y ) {
Debug(9, "Top neighbour is %d", last_y);
bsy = &blob_stats[last_y];
2017-05-20 21:41:13 +08:00
if ( last_x == last_y ) {
Debug(9, "Matching neighbours, setting to %d", last_x);
// Add to the blob from the x side (either side really)
*pdiff = last_x;
alarm_blob_pixels++;
bsx->count++;
if ( x > bsx->hi_x ) bsx->hi_x = x;
if ( (int)y > bsx->hi_y ) bsx->hi_y = y;
2017-05-20 21:41:13 +08:00
} else {
// Aggregate blobs
bsm = bsx->count>=bsy->count?bsx:bsy;
bss = bsm==bsx?bsy:bsx;
Debug(9,
"Different neighbours, setting pixels of %d to %d\n"
"Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d\n"
"Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d\n",
bss->tag, bsm->tag,
bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y,
bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y
);
// Now change all those pixels to the other setting
int changed = 0;
for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++ ) {
int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x;
int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x;
Debug(9,
"Changing %d, %d->%d Range %d->%d",
sy, lo_sx, hi_sx, ranges[sy].lo_x, ranges[sy].hi_x
);
spdiff = diff_buff + ((diff_width * sy) + lo_sx);
2017-05-20 21:41:13 +08:00
for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) {
Debug(9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff);
2017-05-20 21:41:13 +08:00
if ( *spdiff == bss->tag ) {
Debug(9, "Setting pixel");
*spdiff = bsm->tag;
changed++;
}
}
}
*pdiff = bsm->tag;
alarm_blob_pixels++;
2017-05-20 21:41:13 +08:00
if ( !changed ) {
Info(
"Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d\n"
"Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d",
bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y,
bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y
);
Error("No pixels changed, exiting");
exit(-1);
}
// Merge the slave blob into the master
bsm->count += bss->count+1;
if ( x > bsm->hi_x ) bsm->hi_x = x;
if ( (int)y > bsm->hi_y ) bsm->hi_y = y;
if ( bss->lo_x < bsm->lo_x ) bsm->lo_x = bss->lo_x;
if ( bss->lo_y < bsm->lo_y ) bsm->lo_y = bss->lo_y;
if ( bss->hi_x > bsm->hi_x ) bsm->hi_x = bss->hi_x;
if ( bss->hi_y > bsm->hi_y ) bsm->hi_y = bss->hi_y;
alarm_blobs--;
2018-08-22 23:14:00 +08:00
Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs",
bss->tag, bsm->tag, x, y, alarm_blobs);
// Clear out the old blob
bss->tag = 0;
bss->count = 0;
bss->lo_x = 0;
bss->lo_y = 0;
bss->hi_x = 0;
bss->hi_y = 0;
}
2017-05-20 21:41:13 +08:00
} else {
Debug(9, "Setting to left neighbour %d", last_x);
// Add to the blob from the x side
*pdiff = last_x;
alarm_blob_pixels++;
bsx->count++;
if ( x > bsx->hi_x ) bsx->hi_x = x;
if ( (int)y > bsx->hi_y ) bsx->hi_y = y;
}
2017-05-20 21:41:13 +08:00
} else {
if ( last_y ) {
Debug(9, "Top neighbour is %d", last_y);
2017-05-20 21:41:13 +08:00
// Add to the blob from the y side
BlobStats *bsy = &blob_stats[last_y];
*pdiff = last_y;
alarm_blob_pixels++;
bsy->count++;
if ( x > bsy->hi_x ) bsy->hi_x = x;
if ( (int)y > bsy->hi_y ) bsy->hi_y = y;
2017-05-20 21:41:13 +08:00
} else {
// Create a new blob
int i;
2017-05-20 21:41:13 +08:00
for ( i = (WHITE-1); i > 0; i-- ) {
BlobStats *bs = &blob_stats[i];
// See if we can recycle one first, only if it's at least two rows up
2017-05-20 21:41:13 +08:00
if ( bs->count && bs->hi_y < (int)(y-1) ) {
2018-08-22 23:14:00 +08:00
if (
(min_blob_pixels && bs->count < min_blob_pixels)
||
(max_blob_pixels && bs->count > max_blob_pixels)
) {
if ( ( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images ) {
2017-05-20 21:41:13 +08:00
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) {
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
2017-05-20 21:41:13 +08:00
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) {
if ( *spdiff == bs->tag ) {
*spdiff = BLACK;
}
}
}
}
alarm_blobs--;
alarm_blob_pixels -= bs->count;
2017-05-20 21:41:13 +08:00
Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs",
i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs);
bs->tag = 0;
bs->count = 0;
bs->lo_x = 0;
bs->lo_y = 0;
bs->hi_x = 0;
bs->hi_y = 0;
}
}
2017-05-20 21:41:13 +08:00
if ( !bs->count ) {
Debug(9, "Creating new blob %d", i);
*pdiff = i;
alarm_blob_pixels++;
bs->tag = i;
bs->count++;
bs->lo_x = bs->hi_x = x;
bs->lo_y = bs->hi_y = y;
alarm_blobs++;
Debug(6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, alarm_blobs);
break;
}
}
2017-05-20 21:41:13 +08:00
if ( i == 0 ) {
Warning("Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive.");
x = hi_x+1;
y = hi_y+1;
}
}
}
}
}
}
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
2017-05-20 21:41:13 +08:00
if ( !alarm_blobs ) {
return false;
}
2017-05-20 21:41:13 +08:00
Debug(5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d",
alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs);
if ( config.record_diag_images_fifo ) {
FifoDebug(5, "{\"zone\":%d,\"type\":\"RBLB\",\"pixels\":%d,\"blobs\":%d}",
id, alarm_blob_pixels, alarm_blobs);
}
// Now eliminate blobs under the threshold
2017-05-20 21:41:13 +08:00
for ( int i = 1; i < WHITE; i++ ) {
BlobStats *bs = &blob_stats[i];
2017-05-20 21:41:13 +08:00
if ( bs->count ) {
if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) {
if ( ( monitor->GetOptSaveJPEGs() > 1 ) || config.record_diag_images ) {
2017-05-20 21:41:13 +08:00
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) {
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
2017-05-20 21:41:13 +08:00
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) {
if ( *spdiff == bs->tag ) {
*spdiff = BLACK;
}
}
}
}
alarm_blobs--;
alarm_blob_pixels -= bs->count;
2017-05-20 21:41:13 +08:00
Debug(6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs",
i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs);
bs->tag = 0;
bs->count = 0;
bs->lo_x = 0;
bs->lo_y = 0;
bs->hi_x = 0;
bs->hi_y = 0;
2017-05-20 21:41:13 +08:00
} else {
Debug(6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs",
i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs);
if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count;
if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count;
}
} // end if bs_count
} // end for i < WHITE
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
2017-05-20 21:41:13 +08:00
Debug(5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d",
alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs);
if ( config.record_diag_images_fifo ) {
FifoDebug(5, "{\"zone\":%d,\"type\":\"FBLB\",\"pixels\":%d,\"blobs\":%d}",
id, alarm_blob_pixels, alarm_blobs);
}
if ( alarm_blobs ) {
if ( min_blobs && (alarm_blobs < min_blobs) ) {
/* Not enough pixels alarmed */
return false;
} else if ( max_blobs && (alarm_blobs > max_blobs) ) {
/* Too many pixels alarmed */
overload_count = overload_frames;
return false;
}
} else {
/* No blobs */
return false;
}
if ( max_blob_pixels != 0 )
score = (100*alarm_blob_pixels)/max_blob_pixels;
else
score = (100*alarm_blob_pixels)/polygon.Area();
2017-05-20 21:41:13 +08:00
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
alarm_lo_x = polygon.HiX()+1;
alarm_hi_x = polygon.LoX()-1;
alarm_lo_y = polygon.HiY()+1;
alarm_hi_y = polygon.LoY()-1;
2017-05-20 21:41:13 +08:00
for ( int i = 1; i < WHITE; i++ ) {
BlobStats *bs = &blob_stats[i];
2017-05-20 21:41:13 +08:00
if ( bs->count ) {
if ( bs->count == max_blob_size ) {
if ( config.weighted_alarm_centres ) {
unsigned long x_total = 0;
unsigned long y_total = 0;
2017-05-20 21:41:13 +08:00
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) {
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
2017-05-20 21:41:13 +08:00
for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) {
if ( *spdiff == bs->tag ) {
x_total += sx;
y_total += sy;
}
}
}
alarm_mid_x = int(round(x_total/bs->count));
alarm_mid_y = int(round(y_total/bs->count));
2017-05-20 21:41:13 +08:00
} else {
alarm_mid_x = int((bs->hi_x+bs->lo_x+1)/2);
alarm_mid_y = int((bs->hi_y+bs->lo_y+1)/2);
}
}
if ( alarm_lo_x > bs->lo_x ) alarm_lo_x = bs->lo_x;
if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y;
if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x;
if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y;
2017-05-20 21:41:13 +08:00
} // end if bs->count
} // end for i < WHITE
} else {
alarm_mid_x = int((alarm_hi_x+alarm_lo_x+1)/2);
alarm_mid_y = int((alarm_hi_y+alarm_lo_y+1)/2);
}
}
2017-05-20 21:41:13 +08:00
if ( type == INCLUSIVE ) {
// score >>= 1;
score /= 2;
2017-05-20 21:41:13 +08:00
} else if ( type == EXCLUSIVE ) {
// score <<= 1;
score *= 2;
}
Debug(5, "Adjusted score is %d", score);
// Now outline the changed region
2017-05-20 21:41:13 +08:00
if ( score ) {
alarm_box = Box(Coord(alarm_lo_x, alarm_lo_y), Coord(alarm_hi_x, alarm_hi_y));
//if ( monitor->followMotion() )
2017-05-20 21:41:13 +08:00
if ( true ) {
alarm_centre = Coord(alarm_mid_x, alarm_mid_y);
2017-05-20 21:41:13 +08:00
} else {
alarm_centre = alarm_box.Centre();
}
if ( (type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1) ) {
// First mask out anything we don't want
2017-05-20 21:41:13 +08:00
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
pdiff = diff_buff + ((diff_width * y) + lo_x);
int lo_x2 = ranges[y].lo_x;
int hi_x2 = ranges[y].hi_x;
int lo_gap = lo_x2-lo_x;
2017-05-20 21:41:13 +08:00
if ( lo_gap > 0 ) {
if ( lo_gap == 1 ) {
*pdiff++ = BLACK;
2017-05-20 21:41:13 +08:00
} else {
memset(pdiff, BLACK, lo_gap);
pdiff += lo_gap;
}
}
const uint8_t* ppoly = pg_image->Buffer(lo_x2, y);
2017-05-20 21:41:13 +08:00
for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) {
if ( !*ppoly ) {
*pdiff = BLACK;
}
}
int hi_gap = hi_x-hi_x2;
2017-05-20 21:41:13 +08:00
if ( hi_gap > 0 ) {
if ( hi_gap == 1 ) {
*pdiff = BLACK;
2017-05-20 21:41:13 +08:00
} else {
memset(pdiff, BLACK, hi_gap);
}
}
} // end for y
2017-05-20 21:41:13 +08:00
if ( monitor->Colours() == ZM_COLOUR_GRAY8 ) {
image = diff_image->HighlightEdges(alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent());
} else {
image = diff_image->HighlightEdges(alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent());
}
2017-05-20 21:41:13 +08:00
// Only need to delete this when 'image' becomes detached and points somewhere else
delete diff_image;
2020-10-03 00:47:05 +08:00
diff_image = nullptr;
} // end if ( (type < PRECLUSIVE) && (check_method >= BLOBS) && (monitor->GetOptSaveJPEGs() > 1)
Debug(1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d",
Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score);
2020-10-03 00:47:05 +08:00
} // end if score
return true;
}
bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon) {
char *str = (char *)poly_string;
char *ws;
char *cp;
int n_coords = 0;
int max_n_coords = strlen(str)/4;
Coord *coords = new Coord[max_n_coords];
while ( *str != '\0' ) {
cp = strchr(str, ',');
2017-05-20 21:41:13 +08:00
if ( !cp ) {
Error("Bogus coordinate %s found in polygon string", str);
break;
}
int x = atoi(str);
int y = atoi(cp+1);
Debug(3, "Got coordinate %d,%d from polygon string", x, y);
coords[n_coords++] = Coord(x, y);
ws = strchr(cp+2, ' ');
if ( ws )
str = ws+1;
else
break;
} // end while ! end of string
if ( n_coords > 2 ) {
Debug(3, "Successfully parsed polygon string %s", str);
polygon = Polygon(n_coords, coords);
} else {
Error("Not enough coordinates to form a polygon!");
n_coords = 0;
}
delete[] coords;
return n_coords ? true : false;
} // end bool Zone::ParsePolygonString(const char *poly_string, Polygon &polygon)
bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon) {
Debug(3, "Parsing zone string '%s'", zone_string);
char *str_ptr = new char[strlen(zone_string)+1];
char *str = str_ptr;
strcpy(str, zone_string);
zone_id = strtol(str, 0, 10);
Debug(3, "Got zone %d from zone string", zone_id);
char *ws = strchr(str, ' ');
2017-05-20 21:41:13 +08:00
if ( !ws ) {
Debug(3, "No initial whitespace found in zone string '%s', finishing", str);
2017-06-14 22:54:21 +08:00
delete[] str_ptr;
return true;
}
*ws = '\0';
str = ws+1;
colour = strtol(str, 0, 16);
Debug(3, "Got colour %06x from zone string", colour);
ws = strchr(str, ' ');
2017-05-20 21:41:13 +08:00
if ( !ws ) {
Debug(3, "No secondary whitespace found in zone string '%s', finishing", zone_string);
2017-06-14 22:54:21 +08:00
delete[] str_ptr;
return true;
}
*ws = '\0';
str = ws+1;
bool result = ParsePolygonString(str, polygon);
delete[] str_ptr;
return result;
} // end bool Zone::ParseZoneString(const char *zone_string, int &zone_id, int &colour, Polygon &polygon)
int Zone::Load(Monitor *monitor, Zone **&zones) {
static char sql[ZM_SQL_MED_BUFSIZ];
2018-05-02 00:19:34 +08:00
2018-02-16 04:54:13 +08:00
db_mutex.lock();
snprintf(sql, sizeof(sql), "SELECT Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,"
"MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,"
"FilterX,FilterY,MinFilterPixels,MaxFilterPixels,"
"MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,"
"OverloadFrames,ExtendAlarmFrames"
" FROM Zones WHERE MonitorId = %d ORDER BY Type, Id", monitor->Id());
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
db_mutex.unlock();
return 0;
}
MYSQL_RES *result = mysql_store_result(&dbconn);
2018-02-16 04:54:13 +08:00
db_mutex.unlock();
2017-05-20 21:41:13 +08:00
if ( !result ) {
Error("Can't use query result: %s", mysql_error(&dbconn));
2018-02-16 04:54:13 +08:00
return 0;
}
int n_zones = mysql_num_rows(result);
Debug(1, "Got %d zones for monitor %s", n_zones, monitor->Name());
delete[] zones;
zones = new Zone *[n_zones];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
int col = 0;
int Id = atoi(dbrow[col++]);
const char *Name = dbrow[col++];
int Type = atoi(dbrow[col++]);
const char *Units = dbrow[col++];
const char *Coords = dbrow[col++];
int AlarmRGB = dbrow[col]?atoi(dbrow[col]):0; col++;
int CheckMethod = atoi(dbrow[col++]);
int MinPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++;
int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++;
int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int MaxAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int FilterX = dbrow[col]?atoi(dbrow[col]):0; col++;
int FilterY = dbrow[col]?atoi(dbrow[col]):0; col++;
int MinFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int MaxFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int MinBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int MaxBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++;
int MinBlobs = dbrow[col]?atoi(dbrow[col]):0; col++;
int MaxBlobs = dbrow[col]?atoi(dbrow[col]):0; col++;
int OverloadFrames = dbrow[col]?atoi(dbrow[col]):0; col++;
int ExtendAlarmFrames = dbrow[col]?atoi(dbrow[col]):0; col++;
2017-05-20 21:41:13 +08:00
/* HTML colour code is actually BGR in memory, we want RGB */
AlarmRGB = rgb_convert(AlarmRGB, ZM_SUBPIX_ORDER_BGR);
Debug(5, "Parsing polygon %s", Coords);
Polygon polygon;
if ( !ParsePolygonString(Coords, polygon) ) {
Error("Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name());
n_zones -= 1;
continue;
}
if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width()
2017-05-20 21:41:13 +08:00
|| polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) {
Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing",
Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY());
if ( polygon.LoX() < 0 ) polygon.LoX(0);
if ( polygon.HiX() >= (int)monitor->Width()) polygon.HiX((int)monitor->Width());
if ( polygon.LoY() < 0 ) polygon.LoY(0);
if ( polygon.HiY() >= (int)monitor->Height() ) polygon.HiY((int)monitor->Height());
}
2017-05-20 21:41:13 +08:00
if ( false && !strcmp( Units, "Percent" ) ) {
MinAlarmPixels = (MinAlarmPixels*polygon.Area())/100;
MaxAlarmPixels = (MaxAlarmPixels*polygon.Area())/100;
MinFilterPixels = (MinFilterPixels*polygon.Area())/100;
MaxFilterPixels = (MaxFilterPixels*polygon.Area())/100;
MinBlobPixels = (MinBlobPixels*polygon.Area())/100;
MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100;
}
2017-05-20 21:41:13 +08:00
if ( atoi(dbrow[2]) == Zone::INACTIVE ) {
zones[i] = new Zone(monitor, Id, Name, polygon);
2017-05-20 21:41:13 +08:00
} else if ( atoi(dbrow[2]) == Zone::PRIVACY ) {
zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon);
} else {
zones[i] = new Zone(
monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB,
(Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold,
MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ),
MinFilterPixels, MaxFilterPixels,
MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs,
OverloadFrames, ExtendAlarmFrames);
}
} // end foreach row
mysql_free_result(result);
return n_zones;
} // end int Zone::Load(Monitor *monitor, Zone **&zones)
bool Zone::DumpSettings(char *output, bool /*verbose*/) {
output[0] = 0;
sprintf(output+strlen(output), " Id : %d\n", id );
sprintf(output+strlen(output), " Label : %s\n", label );
sprintf(output+strlen(output), " Type: %d - %s\n", type,
2017-05-20 21:41:13 +08:00
type==ACTIVE?"Active":(
type==INCLUSIVE?"Inclusive":(
type==EXCLUSIVE?"Exclusive":(
type==PRECLUSIVE?"Preclusive":(
type==INACTIVE?"Inactive":(
type==PRIVACY?"Privacy":"Unknown"
))))));
sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() );
2017-05-20 21:41:13 +08:00
for ( int i = 0; i < polygon.getNumCoords(); i++ ) {
sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() );
}
sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb );
sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method,
2017-05-20 21:41:13 +08:00
check_method==ALARMED_PIXELS?"Alarmed Pixels":(
check_method==FILTERED_PIXELS?"FilteredPixels":(
check_method==BLOBS?"Blobs":"Unknown"
)));
sprintf( output+strlen(output), " Min Pixel Threshold : %d\n", min_pixel_threshold );
sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold );
sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels );
sprintf( output+strlen(output), " Max Alarm Pixels : %d\n", max_alarm_pixels );
sprintf( output+strlen(output), " Filter Box : %d,%d\n", filter_box.X(), filter_box.Y() );
sprintf( output+strlen(output), " Min Filter Pixels : %d\n", min_filter_pixels );
sprintf( output+strlen(output), " Max Filter Pixels : %d\n", max_filter_pixels );
sprintf( output+strlen(output), " Min Blob Pixels : %d\n", min_blob_pixels );
sprintf( output+strlen(output), " Max Blob Pixels : %d\n", max_blob_pixels );
sprintf( output+strlen(output), " Min Blobs : %d\n", min_blobs );
sprintf( output+strlen(output), " Max Blobs : %d\n", max_blobs );
2020-07-23 05:30:39 +08:00
return true;
}
2020-07-23 05:30:39 +08:00
void Zone::std_alarmedpixels(
Image* pdiff_image,
const Image* ppoly_image,
unsigned int* pixel_count,
unsigned int* pixel_sum) {
uint32_t pixelsalarmed = 0;
uint32_t pixelsdifference = 0;
uint8_t calc_max_pixel_threshold = 255;
unsigned int lo_y;
unsigned int hi_y;
2017-05-20 21:41:13 +08:00
if ( max_pixel_threshold )
calc_max_pixel_threshold = max_pixel_threshold;
2017-05-20 21:41:13 +08:00
lo_y = polygon.LoY();
hi_y = polygon.HiY();
2017-05-20 21:41:13 +08:00
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
2017-11-19 05:00:10 +08:00
unsigned int lo_x = ranges[y].lo_x;
unsigned int hi_x = ranges[y].hi_x;
2017-05-20 21:41:13 +08:00
Debug(7, "Checking line %d from %d -> %d", y, lo_x, hi_x);
uint8_t *pdiff = (uint8_t*)pdiff_image->Buffer(lo_x, y);
const uint8_t *ppoly = ppoly_image->Buffer(lo_x, y);
2017-05-20 21:41:13 +08:00
for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) {
if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) {
pixelsalarmed++;
pixelsdifference += *pdiff;
*pdiff = WHITE;
2017-05-20 21:41:13 +08:00
} else {
*pdiff = BLACK;
}
}
} // end for y = lo_y to hi_y
2017-05-20 21:41:13 +08:00
/* Store the results */
*pixel_count = pixelsalarmed;
*pixel_sum = pixelsdifference;
2017-09-26 04:23:27 +08:00
Debug(7, "STORED pixelsalarmed(%d), pixelsdifference(%d)", pixelsalarmed, pixelsdifference);
} // end void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum)