├── __pycache__ ├── al_Alerts.cpython-35.pyc ├── camera_pi.cpython-35.pyc ├── eml_Email.cpython-35.pyc ├── image_draw.cpython-35.pyc ├── base_camera.cpython-35.pyc └── ms_cognitive_imagerec.cpython-35.pyc ├── templates.yaml ├── templates ├── index.html ├── who_see.html └── what_see.html ├── camera.py ├── camera_opencv.py ├── camera_pi.py ├── README.md ├── eml_Email.py ├── st_Camera.py ├── al_Alerts.py ├── image_draw.py ├── base_camera.py ├── ms_cognitive_imagerec.py └── st_main.py /__pycache__/al_Alerts.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/al_Alerts.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/camera_pi.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/camera_pi.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/eml_Email.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/eml_Email.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/image_draw.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/image_draw.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/base_camera.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/base_camera.cpython-35.pyc -------------------------------------------------------------------------------- /__pycache__/ms_cognitive_imagerec.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walkken/SeeTalker/HEAD/__pycache__/ms_cognitive_imagerec.cpython-35.pyc -------------------------------------------------------------------------------- /templates.yaml: -------------------------------------------------------------------------------- 1 | welcome: Welcome to Ken's Raspberry Pi 2 | round: Can you repeat the numbers {{ numbers|join(", ") }} backwards? 3 | 4 | win: Good job! 5 | 6 | lose: Sorry, that's the wrong answer. -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Streaming 4 | 5 | 6 |

Live Video Streaming

7 | 8 | 9 | -------------------------------------------------------------------------------- /templates/who_see.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Who Do I See? 4 | 5 | 6 |

{{who_see}}

7 | 8 |

9 | {% if who_see_image != None %} 10 | 11 | {% endif %} 12 |

13 | -------------------------------------------------------------------------------- /templates/what_see.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | What do I See? 4 | 5 | 6 |

{{what_see}}!

7 | 8 |

9 | {% if what_see_image != None %} 10 | 11 | {% endif %} 12 |

