"""
Copyright 2022-2023 NXP
SPDX-License-Identifier: Apache-2.0
The following demo shows how to create a video pipeline
with GStreamer and detects if the user is distracted or
not present.
"""
import cairo
import gi
import time
import numpy as np
import os
import math
import glob
import sys
from face_detection import MediapipeFace
from face_landmark import FaceLandmark
from eye import Eye
from mouth import Mouth
gi.require_version("Gtk", "3.0")
gi.require_version("Gst", "1.0")
from gi.repository import Gtk, Gst, Gio, GLib, Gdk
sys.path.append("/home/root/.nxp-demo-experience/scripts/")
import utils
FACE_MODEL = ""
LANDMARK_MODEL = ""
IRIS_MODEL = ""
VIDEO = "/dev/video0"
""" Camera to use """
MONO = 0
""" Camera is monochrome """
FRAME_WIDTH = 1280
""" Width of incoming video """
if MONO == 1:
FRAME_HEIGHT = 800
else:
FRAME_HEIGHT = 720
""" Height of incoming video """
BAD_FACE_PENALTY = 0.01
""" % to remove for far away face """
NO_FACE_PENALTY = 0.01
""" % to remove for no faces in frame """
YAWN_PENALTY = 0.02
""" % to remove for yawning """
DISTRACT_PENALTY = 0.02
""" % to remove for looking away """
SLEEP_PENALTY = 0.03
""" % to remove for sleeping """
RESTORE_CREDIT = -0.01
""" % to restore for doing everything right """
FACE_THRESHOLD = 0.7
""" The threshold value for face detection """
LEFT_EYE_THRESHOLD = 0.3
""" if the left_eye ratio is greater then this value, then left eye will be
considered as open, otherwise be considered as closed. """
RIGHT_EYE_THRESHOLD = 0.3
""" if the right_eye ratio is greater then this value, then right eye will be
considered as open, otherwise be considered as closed. """
if MONO == 1:
MOUTH_THRESHOLD = 0.2
else:
MOUTH_THRESHOLD = 0.4
""" if the mouth ratio is greater then this value, then mouth will be
considered as open, otherwise be considered as closed. """
FACING_LEFT_THRESHOLD = 0.5
""" if the mouth_face_ratio is less then this value then face will be
considered as turning left """
FACING_RIGHT_THRESHOLD = 2
""" if the mouth_face_ratio is greater then this value, then face will be
considered as turning right"""
LEFT_W = 3
RIGHT_W = 3
LEFT_EYE_STATUS = np.zeros(LEFT_W)
""" To filter status glitch, 0 means closed, 1 means open """
RIGHT_EYE_STATUS = np.zeros(RIGHT_W)
""" to filter status glitch, 0 means closed, 1 means open """
class MLVideoDemo(Gtk.Window):
"""A class that contains the UI and camera elements."""
def __init__(self):
"""Create the UI and start the video feed."""
# Class variables
super().__init__()
self.face_cords = []
self.marks = []
self.attention = True
self.sleep = True
self.yawn = True
self.sample = None
# Window Set-up
self.setup_inference()
self.set_default_size(300 + FRAME_WIDTH, FRAME_HEIGHT)
self.set_resizable(False)
self.overall_status = Gtk.Label.new("")
self.overall_status.set_markup(
'Driver is OK' + ""
)
self.attention_bar = Gtk.LevelBar.new()
self.attention_bar.set_value(0.0)
self.attention_bar.set_size_request(300, 30)
css = b"""
levelbar block.low {
background-color: #00FF00;
}
levelbar block.high {
background-color: #FFFF00;
}
levelbar block.full {
background-color: #FF0000;
}
"""
css_provider = Gtk.CssProvider()
css_provider.load_from_data(css)
context = Gtk.StyleContext()
screen = Gdk.Screen.get_default()
context.add_provider_for_screen(
screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
div = Gtk.Separator.new(Gtk.Orientation(0))
attention_label = Gtk.Label.new("Attention: ")
attention_label.set_markup('Attention: ')
self.attention_status = Gtk.Label.new("OK")
self.attention_status.set_markup(
'OK'
)
sleep_label = Gtk.Label.new("Drowsy: ")
sleep_label.set_markup('Drowsy: ')
self.sleep_status = Gtk.Label.new("OK")
self.sleep_status.set_markup(
'OK'
)
yawn_label = Gtk.Label.new("Yawn: ")
yawn_label.set_markup('Yawn: ')
self.yawn_status = Gtk.Label.new("OK")
self.yawn_status.set_markup(
'OK'
)
sep = Gtk.Label.new(" ")
sep.set_size_request(300, 350)
# Create a custom header
header = Gtk.HeaderBar()
header.set_title("Driver Monitoring System Demo")
header.set_subtitle("i.MX 93 Demos")
self.set_titlebar(header)
# Button to quit
quit_button = Gtk.Button()
quit_icon = Gio.ThemedIcon(name="application-exit-symbolic")
quit_image = Gtk.Image.new_from_gicon(quit_icon, Gtk.IconSize.BUTTON)
quit_button.add(quit_image)
header.pack_end(quit_button)
quit_button.connect("clicked", Gtk.main_quit)
# Settings button
settings_button = Gtk.Button()
settings_icon = Gio.ThemedIcon(name="applications-system-symbolic")
settings_image = Gtk.Image.new_from_gicon(settings_icon, Gtk.IconSize.BUTTON)
settings_button.add(settings_image)
header.pack_start(settings_button)
self.settings = SettingsWindow()
settings_button.connect("clicked", self.open_settings)
# Area to display video
self.draw_area = Gtk.DrawingArea.new()
self.draw_area.set_hexpand(True)
self.draw_area.set_size_request(FRAME_WIDTH, FRAME_HEIGHT)
# Tell GTK what function to use to draw
self.draw_area.connect("draw", self.draw_cb)
# Add video and label to window
main_grid = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
side_grid = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
side_grid.set_margin_start(30)
side_grid.set_margin_end(30)
side_grid.set_margin_top(30)
side_grid.set_margin_bottom(30)
attention_grid = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
sleep_grid = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
yawn_grid = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
side_grid.pack_start(self.overall_status, False, True, 0)
side_grid.pack_start(self.attention_bar, False, True, 0)
side_grid.pack_start(div, False, True, 0)
side_grid.pack_start(attention_grid, True, True, 0)
attention_grid.pack_start(attention_label, True, True, 0)
attention_grid.pack_start(self.attention_status, True, True, 0)
side_grid.pack_start(sleep_grid, True, True, 0)
sleep_grid.pack_start(sleep_label, True, True, 0)
sleep_grid.pack_start(self.sleep_status, True, True, 0)
side_grid.pack_start(yawn_grid, True, True, 0)
yawn_grid.pack_start(yawn_label, True, True, 0)
yawn_grid.pack_start(self.yawn_status, True, True, 0)
side_grid.pack_start(sleep_label, False, True, 0)
side_grid.pack_start(yawn_label, False, True, 0)
side_grid.pack_start(sep, True, True, 0)
main_grid.pack_start(side_grid, True, True, 0)
main_grid.pack_start(self.draw_area, True, True, 0)
self.add(main_grid)
# GStreamer pipeline to use. Note that the format is in RGB16. I'm
# not sure if this is the only format that can be used, but it seems
# the most straight forward
if MONO == 1:
cam_pipeline = (
"v4l2src device="
+ VIDEO
+ " ! video/x-raw,format=GRAY16_LE,width="
+ str(int(FRAME_WIDTH))
+ ",height="
+ str(int(FRAME_HEIGHT))
+ "! "
+ "tee name=t t. ! queue max-size-buffers=2 leaky=2 ! "
+ "appsink emit-signals=true name=sink t. ! queue "
+ "max-size-buffers=2 leaky=2 ! appsink "
+ "emit-signals=true name=sink2"
)
else:
cam_pipeline = (
"v4l2src device="
+ VIDEO
+ " ! imxvideoconvert_pxp "
+ " ! video/x-raw,format=RGB16,width="
+ str(int(FRAME_WIDTH))
+ ",height="
+ str(int(FRAME_HEIGHT))
+ "! "
+ "tee name=t t. ! queue max-size-buffers=2 leaky=2 ! "
+ "appsink emit-signals=true name=sink t. ! queue "
+ "max-size-buffers=2 leaky=2 ! videoconvert ! "
+ "video/x-raw,format=RGB ! appsink "
+ "emit-signals=true name=sink2"
)
self.refresh_clock = time.perf_counter()
# Parse the above pipeline
self.pipeline = Gst.parse_launch(cam_pipeline)
# Set a callback function to get the frame
tensor_sink = self.pipeline.get_by_name("sink")
tensor_sink2 = self.pipeline.get_by_name("sink2")
tensor_sink.connect("new-sample", self.on_new_data)
tensor_sink2.connect("new-sample", self.on_new_data2)
# Run the pipeline
self.frame_count = 0
self.timer = time.perf_counter()
self.pipeline.set_state(Gst.State.PLAYING)
def setup_inference(self):
"""Sets up inference"""
self.tflite_labels = []
self.detector = MediapipeFace(FACE_MODEL, FACE_THRESHOLD)
self.eye = Eye(IRIS_MODEL)
self.mouth = Mouth()
self.face_landmark = FaceLandmark(LANDMARK_MODEL)
def on_new_data(self, element):
"""Get the new frame and signal a redraw."""
# Get the new frame and save it
self.sample = element.emit("pull-sample")
# Notify the draw area to redraw itself
self.draw_area.queue_draw()
return 0
def on_new_data2(self, element):
"""Get the new frame and Run inference."""
self.sample2 = element.emit("pull-sample")
if self.sample2 is None:
return
# Get frame details
buffer = self.sample2.get_buffer()
caps = self.sample2.get_caps()
ret, mem_buf = buffer.map(Gst.MapFlags.READ)
height = caps.get_structure(0).get_value("height")
width = caps.get_structure(0).get_value("width")
if MONO == 1:
frame_org = np.ndarray(
shape=(height, width), dtype=np.uint16, buffer=mem_buf.data
)
frame_org = (frame_org / 16).astype(np.uint8)
frame_org = self.increase_brightness(frame_org)
frame_org = np.expand_dims(frame_org, 2).repeat(3, axis=2)
else:
frame_org = np.ndarray(
shape=(height, width, 3), dtype=np.uint8, buffer=mem_buf.data
)
frame = frame_org[..., ::-1].copy()
boxes = self.detector.detect(frame)
self.face_cords = []
if np.size(boxes, 0) > 0:
mark_group = []
for i in range(np.size(boxes, 0)):
boxes[i][[0, 2]] *= FRAME_WIDTH
boxes[i][[1, 3]] *= FRAME_HEIGHT
# Transform the boxes into squares.
if MONO == 1:
boxes = self.transform_to_square(boxes, scale=1.5, offset=(0, 0))
else:
boxes = self.transform_to_square(boxes, scale=1.26, offset=(0, 0))
# Clip the boxes if they cross the image boundaries.
boxes, _ = self.clip_boxes(boxes, (0, 0, FRAME_WIDTH, FRAME_HEIGHT))
boxes = boxes.astype(np.int32)
# only do landmark for one face closest to the center
face_in_center = 0
distance_to_center = math.hypot(FRAME_WIDTH / 2, FRAME_HEIGHT / 2)
for i in range(np.size(boxes, 0)):
x1, y1, x2, y2 = boxes[i]
mid_to_center = math.hypot(
(x2 + x1 - FRAME_WIDTH) / 2, (y2 + y1 - FRAME_HEIGHT) / 2
)
if mid_to_center < distance_to_center:
face_in_center = i
distance_to_center = mid_to_center
x1, y1, x2, y2 = boxes[face_in_center]
self.face_cords.append([x1, y1, x2, y2])
# now do face landmark inference
face_image = frame[y1:y2, x1:x2]
face_marks = self.face_landmark.get_landmark(face_image, (x1, y1, x2, y2))
face_marks = np.array(face_marks)
mark_group.append(face_marks)
# process landmarks for left eye
x1, y1, x2, y2 = self.eye.get_eye_roi(face_marks, 0)
left_eye_image = frame[y1:y2, x1:x2]
left_eye_marks, left_iris_marks = self.eye.get_landmark(
left_eye_image, (x1, y1, x2, y2), 0
)
mark_group.append(np.array(left_iris_marks))
# process landmarks for right eye
x1, y1, x2, y2 = self.eye.get_eye_roi(face_marks, 1)
right_eye_image = frame[y1:y2, x1:x2]
right_eye_marks, right_iris_marks = self.eye.get_landmark(
right_eye_image, (x1, y1, x2, y2), 1
)
mark_group.append(np.array(right_iris_marks))
self.marks = mark_group
# process landmarks for eyes
left_eye_ratio = self.eye.blinking_ratio(left_eye_marks, 0)
right_eye_ratio = self.eye.blinking_ratio(right_eye_marks, 1)
# average the left eye status in a window of LEFT_W frames
for i in range(LEFT_W - 1):
LEFT_EYE_STATUS[i] = LEFT_EYE_STATUS[i + 1]
if left_eye_ratio > LEFT_EYE_THRESHOLD:
LEFT_EYE_STATUS[LEFT_W - 1] = 1
else:
LEFT_EYE_STATUS[LEFT_W - 1] = 0
# average the right eye status in a window of RIGHT_W frames
for i in range(RIGHT_W - 1):
RIGHT_EYE_STATUS[i] = RIGHT_EYE_STATUS[i + 1]
if right_eye_ratio > RIGHT_EYE_THRESHOLD:
RIGHT_EYE_STATUS[RIGHT_W - 1] = 1
else:
RIGHT_EYE_STATUS[RIGHT_W - 1] = 0
if np.mean(LEFT_EYE_STATUS) < 0.5 and np.mean(RIGHT_EYE_STATUS) < 0.5:
self.sleep = False
else:
self.sleep = True
mouth_ratio = self.mouth.yawning_ratio(face_marks)
if mouth_ratio > MOUTH_THRESHOLD:
self.yawn = False
else:
self.yawn = True
mouth_face_ratio = self.mouth.mouth_face_ratio(face_marks)
if (
mouth_face_ratio < FACING_LEFT_THRESHOLD
or mouth_face_ratio > FACING_RIGHT_THRESHOLD
):
self.attention = False
else:
self.attention = True
else:
self.face_cords = []
buffer.unmap(mem_buf)
return 0
def draw_cb(self, widget, context):
"""Draw the frame in the GUI."""
# Protect against empty frames at the beginning
# Draw a black background if there is nothing
# video_time = time.monotonic()
if self.sample is None:
context.set_source_rgb(0, 0, 0)
context.paint()
return
# Get frame details
buffer = self.sample.get_buffer()
caps = self.sample.get_caps()
ret, mem_buf = buffer.map(Gst.MapFlags.READ)
height = caps.get_structure(0).get_value("height")
width = caps.get_structure(0).get_value("width")
# While GStreamer provides a buffer, it is read only even if write
# flags are set above. Cairo requires the buffer to be writable so the
# two cannot interface with each other. The workaround is to use Numpy
# to create a writable copy of the buffer.
frame = np.ndarray(shape=(height, width), dtype=np.uint16, buffer=mem_buf.data)
frame = frame.copy()
if MONO == 1:
frame = (frame / 16).astype(np.uint8)
frame = self.increase_brightness(frame)
# expand GRAY to RGB, the upper 8 bits will not be used
frame = np.expand_dims(frame, 2).repeat(4, axis=2).view("uint32")
surface = cairo.ImageSurface.create_for_data(
frame, cairo.Format.RGB24, width, height
)
else:
surface = cairo.ImageSurface.create_for_data(
frame, cairo.Format.RGB16_565, width, height
)
context.set_source_surface(surface)
context.paint()
context.set_source_rgb(255, 0, 0)
if len(self.face_cords) != 0:
for face in self.face_cords:
if (face[2] - face[0]) < 250:
context.set_source_rgb(255, 0, 0)
GLib.idle_add(self.change_meter, BAD_FACE_PENALTY)
else:
context.set_source_rgb(0, 255, 0)
context.rectangle(
face[0], face[1], (face[2] - face[0]), (face[3] - face[1])
)
context.stroke()
for m in self.marks:
for mark in m:
point = tuple(mark.astype(int))
context.arc(point[0], point[1], 1, 0, 1)
context.stroke()
GLib.idle_add(self.update_status, True)
else:
GLib.idle_add(self.update_status, False)
# Clean up
buffer.unmap(mem_buf)
def update_status(self, face_here):
"""Update the current status"""
if face_here:
ok = True
if self.attention:
self.attention_status.set_markup(
'OK'
)
else:
self.attention_status.set_markup(
'' "Distracted!"
)
self.change_meter(DISTRACT_PENALTY)
ok = False
if self.sleep:
self.sleep_status.set_markup(
'OK'
)
else:
self.sleep_status.set_markup(
'' "Detected!"
)
self.change_meter(SLEEP_PENALTY)
ok = False
if self.yawn:
self.yawn_status.set_markup(
'OK'
)
else:
self.yawn_status.set_markup(
'' "Detected!"
)
self.change_meter(YAWN_PENALTY)
ok = False
if ok:
self.change_meter(RESTORE_CREDIT)
else:
self.attention_status.set_markup(
'Unknown'
)
self.sleep_status.set_markup(
'Unknown'
)
self.yawn_status.set_markup(
'Unknown'
)
self.change_meter(NO_FACE_PENALTY)
def change_meter(self, change):
"""Change the meter"""
cur_val = self.attention_bar.get_value()
new_val = cur_val + change
if new_val > 1.0:
new_val = 1.0
if new_val < 0.0:
new_val = 0.0
self.attention_bar.set_value(new_val)
if new_val < 0.25:
self.overall_status.set_markup(
'' "Driver is OK"
)
if new_val >= 0.25 and new_val <= 0.75:
self.overall_status.set_markup(
'' "Warning!"
)
if new_val > 0.75:
self.overall_status.set_markup(
'Danger!'
)
def transform_to_square(self, boxes, scale=1.0, offset=(0, 0)):
"""Get the square bounding boxes.
Args:
boxes: input boxes [[xmin, ymin, xmax, ymax], ...]
scale: ratio to scale the boxes
offset: a tuple of offset ratio to move the boxes (x, y)
Returns:
boxes: square boxes.
"""
xmins, ymins, xmaxs, ymaxs = np.split(boxes, 4, 1)
width = xmaxs - xmins
height = ymaxs - ymins
# How much to move.
offset_x = offset[0] * width
offset_y = offset[1] * height
# Where is the center location.
center_x = np.floor_divide(xmins + xmaxs, 2) + offset_x
center_y = np.floor_divide(ymins + ymaxs, 2) + offset_y
# Make them squares.
margin = np.floor_divide(np.maximum(height, width) * scale, 2)
boxes = np.concatenate(
(
center_x - margin,
center_y - margin,
center_x + margin,
center_y + margin,
),
axis=1,
)
return boxes
def clip_boxes(self, boxes, margins):
"""Clip the boxes to the safe margins.
Args:
boxes: input boxes [[xmin, ymin, xmax, ymax], ...].
margins: a tuple of 4 int (left, top, right, bottom)
as safe margins.
Returns:
boxes: clipped boxes.
clip_mark: the mark of clipped sides, like [[True,
False, False, False], ...]
"""
left, top, right, bottom = margins
clip_mark = (
boxes[:, 1] < top,
boxes[:, 0] < left,
boxes[:, 3] > bottom,
boxes[:, 2] > right,
)
boxes[:, 1] = np.maximum(boxes[:, 1], top)
boxes[:, 0] = np.maximum(boxes[:, 0], left)
boxes[:, 3] = np.minimum(boxes[:, 3], bottom)
boxes[:, 2] = np.minimum(boxes[:, 2], right)
return boxes, clip_mark
def increase_brightness(self, image):
image = image.astype(np.uint16) * 4
image[image > 255] = 255
image = image.astype(np.uint8)
return image
def open_settings(self, unused):
GLib.idle_add(self.settings.show_all)
class SettingsWindow(Gtk.Window):
"""A class that contains the UI and camera elements."""
def __init__(self):
"""Create the UI for settings."""
super().__init__()
self.set_default_size(300, 100)
self.set_resizable(False)
header = Gtk.HeaderBar()
header.set_title("Settings")
header.set_subtitle("Driver Monitoring System Demo")
self.set_titlebar(header)
quit_button = Gtk.Button()
quit_icon = Gio.ThemedIcon(name="application-exit-symbolic")
quit_image = Gtk.Image.new_from_gicon(quit_icon, Gtk.IconSize.BUTTON)
quit_button.add(quit_image)
header.pack_end(quit_button)
quit_button.connect("clicked", self.close_window)
bad_label = Gtk.Label.new("Penalty for far face: ")
no_label = Gtk.Label.new("Penalty for no face: ")
attention_label = Gtk.Label.new("Penalty for being distracted: ")
sleepy_label = Gtk.Label.new("Penalty for drowsiness: ")
yawn_label = Gtk.Label.new("Penalty for yawning: ")
restore_label = Gtk.Label.new("Healing rate: ")
self.bad_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.bad_spin.set_value(BAD_FACE_PENALTY)
self.no_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.no_spin.set_value(NO_FACE_PENALTY)
self.attention_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.attention_spin.set_value(DISTRACT_PENALTY)
self.sleepy_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.sleepy_spin.set_value(SLEEP_PENALTY)
self.yawn_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.yawn_spin.set_value(YAWN_PENALTY)
self.restore_spin = Gtk.SpinButton.new_with_range(0.00, 1.00, 0.01)
self.restore_spin.set_value(-1.0 * RESTORE_CREDIT)
button = Gtk.Button.new_with_label("Apply")
button.connect("clicked", self.save)
grid = Gtk.Grid.new()
grid.attach(bad_label, 0, 0, 1, 1)
grid.attach(self.bad_spin, 1, 0, 1, 1)
grid.attach(no_label, 0, 1, 1, 1)
grid.attach(self.no_spin, 1, 1, 1, 1)
grid.attach(attention_label, 0, 2, 1, 1)
grid.attach(self.attention_spin, 1, 2, 1, 1)
grid.attach(sleepy_label, 0, 3, 1, 1)
grid.attach(self.sleepy_spin, 1, 3, 1, 1)
grid.attach(yawn_label, 0, 4, 1, 1)
grid.attach(self.yawn_spin, 1, 4, 1, 1)
grid.attach(restore_label, 0, 5, 1, 1)
grid.attach(self.restore_spin, 1, 5, 1, 1)
grid.attach(button, 0, 6, 2, 1)
grid.props.margin = 30
grid.set_column_spacing(30)
grid.set_row_spacing(30)
self.add(grid)
def save(self, unused):
"""Save selections"""
global BAD_FACE_PENALTY
global NO_FACE_PENALTY
global YAWN_PENALTY
global DISTRACT_PENALTY
global SLEEP_PENALTY
global RESTORE_CREDIT
BAD_FACE_PENALTY = self.bad_spin.get_value()
NO_FACE_PENALTY = self.no_spin.get_value()
YAWN_PENALTY = self.yawn_spin.get_value()
DISTRACT_PENALTY = self.attention_spin.get_value()
SLEEP_PENALTY = self.sleepy_spin.get_value()
RESTORE_CREDIT = -1.0 * self.restore_spin.get_value()
self.close_window(None)
def close_window(self, unused):
self.hide()
class StartWindow(Gtk.Window):
"""A window that lets a user select the camera."""
def __init__(self):
"""Create the UI to selct camera."""
super().__init__()
self.set_default_size(500, 300)
self.set_resizable(False)
header = Gtk.HeaderBar()
header.set_title("Driver Monitoring System Demo")
header.set_subtitle("i.MX 93 Demos")
self.set_titlebar(header)
quit_button = Gtk.Button()
quit_icon = Gio.ThemedIcon(name="application-exit-symbolic")
quit_image = Gtk.Image.new_from_gicon(quit_icon, Gtk.IconSize.BUTTON)
quit_button.add(quit_image)
header.pack_end(quit_button)
quit_button.connect("clicked", Gtk.main_quit)
vid_label = Gtk.Label.new("Video device: ")
height_label = Gtk.Label.new("Height: ")
width_label = Gtk.Label.new("Width: ")
self.status_label = Gtk.Label.new("")
devices = []
for device in glob.glob("/dev/video*"):
devices.append(device)
self.source_select = Gtk.ComboBoxText()
self.source_select.set_entry_text_column(0)
self.source_select.set_hexpand(True)
for option in devices:
self.source_select.append_text(option)
self.source_select.set_active(0)
self.height_spin = Gtk.SpinButton.new_with_range(0, 1080, 10)
self.height_spin.set_value(FRAME_HEIGHT)
self.width_spin = Gtk.SpinButton.new_with_range(0, 1920, 10)
self.width_spin.set_value(FRAME_WIDTH)
self.button = Gtk.Button.new_with_label("Start")
self.button.connect("clicked", self.start)
self.width_spin.set_sensitive(False)
self.height_spin.set_sensitive(False)
grid = Gtk.Grid.new()
grid.attach(vid_label, 0, 0, 1, 1)
grid.attach(self.source_select, 1, 0, 1, 1)
grid.attach(height_label, 0, 1, 1, 1)
grid.attach(self.height_spin, 1, 1, 1, 1)
grid.attach(width_label, 0, 2, 1, 1)
grid.attach(self.width_spin, 1, 2, 1, 1)
grid.attach(self.status_label, 0, 3, 2, 1)
grid.attach(self.button, 0, 4, 2, 1)
grid.props.margin = 30
grid.set_column_spacing(30)
grid.set_row_spacing(30)
self.add(grid)
def start(self, unused):
"""Start the video feed"""
global VIDEO
global FRAME_WIDTH
global FRAME_HEIGHT
global LANDMARK_MODEL
global FACE_MODEL
global IRIS_MODEL
VIDEO = self.source_select.get_active_text()
FRAME_WIDTH = self.width_spin.get_value()
FRAME_HEIGHT = self.height_spin.get_value()
self.button.set_sensitive(False)
self.width_spin.set_sensitive(False)
self.height_spin.set_sensitive(False)
GLib.idle_add(self.status_label.set_text, "Downloading landmark model...")
LANDMARK_MODEL = utils.download_file("face_landmark_ptq_vela.tflite")
if LANDMARK_MODEL == -1 or LANDMARK_MODEL == -2 or LANDMARK_MODEL == -3:
GLib.idle_add(
self.status_label.set_text,
"Download landmark model failed! " + "Restart demo and try again!",
)
else:
GLib.idle_add(self.status_label.set_text, "Downloading face model...")
FACE_MODEL = utils.download_file("face_detection_ptq_vela.tflite")
if FACE_MODEL == -1 or FACE_MODEL == -2 or FACE_MODEL == -3:
GLib.idle_add(
self.status_label.set_text,
"Download face model failed! " + "Restart demo and try again!",
)
else:
GLib.idle_add(self.status_label.set_text, "Downloading iris model...")
IRIS_MODEL = utils.download_file("iris_landmark_ptq_vela.tflite")
if IRIS_MODEL == -1 or IRIS_MODEL == -2 or IRIS_MODEL == -3:
GLib.idle_add(
self.status_label.set_text,
"Download iris model failed! " + "Restart demo and try again!",
)
else:
GLib.idle_add(self.launch)
def launch(self):
"""Launch demo"""
window = MLVideoDemo()
window.show_all()
self.close()
if __name__ == "__main__":
# Start GStreamer engine
Gst.init(None)
# Display window
window = StartWindow()
window.show_all()
# Run GTK loop
Gtk.main()