opencv: remove legacy project

This commit is contained in:
Koushik Dutta
2021-12-10 19:40:54 -08:00
parent 96593efb73
commit 40e61a473d
9 changed files with 0 additions and 2681 deletions

View File

@@ -1,4 +0,0 @@
.DS_Store
out/
node_modules/
dist/

View File

@@ -1,4 +0,0 @@
.DS_Store
out/
node_modules/
dist/*.map

View File

@@ -1,22 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Scrypted Debugger",
"address": "${config:scrypted.debugHost}",
"port": 10081,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
}
]
}

View File

@@ -1,3 +0,0 @@
{
"scrypted.debugHost": "127.0.0.1",
}

View File

@@ -1,20 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "scrypted: deploy+debug",
"type": "shell",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"command": "npm run scrypted-vscode-launch ${config:scrypted.debugHost}",
},
]
}

View File

@@ -1,15 +0,0 @@
# OpenCV Motion detection for VideoCamera devices.
## npm commands
* npm run scrypted-webpack
* npm run scrypted-deploy <ipaddress>
* npm run scrypted-debug <ipaddress>
## scrypted distribution via npm
1. Ensure package.json is set up properly for publishing on npm.
2. npm publish
## Visual Studio Code configuration
* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server.
* Launch Scrypted Debugger from the launch menu.

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
{
"name": "@scrypted/opencv",
"version": "0.0.19",
"description": "Motion Detection for VideoCameras.",
"author": "Scrypted",
"license": "Apache-2.0",
"scripts": {
"prepublishOnly": "NODE_ENV=production scrypted-webpack",
"prescrypted-vscode-launch": "scrypted-webpack",
"scrypted-vscode-launch": "scrypted-deploy-debug",
"scrypted-deploy-debug": "scrypted-deploy-debug",
"scrypted-debug": "scrypted-debug",
"scrypted-deploy": "scrypted-deploy",
"scrypted-readme": "scrypted-readme",
"scrypted-package-json": "scrypted-package-json",
"scrypted-webpack": "scrypted-webpack"
},
"keywords": [
"scrypted",
"plugin",
"opencv",
"motion",
"object",
"detect",
"detection"
],
"scrypted": {
"name": "OpenCV Motion Detection",
"singleInstance": true,
"type": "API",
"interfaces": [
"MixinProvider"
],
"realfs": true
},
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk"
},
"optionalDependencies": {
"@koush/opencv4nodejs": "^5.7.2"
},
"devDependencies": {
"@types/node": "^14.17.11"
}
}

View File

@@ -1,296 +0,0 @@
import { MixinProvider, ScryptedDeviceType, ScryptedInterface, VideoCamera, MediaStreamOptions, Settings, Setting, ScryptedMimeTypes, FFMpegInput, MotionSensor, ScryptedDevice } from '@scrypted/sdk';
import sdk from '@scrypted/sdk';
import { once } from 'events';
import { SettingsMixinDeviceBase } from "../../../common/src/settings-mixin";
import { FFMpegRebroadcastSession, startRebroadcastSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
import { StreamChunk, createRawVideoParser } from '@scrypted/common/src/stream-parser';
import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
import cv, { Mat, Size } from "@koush/opencv4nodejs";
const { mediaManager, log, systemManager, deviceManager } = sdk;
const defaultInterval = 10;
const defaultArea = 2000;
const defaultThreshold = 25;
async function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class OpenCVMixin extends SettingsMixinDeviceBase<VideoCamera> implements MotionSensor, Settings {
area: number;
threshold: number;
released = false;
sessionPromise: Promise<FFMpegRebroadcastSession>;
constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
super(mixinDevice, mixinDeviceState, {
providerNativeId,
mixinDeviceInterfaces,
group: "OpenCV Settings",
groupKey: "opencv",
});
this.area = parseInt(localStorage.getItem('area')) || defaultArea;
this.threshold = parseInt(localStorage.getItem('threshold')) || defaultThreshold;
// to prevent noisy startup/reload/shutdown, delay the prebuffer starting.
this.console.log('session starting in 5 seconds');
setTimeout(async () => {
while (!this.released) {
try {
await this.start();
this.console.log('shut down gracefully');
}
catch (e) {
this.console.error(this.name, 'session unexpectedly terminated, restarting in 5 seconds', e);
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
}, 5000);
}
async start() {
const realDevice = systemManager.getDeviceById<VideoCamera>(this.id);
let selectedStream: MediaStreamOptions;
const motionChannel = this.storage.getItem('motionChannel');
if (motionChannel) {
const msos = await realDevice.getVideoStreamOptions();
selectedStream = msos.find(mso => mso.name === motionChannel);
}
const ffmpegInput = JSON.parse((await mediaManager.convertMediaObjectToBuffer(await realDevice.getVideoStream(selectedStream), ScryptedMimeTypes.FFmpegInput)).toString()) as FFMpegInput;
let video = ffmpegInput.mediaStreamOptions?.video;
if (!video?.width || !video?.height) {
this.console.error("Width and Height were not provided. Defaulting to 1920x1080.");
video = {
width: 1920,
height: 1080,
};
}
let { width, height } = video;
// we'll use an image 1/6 of the dimension in size for motion.
// however, opencv also expects that input images are modulo 6.
// so make sure both are satisfied.
if (width > height) {
if (width > 318) {
height = height / width * 318;
width = 318;
}
}
else {
if (height > 318) {
width = width / height * 318;
height = 318;
}
}
// square em up
width = Math.floor(width / 6) * 6;
height = Math.floor(height / 6) * 6;
this.sessionPromise = startRebroadcastSession(ffmpegInput, {
console: this.console,
parsers: {
rawvideo: createRawVideoParser({
size: {
width,
height,
},
everyNFrames: parseInt(this.storage.getItem('interval')) || 10,
}),
}
});
const session = await this.sessionPromise;
let watchdog: NodeJS.Timeout;
const restartWatchdog = () => {
clearTimeout(watchdog);
watchdog = setTimeout(() => {
this.console.error('watchdog for raw video parser timed out... killing ffmpeg session');
session.kill();
}, 60000);
}
session.events.on('rawvideo-data', restartWatchdog);
session.events.once('killed', () => {
clearTimeout(watchdog);
});
restartWatchdog();
try {
await this.startWrapped(session);
}
finally {
session.kill();
}
}
async startWrapped(session: FFMpegRebroadcastSession) {
let previousFrame: Mat;
let timeout: NodeJS.Timeout;
const triggerMotion = () => {
this.motionDetected = true;
clearTimeout(timeout);
setTimeout(() => this.motionDetected = false, 10000);
}
this.motionDetected = false;
while (!this.released) {
if (this.motionDetected) {
// during motion just eat the frames.
previousFrame = undefined;
await sleep(1000);
continue;
}
const args = await once(session.events, 'rawvideo-data');
const chunk: StreamChunk = args[0];
// should be one chunk from the parser, but let's not assume that.
const raw = chunk.chunks.length === 1 ? chunk.chunks[0] : Buffer.concat(chunk.chunks);
const mat = new Mat(raw, chunk.height * 3 / 2, chunk.width, cv.CV_8U);
const gray = await mat.cvtColorAsync(cv.COLOR_YUV420p2GRAY);
const curFrame = await gray.gaussianBlurAsync(new Size(21, 21), 0);
try {
if (!previousFrame) {
continue;
}
const frameDelta = previousFrame.absdiff(curFrame);
const thresh = await frameDelta.thresholdAsync(this.threshold, 255, cv.THRESH_BINARY);
const dilated = await thresh.dilateAsync(cv.getStructuringElement(cv.MORPH_ELLIPSE, new cv.Size(4, 4)), new cv.Point2(-1, -1), 2)
const contours = await dilated.findContoursAsync(cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE);
const filteredContours = contours.filter(cnt => cnt.area > this.area).map(cnt => cnt.area);
if (filteredContours.length) {
this.console.log('motion triggered by area(s)', filteredContours.join(','));
triggerMotion();
}
}
finally {
previousFrame = curFrame;
}
}
}
async getMixinSettings(): Promise<Setting[]> {
const settings: Setting[] = [];
const realDevice = systemManager.getDeviceById<VideoCamera>(this.id);
let msos: MediaStreamOptions[] = [];
try {
msos = await realDevice.getVideoStreamOptions();
}
catch (e) {
}
if (this.mixinDeviceInterfaces.includes(ScryptedInterface.MotionSensor)) {
settings.push({
title: 'Existing Motion Sensor',
description: 'This camera has a built in motion sensor. Using OpenCV Motion Sensing may be unnecessary and will use additional CPU.',
readonly: true,
value: 'WARNING',
key: 'existingMotionSensor',
})
}
if (msos?.length) {
settings.push({
title: 'Motion Stream',
key: 'motionChannel',
value: this.storage.getItem('motionChannel') || msos[0].name,
description: 'The stream to use for detecting motion. Using the lowest resolution stream is recommended.',
choices: msos.map(mso => mso.name),
});
}
settings.push(
{
title: "Motion Area",
description: "The area size required to trigger motion. Higher values (larger areas) are less sensitive.",
value: this.storage.getItem('area') || defaultArea.toString(),
key: 'area',
placeholder: defaultArea.toString(),
type: 'number',
},
{
title: "Motion Threshold",
description: "The threshold required to consider a pixel changed. Higher values (larger changes) are less sensitive.",
value: this.storage.getItem('threshold') || defaultThreshold.toString(),
key: 'threshold',
placeholder: defaultThreshold.toString(),
type: 'number',
},
{
title: "Frame Analysis Interval",
description: "The number of frames to wait between motion analysis.",
value: this.storage.getItem('interval') || defaultInterval.toString(),
key: 'interval',
placeholder: defaultInterval.toString(),
type: 'number',
},
);
return settings;
}
async putMixinSetting(key: string, value: string | number | boolean): Promise<void> {
this.storage.setItem(key, value.toString());
if (key === 'area')
this.area = parseInt(value.toString()) || defaultArea;
if (key === 'threshold')
this.threshold = parseInt(value.toString()) || defaultThreshold;
if (key === 'motionChannel') {
this.sessionPromise?.then(session => session.kill());
}
}
release() {
this.released = true;
}
}
class OpenCVProvider extends AutoenableMixinProvider implements MixinProvider {
constructor(nativeId?: string) {
super(nativeId);
// trigger opencv.
for (const id of Object.keys(systemManager.getSystemState())) {
const device = systemManager.getDeviceById<VideoCamera>(id);
if (!device.mixins?.includes(this.id))
continue;
device.getVideoStreamOptions();
}
}
async shouldEnableMixin(device: ScryptedDevice) {
return !device.interfaces.includes(ScryptedInterface.MotionSensor);
}
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
if (!interfaces.includes(ScryptedInterface.VideoCamera))
return null;
return [ScryptedInterface.MotionSensor, ScryptedInterface.Settings];
}
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
this.setHasEnabledMixin(mixinDeviceState.id);
return new OpenCVMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
}
async releaseMixin(id: string, mixinDevice: any) {
mixinDevice.release();
}
}
export default new OpenCVProvider();