Skip to content

Commit 3eba261

Browse files
authored
Merge pull request #118 from lucidcode/feature/tensorflow
Add TensorFlow Edge Impulse
2 parents 3c12937 + f724d89 commit 3eba261

6 files changed

Lines changed: 94 additions & 17 deletions

File tree

release/firmware.bin

1.11 KB
Binary file not shown.

software/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(self):
3535
self.default['CreateGifs'] = 1
3636
self.default['TrackFace'] = 1
3737
self.default['TrackEyes'] = 0
38+
self.default['TensorFlow'] = 1
3839
self.default['FaceFeatures'] = 16
3940
self.default['DrawFaceRegion'] = 1
4041
self.default['FaceStages'] = 25

software/face.py

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import sensor
22
import image
3-
import utime
3+
import math
4+
import ml
5+
from ml.utils import NMS
46

57
class face_detection:
68
def __init__(self, config, comms):
@@ -9,34 +11,63 @@ def __init__(self, config, comms):
911
self.has_face = False
1012
self.face_cascade = image.HaarCascade("frontalface", stages=self.config.get('FaceStages'))
1113
self.face_object = [0, 0, 1, 1]
14+
self.face_angle = 0
15+
self.correct_angle = False
16+
self.ml_model = None
17+
self.min_confidence = 0.4
18+
self.threshold_list = [(math.ceil(self.min_confidence * 255), 255)]
1219

1320
self.extra_fb = sensor.alloc_extra_fb(sensor.width(), sensor.height(), sensor.GRAYSCALE)
1421

15-
def detect(self, img):
22+
def detect(self, img, global_variance):
1623
if not self.config.get('TrackFace'):
1724
self.face_object = [0, 0, img.width(), img.height()]
1825
self.has_face = False
1926
return
2027

21-
now = utime.ticks_ms()
28+
if global_variance >= self.config.get('TossThreshold'):
29+
self.face_object = [0, 0, img.width(), img.height()]
30+
self.has_face = False
31+
32+
if self.config.get('TensorFlow'):
33+
if self.ml_model == None:
34+
self.ml_model = ml.Model('fomo_face_detection')
35+
36+
for i, detection_list in enumerate(self.ml_model.predict([img], callback=self.post_process)):
37+
if i == 0 or len(detection_list) == 0:
38+
continue
39+
40+
for (x, y, w, h), score in detection_list:
41+
self.face_object = (x - w, y - h, w * 4, h * 4)
42+
self.has_face = True
43+
return
44+
45+
self.face_angle = 0
2246
face_objects = img.find_features(self.face_cascade, threshold=self.config.get('FaceThreshold'), scale_factor=self.config.get('FaceScaleFactor'))
2347

2448
if len(face_objects) == 0 and self.config.get('FaceAngles'):
2549
for angle in self.config.get('FaceAngles'):
2650
self.extra_fb.replace(img)
2751
self.extra_fb.rotation_corr(x_rotation=0.0, y_rotation=0.0, z_rotation=angle)
52+
2853
face_objects = self.extra_fb.find_features(self.face_cascade, threshold=self.config.get('FaceThreshold'), scale_factor=self.config.get('FaceScaleFactor'))
2954
if face_objects:
55+
self.face_angle = angle
3056
break
3157

3258
if face_objects:
3359
self.face_object = face_objects[0]
3460
self.has_face = True
61+
3562
if self.config.get('TrackEyes'):
3663
self.face_object = [self.face_object[0], self.face_object[1] + int(self.face_object[3] * 1/5), self.face_object[2], int(self.face_object[3] * 2/5)]
37-
else:
38-
self.face_object = [0, 0, img.width(), img.height()]
39-
self.has_face = False
64+
65+
if self.correct_angle:
66+
eyes_x = self.face_object[0] + int(self.face_object[2] * 1/5)
67+
eyes_y = self.face_object[1] + int(self.face_object[3] * 1/5)
68+
eyes_width = self.face_object[2] - int(self.face_object[2] * 1/5)
69+
eyes_height = int(self.face_object[3] * 2/5)
70+
self.face_object = [eyes_x, eyes_y, eyes_width, eyes_height]
4071

