-
Notifications
You must be signed in to change notification settings - Fork 40
add legacy object detection cocossd code and example from p5js #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
80c35b2
4db442f
7deb4c8
ce82f51
9375570
49b2e8e
bcd1788
cdaa7c7
8864cb5
02bdb6d
946b084
d8ed389
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>ml5.js objectDetector Webcam Example</title> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.js"></script> | ||
<script src="../../dist/ml5.js"></script> | ||
</head> | ||
<body> | ||
<main> | ||
</main> | ||
<script src="sketch.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,52 @@ | ||||||
// Copyright (c) 2020 ml5 | ||||||
// | ||||||
// This software is released under the MIT License. | ||||||
// https://opensource.org/licenses/MIT | ||||||
|
||||||
/* === | ||||||
ml5 Example | ||||||
Object Detection using COCOSSD | ||||||
This example uses a callback pattern to create the classifier | ||||||
=== */ | ||||||
|
||||||
let video; | ||||||
let detector; | ||||||
let detections = []; | ||||||
|
||||||
function preload(){ | ||||||
detector = ml5.objectDetector("cocossd"); | ||||||
} | ||||||
|
||||||
function setup() { | ||||||
createCanvas(640, 480); | ||||||
|
||||||
video = createCapture(VIDEO); | ||||||
video.size(width, height); | ||||||
video.hide(); | ||||||
|
||||||
detector.detectStart(video, gotDetections); | ||||||
} | ||||||
|
||||||
function gotDetections(results) { | ||||||
detections = results; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't over do it, but a few concise explanatory comments might be good to add. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shiffman thank you for the comments! I will update it for review. |
||||||
|
||||||
function draw() { | ||||||
image(video, 0, 0); | ||||||
|
||||||
for (let i = 0; i < detections.length; i += 1) { | ||||||
const detection = detections[i]; | ||||||
|
const detection = detections[i]; | |
let detection = detections[i]; |
We're adopting p5.js style of using let
even where const
would be more typical JS.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright (c) 2019 ml5 | ||
// | ||
// This software is released under the MIT License. | ||
// https://opensource.org/licenses/MIT | ||
|
||
/* | ||
COCO-SSD Object detection model | ||
Wraps the coco-ssd model in tfjs to be used in ml5 | ||
*/ | ||
import * as tf from "@tensorflow/tfjs"; | ||
import * as cocoSsd from "@tensorflow-models/coco-ssd"; | ||
import { mediaReady } from "../utils/imageUtilities"; | ||
|
||
const DEFAULTS = { | ||
base: "lite_mobilenet_v2", | ||
modelUrl: undefined, | ||
}; | ||
|
||
export class CocoSsd { | ||
constructor(options = {}) { | ||
this.model = null; | ||
this.config = { | ||
base: options.base || DEFAULTS.base, | ||
modelUrl: options.modelUrl || DEFAULTS.modelUrl, | ||
}; | ||
} | ||
|
||
async load() { | ||
await tf.setBackend("webgl"); // this line resolves warning : performance is poor on webgpu backend | ||
await tf.ready(); | ||
|
||
this.model = await cocoSsd.load(this.config); | ||
return this; | ||
} | ||
|
||
/** | ||
* Detect objects that are in the image/video/canvas | ||
* @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} imgToPredict - Subject of the detection. | ||
* @returns {Array} Array of detection detections | ||
*/ | ||
async detect(imgToPredict) { | ||
mediaReady(imgToPredict, true); | ||
await tf.nextFrame(); | ||
|
||
const detections = await this.model.detect(imgToPredict); | ||
const formattedDetections = detections.map(prediction => { | ||
return { | ||
label: prediction.class, | ||
confidence: prediction.score, | ||
x: prediction.bbox[0], | ||
y: prediction.bbox[1], | ||
width: prediction.bbox[2], | ||
height: prediction.bbox[3], | ||
normalized: { | ||
x: prediction.bbox[0] / imgToPredict.width, | ||
y: prediction.bbox[1] / imgToPredict.height, | ||
width: prediction.bbox[2] / imgToPredict.width, | ||
height: prediction.bbox[3] / imgToPredict.height, | ||
}, | ||
}; | ||
}); | ||
|
||
return formattedDetections; | ||
} | ||
} | ||
|
||
export async function load(modelConfig = {}) { | ||
const cocoSsdInstance = new CocoSsd(modelConfig); | ||
await cocoSsdInstance.load(); | ||
return cocoSsdInstance; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Copyright (c) 2019 ml5 | ||
// | ||
// This software is released under the MIT License. | ||
// https://opensource.org/licenses/MIT | ||
|
||
/* | ||
ObjectDetection | ||
*/ | ||
|
||
import * as cocoSsd from "./cocossd.js"; | ||
import { handleModelName } from "../utils/handleOptions"; | ||
import handleArguments from "../utils/handleArguments"; | ||
import callCallback from "../utils/callcallback"; | ||
import { mediaReady } from "../utils/imageUtilities"; | ||
|
||
const MODEL_OPTIONS = ["cocossd"]; // Expandable for other models like YOLO | ||
|
||
class ObjectDetector { | ||
/** | ||
* @typedef {Object} options | ||
* @property {number} filterBoxesThreshold - Optional. default 0.01 | ||
* @property {number} IOUThreshold - Optional. default 0.4 | ||
* @property {number} classProbThreshold - Optional. default 0.4 | ||
*/ | ||
/** | ||
* Create ObjectDetector model. Works on video and images. | ||
* @param {string} modelNameOrUrl - The name or the URL of the model to use. Current model name options | ||
* are: 'YOLO' and 'CocoSsd'. | ||
* @param {Object} options - Optional. A set of options. | ||
* @param {function} callback - Optional. A callback function that is called once the model has loaded. | ||
*/ | ||
constructor(modelNameOrUrl, options = {}, callback) { | ||
this.model = null; | ||
this.modelName = null; | ||
this.modelToUse = null; | ||
|
||
// flags for detectStart() and detectStop() | ||
this.isDetecting = false; | ||
this.signalStop = false; | ||
this.prevCall = ""; | ||
|
||
this.modelName = handleModelName( | ||
modelNameOrUrl, | ||
MODEL_OPTIONS, | ||
"cocossd", | ||
"objectDetector" | ||
); | ||
|
||
|
||
switch (this.modelName) { | ||
case "cocossd": | ||
this.modelToUse = cocoSsd; | ||
break; | ||
case "yolo": | ||
this.modelToUse = yolo; | ||
break; | ||
// more models... currently only cocossd is supported | ||
default: | ||
console.warn(`Unknown model: ${this.modelName}, defaulting to CocoSsd`); | ||
this.modelToUse = cocoSsd; | ||
Comment on lines
48
to
55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
// load model and assign ready promise | ||
this.ready = callCallback(this.loadModel(options), callback); | ||
} | ||
|
||
async loadModel(options) { | ||
if (!this.modelToUse || !this.modelToUse.load) { | ||
throw new Error(`Model loader is missing or invalid for: ${this.modelName}`); | ||
} | ||
|
||
this.model = await this.modelToUse.load(options); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* @typedef {Object} ObjectDetectorPrediction | ||
* @property {number} x - top left x coordinate of the prediction box in pixels. | ||
* @property {number} y - top left y coordinate of the prediction box in pixels. | ||
* @property {number} width - width of the prediction box in pixels. | ||
* @property {number} height - height of the prediction box in pixels. | ||
* @property {string} label - the label given. | ||
* @property {number} confidence - the confidence score (0 to 1). | ||
* @property {ObjectDetectorPredictionNormalized} normalized - a normalized object of the predicition | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} ObjectDetectorPredictionNormalized | ||
* @property {number} x - top left x coordinate of the prediction box (0 to 1). | ||
* @property {number} y - top left y coordinate of the prediction box (0 to 1). | ||
* @property {number} width - width of the prediction box (0 to 1). | ||
* @property {number} height - height of the prediction box (0 to 1). | ||
*/ | ||
|
||
/** | ||
* Detect objects once from the input image/video/canvas. | ||
* @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} input - Target element. | ||
* @param {function} cb - Optional callback. | ||
* @returns {ObjectDetectorPrediction} | ||
*/ | ||
async detect(input, cb) { | ||
const args = handleArguments(input, cb).require("image", "No valid image input."); | ||
await this.ready; | ||
return callCallback(this.model.detect(args.image), args.callback); | ||
} | ||
|
||
/** | ||
* Start continuous detection on video/canvas input | ||
* @param {HTMLVideoElement|HTMLImageElement|HTMLCanvasElement|ImageData} input - Target element. | ||
* @param {function} callback - Callback function called with each detection result. | ||
*/ | ||
async detectStart(input, callback) { | ||
const args = handleArguments(input, callback).require("image", "No input provided."); | ||
|
||
const detectFrame = async () => { | ||
await mediaReady(args.image, true); | ||
await callCallback(this.model.detect(args.image), args.callback); | ||
|
||
if (!this.signalStop) { | ||
requestAnimationFrame(detectFrame); | ||
} else { | ||
this.isDetecting = false; | ||
} | ||
}; | ||
|
||
this.signalStop = false; | ||
if (!this.isDetecting) { | ||
this.isDetecting = true; | ||
detectFrame(); | ||
} | ||
|
||
if (this.prevCall === "start") { | ||
console.warn( | ||
"detectStart() called again without detectStop(). Only the latest call is running." | ||
); | ||
} | ||
|
||
this.prevCall = "start"; | ||
} | ||
|
||
detectStop() { | ||
if (this.isDetecting) { | ||
this.signalStop = true; | ||
} | ||
this.prevCall = "stop"; | ||
} | ||
} | ||
|
||
const objectDetector = (modelNameOrUrl, optionsOrCallback, cb) => { | ||
const { string, options = {}, callback } = handleArguments(modelNameOrUrl, optionsOrCallback, cb); | ||
const instance = new ObjectDetector(string, options, callback); | ||
return instance; | ||
}; | ||
|
||
export default objectDetector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a tiny thing, but sometime this year we adopted a "friendlier" pattern for comments at the top of an example sketch. I don't think we need the copyright and technically the license should be the ml5.js one. Here is what it looks like from a hand pose example:
(This also reminds me that we discussed moving the Code of Conduct to the website, I forgot where we are with that though maybe @MOQN remembers?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll include it for sure!