13 | -------------------------------------------------------------------------------- /camera.py: -------------------------------------------------------------------------------- 1 | import time 2 | from base_camera import BaseCamera 3 | 4 | 5 | class Camera(BaseCamera): 6 | """An emulated camera implementation that streams a repeated sequence of 7 | files 1.jpg, 2.jpg and 3.jpg at a rate of one frame per second.""" 8 | imgs = [open(f + '.jpg', 'rb').read() for f in ['1', '2', '3']] 9 | 10 | @staticmethod 11 | def frames(): 12 | while True: 13 | time.sleep(1) 14 | yield Camera.imgs[int(time.time()) % 3] -------------------------------------------------------------------------------- /camera_opencv.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | from base_camera import BaseCamera 3 | 4 | 5 | class Camera(BaseCamera): 6 | video_source = 0 7 | 8 | @staticmethod 9 | def set_video_source(source): 10 | Camera.video_source = source 11 | 12 | @staticmethod 13 | def frames(): 14 | camera = cv2.VideoCapture(Camera.video_source) 15 | if not camera.isOpened(): 16 | raise RuntimeError('Could not start camera.') 17 | 18 | while True: 19 | # read current frame 20 | _, img = camera.read() 21 | 22 | # encode as a jpeg image and return it 23 | yield cv2.imencode('.jpg', img)[1].tobytes() -------------------------------------------------------------------------------- /camera_pi.py: -------------------------------------------------------------------------------- 1 | import io 2 | import time 3 | import picamera 4 | from base_camera import BaseCamera 5 | 6 | class Camera(BaseCamera): 7 | @staticmethod 8 | def frames(): 9 | with picamera.PiCamera() as camera: 10 | # let camera warm up 11 | time.sleep(2) 12 | 13 | camera.resolution = (800, 640) 14 | camera.rotation = 270 15 | 16 | 17 | stream = io.BytesIO() 18 | for _ in camera.capture_continuous(stream, 'jpeg', 19 | use_video_port=True): 20 | # return current frame 21 | stream.seek(0) 22 | yield stream.read() 23 | 24 | # reset stream for next frame 25 | stream.seek(0) 26 | stream.truncate() 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SeeTalker 2 | SeeTalker gives Amazon's Alexa the ability to tell you what it sees. SeeTalker is triggered by Alexa requests to describe what it sees or take a selfie. SeeTalker calls Microsoft's Face API and Vision Services API to interpret video images, returning a natural language response using Alexa. 3 | 4 | SeeTalker was developed by the original developer to explore image recognition and Alexa skill development. The develper also learned Python along the way and how to utlize a Raspberry Pi as a web server and IoT device. 5 | 6 | Key learning opportunities from SeeTalker: 7 | 1. Alexa skill development 8 | 2. Utilize Raspberry Pi as a web server 9 | 3. Microsoft Cognitive Services: Face API, Vision Services API 10 | 4. Alexa skills directives 11 | 5. Sending Email 12 | 6. Python Flask and Amazon Ask web services framework (Flask-Ask) 13 | 14 | An overview and code documentation will be maintained on project Hackster.IO site at: 15 | https://www.hackster.io/ken-walker/raspberry-pi-image-recognition-with-alexa-voice-6b7a01 16 | -------------------------------------------------------------------------------- /eml_Email.py: -------------------------------------------------------------------------------- 1 | import os 2 | #!/usr/bin/env python 3 | import smtplib 4 | from email.mime.multipart import MIMEMultipart 5 | from email.mime.text import MIMEText 6 | from email.mime.base import MIMEBase 7 | from email import encoders 8 | 9 | # set for gmail 10 | smtp_server = "smtp.gmail.com" 11 | 12 | def eml_SendEmail(fromAddr, toAddr, email_pwd, subject, body, attachFile=None): 13 | 14 | msg = MIMEMultipart() 15 | msg["From"] = fromAddr 16 | msg["To"] = toAddr 17 | msg["Subject"] = subject 18 | msg.attach(MIMEText(body, "plain")) 19 | 20 | if attachFile != None: 21 | attachment = open(attachFile, 'rb') 22 | 23 | part = MIMEBase("application", "octet-stream") 24 | part.set_payload((attachment).read()) 25 | encoders.encode_base64(part) 26 | part.add_header('Content-Disposition', 'attachment', filename=os.path.basename(attachFile)) 27 | msg.attach(part) 28 | 29 | server = smtplib.SMTP(smtp_server, 587) 30 | server.starttls() 31 | server.login(fromAddr, email_pwd) 32 | text = msg.as_string() 33 | 34 | server.sendmail(fromAddr, toAddr, text) 35 | server.quit() 36 | -------------------------------------------------------------------------------- /st_Camera.py: -------------------------------------------------------------------------------- 1 | import io 2 | import picamera 3 | import logging 4 | 5 | from threading import Condition 6 | from Thread import Timer 7 | 8 | 9 | class StreamingOutput(object): 10 | def __init__(self): 11 | self.frame = None 12 | self.buffer = io.BytesIO() 13 | self.condition = Condition() 14 | 15 | def write(self, buf): 16 | if buf.startswith(b'\xff\xd8'): 17 | # New frame, copy the existing buffer's content and notify all 18 | # clients it's available 19 | self.buffer.truncate() 20 | with self.condition: 21 | self.frame = self.buffer.getvalue() 22 | self.condition.notify_all() 23 | self.buffer.seek(0) 24 | return self.buffer.write(buf) 25 | 26 | 27 | def cam_StartLiveCamera (): 28 | 29 | camera = picamera.PiCamera(resolution='640x480', framerate=24) 30 | 31 | output = StreamingOutput() 32 | #Uncomment the next line to change your Pi's Camera rotation (in degrees) 33 | camera.rotation = 270 34 | print ("start recording") 35 | camera.start_recording(output, format='mjpeg') 36 | 37 | camera.rotation = 90 38 | camera.vflip = 1 39 | 40 | return(camera, output) 41 | -------------------------------------------------------------------------------- /al_Alerts.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | 5 | def al_StartAlertTriggers (interval_secs, AlertHandler, fRepeat=True): 6 | if fRepeat: # repeat timer 7 | tmr = RepeatedTimer(interval_secs, AlertHandler) 8 | else: # one-time timer 9 | tmr = threading.Timer(interval_secs, AlertHandler) 10 | tmr.start() # start the timer 11 | 12 | return (tmr) 13 | 14 | 15 | def al_CancelAlertTriggers (tmr): 16 | #tmr.cancel() 17 | tmr.stop() 18 | 19 | 20 | def CheckAlertTriggers (): 21 | print ("Check Alert Triggers") 22 | return (True) 23 | 24 | 25 | class RepeatedTimer(object): 26 | def __init__(self, interval, function, *args, **kwargs): 27 | self._timer = None 28 | self.interval = interval 29 | self.function = function 30 | self.args = args 31 | self.kwargs = kwargs 32 | self.is_running = False 33 | self.next_call = time.time() 34 | self.start() 35 | 36 | def _run(self): 37 | self.is_running = False 38 | self.start() 39 | self.function(*self.args, **self.kwargs) 40 | 41 | def start(self): 42 | if not self.is_running: 43 | self.next_call += self.interval 44 | self._timer = threading.Timer(self.next_call - time.time(), self._run) 45 | self._timer.start() 46 | self.is_running = True 47 | 48 | def stop(self): 49 | self._timer.cancel() 50 | self.is_running = False -------------------------------------------------------------------------------- /image_draw.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from io import BytesIO 3 | from PIL import Image, ImageDraw, ImageFont 4 | 5 | 6 | #Convert width height to a point in a rectangle 7 | def getRectangle(faceDictionary): 8 | rect = faceDictionary['faceRectangle'] 9 | left = rect['left'] 10 | top = rect['top'] 11 | bottom = top + rect['height'] 12 | right = left + rect['width'] 13 | return ((left, top), (right, bottom)) 14 | 15 | 16 | def drawFaceRectangles(face, image, upperLeftText, upperRightText, lowerText1, lowerText2): 17 | 18 | if len(face) > 0 : 19 | 20 | #For each face returned use the face rectangle and draw a red box. 21 | draw = ImageDraw.Draw(image) 22 | 23 | #font = ImageFont.truetype('/usr/share/fonts/truetype/msttcorefonts/arial.ttf', 10) 24 | #size = font.getsize(showtext) 25 | 26 | (left, top), (right, bottom) = getRectangle(face) 27 | draw.rectangle(getRectangle(face), outline='red') 28 | 29 | font_size = 16 30 | font_name = "FreeSans.ttf" 31 | font_path = "/usr/share/fonts//truetype/freefont/%s"% font_name 32 | font = ImageFont.truetype(font_path, font_size) 33 | 34 | # UpperLeftText needs to be required 35 | (text_width, text_height) = font.getsize(upperLeftText) 36 | 37 | if len(upperLeftText) > 0: 38 | draw.text((left, top-text_height), upperLeftText, font=font) 39 | if len(upperRightText) > 0: 40 | draw.text((right-text_width, top-text_height), upperRightText, font=font) 41 | if len(lowerText1) > 0: 42 | draw.text((left, bottom), lowerText1, font=font) 43 | if len(lowerText2) > 0: 44 | draw.text((left, bottom+text_height), lowerText2, font=font) 45 | 46 | return (True) 47 | else: 48 | return (False) 49 | -------------------------------------------------------------------------------- /base_camera.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | try: 4 | from greenlet import getcurrent as get_ident 5 | except ImportError: 6 | try: 7 | from thread import get_ident 8 | except ImportError: 9 | from _thread import get_ident 10 | 11 | 12 | class CameraEvent(object): 13 | """An Event-like class that signals all active clients when a new frame is 14 | available. 15 | """ 16 | def __init__(self): 17 | self.events = {} 18 | 19 | def wait(self): 20 | """Invoked from each client's thread to wait for the next frame.""" 21 | ident = get_ident() 22 | if ident not in self.events: 23 | # this is a new client 24 | # add an entry for it in the self.events dict 25 | # each entry has two elements, a threading.Event() and a timestamp 26 | self.events[ident] = [threading.Event(), time.time()] 27 | return self.events[ident][0].wait() 28 | 29 | def set(self): 30 | """Invoked by the camera thread when a new frame is available.""" 31 | now = time.time() 32 | remove = None 33 | for ident, event in self.events.items(): 34 | if not event[0].isSet(): 35 | # if this client's event is not set, then set it 36 | # also update the last set timestamp to now 37 | event[0].set() 38 | event[1] = now 39 | else: 40 | # if the client's event is already set, it means the client 41 | # did not process a previous frame 42 | # if the event stays set for more than 5 seconds, then assume 43 | # the client is gone and remove it 44 | if now - event[1] > 5: 45 | remove = ident 46 | if remove: 47 | del self.events[remove] 48 | 49 | def clear(self): 50 | """Invoked from each client's thread after a frame was processed.""" 51 | self.events[get_ident()][0].clear() 52 | 53 | 54 | class BaseCamera(object): 55 | thread = None # background thread that reads frames from camera 56 | frame = None # current frame is stored here by background thread 57 | last_access = 0 # time of last client access to the camera 58 | event = CameraEvent() 59 | 60 | def __init__(self): 61 | """Start the background camera thread if it isn't running yet.""" 62 | if BaseCamera.thread is None: 63 | BaseCamera.last_access = time.time() 64 | 65 | # start background frame thread 66 | BaseCamera.thread = threading.Thread(target=self._thread) 67 | BaseCamera.thread.start() 68 | 69 | # wait until frames are available 70 | while self.get_frame() is None: 71 | time.sleep(0) 72 | 73 | def get_frame(self): 74 | """Return the current camera frame.""" 75 | BaseCamera.last_access = time.time() 76 | 77 | # wait for a signal from the camera thread 78 | BaseCamera.event.wait() 79 | BaseCamera.event.clear() 80 | 81 | return BaseCamera.frame 82 | 83 | @staticmethod 84 | def frames(): 85 | """"Generator that returns frames from the camera.""" 86 | raise RuntimeError('Must be implemented by subclasses.') 87 | 88 | 89 | @classmethod 90 | def _thread(cls): 91 | """Camera background thread.""" 92 | print('Starting camera thread.') 93 | frames_iterator = cls.frames() 94 | for frame in frames_iterator: 95 | BaseCamera.frame = frame 96 | BaseCamera.event.set() # send signal to clients 97 | time.sleep(0) 98 | 99 | # if there hasn't been any clients asking for frames in 100 | # the last 10 seconds then stop the thread 101 | #if time.time() - BaseCamera.last_access > 60: 102 | # frames_iterator.close() 103 | # print('Stopping camera thread due to inactivity.') 104 | #break 105 | BaseCamera.thread = None -------------------------------------------------------------------------------- /ms_cognitive_imagerec.py: -------------------------------------------------------------------------------- 1 | import http.client, urllib, base64, json 2 | import requests 3 | from collections import namedtuple 4 | import operator 5 | 6 | # change this 7 | # the subscription and endpoints for Face API and Vision API 8 | # make sure endpoint is correct for your region, default is western us 9 | face_api_sub_key = 'your subscription key' 10 | face_api_endpoint = 'https://westus.api.cognitive.microsoft.com/face/v1.0/detect' 11 | 12 | vision_api_sub_key = 'your subscription key' 13 | vision_api_endpoint = 'westus.api.cognitive.microsoft.com' 14 | 15 | 16 | class FaceAttribs(object): 17 | """__init__() functions as the class constructor""" 18 | def __init__(self, age=None, gender=None, gender_noun=None, gender_possessive=None, glasses=None, glasses_txt=None, top_emotion=None, top_emotion_conf=None, profile_txt=None): 19 | self.age = age 20 | self.gender = gender 21 | self.gender_noun = gender_noun 22 | self.gender_possessive = gender_possessive 23 | self.glasses = glasses 24 | self.top_emotion = top_emotion 25 | self.top_emotion_conf = top_emotion_conf 26 | self.profile_txt = profile_txt 27 | self.glasses_txt = glasses_txt 28 | 29 | 30 | def ms_GetFaceAttribs (face): 31 | 32 | if len(face)> 0: 33 | 34 | faceAttribs = FaceAttribs() 35 | print(face["faceAttributes"]["age"]) 36 | 37 | faceAttribs.age = face["faceAttributes"]["age"] 38 | faceAttribs.gender = face["faceAttributes"]["gender"] 39 | faceAttribs.glasses = face["faceAttributes"]["glasses"] 40 | 41 | if faceAttribs.gender == 'male' : 42 | faceAttribs.gender_noun = "He" 43 | faceAttribs.gender_possessive = "His" 44 | else: 45 | faceAttribs.gender_noun= "She" 46 | faceAttribs.gender_possessive = "Her" 47 | 48 | emotion = face["faceAttributes"]["emotion"] 49 | sort_emotion = sorted(emotion.items(), key=operator.itemgetter(1), reverse=True) 50 | 51 | if faceAttribs.glasses == 'NoGlasses': 52 | faceAttribs.glasses_txt = "No Glasses" 53 | else: 54 | faceAttribs.glasses_txt = "wearing %s"% (faceAttribs.glasses) 55 | 56 | 57 | faceAttribs.top_emotion = sort_emotion[0][0] 58 | faceAttribs.top_emotion_conf = sort_emotion[0][1] *100 59 | faceAttribs.profile_txt = "%s age %d"% (faceAttribs.gender, faceAttribs.age) 60 | 61 | return faceAttribs 62 | else: 63 | return False 64 | 65 | def ms_WhoDoYouSee (body): 66 | 67 | # Request headers. 68 | header = { 69 | 'Content-Type': 'application/octet-stream', 70 | 'Ocp-Apim-Subscription-Key': face_api_sub_key 71 | } 72 | 73 | # Request parameters. 74 | params = { 75 | 'returnFaceId': 'true', 76 | # 'returnFaceLandmarks': 'false', 77 | 'returnFaceAttributes': 'age,gender,smile,emotion,glasses', 78 | } 79 | 80 | try: 81 | 82 | api_url = face_api_endpoint 83 | 84 | response = requests.post(api_url, headers=header, data=body, params=params) 85 | 86 | #print (response.json()) 87 | #response = conn.request('POST', uri_base + '/face/v1.0/detect', json=body, data=body, headers=headers, params=params) 88 | #data = response.read().decode('utf-8') 89 | 90 | print ('Response:') 91 | parsed = json.loads(response.text) 92 | print (json.dumps(parsed, sort_keys=True, indent=2)) 93 | 94 | #faceID = parsed[0]["faceId"] 95 | # print("faceID = %s"% faceID) 96 | 97 | #height = parsed[0]["faceRectangle"]["height"] 98 | #print("height= %d"% height) 99 | 100 | #gender = parsed[0]["gender"] 101 | #print("gender = %s"% gender) 102 | 103 | 104 | except Exception as e: 105 | print('Error:') 106 | print(e) 107 | 108 | return(parsed) 109 | 110 | 111 | def ms_WhatDoYouSee (body): 112 | 113 | headers = { 114 | # Request headers. 115 | 'Content-Type': 'application/octet-stream', 116 | 'Ocp-Apim-Subscription-Key': vision_api_sub_key, 117 | } 118 | 119 | params = urllib.parse.urlencode({ 120 | # Request parameters. All of them are optional. 121 | 'visualFeatures': 'Description, Faces', 122 | 'details': '', 123 | 'language': 'en', 124 | }) 125 | 126 | # Execute the REST API call and get the response. 127 | 128 | try: 129 | conn = http.client.HTTPSConnection(vision_api_endpoint) 130 | conn.request("POST", "/vision/v1.0/analyze?%s" % params, body, headers) 131 | response = conn.getresponse() 132 | data = response.read().decode('utf-8') 133 | 134 | # 'data' contains the JSON data. The following formats the JSON data for display. 135 | parsed = json.loads(data) 136 | parsedText = "I see %s"% (parsed['description']['captions'][0]['text']) 137 | conn.close() 138 | 139 | except Exception as e: 140 | print('Error:') 141 | print(e) ########### Python 3.6 ############# 142 | parsedText = e 143 | 144 | return parsedText 145 | -------------------------------------------------------------------------------- /st_main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from importlib import import_module 3 | import os 4 | from flask import Flask, render_template, Response, send_file 5 | 6 | # Libraries created for this program by Ken 7 | from ms_cognitive_imagerec import ms_WhatDoYouSee, ms_WhoDoYouSee, ms_GetFaceAttribs, FaceAttribs 8 | # for Intel Neural Compute Stick 9 | #from ncs_image_classify import ncs_init, ncs_classify_image, ncs_close 10 | from al_Alerts import al_StartAlertTriggers, al_CancelAlertTriggers 11 | from image_draw import drawFaceRectangles 12 | from eml_Email import eml_SendEmail 13 | # other libraries 14 | import requests 15 | from io import BytesIO 16 | from PIL import Image, ImageDraw 17 | from time import sleep 18 | 19 | # Alexa Flask-Ask 20 | import json 21 | import logging 22 | from flask_ask import Ask, statement, question, context as ask_context, request as ask_request, session as ask_session 23 | 24 | 25 | import operator 26 | import base64 27 | import http.client 28 | import webbrowser 29 | 30 | class SelfieAlert: 31 | countdown_secs = 3 32 | countdown_interval = 1 33 | secs_rem = 2 34 | tmr = None 35 | requestId = None 36 | apiEndpoint = None 37 | apiAccessToken = None 38 | image = None 39 | 40 | # import camera driver 41 | ##if os.environ.get('CAMERA'): 42 | # Camera = import_module('camera_' + os.environ['CAMERA']).Camera 43 | #else: 44 | # from camera import Camera 45 | 46 | from camera_pi import Camera 47 | camera = Camera() 48 | 49 | image_file = 'image_file.png' 50 | 51 | # this sound is not guaranteed to be available. replace sound. use ffmpeg to translate to Alexa requirements 52 | camera_sound_url = 'https://videotalker.blob.core.windows.net/videoblob/camera_sound_conv.mp3' 53 | 54 | # Raspberry Pi camera module (requires picamera package) 55 | # from camera_pi import Camera 56 | 57 | app = Flask(__name__) 58 | ask = Ask(app, "/") 59 | 60 | log = logging.getLogger() 61 | log.addHandler(logging.StreamHandler()) 62 | log.setLevel(logging.DEBUG) 63 | logging.getLogger("flask_ask").setLevel(logging.DEBUG) 64 | 65 | @ask.launch 66 | def alexa_launch(): 67 | 68 | return question('See Talker Active, How can I help you?') 69 | 70 | 71 | 72 | @ask.intent('Selfie') 73 | def alexa_Selfie(): 74 | 75 | # do selfie countdown, capture image and set time to email photo with WhatDoYouSee for email body 76 | selfie(True) 77 | 78 | response_txt = "You're selfie was taken and will be emailed" 79 | 80 | return statement(response_txt) 81 | 82 | 83 | @ask.intent('WhoDoYouSee') 84 | def alexa_WhoDoYouSee(): 85 | response = "no response" 86 | response = who_see(fAlexa=True) 87 | 88 | return statement(response) 89 | 90 | @ask.intent('WhatDoYouSee') 91 | def alexa_whatsee(): 92 | 93 | response = "no response" 94 | response = what_see(fAlexa=True) 95 | return statement(response) 96 | 97 | 98 | @ask.session_ended 99 | def session_ended(): 100 | 101 | return "{}", 200 102 | 103 | @ask.on_session_started 104 | def new_session(): 105 | log.info('new session started') 106 | 107 | @app.route('/') 108 | def index(): 109 | """Video streaming home page.""" 110 | print ("index page") 111 | return render_template('index.html') 112 | 113 | 114 | def gen(camera): 115 | """Video streaming generator function.""" 116 | while True: 117 | frame = camera.get_frame() 118 | yield (b'--frame\r\n' 119 | b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') 120 | 121 | 122 | @app.route('/video_feed') 123 | def video_feed(): 124 | """Video streaming route. Put this in the src attribute of an img tag.""" 125 | return Response(gen(Camera()), 126 | mimetype='multipart/x-mixed-replace; boundary=frame') 127 | 128 | @app.route('/what_see') 129 | def what_see(fAlexa=False): 130 | 131 | body = camera.get_frame() 132 | 133 | #body = open(image_file, 'rb').read() 134 | 135 | parsedText = ms_WhatDoYouSee (body) 136 | 137 | imageFile_png = base64.b64encode(body).decode('ascii') 138 | 139 | 140 | if fAlexa: 141 | return (parsedText) 142 | else: 143 | return render_template("what_see.html", what_see = parsedText, what_see_image = imageFile_png) 144 | 145 | 146 | @app.route('/get_image') 147 | def get_image(): 148 | body = camera.get_frame() 149 | 150 | with open(image_file, 'wb') as f: 151 | f.write(body) 152 | f.close() 153 | 154 | return send_file(image_file, mimetype='image/jpg') 155 | 156 | 157 | 158 | @app.route('/who_see') 159 | def who_see(fAlexa=False): 160 | 161 | body = camera.get_frame() 162 | 163 | response = ms_WhoDoYouSee (body) 164 | 165 | n=0 166 | if len(response) > 0: 167 | for face in response: 168 | n += 1 169 | faceAttribs = ms_GetFaceAttribs (face) 170 | if n == 1: 171 | img = Image.open(BytesIO(body)) 172 | 173 | upperLeftText = faceAttribs.profile_txt 174 | upperRightText = "" 175 | 176 | lowerText1 = "Emotion: %s %2.f%%"% (faceAttribs.top_emotion, faceAttribs.top_emotion_conf) 177 | lowerText2 = faceAttribs.glasses_txt 178 | 179 | drawFaceRectangles(face, img, upperLeftText, upperRightText, lowerText1, lowerText2) 180 | 181 | img.save(image_file, 'PNG') 182 | 183 | img_str = open(image_file, 'rb').read() 184 | 185 | if faceAttribs.glasses == 'NoGlasses': 186 | glasses_txt = "" 187 | else: 188 | glasses_txt = "%s is wearing %s"% (faceAttribs.gender_noun, faceAttribs.glasses) 189 | 190 | top_emotion_txt = "%s top emotion is %s at %2.f %% confidence"% (faceAttribs.gender_possessive, faceAttribs.top_emotion, faceAttribs.top_emotion_conf) 191 | 192 | iSeeText = "I see a %s age %d %s. %s. "% (faceAttribs.gender, faceAttribs.age, top_emotion_txt, glasses_txt) 193 | profile = "%s age %d"% (faceAttribs.gender, faceAttribs.age) 194 | 195 | if n==1: 196 | finalText = iSeeText 197 | else: 198 | finalText = finalText + iSeeText 199 | 200 | # show image on screen which can be saved 201 | img.show() 202 | 203 | 204 | if fAlexa: 205 | return(finalText) 206 | else: 207 | buffer = BytesIO() 208 | img.save(buffer, 'PNG') 209 | img_str = base64.b64encode(buffer.getvalue()).decode('ascii') 210 | 211 | return render_template("who_see.html", who_see = finalText, who_see_image = img_str) 212 | 213 | 214 | else: 215 | iSeeText = "No Face Detected" 216 | return (iSeeText) 217 | 218 | @app.route('/selfie') 219 | def selfie(fAlexa=False): 220 | 221 | print ("starting selfie") 222 | 223 | if fAlexa: 224 | SelfieAlert.requestId = ask_request.requestId 225 | 226 | SelfieAlert.apiEndpoint = ask_context.System.apiEndpoint 227 | SelfieAlert.apiAccessToken = 'Bearer ' + ask_context.System.apiAccessToken 228 | 229 | # count down for selfie 230 | for secs in range (SelfieAlert.countdown_secs,0,-1): 231 | selfie_txt = "Selfie will be taken in " if secs == SelfieAlert.countdown_secs else "" 232 | secs_txt = "second" if secs == 1 else "seconds" 233 | if secs != 0: 234 | response_txt = ("%s %d %s"% (selfie_txt, secs, secs_txt)) 235 | PostDirective_SpeechText(SelfieAlert.requestId, SelfieAlert.apiAccessToken, response_txt) 236 | sleep(1) 237 | 238 | #' Smile! 239 | PostDirective_SpeechText(SelfieAlert.requestId, SelfieAlert.apiAccessToken, "Smile") 240 | 241 | # save frame, then aysychronously set timer to email photo later.so that Alexa timout doesn't occur 242 | SelfieAlert.image = camera.get_frame() 243 | # Make camera sound 244 | PostDirective_SpeechText(SelfieAlert.requestId, SelfieAlert.apiAccessToken, GetSound_SSML(camera_sound_url)) 245 | sleep(1) # give the directive time to produce sound 246 | 247 | # take selfie in N seconds 248 | fRepeatTimer = False #want single countdown (2 secs), not repeated intervals 249 | timerSecs = 2 250 | SelfieAlert.tmr = al_StartAlertTriggers (timerSecs, SelfieAlert_EmailHandler, fRepeatTimer) 251 | 252 | return 253 | else: 254 | SelfieAlert.image = camera.get_frame() 255 | # take selfie in N seconds 256 | SelfieAlert.tmr = None 257 | 258 | response_txt = SelfieAlert_EmailHandler() 259 | 260 | return (response_txt) 261 | 262 | 263 | 264 | def SelfieAlert_EmailHandler(): 265 | 266 | # Image should have been captured earlier 267 | if SelfieAlert.image != None: 268 | body = SelfieAlert.image 269 | 270 | # Save image to file 271 | with open(image_file, 'wb') as f: 272 | f.write(body) 273 | f.close() 274 | 275 | see_txt = ms_WhatDoYouSee(body) 276 | print ("see_txt=%s"% see_txt) 277 | 278 | if len(see_txt) > 0: 279 | 280 | # sending email is optional. This is good for testing, but should use secure storage 281 | # change this 282 | # not a good idea to hard code this info, but I used a test email and used as a quick POC for email. Best to get the info from a secure location. 283 | fromAddr = 'change this' 284 | toAddr = 'change this' 285 | email_pwd = 'change this or reference from a function' 286 | 287 | # send email - must define from and to address 288 | # this is not secure 289 | eml_SendEmail(fromAddr, toAddr, email_pwd, "See Talker Selfie", see_txt, image_file) 290 | response_txt = "Selfie taken and email sent successfully" 291 | 292 | img = Image.open(BytesIO(SelfieAlert.image)) 293 | img.show(title=see_txt) 294 | 295 | else: 296 | response_txt = "There was a problem taking the selfie!" 297 | 298 | 299 | return (response_txt) 300 | 301 | def GetSound_SSML(sound_url): 302 | # only called once, but might be extended for more sophisticated SSML 303 | ssml_txt = (" "% sound_url) 304 | print ("ssml text: %s"% ssml_txt) 305 | 306 | # not using this now, but may be needed for more advanced SSML with a POST 307 | output_sound = { 308 | "type": "SSML", 309 | "ssml": ssml_txt 310 | #"ssml": " " 311 | } 312 | 313 | return (ssml_txt) 314 | 315 | 316 | 317 | 318 | def PostDirective_SpeechText(requestId, apiAccessToken, speech_txt): 319 | 320 | directive_hdr = { 321 | 'Content-Type': 'application/json', 322 | 'Authorization': apiAccessToken 323 | } 324 | 325 | 326 | directive_body = { 327 | "header":{ 328 | "requestId":requestId 329 | }, 330 | "directive":{ 331 | "type":"VoicePlayer.Speak", 332 | "speech":speech_txt} 333 | } 334 | 335 | print("requestId=%s apiToken=%s"% (requestId, apiAccessToken)) 336 | print("speech_txt = %s"% speech_txt) 337 | 338 | try: 339 | api_url = 'https://api.amazonalexa.com/v1/directives' 340 | json_body = json.dumps(directive_body) 341 | 342 | conn = http.client.HTTPSConnection('api.amazonalexa.com') 343 | response = conn.request("POST", '/v1/directives', json_body, directive_hdr) 344 | #response = conn.getresponse() 345 | 346 | conn.close() 347 | 348 | #response = requests.post(api_url, headers=directive_hdr, data=directive_hdr) 349 | 350 | 351 | 352 | except Exception as e: 353 | print('Error:') 354 | print(e) 355 | 356 | 357 | if __name__ == '__main__': 358 | app.run(host='0.0.0.0', threaded=True) --------------------------------------------------------------------------------