libraries/ml: Convert ml to a package.

Add preprocessing, model wrapper and utils.
This commit is contained in:
iabdalkader 2024-07-07 11:13:07 +03:00
parent e014e48fe3
commit 9a186f4e27
14 changed files with 136 additions and 40 deletions

View File

@ -0,0 +1,5 @@
metadata(
description="Machine Learning Extension Package.",
version="0.0.1",
)
package("ml")

View File

@ -0,0 +1,11 @@
# This file is part of the OpenMV project.
#
# Copyright (c) 2024 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2024 Kwabena W. Agyeman <kwagyeman@openmv.io>
#
# This work is licensed under the MIT license, see the file LICENSE for details.
#
# This is an extension package to the ml C user-module.
from uml import NMS # noqa
from .model import * # noqa

View File

@ -1,46 +1,21 @@
# This file is part of the OpenMV project.
#
# Copyright (c) 2023 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2023 Kwabena W. Agyeman <kwagyeman@openmv.io>
# Copyright (c) 2024 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2024 Kwabena W. Agyeman <kwagyeman@openmv.io>
#
# This work is licensed under the MIT license, see the file LICENSE for details.
#
# This is an extension to the display C user-module. Add or import any display-related
# drivers here, and freeze this module in the board's manifest, and those drivers will
# be importable from display.
import time
from uml import * # noqa
from ml import Model
from micropython import const
from ulab import numpy as np
try:
import audio
except (ImportError, AttributeError):
pass
def draw_predictions(img, boxes, labels, colors, format="pascal_voc", text_color=(255, 255, 255)):
CHAR_W = 8
CHAR_H = 10
img_w = img.width()
img_h = img.height()
for i, (x, y, w, h) in enumerate(boxes):
label = labels[i]
box_color = colors[i]
if format == "pascal_voc":
x = int(x * img_w)
y = int(y * img_h)
w = int(w * img_w) - x
h = int(h * img_h) - y
img.draw_rectangle(x, y, w, h, color=box_color)
img.draw_rectangle(
x, y - CHAR_H, len(label) * CHAR_W, CHAR_H, fill=True, color=box_color
)
img.draw_string(x, y - CHAR_H, label.upper(), text_color)
class MicroSpeech:
_SLICE_SIZE = const(40)
_SLICE_COUNT = const(49)
@ -71,13 +46,11 @@ class MicroSpeech:
# Roll the spectrogram to the left and add the new slice.
self.spectrogram = np.roll(self.spectrogram, -_SLICE_SIZE, axis=1)
self.spectrogram[0, -_SLICE_SIZE:] = self.preprocessor.predict(
self.audio_buffer
)
self.spectrogram[0, -_SLICE_SIZE:] = self.preprocessor.predict([self.audio_buffer])
# Roll the prediction history and add the new prediction.
self.pred_history = np.roll(self.pred_history, -1, axis=0)
self.pred_history[-1] = self.micro_speech.predict(self.spectrogram)[0]
self.pred_history[-1] = self.micro_speech.predict([self.spectrogram])[0]
def start_audio_streaming(self):
if self.audio_started is False:

View File

@ -0,0 +1,26 @@
# This file is part of the OpenMV project.
#
# Copyright (c) 2024 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2024 Kwabena W. Agyeman <kwagyeman@openmv.io>
#
# This work is licensed under the MIT license, see the file LICENSE for details.
import uml
import image
from ml.preprocessing import Normalization
class Model:
def __new__(cls, *args, **kwargs):
self = super().__new__(cls)
retobj = uml.Model(*args, **kwargs)
if isinstance(retobj, tuple):
labels, self.model = retobj
return labels, self
return self
def __str__(self):
return str(self.model)
def predict(self, args, **kwargs):
args = [Normalization()(x) if isinstance(x, image.Image) else x for x in args]
return self.model.predict(args, **kwargs)

View File

@ -0,0 +1,41 @@
# This file is part of the OpenMV project.
#
# Copyright (c) 2024 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2024 Kwabena W. Agyeman <kwagyeman@openmv.io>
#
# This work is licensed under the MIT license, see the file LICENSE for details.
import image
class Normalization:
def __init__(
self,
image=None,
scale=(0.0, 1.0),
mean=(0.0, 0.0, 0.0),
stdev=(1.0, 1.0, 1.0),
roi=None,
):
self.image = image
self.scale = scale
self.mean = mean
self.stdev = stdev
self.roi = roi
def __call__(self, *args):
if len(args) == 1:
img = args[0]
if not isinstance(img, image.Image):
raise ValueError("Expected an image input")
if self.roi is None:
self.roi = (0, 0, img.width(), img.height())
return Normalization(img, self.scale, self.mean, self.stdev, self.roi)
buffer, shape, dtype = args
# Create an image using the input tensor as buffer.
img = image.Image(shape[2], shape[1], self.image.format(), buffer=buffer)
# Copy and scale (if needed) the input image to the input buffer.
hints = image.BILINEAR | image.CENTER | image.SCALE_ASPECT_EXPAND | image.BLACK_BACKGROUND
img.draw_image(self.image, 0, 0, roi=self.roi, hint=hints)
# Scale and convert the image to input tensor data.
img.unpack(buffer, dtype, scale=self.scale, mean=self.mean, stdev=self.stdev)

View File

@ -0,0 +1,39 @@
# This file is part of the OpenMV project.
#
# Copyright (c) 2024 Ibrahim Abdelkader <iabdalkader@openmv.io>
# Copyright (c) 2024 Kwabena W. Agyeman <kwagyeman@openmv.io>
#
# This work is licensed under the MIT license, see the file LICENSE for details.
def draw_predictions(
image,
boxes,
labels,
colors,
format="pascal_voc",
font_width=8,
font_height=10,
text_color=(255, 255, 255),
):
image_w = image.width()
image_h = image.height()
for i, (x, y, w, h) in enumerate(boxes):
label = labels[i]
box_color = colors[i]
if format == "pascal_voc":
x = int(x * image_w)
y = int(y * image_h)
w = int(w * image_w) - x
h = int(h * image_h) - y
image.draw_rectangle(x, y, w, h, color=box_color)
image.draw_rectangle(
x,
y - font_height,
len(label) * font_width,
font_height,
fill=True,
color=box_color,
)
image.draw_string(x, y - font_height, label.upper(), text_color)

View File

@ -10,7 +10,7 @@ freeze ("$(OMV_LIB_DIR)/", "gt911.py")
freeze ("$(OMV_LIB_DIR)/", "st7701.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -10,7 +10,7 @@ freeze ("$(OMV_LIB_DIR)/", "modbus.py")
freeze ("$(OMV_LIB_DIR)/", "pid.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -14,7 +14,7 @@ freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "bno055.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -13,7 +13,7 @@ freeze ("$(OMV_LIB_DIR)/", "tb6612.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -13,7 +13,7 @@ freeze ("$(OMV_LIB_DIR)/", "tb6612.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -13,6 +13,7 @@ freeze ("$(OMV_LIB_DIR)/", "tb6612.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -13,7 +13,7 @@ freeze ("$(OMV_LIB_DIR)/", "tb6612.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")

View File

@ -13,7 +13,7 @@ freeze ("$(OMV_LIB_DIR)/", "tb6612.py")
freeze ("$(OMV_LIB_DIR)/", "vl53l1x.py")
freeze ("$(OMV_LIB_DIR)/", "machine.py")
freeze ("$(OMV_LIB_DIR)/", "display.py")
freeze ("$(OMV_LIB_DIR)/", "ml.py")
freeze ("$(OMV_LIB_DIR)/ml")
# Networking
require("ssl")