mirror of
https://github.com/Motion-Project/motion.git
synced 2026-02-06 13:01:38 -05:00
1219 lines
34 KiB
C++
1219 lines
34 KiB
C++
/*
|
|
* This file is part of MotionPlus.
|
|
*
|
|
* MotionPlus 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MotionPlus 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 MotionPlus. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
#include "motionplus.hpp"
|
|
#include "conf.hpp"
|
|
#include "util.hpp"
|
|
#include "alg.hpp"
|
|
#include "draw.hpp"
|
|
#include "logger.hpp"
|
|
|
|
#define MAX2(x, y) ((x) > (y) ? (x) : (y))
|
|
#define MAX3(x, y, z) ((x) > (y) ? ((x) > (z) ? (x) : (z)) : ((y) > (z) ? (y) : (z)))
|
|
#define NORM 100
|
|
#define ABS(x) ((x) < 0 ? -(x) : (x))
|
|
#define DIFF(x, y) (ABS((x)-(y)))
|
|
#define NDIFF(x, y) (ABS(x) * NORM / (ABS(x) + 2 * DIFF(x, y)))
|
|
#define MAXS 10000 /* max depth of stack */
|
|
#define EXCLUDE_LEVEL_PERCENT 20
|
|
/* Increment for *smartmask_buffer in alg_diff_standard. */
|
|
#define SMARTMASK_SENSITIVITY_INCR 5
|
|
|
|
typedef struct {
|
|
int y, xl, xr, dy;
|
|
} Segment;
|
|
|
|
|
|
void alg_noise_tune(ctx_dev *cam)
|
|
{
|
|
ctx_images *imgs = &cam->imgs;
|
|
int i;
|
|
unsigned char *ref = imgs->ref;
|
|
int diff, sum = 0, count = 0;
|
|
unsigned char *mask = imgs->mask;
|
|
unsigned char *smartmask = imgs->smartmask_final;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
|
|
i = imgs->motionsize;
|
|
|
|
for (; i > 0; i--) {
|
|
diff = ABS(*ref - *new_img);
|
|
|
|
if (mask) {
|
|
diff = ((diff * *mask++) / 255);
|
|
}
|
|
|
|
if (*smartmask) {
|
|
sum += diff + 1;
|
|
count++;
|
|
}
|
|
|
|
ref++;
|
|
new_img++;
|
|
smartmask++;
|
|
}
|
|
|
|
if (count > 3) {
|
|
/* Avoid divide by zero. */
|
|
sum /= count / 3;
|
|
}
|
|
|
|
/* 5: safe, 4: regular, 3: more sensitive */
|
|
cam->noise = 4 + (cam->noise + sum) / 2;
|
|
}
|
|
|
|
void alg_threshold_tune(ctx_dev *cam)
|
|
{
|
|
int i, top;
|
|
int sum = 0;
|
|
int diffs = cam->current_image->diffs;
|
|
int motion = cam->detecting_motion;
|
|
|
|
if (!diffs) {
|
|
return;
|
|
}
|
|
|
|
top = diffs;
|
|
|
|
if (motion) {
|
|
diffs = cam->threshold / 4;
|
|
}
|
|
|
|
for (i = 0; i < THRESHOLD_TUNE_LENGTH - 1; i++) {
|
|
sum += cam->diffs_last[i];
|
|
|
|
if (cam->diffs_last[i + 1] && !motion) {
|
|
cam->diffs_last[i] = cam->diffs_last[i + 1];
|
|
} else {
|
|
cam->diffs_last[i] = cam->threshold / 4;
|
|
}
|
|
|
|
if (cam->diffs_last[i] > top) {
|
|
top = cam->diffs_last[i];
|
|
}
|
|
}
|
|
|
|
sum += cam->diffs_last[i];
|
|
cam->diffs_last[i] = diffs;
|
|
|
|
sum /= THRESHOLD_TUNE_LENGTH / 4;
|
|
|
|
if (sum < top * 2) {
|
|
sum = top * 2;
|
|
}
|
|
|
|
if (sum < cam->conf->threshold) {
|
|
cam->threshold = (cam->threshold + sum) / 2;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Labeling by Joerg Weber. Based on an idea from Hubert Mara.
|
|
* Floodfill enhanced by Ian McConnel based on code from
|
|
* http://www.acm.org/pubs/tog/GraphicsGems/
|
|
* http://www.codeproject.com/gdi/QuickFill.asp
|
|
|
|
* Filled horizontal segment of scanline y for xl <= x <= xr.
|
|
* Parent segment was on line y - dy. dy = 1 or -1
|
|
*/
|
|
#define PUSH(Y, XL, XR, DY) /* push new segment on stack */ \
|
|
if (sp<stack+MAXS && Y+(DY) >= 0 && Y+(DY) < height) \
|
|
{sp->y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; sp++;}
|
|
|
|
#define POP(Y, XL, XR, DY) /* pop segment off stack */ \
|
|
{sp--; Y = sp->y+(DY = sp->dy); XL = sp->xl; XR = sp->xr;}
|
|
|
|
static int alg_iflood(int x, int y, int width, int height,
|
|
unsigned char *out, int *labels, int newvalue, int oldvalue)
|
|
{
|
|
int l, x1, x2, dy;
|
|
Segment stack[MAXS], *sp = stack; /* Stack of filled segments. */
|
|
int count = 0;
|
|
|
|
if (x < 0 || x >= width || y < 0 || y >= height) {
|
|
return 0;
|
|
}
|
|
|
|
PUSH(y, x, x, 1); /* Needed in some cases. */
|
|
PUSH(y + 1, x, x, -1); /* Seed segment (popped 1st). */
|
|
|
|
while (sp > stack) {
|
|
/* Pop segment off stack and fill a neighboring scan line. */
|
|
POP(y, x1, x2, dy);
|
|
/*
|
|
* Segment of scan line y-dy for x1<=x<=x2 was previously filled,
|
|
* now explore adjacent pixels in scan line y
|
|
*/
|
|
for (x = x1; x >= 0 && out[y * width + x] != 0 && labels[y * width + x] == oldvalue; x--) {
|
|
labels[y * width + x] = newvalue;
|
|
count++;
|
|
}
|
|
|
|
if (x >= x1) {
|
|
goto skip;
|
|
}
|
|
|
|
l = x + 1;
|
|
|
|
if (l < x1) {
|
|
PUSH(y, l, x1 - 1, -dy); /* Leak on left? */
|
|
}
|
|
|
|
x = x1 + 1;
|
|
|
|
do {
|
|
for (; x < width && out[y * width + x] != 0 && labels[y * width + x] == oldvalue; x++) {
|
|
labels[y * width + x] = newvalue;
|
|
count++;
|
|
}
|
|
|
|
PUSH(y, l, x - 1, dy);
|
|
|
|
if (x > x2 + 1) {
|
|
PUSH(y, x2 + 1, x - 1, -dy); /* Leak on right? */
|
|
}
|
|
|
|
skip:
|
|
for (x++; x <= x2 && !(out[y * width + x] != 0 && labels[y * width + x] == oldvalue); x++);
|
|
l = x;
|
|
} while (x <= x2);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static int alg_labeling(ctx_dev *cam)
|
|
{
|
|
ctx_images *imgs = &cam->imgs;
|
|
unsigned char *out = imgs->image_motion.image_norm;
|
|
int *labels = imgs->labels;
|
|
int ix, iy, pixelpos;
|
|
int width = imgs->width;
|
|
int height = imgs->height;
|
|
int labelsize = 0;
|
|
int current_label = 2;
|
|
/* Keep track of the area just under the threshold. */
|
|
int max_under = 0;
|
|
|
|
cam->current_image->total_labels = 0;
|
|
imgs->labelsize_max = 0;
|
|
/* ALL labels above threshold are counted as labelgroup. */
|
|
imgs->labelgroup_max = 0;
|
|
imgs->labels_above = 0;
|
|
|
|
/* Init: 0 means no label set / not checked. */
|
|
memset(labels, 0, width * height * sizeof(*labels));
|
|
pixelpos = 0;
|
|
|
|
for (iy = 0; iy < height - 1; iy++) {
|
|
for (ix = 0; ix < width - 1; ix++, pixelpos++) {
|
|
/* No motion - no label */
|
|
if (out[pixelpos] == 0) {
|
|
labels[pixelpos] = 1;
|
|
continue;
|
|
}
|
|
|
|
/* Already visited by alg_iflood */
|
|
if (labels[pixelpos] > 0) {
|
|
continue;
|
|
}
|
|
|
|
labelsize = alg_iflood(ix, iy, width, height, out, labels, current_label, 0);
|
|
|
|
if (labelsize > 0) {
|
|
/* Label above threshold? Mark it again (add 32768 to labelnumber). */
|
|
if (labelsize > cam->threshold) {
|
|
labelsize = alg_iflood(ix, iy, width, height, out, labels, current_label + 32768, current_label);
|
|
imgs->labelgroup_max += labelsize;
|
|
imgs->labels_above++;
|
|
} else if(max_under < labelsize) {
|
|
max_under = labelsize;
|
|
}
|
|
|
|
if (imgs->labelsize_max < labelsize) {
|
|
imgs->labelsize_max = labelsize;
|
|
imgs->largest_label = current_label;
|
|
}
|
|
|
|
cam->current_image->total_labels++;
|
|
current_label++;
|
|
}
|
|
}
|
|
pixelpos++; /* Compensate for ix < width - 1 */
|
|
}
|
|
|
|
/* Return group of significant labels or if that's none, the next largest
|
|
* group (which is under the threshold, but especially for setup gives an
|
|
* idea how close it was).
|
|
*/
|
|
return imgs->labelgroup_max ? imgs->labelgroup_max : max_under;
|
|
}
|
|
|
|
/** Dilates a 3x3 box. */
|
|
static int alg_dilate9(unsigned char *img, int width, int height, void *buffer)
|
|
{
|
|
/*
|
|
* - row1, row2 and row3 represent lines in the temporary buffer.
|
|
* - Window is a sliding window containing max values of the columns
|
|
* in the 3x3 matrix.
|
|
* - width is an index into the sliding window (this is faster than
|
|
* doing modulo 3 on i).
|
|
* - blob keeps the current max value.
|
|
*/
|
|
int y, i, sum = 0, widx;
|
|
unsigned char *row1, *row2, *row3, *rowTemp, *yp;
|
|
unsigned char window[3], blob, latest;
|
|
|
|
/* Set up row pointers in the temporary buffer. */
|
|
row1 = (unsigned char *)buffer;
|
|
row2 = row1 + width;
|
|
row3 = row2 + width;
|
|
|
|
/* Init rows 2 and 3. */
|
|
memset(row2, 0, width);
|
|
memcpy(row3, img, width);
|
|
|
|
/* Pointer to the current row in img. */
|
|
yp = img;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
/* Move down one step; row 1 becomes the previous row 2 and so on. */
|
|
rowTemp = row1;
|
|
row1 = row2;
|
|
row2 = row3;
|
|
row3 = rowTemp;
|
|
|
|
/* If we're at the last row, fill with zeros, otherwise copy from img. */
|
|
if (y == height - 1) {
|
|
memset(row3, 0, width);
|
|
} else {
|
|
memcpy(row3, yp + width, width);
|
|
}
|
|
|
|
/* Init slots 0 and 1 in the moving window. */
|
|
window[0] = MAX3(row1[0], row2[0], row3[0]);
|
|
window[1] = MAX3(row1[1], row2[1], row3[1]);
|
|
|
|
/* Init blob to the current max, and set window index. */
|
|
blob = MAX2(window[0], window[1]);
|
|
widx = 2;
|
|
|
|
/*
|
|
* Iterate over the current row; index i is off by one to eliminate
|
|
* a lot of +1es in the loop.
|
|
*/
|
|
for (i = 2; i <= width - 1; i++) {
|
|
/* Get the max value of the next column in the 3x3 matrix. */
|
|
latest = window[widx] = MAX3(row1[i], row2[i], row3[i]);
|
|
|
|
/*
|
|
* If the value is larger than the current max, use it. Otherwise,
|
|
* calculate a new max (because the new value may not be the max.
|
|
*/
|
|
if (latest >= blob) {
|
|
blob = latest;
|
|
} else {
|
|
blob = MAX3(window[0], window[1], window[2]);
|
|
}
|
|
|
|
/* Write the max value (blob) to the image. */
|
|
if (blob != 0) {
|
|
*(yp + i - 1) = blob;
|
|
sum++;
|
|
}
|
|
|
|
/* Wrap around the window index if necessary. */
|
|
if (++widx == 3) {
|
|
widx = 0;
|
|
}
|
|
}
|
|
|
|
/* Store zeros in the vertical sides. */
|
|
*yp = *(yp + width - 1) = 0;
|
|
yp += width;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
/** Dilates a + shape. */
|
|
static int alg_dilate5(unsigned char *img, int width, int height, void *buffer)
|
|
{
|
|
/*
|
|
* - row1, row2 and row3 represent lines in the temporary buffer.
|
|
* - mem holds the max value of the overlapping part of two + shapes.
|
|
*/
|
|
int y, i, sum = 0;
|
|
unsigned char *row1, *row2, *row3, *rowTemp, *yp;
|
|
unsigned char blob, mem, latest;
|
|
|
|
/* Set up row pointers in the temporary buffer. */
|
|
row1 = (unsigned char *)buffer;
|
|
row2 = row1 + width;
|
|
row3 = row2 + width;
|
|
|
|
/* Init rows 2 and 3. */
|
|
memset(row2, 0, width);
|
|
memcpy(row3, img, width);
|
|
|
|
/* Pointer to the current row in img. */
|
|
yp = img;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
/* Move down one step; row 1 becomes the previous row 2 and so on. */
|
|
rowTemp = row1;
|
|
row1 = row2;
|
|
row2 = row3;
|
|
row3 = rowTemp;
|
|
|
|
/* If we're at the last row, fill with zeros, otherwise copy from img. */
|
|
if (y == height - 1) {
|
|
memset(row3, 0, width);
|
|
} else {
|
|
memcpy(row3, yp + width, width);
|
|
}
|
|
|
|
/* Init mem and set blob to force an evaluation of the entire + shape. */
|
|
mem = MAX2(row2[0], row2[1]);
|
|
blob = 1; /* dummy value, must be > 0 */
|
|
|
|
for (i = 1; i < width - 1; i++) {
|
|
/* Get the max value of the "right edge" of the + shape. */
|
|
latest = MAX3(row1[i], row2[i + 1], row3[i]);
|
|
|
|
if (blob == 0) {
|
|
/* In case the last blob is zero, only latest matters. */
|
|
blob = latest;
|
|
mem = row2[i + 1];
|
|
} else {
|
|
/* Otherwise, we have to check both latest and mem. */
|
|
blob = MAX2(mem, latest);
|
|
mem = MAX2(row2[i], row2[i + 1]);
|
|
}
|
|
|
|
/* Write the max value (blob) to the image. */
|
|
if (blob != 0) {
|
|
*(yp + i) = blob;
|
|
sum++;
|
|
}
|
|
}
|
|
|
|
/* Store zeros in the vertical sides. */
|
|
*yp = *(yp + width - 1) = 0;
|
|
yp += width;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/** Erodes a 3x3 box. */
|
|
static int alg_erode9(unsigned char *img, int width, int height, void *buffer, unsigned char flag)
|
|
{
|
|
int y, i, sum = 0;
|
|
char *Row1, *Row2, *Row3;
|
|
|
|
Row1 = (char *)buffer;
|
|
Row2 = Row1 + width;
|
|
Row3 = Row1 + 2 * width;
|
|
memset(Row2, flag, width);
|
|
memcpy(Row3, img, width);
|
|
|
|
for (y = 0; y < height; y++) {
|
|
memcpy(Row1, Row2, width);
|
|
memcpy(Row2, Row3, width);
|
|
|
|
if (y == height - 1) {
|
|
memset(Row3, flag, width);
|
|
} else {
|
|
memcpy(Row3, img + (y + 1) * width, width);
|
|
}
|
|
|
|
for (i = width - 2; i >= 1; i--) {
|
|
if (Row1[i - 1] == 0 ||
|
|
Row1[i] == 0 ||
|
|
Row1[i + 1] == 0 ||
|
|
Row2[i - 1] == 0 ||
|
|
Row2[i] == 0 ||
|
|
Row2[i + 1] == 0 ||
|
|
Row3[i - 1] == 0 ||
|
|
Row3[i] == 0 ||
|
|
Row3[i + 1] == 0) {
|
|
img[y * width + i] = 0;
|
|
} else {
|
|
sum++;
|
|
}
|
|
}
|
|
|
|
img[y * width] = img[y * width + width - 1] = flag;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
/* Erodes in a + shape. */
|
|
static int alg_erode5(unsigned char *img, int width, int height, void *buffer, unsigned char flag)
|
|
{
|
|
int y, i, sum = 0;
|
|
char *Row1, *Row2, *Row3;
|
|
|
|
Row1 = (char *)buffer;
|
|
Row2 = Row1 + width;
|
|
Row3 = Row1 + 2 * width;
|
|
memset(Row2, flag, width);
|
|
memcpy(Row3, img, width);
|
|
|
|
for (y = 0; y < height; y++) {
|
|
memcpy(Row1, Row2, width);
|
|
memcpy(Row2, Row3, width);
|
|
|
|
if (y == height - 1) {
|
|
memset(Row3, flag, width);
|
|
} else {
|
|
memcpy(Row3, img + (y + 1) * width, width);
|
|
}
|
|
|
|
for (i = width - 2; i >= 1; i--) {
|
|
if (Row1[i] == 0 ||
|
|
Row2[i - 1] == 0 ||
|
|
Row2[i] == 0 ||
|
|
Row2[i + 1] == 0 ||
|
|
Row3[i] == 0) {
|
|
img[y * width + i] = 0;
|
|
} else {
|
|
sum++;
|
|
}
|
|
}
|
|
|
|
img[y * width] = img[y * width + width - 1] = flag;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
static void alg_despeckle(ctx_dev *cam)
|
|
{
|
|
int diffs, width, height, done, i, len;
|
|
unsigned char *out, *common_buffer;
|
|
|
|
if ((cam->conf->despeckle_filter == "") || cam->current_image->diffs <= 0) {
|
|
if (cam->imgs.labelsize_max) {
|
|
cam->imgs.labelsize_max = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
diffs = 0;
|
|
out = cam->imgs.image_motion.image_norm;
|
|
width = cam->imgs.width;
|
|
height = cam->imgs.height;
|
|
done = 0;
|
|
len = (int)cam->conf->despeckle_filter.length();
|
|
common_buffer = cam->imgs.common_buffer;
|
|
cam->current_image->total_labels = 0;
|
|
cam->imgs.largest_label = 0;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
switch (cam->conf->despeckle_filter[i]) {
|
|
case 'E':
|
|
diffs = alg_erode9(out, width, height, common_buffer, 0);
|
|
if (diffs == 0) {
|
|
i = len;
|
|
}
|
|
done = 1;
|
|
break;
|
|
case 'e':
|
|
diffs = alg_erode5(out, width, height, common_buffer, 0);
|
|
if (diffs == 0) {
|
|
i = len;
|
|
}
|
|
done = 1;
|
|
break;
|
|
case 'D':
|
|
diffs = alg_dilate9(out, width, height, common_buffer);
|
|
done = 1;
|
|
break;
|
|
case 'd':
|
|
diffs = alg_dilate5(out, width, height, common_buffer);
|
|
done = 1;
|
|
break;
|
|
/* No further despeckle after labeling! */
|
|
case 'l':
|
|
diffs = alg_labeling(cam);
|
|
i = len;
|
|
done = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If conf.despeckle_filter contains any valid action EeDdl */
|
|
if (done) {
|
|
if (done != 2) {
|
|
cam->imgs.labelsize_max = 0; // Disable Labeling
|
|
}
|
|
cam->current_image->diffs = diffs;
|
|
return;
|
|
} else {
|
|
cam->imgs.labelsize_max = 0; // Disable Labeling
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void alg_tune_smartmask(ctx_dev *cam)
|
|
{
|
|
int i;
|
|
unsigned char diff;
|
|
int motionsize = cam->imgs.motionsize;
|
|
unsigned char *smartmask = cam->imgs.smartmask;
|
|
unsigned char *smartmask_final = cam->imgs.smartmask_final;
|
|
int *smartmask_buffer = cam->imgs.smartmask_buffer;
|
|
int sensitivity = cam->lastrate * (11 - cam->smartmask_speed);
|
|
|
|
if (!cam->smartmask_speed ||
|
|
(cam->event_curr_nbr == cam->event_prev_nbr) ||
|
|
(--cam->smartmask_count)) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < motionsize; i++) {
|
|
/* Decrease smart_mask sensitivity every 5*speed seconds only. */
|
|
if (smartmask[i] > 0) {
|
|
smartmask[i]--;
|
|
}
|
|
/* Increase smart_mask sensitivity based on the buffered values. */
|
|
diff = (unsigned char)(smartmask_buffer[i] / sensitivity);
|
|
|
|
if (diff) {
|
|
if (smartmask[i] <= diff + 80) {
|
|
smartmask[i] += diff;
|
|
} else {
|
|
smartmask[i] = 80;
|
|
}
|
|
smartmask_buffer[i] %= sensitivity;
|
|
}
|
|
/* Transfer raw mask to the final stage when above trigger value. */
|
|
if (smartmask[i] > 20) {
|
|
smartmask_final[i] = 0;
|
|
} else {
|
|
smartmask_final[i] = 255;
|
|
}
|
|
}
|
|
/* Further expansion (here:erode due to inverted logic!) of the mask. */
|
|
alg_erode9(smartmask_final, cam->imgs.width, cam->imgs.height,
|
|
cam->imgs.common_buffer, 255);
|
|
alg_erode5(smartmask_final, cam->imgs.width, cam->imgs.height,
|
|
cam->imgs.common_buffer, 255);
|
|
cam->smartmask_count = cam->smartmask_ratio;
|
|
}
|
|
|
|
/* Increment for *smartmask_buffer in alg_diff_standard. */
|
|
#define SMARTMASK_SENSITIVITY_INCR 5
|
|
|
|
static void alg_diff_nomask(ctx_dev *cam)
|
|
{
|
|
unsigned char *ref = cam->imgs.ref;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
int i, curdiff;
|
|
int imgsz = cam->imgs.motionsize;
|
|
int diffs = 0, diffs_net = 0;
|
|
int noise = cam->noise;
|
|
int lrgchg = cam->conf->threshold_ratio_change;
|
|
|
|
memset(out + imgsz, 128, (imgsz / 2));
|
|
memset(out, 0, imgsz);
|
|
|
|
for (i = 0; i < imgsz; i++) {
|
|
curdiff = (*ref - *new_img);
|
|
if (abs(curdiff) > noise) {
|
|
*out = *new_img;
|
|
diffs++;
|
|
if (curdiff > lrgchg) {
|
|
diffs_net++;
|
|
} else if (curdiff < -lrgchg) {
|
|
diffs_net--;
|
|
}
|
|
}
|
|
out++;
|
|
ref++;
|
|
new_img++;
|
|
}
|
|
cam->current_image->diffs_raw = diffs;
|
|
cam->current_image->diffs = diffs;
|
|
cam->imgs.image_motion.imgts = cam->current_image->imgts;
|
|
|
|
if (diffs > 0 ) {
|
|
cam->current_image->diffs_ratio = (abs(diffs_net) * 100) / diffs;
|
|
} else {
|
|
cam->current_image->diffs_ratio = 100;
|
|
}
|
|
|
|
}
|
|
|
|
static void alg_diff_mask(ctx_dev *cam)
|
|
{
|
|
unsigned char *ref = cam->imgs.ref;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
unsigned char *mask = cam->imgs.mask;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
int i, curdiff;
|
|
int imgsz = cam->imgs.motionsize;
|
|
int diffs = 0, diffs_net = 0;
|
|
int noise = cam->noise;
|
|
int lrgchg = cam->conf->threshold_ratio_change;
|
|
|
|
memset(out + imgsz, 128, (imgsz / 2));
|
|
memset(out, 0, imgsz);
|
|
|
|
for (i = 0; i < imgsz; i++) {
|
|
curdiff = (*ref - *new_img);
|
|
if (mask) {
|
|
curdiff = ((curdiff * *mask) / 255);
|
|
}
|
|
|
|
if (abs(curdiff) > noise) {
|
|
*out = *new_img;
|
|
diffs++;
|
|
if (curdiff > lrgchg) {
|
|
diffs_net++;
|
|
} else if (curdiff < -lrgchg) {
|
|
diffs_net--;
|
|
}
|
|
}
|
|
|
|
out++;
|
|
ref++;
|
|
new_img++;
|
|
mask++;
|
|
}
|
|
cam->current_image->diffs_raw = diffs;
|
|
cam->current_image->diffs = diffs;
|
|
cam->imgs.image_motion.imgts = cam->current_image->imgts;
|
|
|
|
if (diffs > 0 ) {
|
|
cam->current_image->diffs_ratio = (abs(diffs_net) * 100) / diffs;
|
|
} else {
|
|
cam->current_image->diffs_ratio = 100;
|
|
}
|
|
|
|
}
|
|
|
|
static void alg_diff_smart(ctx_dev *cam)
|
|
{
|
|
|
|
unsigned char *ref = cam->imgs.ref;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
unsigned char *smartmask_final = cam->imgs.smartmask_final;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
int i, curdiff;
|
|
int imgsz = cam->imgs.motionsize;
|
|
int diffs = 0, diffs_net = 0;
|
|
int noise = cam->noise;
|
|
int smartmask_speed = cam->smartmask_speed;
|
|
int *smartmask_buffer = cam->imgs.smartmask_buffer;
|
|
int lrgchg = cam->conf->threshold_ratio_change;
|
|
|
|
imgsz = cam->imgs.motionsize;
|
|
memset(out + imgsz, 128, (imgsz / 2));
|
|
memset(out, 0, imgsz);
|
|
|
|
for (i = 0; i < imgsz; i++) {
|
|
curdiff = (*ref - *new_img);
|
|
if (smartmask_speed) {
|
|
if (abs(curdiff) > noise) {
|
|
if (cam->event_curr_nbr != cam->event_prev_nbr) {
|
|
(*smartmask_buffer) += SMARTMASK_SENSITIVITY_INCR;
|
|
}
|
|
if (!*smartmask_final) {
|
|
curdiff = 0;
|
|
}
|
|
}
|
|
smartmask_final++;
|
|
smartmask_buffer++;
|
|
}
|
|
/* Pixel still in motion after all the masks? */
|
|
if (abs(curdiff) > noise) {
|
|
*out = *new_img;
|
|
diffs++;
|
|
if (curdiff > lrgchg) {
|
|
diffs_net++;
|
|
} else if (curdiff < -lrgchg) {
|
|
diffs_net--;
|
|
}
|
|
}
|
|
out++;
|
|
ref++;
|
|
new_img++;
|
|
}
|
|
cam->current_image->diffs_raw = diffs;
|
|
cam->current_image->diffs = diffs;
|
|
cam->imgs.image_motion.imgts = cam->current_image->imgts;
|
|
|
|
if (diffs > 0 ) {
|
|
cam->current_image->diffs_ratio = (abs(diffs_net) * 100) / diffs;
|
|
} else {
|
|
cam->current_image->diffs_ratio = 100;
|
|
}
|
|
}
|
|
|
|
static void alg_diff_masksmart(ctx_dev *cam)
|
|
{
|
|
unsigned char *ref = cam->imgs.ref;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
unsigned char *mask = cam->imgs.mask;
|
|
unsigned char *smartmask_final = cam->imgs.smartmask_final;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
int i, curdiff;
|
|
int imgsz = cam->imgs.motionsize;
|
|
int diffs = 0, diffs_net = 0;
|
|
int noise = cam->noise;
|
|
int smartmask_speed = cam->smartmask_speed;
|
|
int *smartmask_buffer = cam->imgs.smartmask_buffer;
|
|
int lrgchg = cam->conf->threshold_ratio_change;
|
|
|
|
imgsz= cam->imgs.motionsize;
|
|
memset(out + imgsz, 128, (imgsz / 2));
|
|
memset(out, 0, imgsz);
|
|
|
|
for (i = 0; i < imgsz; i++) {
|
|
curdiff = (*ref - *new_img);
|
|
if (mask) {
|
|
curdiff = ((curdiff * *mask) / 255);
|
|
}
|
|
|
|
if (smartmask_speed) {
|
|
if (abs(curdiff) > noise) {
|
|
if (cam->event_curr_nbr != cam->event_prev_nbr) {
|
|
(*smartmask_buffer) += SMARTMASK_SENSITIVITY_INCR;
|
|
}
|
|
if (!*smartmask_final) {
|
|
curdiff = 0;
|
|
}
|
|
}
|
|
smartmask_final++;
|
|
smartmask_buffer++;
|
|
}
|
|
|
|
/* Pixel still in motion after all the masks? */
|
|
if (abs(curdiff) > noise) {
|
|
*out = *new_img;
|
|
diffs++;
|
|
if (curdiff > lrgchg) {
|
|
diffs_net++;
|
|
} else if (curdiff < -lrgchg) {
|
|
diffs_net--;
|
|
}
|
|
}
|
|
|
|
out++;
|
|
ref++;
|
|
new_img++;
|
|
mask++;
|
|
}
|
|
|
|
cam->current_image->diffs_raw = diffs;
|
|
cam->current_image->diffs = diffs;
|
|
cam->imgs.image_motion.imgts = cam->current_image->imgts;
|
|
|
|
if (diffs > 0 ) {
|
|
cam->current_image->diffs_ratio = (abs(diffs_net) * 100) / diffs;
|
|
} else {
|
|
cam->current_image->diffs_ratio = 100;
|
|
}
|
|
|
|
}
|
|
|
|
static bool alg_diff_fast(ctx_dev *cam)
|
|
{
|
|
ctx_images *imgs = &cam->imgs;
|
|
int i, curdiff, diffs = 0;
|
|
int step = cam->imgs.motionsize / 10000;
|
|
int noise = cam->noise;
|
|
int max_n_changes = cam->conf->threshold / 2;
|
|
unsigned char *ref = imgs->ref;
|
|
unsigned char *new_img = cam->imgs.image_vprvcy;
|
|
|
|
if (!step % 2) {
|
|
step++;
|
|
}
|
|
|
|
max_n_changes /= step;
|
|
|
|
i = imgs->motionsize;
|
|
|
|
for (; i > 0; i -= step) {
|
|
curdiff = abs(*ref - *new_img); /* Using a temp variable is 12% faster. */
|
|
if (curdiff > noise) {
|
|
diffs++;
|
|
if (diffs > max_n_changes) {
|
|
return true;
|
|
}
|
|
}
|
|
ref += step;
|
|
new_img += step;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void alg_diff_standard(ctx_dev *cam)
|
|
{
|
|
|
|
if (cam->smartmask_speed == 0) {
|
|
if (cam->imgs.mask == NULL) {
|
|
alg_diff_nomask(cam);
|
|
} else {
|
|
alg_diff_mask(cam);
|
|
}
|
|
} else {
|
|
if (cam->imgs.mask == NULL) {
|
|
alg_diff_smart(cam);
|
|
} else {
|
|
alg_diff_masksmart(cam);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void alg_lightswitch(ctx_dev *cam)
|
|
{
|
|
|
|
if (cam->conf->lightswitch_percent >= 1 && !cam->lost_connection) {
|
|
if (cam->current_image->diffs > (cam->imgs.motionsize * cam->conf->lightswitch_percent / 100)) {
|
|
MOTPLS_LOG(INF, TYPE_ALL, NO_ERRNO, _("Lightswitch detected"));
|
|
if (cam->frame_skip < (unsigned int)cam->conf->lightswitch_frames) {
|
|
cam->frame_skip = (unsigned int)cam->conf->lightswitch_frames;
|
|
}
|
|
cam->current_image->diffs = 0;
|
|
alg_update_reference_frame(cam, RESET_REF_FRAME);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* alg_update_reference_frame
|
|
*
|
|
* Called from 'motion_loop' to calculate the reference frame
|
|
* Moving objects are excluded from the reference frame for a certain
|
|
* amount of time to improve detection.
|
|
*
|
|
* Parameters:
|
|
*
|
|
* cam - current thread's context
|
|
* action - UPDATE_REF_FRAME or RESET_REF_FRAME
|
|
*
|
|
*/
|
|
void alg_update_reference_frame(ctx_dev *cam, int action)
|
|
{
|
|
int accept_timer = cam->lastrate * cam->conf->static_object_time;
|
|
int i, threshold_ref;
|
|
int *ref_dyn = cam->imgs.ref_dyn;
|
|
unsigned char *image_virgin = cam->imgs.image_vprvcy;
|
|
unsigned char *ref = cam->imgs.ref;
|
|
unsigned char *smartmask = cam->imgs.smartmask_final;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
|
|
if (cam->lastrate > 5) {
|
|
/* Match rate limit */
|
|
accept_timer /= (cam->lastrate / 3);
|
|
}
|
|
|
|
if (action == UPDATE_REF_FRAME) { /* Black&white only for better performance. */
|
|
threshold_ref = cam->noise * EXCLUDE_LEVEL_PERCENT / 100;
|
|
|
|
for (i = cam->imgs.motionsize; i > 0; i--) {
|
|
/* Exclude pixels from ref frame well below noise level. */
|
|
if (((int)(abs(*ref - *image_virgin)) > threshold_ref) && (*smartmask)) {
|
|
if (*ref_dyn == 0) { /* Always give new pixels a chance. */
|
|
*ref_dyn = 1;
|
|
} else if (*ref_dyn > accept_timer) { /* Include static Object after some time. */
|
|
*ref_dyn = 0;
|
|
*ref = *image_virgin;
|
|
} else if (*out) {
|
|
(*ref_dyn)++; /* Motionpixel? Keep excluding from ref frame. */
|
|
} else {
|
|
*ref_dyn = 0; /* Nothing special - release pixel. */
|
|
*ref = (unsigned char)((*ref + *image_virgin) / 2);
|
|
}
|
|
|
|
} else { /* No motion: copy to ref frame. */
|
|
*ref_dyn = 0; /* Reset pixel */
|
|
*ref = *image_virgin;
|
|
}
|
|
|
|
ref++;
|
|
image_virgin++;
|
|
smartmask++;
|
|
ref_dyn++;
|
|
out++;
|
|
} /* end for i */
|
|
|
|
} else { /* action == RESET_REF_FRAME - also used to initialize the frame at startup. */
|
|
/* Copy fresh image */
|
|
memcpy(cam->imgs.ref, cam->imgs.image_vprvcy, cam->imgs.size_norm);
|
|
/* Reset static objects */
|
|
memset(cam->imgs.ref_dyn, 0, cam->imgs.motionsize * sizeof(*cam->imgs.ref_dyn));
|
|
}
|
|
}
|
|
|
|
/*Copy in new reference frame*/
|
|
void alg_new_update_frame(ctx_dev *cam)
|
|
{
|
|
|
|
/* There used to be a lot more to this function before.....*/
|
|
memcpy(cam->imgs.ref, cam->imgs.image_vprvcy, cam->imgs.size_norm);
|
|
|
|
}
|
|
|
|
/*Calculate the center location of changes*/
|
|
static void alg_location_center(ctx_dev *cam)
|
|
{
|
|
int width = cam->imgs.width;
|
|
int height = cam->imgs.height;
|
|
ctx_coord *cent = &cam->current_image->location;
|
|
unsigned char *out = cam->imgs.image_motion.image_norm;
|
|
int x, y, centc = 0;
|
|
|
|
cent->x = 0;
|
|
cent->y = 0;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
if (*(out++)) {
|
|
cent->x += x;
|
|
cent->y += y;
|
|
centc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (centc) {
|
|
cent->x = cent->x / centc;
|
|
cent->y = cent->y / centc;
|
|
}
|
|
|
|
/* This allows for the redcross and boxes to be drawn*/
|
|
if (cent->x < 10) {
|
|
cent->x = 15;
|
|
}
|
|
if (cent->y < 10) {
|
|
cent->y = 15;
|
|
}
|
|
if ((cent->x + 10) > width) {
|
|
cent->x = width - 15;
|
|
}
|
|
if ((cent->y + 10) > height) {
|
|
cent->y = height - 15;
|
|
}
|
|
|
|
}
|
|
|
|
/*Calculate distribution and variances of changes*/
|
|
static void alg_location_dist(ctx_dev *cam)
|
|
{
|
|
ctx_images *imgs = &cam->imgs;
|
|
int width = cam->imgs.width;
|
|
int height = cam->imgs.height;
|
|
ctx_coord *cent = &cam->current_image->location;
|
|
unsigned char *out = imgs->image_motion.image_norm;
|
|
int x, y, centc = 0, xdist = 0, ydist = 0;
|
|
uint64_t variance_x, variance_y, variance_xy, distance_mean;
|
|
|
|
/* Note that the term variance refers to the statistical calulation. It is
|
|
* not really precise however since we are using integers rather than floats.
|
|
* This is done to improve performance over the statistically correct
|
|
* calculation for mean and variance
|
|
*/
|
|
cent->maxx = 0;
|
|
cent->maxy = 0;
|
|
cent->minx = width;
|
|
cent->miny = height;
|
|
variance_x = 0;
|
|
variance_y = 0;
|
|
distance_mean = 0;
|
|
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
if (*(out++)) {
|
|
variance_x += ((x - cent->x) * (x - cent->x));
|
|
variance_y += ((y - cent->y) * (y - cent->y));
|
|
distance_mean += (uint64_t)sqrt(
|
|
((x - cent->x) * (x - cent->x)) +
|
|
((y - cent->y) * (y - cent->y)));
|
|
|
|
if (x > cent->x) {
|
|
xdist += x - cent->x;
|
|
} else if (x < cent->x) {
|
|
xdist += cent->x - x;
|
|
}
|
|
|
|
if (y > cent->y) {
|
|
ydist += y - cent->y;
|
|
} else if (y < cent->y) {
|
|
ydist += cent->y - y;
|
|
}
|
|
|
|
centc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (centc) {
|
|
cent->minx = cent->x - xdist / centc * 3;
|
|
cent->maxx = cent->x + xdist / centc * 3;
|
|
cent->miny = cent->y - ydist / centc * 3;
|
|
cent->maxy = cent->y + ydist / centc * 3;
|
|
cent->stddev_x = (int)sqrt((variance_x / centc));
|
|
cent->stddev_y = (int)sqrt((variance_y / centc));
|
|
distance_mean = (uint64_t)(distance_mean / centc);
|
|
} else {
|
|
cent->stddev_y = 0;
|
|
cent->stddev_x = 0;
|
|
distance_mean = 0;
|
|
}
|
|
|
|
variance_xy = 0;
|
|
out = imgs->image_motion.image_norm;
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
if (*(out++)) {
|
|
variance_xy += (
|
|
((uint64_t)sqrt(((x - cent->x) * (x - cent->x)) +
|
|
((y - cent->y) * (y - cent->y))) - distance_mean) *
|
|
((uint64_t)sqrt(((x - cent->x) * (x - cent->x)) +
|
|
((y - cent->y) * (y - cent->y))) - distance_mean));
|
|
}
|
|
}
|
|
}
|
|
/* Per statistics, divide by n-1 for calc of a standard deviation */
|
|
if ((centc-1) > 0) {
|
|
cent->stddev_xy = (int)sqrt((variance_xy / (centc-1)));
|
|
}
|
|
}
|
|
|
|
/* Ensure min/max are within limits*/
|
|
static void alg_location_minmax(ctx_dev *cam)
|
|
{
|
|
|
|
int width = cam->imgs.width;
|
|
int height = cam->imgs.height;
|
|
ctx_coord *cent = &cam->current_image->location;
|
|
|
|
if (cent->maxx > width - 1) {
|
|
cent->maxx = width - 1;
|
|
} else if (cent->maxx < 0) {
|
|
cent->maxx = 0;
|
|
}
|
|
|
|
if (cent->maxy > height - 1) {
|
|
cent->maxy = height - 1;
|
|
} else if (cent->maxy < 0) {
|
|
cent->maxy = 0;
|
|
}
|
|
|
|
if (cent->minx > width - 1) {
|
|
cent->minx = width - 1;
|
|
} else if (cent->minx < 0) {
|
|
cent->minx = 0;
|
|
}
|
|
|
|
if (cent->miny > height - 1) {
|
|
cent->miny = height - 1;
|
|
} else if (cent->miny < 0) {
|
|
cent->miny = 0;
|
|
}
|
|
|
|
/* Align for better locate box handling */
|
|
cent->minx += cent->minx % 2;
|
|
cent->miny += cent->miny % 2;
|
|
cent->maxx -= cent->maxx % 2;
|
|
cent->maxy -= cent->maxy % 2;
|
|
|
|
cent->width = cent->maxx - cent->minx;
|
|
cent->height = cent->maxy - cent->miny;
|
|
cent->y = (cent->miny + cent->maxy) / 2;
|
|
}
|
|
|
|
/* Determine the location and standard deviations of changes*/
|
|
void alg_location(ctx_dev *cam)
|
|
{
|
|
|
|
alg_location_center(cam);
|
|
|
|
alg_location_dist(cam);
|
|
|
|
alg_location_minmax(cam);
|
|
}
|
|
|
|
/* Apply user or default thresholds on standard deviations*/
|
|
void alg_stddev(ctx_dev *cam)
|
|
{
|
|
|
|
/*
|
|
MOTPLS_LOG(DBG, TYPE_ALL, NO_ERRNO, "dev_x %d dev_y %d dev_xy %d, diff %d ratio %d"
|
|
, cam->current_image->location.stddev_x
|
|
, cam->current_image->location.stddev_y
|
|
, cam->current_image->location.stddev_xy
|
|
, cam->current_image->diffs
|
|
, cam->current_image->diffs_ratio);
|
|
*/
|
|
|
|
if (cam->conf->threshold_sdevx > 0) {
|
|
if (cam->current_image->location.stddev_x > cam->conf->threshold_sdevx) {
|
|
cam->current_image->diffs = 0;
|
|
return;
|
|
}
|
|
} else if (cam->conf->threshold_sdevy > 0) {
|
|
if (cam->current_image->location.stddev_y > cam->conf->threshold_sdevy) {
|
|
cam->current_image->diffs = 0;
|
|
return;
|
|
}
|
|
} else if (cam->conf->threshold_sdevxy > 0) {
|
|
if (cam->current_image->location.stddev_xy > cam->conf->threshold_sdevxy) {
|
|
cam->current_image->diffs = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
void alg_diff(ctx_dev *cam)
|
|
{
|
|
|
|
if (cam->detecting_motion || cam->motapp->conf->setup_mode) {
|
|
alg_diff_standard(cam);
|
|
} else {
|
|
if (alg_diff_fast(cam)) {
|
|
alg_diff_standard(cam);
|
|
} else {
|
|
cam->current_image->diffs = 0;
|
|
cam->current_image->diffs_raw = 0;
|
|
cam->current_image->diffs_ratio = 100;
|
|
}
|
|
}
|
|
|
|
alg_lightswitch(cam);
|
|
|
|
alg_despeckle(cam);
|
|
|
|
return;
|
|
|
|
}
|