413 lines
18 KiB
JavaScript
413 lines
18 KiB
JavaScript
const movingThings = require('shinobi-node-moving-things-tracker').Tracker
|
|
module.exports = (config) => {
|
|
const objectTrackers = {}
|
|
const objectTrackerTimeouts = {}
|
|
const peopleTags = new Set(config.peopleTags || ["person", "Person", "Man", "Woman", "Boy", "Girl"]);
|
|
const trackedMatrices = {}
|
|
function shiftSet(set) {
|
|
for (const res of set) {
|
|
set.delete(res);
|
|
return res
|
|
}
|
|
}
|
|
function resetObjectTracker(trackerId,matrices){
|
|
const Tracker = movingThings.newTracker();
|
|
objectTrackers[trackerId] = {
|
|
frameCount: 1,
|
|
tracker: Tracker,
|
|
lastPositions: []
|
|
}
|
|
return objectTrackers[trackerId]
|
|
}
|
|
function setLastTracked(trackerId, trackedMatrices){
|
|
const theTracker = objectTrackers[trackerId]
|
|
theTracker.lastPositions = trackedMatrices
|
|
}
|
|
function getTracked(trackerId){
|
|
const theTracker = objectTrackers[trackerId]
|
|
const frameCount = theTracker.frameCount
|
|
const trackedObjects = theTracker.tracker.getJSONOfTrackedItems().map((matrix) => {
|
|
return {
|
|
id: matrix.id,
|
|
tag: matrix.name,
|
|
x: matrix.x,
|
|
y: matrix.y,
|
|
width: matrix.w,
|
|
height: matrix.h,
|
|
confidence: matrix.confidence,
|
|
isZombie: matrix.isZombie,
|
|
}
|
|
})
|
|
return trackedObjects;
|
|
}
|
|
function trackObject(trackerId,matrices){
|
|
if(!objectTrackers[trackerId]){
|
|
resetObjectTracker(trackerId)
|
|
}
|
|
const mappedMatrices = matrices.map((matrix) => {
|
|
return {
|
|
x: matrix.x,
|
|
y: matrix.y,
|
|
w: matrix.width,
|
|
h: matrix.height,
|
|
confidence: matrix.confidence,
|
|
name: matrix.tag,
|
|
}
|
|
});
|
|
const theTracker = objectTrackers[trackerId]
|
|
theTracker.tracker.updateTrackedItemsWithNewFrame(mappedMatrices, theTracker.frameCount);
|
|
++theTracker.frameCount
|
|
}
|
|
function trackObjectWithTimeout(trackerId,matrices){
|
|
clearTimeout(objectTrackerTimeouts[trackerId]);
|
|
objectTrackerTimeouts[trackerId] = setTimeout(() => {
|
|
objectTrackers[trackerId].tracker.reset()
|
|
delete(objectTrackers[trackerId])
|
|
delete(objectTrackerTimeouts[trackerId])
|
|
},1000 * 60);
|
|
trackObject(trackerId,matrices);
|
|
}
|
|
function objectHasMoved(matrices, options = {}) {
|
|
const { imgHeight = 1, imgWidth = 1, threshold = 0 } = options;
|
|
for (let i = 0; i < matrices.length; i++) {
|
|
const current = matrices[i];
|
|
if (i < matrices.length - 1) {
|
|
const next = matrices[i + 1];
|
|
let totalDistanceMoved = 0;
|
|
let numPointsCompared = 0;
|
|
if (next) {
|
|
// Compare each corner of the matrices
|
|
const currentCorners = [
|
|
{ x: current.x, y: current.y },
|
|
{ x: current.x + current.width, y: current.y },
|
|
{ x: current.x, y: current.y + current.height },
|
|
{ x: current.x + current.width, y: current.y + current.height }
|
|
];
|
|
const nextCorners = [
|
|
{ x: next.x, y: next.y },
|
|
{ x: next.x + next.width, y: next.y },
|
|
{ x: next.x, y: next.y + next.height },
|
|
{ x: next.x + next.width, y: next.y + next.height }
|
|
];
|
|
for (let j = 0; j < currentCorners.length; j++) {
|
|
const currentCorner = currentCorners[j];
|
|
const nextCorner = nextCorners[j];
|
|
const dx = nextCorner.x - currentCorner.x;
|
|
const dy = nextCorner.y - currentCorner.y;
|
|
const distanceMoved = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
|
|
const distanceMovedPercent =
|
|
(100 * distanceMoved) / Math.max(current.width, current.height);
|
|
totalDistanceMoved += distanceMovedPercent;
|
|
numPointsCompared++;
|
|
}
|
|
const averageDistanceMoved = totalDistanceMoved / numPointsCompared;
|
|
if (averageDistanceMoved < threshold) {
|
|
continue;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function groupMatricesById(matrices) {
|
|
const matrixById = {};
|
|
const matrixTags = {};
|
|
|
|
matrices.forEach(matrix => {
|
|
const id = matrix.id;
|
|
const tag = matrix.tag;
|
|
if (!matrixById[id]) {
|
|
matrixById[id] = [];
|
|
}
|
|
matrixTags[tag] = id;
|
|
matrixById[id].push(matrix);
|
|
});
|
|
|
|
return matrixById
|
|
}
|
|
function getAllMatricesThatMoved(monitorConfig,matrices){
|
|
const monitorDetails = monitorConfig.details
|
|
const imgWidth = parseInt(monitorDetails.detector_scale_x_object) || 1280
|
|
const imgHeight = parseInt(monitorDetails.detector_scale_y_object) || 720
|
|
const objectMovePercent = parseInt(monitorDetails.detector_object_move_percent) || 5
|
|
const groupKey = monitorConfig.ke
|
|
const monitorId = monitorConfig.mid
|
|
const trackerId = `${groupKey}${monitorId}`
|
|
const theTracker = objectTrackers[trackerId]
|
|
const lastPositions = theTracker.lastPositions
|
|
const sortedById = groupMatricesById([...lastPositions,...matrices])
|
|
const movedMatrices = []
|
|
for (const objectId in sortedById) {
|
|
const sortedList = sortedById[objectId]
|
|
if(sortedList[1]){
|
|
const matrixHasMoved = objectHasMoved(sortedList,{
|
|
threshold: objectMovePercent,
|
|
imgWidth: imgWidth,
|
|
imgHeight: imgHeight,
|
|
});
|
|
if(matrixHasMoved){
|
|
movedMatrices.push(sortedList[1])
|
|
}
|
|
}
|
|
}
|
|
return movedMatrices
|
|
}
|
|
function isMatrixNearAnother(firstMatrix, secondMatrix, imgWidth, imgHeight) {
|
|
// Calculate the distance between two rectangles
|
|
function rectDistance(rect1, rect2) {
|
|
const xDist = Math.max(rect1.x - (rect2.x + rect2.width), rect2.x - (rect1.x + rect1.width), 0);
|
|
const yDist = Math.max(rect1.y - (rect2.y + rect2.height), rect2.y - (rect1.y + rect1.height), 0);
|
|
return Math.sqrt(xDist * xDist + yDist * yDist);
|
|
}
|
|
|
|
// Calculate the overlap area
|
|
function overlapArea(rect1, rect2) {
|
|
const xOverlap = Math.max(0, Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - Math.max(rect1.x, rect2.x));
|
|
const yOverlap = Math.max(0, Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - Math.max(rect1.y, rect2.y));
|
|
return xOverlap * yOverlap;
|
|
}
|
|
|
|
const pxDistance = rectDistance(firstMatrix, secondMatrix);
|
|
const overlapAreaValue = overlapArea(firstMatrix, secondMatrix);
|
|
const totalArea = firstMatrix.width * firstMatrix.height + secondMatrix.width * secondMatrix.height - overlapAreaValue;
|
|
const overlapPercent = totalArea > 0 ? (overlapAreaValue / totalArea) * 100 : 0;
|
|
const distancePercent = Math.sqrt(Math.pow(pxDistance / imgWidth, 2) + Math.pow(pxDistance / imgHeight, 2)) * 100;
|
|
const isOverlap = overlapAreaValue > 0;
|
|
const nearThreshold = 50;
|
|
const isNear = pxDistance < nearThreshold;
|
|
|
|
return { pxDistance, overlapPercent, distancePercent, isOverlap, isNear };
|
|
}
|
|
function combinePeopleAndMiscObjects(matrices, imgWidth, imgHeight) {
|
|
const peopleMatrices = [];
|
|
const otherMatrices = [];
|
|
|
|
// Separate the matrices into two arrays
|
|
matrices.forEach(matrix => {
|
|
if (peopleTags.has(matrix.tag)) {
|
|
peopleMatrices.push({...matrix, nearBy: []});
|
|
} else {
|
|
otherMatrices.push({...matrix, associatedPeople: [], color: 'green'}); // Initialize associatedPeople array
|
|
}
|
|
});
|
|
|
|
// Compare each people matrix with each other matrix
|
|
peopleMatrices.forEach(personMatrix => {
|
|
otherMatrices.forEach(otherMatrix => {
|
|
const comparisonResult = isMatrixNearAnother(personMatrix, otherMatrix, imgWidth, imgHeight);
|
|
// console.error(`comparisonResult (${comparisonResult.overlapPercent}%) : ${otherMatrix.tag} (${otherMatrix.id}) is on ${personMatrix.tag} (${personMatrix.id}) about ${comparisonResult.overlapPercent}%`,comparisonResult)
|
|
if (comparisonResult.overlapPercent > 35) {
|
|
// Attach the person's ID to the otherMatrix
|
|
|
|
// Combine comparison result with the properties of the other matrix
|
|
personMatrix.nearBy.push({
|
|
...otherMatrix,
|
|
...comparisonResult,
|
|
});
|
|
otherMatrix.associatedPeople.push(personMatrix.id);
|
|
otherMatrix.color = 'yellow'
|
|
}
|
|
});
|
|
});
|
|
|
|
return [...peopleMatrices, ...otherMatrices];
|
|
}
|
|
function addToTrackedHistory(theEvent){
|
|
const groupKey = theEvent.ke
|
|
const monitorId = theEvent.id
|
|
const matrices = theEvent.details.matrices
|
|
matrices.forEach((matrix) => {
|
|
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
|
|
if(!trackedMatrices[trackerId])trackedMatrices[trackerId] = new Set();
|
|
trackedMatrices[trackerId].add(matrix);
|
|
if (trackedMatrices[trackerId].length > 30) {
|
|
shiftSet(trackedMatrices[trackerId]);
|
|
}
|
|
});
|
|
}
|
|
function filterOutLessSeenNearBy(theEvent){
|
|
const groupKey = theEvent.ke;
|
|
const monitorId = theEvent.id;
|
|
const matrices = theEvent.details.matrices;
|
|
matrices.forEach(matrix => {
|
|
if(!matrix.nearBy)matrix.nearBy = [];
|
|
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
|
|
const trackedSet = trackedMatrices[trackerId];
|
|
if (trackedSet && trackedSet.size > 0) {
|
|
const frequencyMap = new Map();
|
|
trackedSet.forEach(trackedMatrix => {
|
|
trackedMatrix.nearBy.forEach(nearByMatrix => {
|
|
const key = JSON.stringify(nearByMatrix); // Assuming 'nearByMatrix' is an object
|
|
frequencyMap.set(key, (frequencyMap.get(key) || 0) + 1);
|
|
});
|
|
});
|
|
matrix.nearBy = matrix.nearBy.filter(nearByMatrix => {
|
|
const key = JSON.stringify(nearByMatrix);
|
|
return frequencyMap.get(key) / trackedSet.size >= 0.8;
|
|
});
|
|
}
|
|
});
|
|
return theEvent;
|
|
}
|
|
function separateMatricesByTag(matrices) {
|
|
const groupedByTag = matrices.reduce((acc, matrix) => {
|
|
if (!acc[matrix.tag]) {
|
|
acc[matrix.tag] = [];
|
|
}
|
|
acc[matrix.tag].push(matrix);
|
|
return acc;
|
|
}, {});
|
|
return Object.values(groupedByTag);
|
|
}
|
|
function trackMatrices(theEvent){
|
|
const groupKey = theEvent.ke;
|
|
const monitorId = theEvent.id;
|
|
const eventDetails = theEvent.details;
|
|
const trackedObjects = []
|
|
separateMatricesByTag(eventDetails.matrices).forEach((matrices) => {
|
|
if(!matrices[0])return;
|
|
const matrixTag = matrices[0].tag
|
|
const trackerId = `${groupKey}${monitorId}${matrixTag}`;
|
|
trackObjectWithTimeout(trackerId,matrices);
|
|
trackedObjects.push(...getTracked(trackerId));
|
|
setLastTracked(trackerId, trackedObjects);
|
|
});
|
|
return trackedObjects;
|
|
}
|
|
function markMatricesWithRedFlagTags(theEvent, redFlags) {
|
|
const groupKey = theEvent.ke;
|
|
const monitorId = theEvent.id;
|
|
const matrices = theEvent.details.matrices;
|
|
|
|
matrices.forEach((matrix) => {
|
|
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
|
|
const trackedMatrixSet = trackedMatrices[trackerId];
|
|
|
|
if (trackedMatrixSet) {
|
|
let redFlagCount = 0; // Counter for matrices with red flag tags
|
|
|
|
trackedMatrixSet.forEach((trackedMatrix) => {
|
|
// Check if any nearBy matrix has a tag that matches the red flags
|
|
if (trackedMatrix.nearBy && trackedMatrix.nearBy.some(nearByMatrix => redFlags.includes(nearByMatrix.tag))) {
|
|
redFlagCount++; // Increment counter for each match
|
|
}
|
|
});
|
|
|
|
// Calculate if the red flag count is at least 30% of the trackedMatrixSet
|
|
if (redFlagCount / trackedMatrixSet.size >= 0.3) {
|
|
matrix.suspect = true; // Mark the matrix as suspect
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function setMissingRecentlyMatrices(theEvent, redFlags) {
|
|
const groupKey = theEvent.ke;
|
|
const monitorId = theEvent.id;
|
|
let eventMatrices = theEvent.details.matrices.map(matrix => {
|
|
return { ...matrix, missingRecently: [] }
|
|
});
|
|
let nearByFrequencies = {};
|
|
|
|
// Calculate frequencies of nearBy tags across all trackedMatrixSets
|
|
eventMatrices.forEach((matrix) => {
|
|
if(!matrix.suspect)return;
|
|
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
|
|
const trackedMatrixSet = trackedMatrices[trackerId];
|
|
|
|
if (trackedMatrixSet) {
|
|
trackedMatrixSet.forEach((trackedMatrix) => {
|
|
if (trackedMatrix.nearBy) {
|
|
trackedMatrix.nearBy.forEach((nearByMatrix) => {
|
|
nearByFrequencies[nearByMatrix.tag] = (nearByFrequencies[nearByMatrix.tag] || 0) + 1;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Determine which nearBy items are seen in at least 30% of trackedMatrixSet
|
|
let frequentItems = Object.keys(nearByFrequencies).filter(tag => nearByFrequencies[tag] / Object.keys(trackedMatrices).length >= 0.3);
|
|
|
|
// Update eventMatrices with missingRecently data
|
|
eventMatrices = eventMatrices.map(matrix => {
|
|
if(!matrix.suspect)return matrix;
|
|
let missingTags = frequentItems.filter(item => !!item && !(matrix.nearBy && matrix.nearBy.some(nearByMatrix => nearByMatrix.tag === item)));
|
|
if (missingTags.length > 0) {
|
|
matrix.missingRecently = missingTags;
|
|
}
|
|
return matrix;
|
|
});
|
|
|
|
return eventMatrices;
|
|
}
|
|
|
|
function checkMissingItemsNearRedFlagContainers(theEvent, redFlagContainers) {
|
|
const groupKey = theEvent.ke;
|
|
const monitorId = theEvent.id;
|
|
let eventMatrices = theEvent.details.matrices;
|
|
|
|
eventMatrices.forEach(matrix => {
|
|
if(!matrix.suspect)return;
|
|
// Initialize an array to store red flag containers that are near missing items, if not already present
|
|
if (!matrix.nearRedFlagContainers) {
|
|
matrix.nearRedFlagContainers = [];
|
|
}
|
|
|
|
// Proceed if the matrix has missingRecently items
|
|
if (matrix.missingRecently && matrix.missingRecently.length > 0) {
|
|
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
|
|
const trackedMatrixSet = trackedMatrices[trackerId];
|
|
|
|
if (trackedMatrixSet) {
|
|
trackedMatrixSet.forEach(trackedMatrix => {
|
|
// Check if this trackedMatrix is a redFlagContainer
|
|
if (redFlagContainers.includes(trackedMatrix.tag)) {
|
|
// Now, check each missingRecently item
|
|
matrix.missingRecently.forEach(missingItemTag => {
|
|
console.log(`missingItemTag`,missingItemTag)
|
|
// Find the original matrix for the missingItemTag to perform proximity check
|
|
const missingItemMatrix = Array.from(trackedMatrixSet).find(m => m.tag === missingItemTag);
|
|
if (missingItemMatrix) {
|
|
// Check if missingItemMatrix is near the redFlagContainer
|
|
const isNear = isMatrixNearAnother(missingItemMatrix, trackedMatrix, theEvent.imgWidth, theEvent.imgHeight);
|
|
if (isNear) {
|
|
// If near, add redFlagContainer information to the matrix
|
|
matrix.nearRedFlagContainers.push({
|
|
tag: trackedMatrix.tag,
|
|
id: trackedMatrix.id,
|
|
missingItemTag: missingItemTag
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return eventMatrices;
|
|
}
|
|
|
|
return {
|
|
trackObjectWithTimeout,
|
|
resetObjectTracker,
|
|
trackObject,
|
|
getTracked,
|
|
setLastTracked,
|
|
getAllMatricesThatMoved,
|
|
isMatrixNearAnother,
|
|
combinePeopleAndMiscObjects,
|
|
filterOutLessSeenNearBy,
|
|
separateMatricesByTag,
|
|
addToTrackedHistory,
|
|
trackMatrices,
|
|
markMatricesWithRedFlagTags,
|
|
setMissingRecentlyMatrices,
|
|
checkMissingItemsNearRedFlagContainers,
|
|
peopleTags,
|
|
}
|
|
}
|