4172
def draw_region(self, img):
4273
if not self.config.get('DrawFaceRegion'):
@@ -45,7 +76,56 @@ def draw_region(self, img):
4576
if self.face_object[2] == img.width():
4677
return
4778

48-
if self.config.get('PixelFormat') == 'RGB565':
49-
img.draw_rectangle(self.face_object, color=(70, 130, 180))
50-
else:
51-
img.draw_rectangle(self.face_object)
79+
img.draw_rectangle(self.face_object, color=(70, 130, 180))
80+
81+
if self.correct_angle:
82+
face_x = self.face_object[0]
83+
face_y = self.face_object[1]
84+
face_width = self.face_object[2]
85+
face_height = self.face_object[3]
86+
87+
theta = math.radians(self.face_angle * -1)
88+
offset = (0, 0)
89+
90+
rect = [
91+
(face_x, face_y),
92+
(face_x, face_y + face_height),
93+
(face_x + face_width, face_y + face_height),
94+
(face_x + face_width, face_y)
95+
]
96+
97+
rotated_rect = [translate(rotate(xy, theta), offset) for xy in rect]
98+
99+
img.draw_line((rotated_rect[0][0], rotated_rect[0][1], rotated_rect[1][0], rotated_rect[1][1]), color=(220, 220, 0))
100+
img.draw_line((rotated_rect[1][0], rotated_rect[1][1], rotated_rect[2][0], rotated_rect[2][1]), color=(220, 220, 0))
101+
img.draw_line((rotated_rect[2][0], rotated_rect[2][1], rotated_rect[3][0], rotated_rect[3][1]), color=(220, 220, 0))
102+
img.draw_line((rotated_rect[3][0], rotated_rect[3][1], rotated_rect[0][0], rotated_rect[0][1]), color=(220, 220, 0))
103+
img.draw_string(face_x, face_y + face_height - 10, str(self.face_angle) + "^", color=(70, 130, 180), mono_space=False)
104+
105+
def post_process(self, model, inputs, outputs):
106+
n, oh, ow, oc = model.output_shape[0]
107+
nms = NMS(ow, oh, inputs[0].roi)
108+
for i in range(oc):
109+
img = image.Image(outputs[0][0, :, :, i] * 255)
110+
blobs = img.find_blobs(
111+
self.threshold_list, x_stride=1, area_threshold=1, pixels_threshold=1
112+
)
113+
for b in blobs:
114+
rect = b.rect()
115+
x, y, w, h = rect
116+
score = (
117+
img.get_statistics(thresholds=self.threshold_list, roi=rect).l_mean() / 255.0
118+
)
119+
nms.add_bounding_box(x, y, x + w, y + h, score, i)
120+
return nms.get_bounding_boxes()
121+
122+
def rotate(xy, theta):
123+
cos_theta, sin_theta = math.cos(theta), math.sin(theta)
124+
125+
return (
126+
int(xy[0] * cos_theta - xy[1] * sin_theta),
127+
int(xy[0] * sin_theta + xy[1] * cos_theta)
128+
)
129+
130+
def translate(xy, offset):
131+
return xy[0] + offset[0], xy[1] + offset[1]

software/inspec.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,7 @@ def monitor(self):
9898
try:
9999
self.img = sensor.snapshot()
100100

101-
if self.config.get('TrackFace'):
102-
self.img.gamma(gamma=1.0, contrast=1.5, brightness=0.0)
103-
else:
104-
time.sleep_ms(64)
105-
106-
self.face.detect(self.img)
101+
self.face.detect(self.img, self.global_variance)
107102
self.global_variance, self.variance = self.img.variance(self.extra_fb, self.config.get('PixelThreshold'), self.config.get('PixelRange'), self.face.face_object)
108103

109104
if self.variance > self.peak_variance:

software/rem.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def __init__(self, config, face):
1010
def detect(self, variance, global_variance):
1111
if global_variance >= self.config.get('TossThreshold'):
1212
self.eye_movements = 0
13+
self.last_eye_movement = now + 1000 * 10
1314

1415
now = utime.ticks_ms()
1516
if now - self.last_eye_movement > 1000 * 60:

software/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = '1.2.5'
1+
version = '1.2.6'

0 commit comments

Comments
 (0)