So I am making a magic mirror. Ive made it so i can turn the screen off with my Alexa device, but i also want to stop the face recognition module from using the camera when the screen is off. When i use python.send() like this it works.
/* Magic Mirror
* Module: MMM-Face-Reco-DNN
*
* By Thierry Nischelwitzer http://nischi.ch
* MIT Licensed.
*/
// 'use strict';
const NodeHelper = require('node_helper');
const { PythonShell } = require('python-shell');
const onExit = require('signal-exit');
var pythonStarted = false;
module.exports = NodeHelper.create({
onoff: 'on',
python_start: function() {
const self = this;
const extendedDataset = this.config.extendDataset ? 'True' : 'False';
const options = {
mode: 'json',
stderrParser: line => JSON.stringify(line),
args: [
'--cascade=' + this.config.cascade,
'--encodings=' + this.config.encodings,
'--usePiCamera=' + this.config.usePiCamera,
'--source=' + this.config.source,
'--rotateCamera=' + this.config.rotateCamera,
'--method=' + this.config.method,
'--detectionMethod=' + this.config.detectionMethod,
'--interval=' + this.config.checkInterval,
'--output=' + this.config.output,
'--extendDataset=' + extendedDataset,
'--dataset=' + this.config.dataset,
'--tolerance=' + this.config.tolerance,
],
};
if (this.config.pythonPath != null && this.config.pythonPath !== '') {
options.pythonPath = this.config.pythonPath;
};
// Start face reco script
const pyshell = new PythonShell(
'modules/' + this.name + '/tools/facerecognition.py',
options
);
// check if a message of the python script is comming in
// Send message to hault face-recognition loop
pyshell.send({control: "off"}).end()
pyshell.on('message', function(message) {
// A status message has received and will log
if (message.hasOwnProperty('status')) {
console.log('[' + self.name + '] ' + message.status);
}
// Somebody new are in front of the camera, send it back to the Magic Mirror Module
if (message.hasOwnProperty('login')) {
console.log('[' + self.name + '] ' + 'Users ' + message.login.names.join(' - ') + ' logged in.');
self.sendSocketNotification('user', {action: 'login', users: message.login.names,});
}
// Somebody left the camera, send it back to the Magic Mirror Module
if (message.hasOwnProperty('logout')) {
console.log('[' + self.name + '] ' + 'Users ' + message.logout.names.join(' - ') + ' logged out.');
self.sendSocketNotification('user', {action: 'logout', users: message.logout.names,});
}
})
// Shutdown node helper
pyshell.end(function(err) {
if (err) throw err;
console.log('[' + self.name + '] ' + 'finished running...');
});
onExit(function(code, signal) {
self.destroy();
});
},
python_stop: function(){
module.exports.onoff = 'off';
},
destroy: function() {
console.log('[' + this.name + '] ' + 'Terminate python');
},
socketNotificationReceived: function(notification, payload) {
// Configuration are received
if (notification === 'CONFIG') {
this.config = payload;
// Set static output to 0, because we do not need any output for MMM
this.config.output = 0;
if (!pythonStarted) {
pythonStarted = true;
this.python_start();
}
}
// added
// receive notification from mmm-face-reco-dnn.js to stop or start
// the python script
if (notification === "START_STOP"){
if (payload == 'off'){
this.python_stop();
} else {
this.python_start();
}
}
},
stop: function() {
pythonStarted = false;
this.python_stop();
},
});
This is the python code:
# USAGE
# python pi_face_recognition.py --cascade haarcascade_frontalface_default.xml --encodings encodings.pickle
# import the necessary packages
from distutils.log import error
from ossaudiodev import control_names
from imutils.video import FPS, VideoStream
from datetime import datetime
import face_recognition
import argparse
import imutils
import pickle
import time
import cv2
import json
import sys
import signal
import os
import numpy as np
# added to control loop at the end
loop_on = True
# To properly pass JSON.stringify()ed bool command line parameters, e.g. "--extendDataset"
# See: https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
def printjson(type, message):
print(json.dumps({type: message}))
sys.stdout.flush()
def signalHandler(signal, frame):
global closeSafe
closeSafe = True
signal.signal(signal.SIGINT, signalHandler)
closeSafe = False
# construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-c", "--cascade", type=str, required=False, default="haarcascade_frontalface_default.xml",
help = "path to where the face cascade resides")
ap.add_argument("-e", "--encodings", type=str, required=False, default="encodings.pickle",
help="path to serialized db of facial encodings")
ap.add_argument("-p", "--usePiCamera", type=int, required=False, default=1,
help="Is using picamera or builtin/usb cam")
ap.add_argument("-s", "--source", required=False, default=0,
help="Use 0 for /dev/video0 or 'http://link.to/stream'")
ap.add_argument("-r", "--rotateCamera", type=int, required=False, default=0,
help="rotate camera")
ap.add_argument("-m", "--method", type=str, required=False, default="dnn",
help="method to detect faces (dnn, haar)")
ap.add_argument("-d", "--detectionMethod", type=str, required=False, default="hog",
help="face detection model to use: either `hog` or `cnn`")
ap.add_argument("-i", "--interval", type=int, required=False, default=2000,
help="interval between recognitions")
ap.add_argument("-o", "--output", type=int, required=False, default=1,
help="Show output")
ap.add_argument("-eds", "--extendDataset", type=str2bool, required=False, default=False,
help="Extend Dataset with unknown pictures")
ap.add_argument("-ds", "--dataset", required=False, default="../dataset/",
help="path to input directory of faces + images")
ap.add_argument("-t", "--tolerance", type=float, required=False, default=0.6,
help="How much distance between faces to consider it a match. Lower is more strict.")
args = vars(ap.parse_args())
# load the known faces and embeddings along with OpenCV's Haar
# cascade for face detection
printjson("status", "loading encodings + face detector...")
data = pickle.loads(open(args["encodings"], "rb").read())
detector = cv2.CascadeClassifier(args["cascade"])
# initialize the video stream and allow the camera sensor to warm up
printjson("status", "starting video stream...")
if args["source"].isdigit():
src = int(args["source"])
else:
src = args["source"]
if args["usePiCamera"] >= 1:
vs = VideoStream(usePiCamera=True, rotation=args["rotateCamera"]).start()
else:
vs = VideoStream(src=src).start()
time.sleep(2.0)
# variable for prev names
prevNames = []
# create unknown path if needed
if args["extendDataset"] is True:
unknownPath = os.path.dirname(args["dataset"] + "unknown/")
try:
os.stat(unknownPath)
except:
os.mkdir(unknownPath)
tolerance = float(args["tolerance"])
# start the FPS counter
fps = FPS().start()
# loop over frames from the video file stream
while loop_on:
# grab the frame from the threaded video stream and resize it
# to 500px (to speedup processing)
originalFrame = vs.read()
frame = imutils.resize(originalFrame, width=500)
try:
printjson('status', 'looping')
for line in sys.stdin.readlines():
printjson('status', 'got here')
control_message = json.loads(line)['control']
printjson('status', control_message)
if control_message == "off":
vs.stop()
loop_on = False
if control_message == "off":
break
except:
pass
if args["method"] == "dnn":
# load the input image and convert it from BGR (OpenCV ordering)
# to dlib ordering (RGB)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# detect the (x, y)-coordinates of the bounding boxes
# corresponding to each face in the input image
boxes = face_recognition.face_locations(rgb,
model=args["detectionMethod"])
elif args["method"] == "haar":
# convert the input frame from (1) BGR to grayscale (for face
# detection) and (2) from BGR to RGB (for face recognition)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# detect faces in the grayscale frame
rects = detector.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=5, minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE)
# OpenCV returns bounding box coordinates in (x, y, w, h) order
# but we need them in (top, right, bottom, left) order, so we
# need to do a bit of reordering
boxes = [(y, x + w, y + h, x) for (x, y, w, h) in rects]
# compute the facial embeddings for each face bounding box
encodings = face_recognition.face_encodings(rgb, boxes)
names = []
# loop over the facial embeddings
for encoding in encodings:
# compute distances between this encoding and the faces in dataset
distances = face_recognition.face_distance(data["encodings"], encoding)
minDistance = 1.0
if len(distances) > 0:
# the smallest distance is the closest to the encoding
minDistance = min(distances)
# save the name if the distance is below the tolerance
if minDistance < tolerance:
idx = np.where(distances == minDistance)[0][0]
name = data["names"][idx]
else:
name = "unknown"
# update the list of names
names.append(name)
# loop over the recognized faces
for ((top, right, bottom, left), name) in zip(boxes, names):
# draw the predicted face name on the image
cv2.rectangle(frame, (left, top), (right, bottom),
(0, 255, 0), 2)
y = top - 15 if top - 15 > 15 else top + 15
txt = name + " (" + "{:.2f}".format(minDistance) + ")"
cv2.putText(frame, txt, (left, y), cv2.FONT_HERSHEY_SIMPLEX,
0.75, (0, 255, 0), 2)
# display the image to our screen
if (args["output"] == 1):
cv2.imshow("Frame", frame)
# update the FPS counter
fps.update()
logins = []
logouts = []
# Check which names are new login and which are new logout with prevNames
for n in names:
if (prevNames.__contains__(n) == False and n is not None):
logins.append(n)
# if extendDataset is active we need to save the picture
if args["extendDataset"] is True:
# set correct path to the dataset
path = os.path.dirname(args["dataset"] + '/' + n + '/')
today = datetime.now()
cv2.imwrite(path + '/' + n + '_' + today.strftime("%Y%m%d_%H%M%S") + '.jpg', originalFrame)
for n in prevNames:
if (names.__contains__(n) == False and n is not None):
logouts.append(n)
# send inforrmation to prompt, only if something has changes
if (logins.__len__() > 0):
printjson("login", {
"names": logins
})
if (logouts.__len__() > 0):
printjson("logout", {
"names": logouts
})
# set this names as new prev names for next iteration
prevNames = names
key = cv2.waitKey(1) & 0xFF
# if the `q` key was pressed, break from the loop
if key == ord("q") or closeSafe == True:
break
time.sleep(args["interval"] / 1000)
# stop the timer and display FPS information
fps.stop()
printjson("status", "elasped time: {:.2f}".format(fps.elapsed()))
printjson("status", "approx. FPS: {:.2f}".format(fps.fps()))
# do a bit of cleanup
cv2.destroyAllWindows()
vs.stop()
I put the send command inside of the pyshell.on() section and that worked once, but now I cant replicate. Im trying to change the value of onoff with an Alexa command, which I can do. But I need to be able to send the stop command after doing so. Any help would be appreciated.