mirror of
https://github.com/koush/scrypted.git
synced 2026-03-01 16:52:56 +00:00
141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import sdk, { MediaObject, ObjectDetection, ObjectDetectionGeneratorResult, ObjectDetectionGeneratorSession, ObjectDetectionModel, ObjectDetectionResult, ObjectDetectionSession, ObjectsDetected, ScryptedDeviceBase, VideoFrame } from '@scrypted/sdk';
|
|
|
|
import PD from 'pam-diff';
|
|
import P2P from 'pipe2pam';
|
|
import { PassThrough, Writable } from 'stream';
|
|
|
|
const defaultDifference = 9;
|
|
const defaultPercentage = 2;
|
|
|
|
|
|
class PamDiff extends ScryptedDeviceBase implements ObjectDetection {
|
|
|
|
|
|
async * generateObjectDetectionsInternal(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown> {
|
|
videoFrames = await sdk.connectRPCObject(videoFrames);
|
|
|
|
const width = 640;
|
|
const height = 360;
|
|
const p2p: Writable = new P2P();
|
|
const pt = new PassThrough();
|
|
const pamDiff = new PD({
|
|
difference: parseInt(session.settings?.difference) || defaultDifference,
|
|
percent: parseInt(session.settings?.percent) || defaultPercentage,
|
|
response: session?.settings?.motionAsObjects ? 'blobs' : 'percent',
|
|
});
|
|
pt.pipe(p2p).pipe(pamDiff);
|
|
|
|
const queued: ObjectsDetected[] = [];
|
|
pamDiff.on('diff', async (data: any) => {
|
|
const trigger = data.trigger[0];
|
|
// console.log(trigger.blobs.length);
|
|
const { blobs } = trigger;
|
|
|
|
const detections: ObjectDetectionResult[] = [];
|
|
if (blobs?.length) {
|
|
for (const blob of blobs) {
|
|
detections.push(
|
|
{
|
|
className: 'motion',
|
|
score: 1,
|
|
boundingBox: [blob.minX, blob.minY, blob.maxX - blob.minX, blob.maxY - blob.minY],
|
|
}
|
|
)
|
|
}
|
|
}
|
|
else {
|
|
detections.push(
|
|
{
|
|
className: 'motion',
|
|
score: trigger.percent / 100,
|
|
}
|
|
)
|
|
}
|
|
const event: ObjectsDetected = {
|
|
timestamp: Date.now(),
|
|
inputDimensions: [width, height],
|
|
detections,
|
|
}
|
|
queued.push(event);
|
|
});
|
|
|
|
|
|
for await (const videoFrame of videoFrames) {
|
|
const header = `P7
|
|
WIDTH ${width}
|
|
HEIGHT ${height}
|
|
DEPTH 3
|
|
MAXVAL 255
|
|
TUPLTYPE RGB
|
|
ENDHDR
|
|
`;
|
|
|
|
const { image } = videoFrame;
|
|
|
|
const buffer = await image.toBuffer({
|
|
resize: (image.width !== width || image.height !== height) ? {
|
|
width,
|
|
height,
|
|
} : undefined,
|
|
format: 'rgb',
|
|
});
|
|
pt.write(Buffer.from(header));
|
|
pt.write(buffer);
|
|
|
|
if (!queued.length) {
|
|
yield {
|
|
__json_copy_serialize_children: true,
|
|
videoFrame,
|
|
detected: {
|
|
timestamp: Date.now(),
|
|
detections: [],
|
|
}
|
|
}
|
|
}
|
|
while (queued.length) {
|
|
yield {
|
|
__json_copy_serialize_children: true,
|
|
detected: queued.pop(),
|
|
videoFrame,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
async generateObjectDetections(videoFrames: AsyncGenerator<VideoFrame, any, unknown>, session: ObjectDetectionGeneratorSession): Promise<AsyncGenerator<ObjectDetectionGeneratorResult, any, unknown>> {
|
|
return this.generateObjectDetectionsInternal(videoFrames, session);
|
|
}
|
|
|
|
async detectObjects(mediaObject: MediaObject, session?: ObjectDetectionSession): Promise<ObjectsDetected> {
|
|
throw new Error('can not run motion detection on image')
|
|
}
|
|
|
|
async getDetectionModel(): Promise<ObjectDetectionModel> {
|
|
return {
|
|
name: '@scrypted/pam-diff',
|
|
classes: ['motion'],
|
|
inputFormat: 'rgb',
|
|
inputSize: [640, 360],
|
|
settings: [
|
|
{
|
|
title: 'Motion Difference',
|
|
description: 'The color difference required to trigger motion on a pixel.',
|
|
key: 'difference',
|
|
value: this.storage.getItem('difference') || defaultDifference,
|
|
type: 'number',
|
|
},
|
|
{
|
|
title: 'Motion Percent',
|
|
description: 'The percentage of pixels required to trigger motion',
|
|
key: 'percent',
|
|
value: this.storage.getItem('percent]') || defaultPercentage,
|
|
type: 'number',
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
export default PamDiff;
|