videoanalysis: better object timeout. fix motion zones.

This commit is contained in:
Koushik Dutta
2022-07-25 10:34:33 -07:00
parent 19e366ed36
commit 02045cab65
4 changed files with 46 additions and 45 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/objectdetector",
"version": "0.0.46",
"version": "0.0.47",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/objectdetector",
"version": "0.0.46",
"version": "0.0.47",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/objectdetector",
"version": "0.0.46",
"version": "0.0.47",
"description": "Scrypted Video Analysis Plugin. Installed alongside a detection service like OpenCV or TensorFlow.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -2,7 +2,7 @@ export class DenoisedDetectionEntry<T> {
id: string;
name: string;
detection: T;
timeout?: NodeJS.Timeout;
lastSeen: number;
}
export interface DenoisedDetectionOptions<T> {
@@ -15,6 +15,7 @@ export function denoiseDetections<T>(previousDetections: DenoisedDetectionEntry<
currentDetections: DenoisedDetectionEntry<T>[],
options?: DenoisedDetectionOptions<T>
) {
const now = Date.now();
const newAndExisting: DenoisedDetectionEntry<T>[] = [];
for (const cd of currentDetections) {
let index = -1;
@@ -28,24 +29,20 @@ export function denoiseDetections<T>(previousDetections: DenoisedDetectionEntry<
}
else {
const [found] = previousDetections.splice(index, 1);
if (found.timeout) {
clearTimeout(found.timeout);
found.timeout = undefined;
}
found.lastSeen = now;
newAndExisting.push(cd);
}
}
const purgeTime = options?.timeout || 10000;
// anything remaining in previousDetections at this point has possibly left the scene.
for (const cd of previousDetections) {
if (!cd.timeout) {
cd.timeout = setTimeout(() => {
const index = previousDetections.findIndex(check => check === cd);
if (index !== -1)
previousDetections.splice(index, 1);
options?.removed?.(cd);
}, options?.timeout || 10000);
}
for (const cd of previousDetections.slice()) {
if (now - cd.lastSeen < purgeTime)
continue;
const index = previousDetections.findIndex(check => check === cd);
if (index !== -1)
previousDetections.splice(index, 1);
options?.removed?.(cd);
}
// add all the detections that are pending removal

View File

@@ -220,6 +220,8 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
if (!this.internal) {
stream = await this.cameraDevice.getVideoStream({
destination: 'low-resolution',
// request no prebuffer because it will throw off detection timestamps.
prebuffer: 0,
});
}
else {
@@ -250,6 +252,34 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
if (detectionInput)
this.setDetection(this.detectionId, detectionInput);
// determine zones of the objects, if configured.
if (detection.detections && Object.keys(this.zones).length) {
for (const o of detection.detections) {
if (!o.boundingBox)
continue;
o.zones = []
let [x, y, width, height] = o.boundingBox;
let x2 = x + width;
let y2 = y + height;
// the zones are point paths in percentage format
x = x * 100 / detection.inputDimensions[0];
y = y * 100 / detection.inputDimensions[1];
x2 = x2 * 100 / detection.inputDimensions[0];
y2 = y2 * 100 / detection.inputDimensions[1];
const box = [[x, y], [x2, y], [x2, y2], [x, y2]];
for (const [zone, zoneValue] of Object.entries(this.zones)) {
if (polygonOverlap(box, zoneValue)) {
this.console.log(o.className, 'inside', zone);
o.zones.push(zone);
}
}
}
}
// if this detector supports bounding boxes, and there are zones configured,
// filter the detections to the zones.
detection.detections = detection.detections.filter(o => !o.boundingBox || o?.zones?.length);
if (this.hasMotionType) {
const found = detection.detections?.find(d => d.className === 'motion');
if (found) {
@@ -263,35 +293,8 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
}
}
if (!this.hasMotionType || this.motionAsObjects) {
if (detection.detections && Object.keys(this.zones).length) {
for (const o of detection.detections) {
if (!o.boundingBox)
continue;
o.zones = []
let [x, y, width, height] = o.boundingBox;
let x2 = x + width;
let y2 = y + height;
// the zones are point paths in percentage format
x = x * 100 / detection.inputDimensions[0];
y = y * 100 / detection.inputDimensions[1];
x2 = x2 * 100 / detection.inputDimensions[0];
y2 = y2 * 100 / detection.inputDimensions[1];
const box = [[x, y], [x2, y], [x2, y2], [x, y2]];
for (const [zone, zoneValue] of Object.entries(this.zones)) {
if (polygonOverlap(box, zoneValue)) {
this.console.log(o.className, 'inside', zone);
o.zones.push(zone);
}
}
}
// if this detector supports bounding boxes, and there are zones configured,
// filter the detections to the zones.
detection.detections = detection.detections.filter(o => !o.boundingBox || o?.zones?.length);
}
if (!this.hasMotionType || this.motionAsObjects)
this.onDeviceEvent(ScryptedInterface.ObjectDetector, detection);
}
}
async extendedObjectDetect() {
@@ -324,6 +327,7 @@ class ObjectDetectionMixin extends SettingsMixinDeviceBase<VideoCamera & Camera
id: detection.id,
name: detection.className,
detection,
lastSeen: detectionResult.timestamp,
})), {
timeout: this.detectionTimeout * 1000,
added: d => found.push(d),