Skip to content

Image classification

Image classification assigns an image to one of a fixed set of classes. You pass in the pixels and get back the classes ranked by probability: what the image most likely shows, and how sure the model is of each answer. Unlike object detection, which locates several objects with bounding boxes, classification labels the image as a whole.

createImageClassifier runs MobileNetV4 Conv-Small, trained on ImageNet-1k: 1000 classes of animals, vehicles, food, tools and household objects.

import {
function createImageClassifier(config?: ImageClassifierModel, opts?: LoadOptions): Promise<ImageClassifier>
createImageClassifier
,
function imageBufferFromImageData(img: ImageData): ImageBuffer
imageBufferFromImageData
,
const models: {
readonly embedding: {
readonly EMBEDDINGGEMMA_2: {
DEFAULT: {
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F16: {
dtype: "f16";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F32: {
dtype: "f32";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
};
readonly ALL_MINILM_L6_V2: {
DEFAULT: {
tokenizerPath: string;
} & {
modelPath: string;
};
F16: {
tokenizerPath: string;
} & {
dtype: "f16";
modelPath: string;
};
F32: {
tokenizerPath: string;
} & {
modelPath: string;
dtype: "f32";
};
};
};
... 6 more ...;
readonly imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
};
}
models
} from 'runntime/zoo';
// 1. Load the model once. The weights download from the Hugging Face Hub.
const
const classifier: ImageClassifier
classifier
= await
function createImageClassifier(config?: ImageClassifierModel, opts?: LoadOptions): Promise<ImageClassifier>
createImageClassifier
(
const models: {
readonly embedding: {
readonly EMBEDDINGGEMMA_2: {
DEFAULT: {
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F16: {
dtype: "f16";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F32: {
dtype: "f32";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
};
readonly ALL_MINILM_L6_V2: {
DEFAULT: {
tokenizerPath: string;
} & {
modelPath: string;
};
F16: {
tokenizerPath: string;
} & {
dtype: "f16";
modelPath: string;
};
F32: {
tokenizerPath: string;
} & {
modelPath: string;
dtype: "f32";
};
};
};
... 6 more ...;
readonly imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
};
}
models
.
imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
}
imageClassification
.
type MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
}
MOBILENETV4
.
type DEFAULT: {
modelPath: string;
}
DEFAULT
);
// 2. Get the pixels of an image, here from a canvas the picture is drawn on.
const
const ctx: CanvasRenderingContext2D
ctx
=
const canvas: HTMLCanvasElement
canvas
.
HTMLCanvasElement.getContext(contextId: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D | null (+5 overloads)
getContext
('2d')!;
const
const image: ImageBuffer
image
=
function imageBufferFromImageData(img: ImageData): ImageBuffer
imageBufferFromImageData
(
const ctx: CanvasRenderingContext2D
ctx
.
CanvasImageData.getImageData(sx: number, sy: number, sw: number, sh: number, settings?: ImageDataSettings): ImageData
getImageData
(0, 0,
const canvas: HTMLCanvasElement
canvas
.
HTMLCanvasElement.width: number
width
,
const canvas: HTMLCanvasElement
canvas
.
HTMLCanvasElement.height: number
height
));
// 3. Classify it.
const
const classes: (Classification & {
label: string;
})[]
classes
= await
const classifier: ImageClassifier
classifier
.
ImageClassifier.classify(image: ImageBuffer, options?: ClassifyOptions): Promise<(Classification & {
label: string;
})[]>
classify
(
const image: ImageBuffer
image
, {
ClassifyOptions.topk?: number | undefined
topk
: 5 });
// 4. Use the result: the five most likely classes, best first.
for (const {
const label: string
label
,
const confidence: number
confidence
} of
const classes: (Classification & {
label: string;
})[]
classes
) {
var console: Console
console
.
Console.log(...data: any[]): void
log
(
const label: string
label
,
const confidence: number
confidence
);
}
// minibus 0.59
// police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria 0.11
// trolleybus, trolley coach, trackless trolley 0.07
// ...
// 5. Free the GPU memory when done.
const classifier: ImageClassifier
classifier
.
ImageClassifier.dispose(): void
dispose
();

The example reads one picture off a canvas. A video or a camera works the same way: draw each frame on the canvas and call classify again, the classifier is loaded once.

The engine must be set up first, see Getting started. The model runs in half precision, so the device needs shader-f16.

Your camera, frame by frame through classify() on your GPU, with the five most likely classes under each frame. Point it at a cup, a keyboard, a dog. The dashed square is what the model sees: everything outside it is cropped away. No camera? The sample picture goes through the same call.

classify returns the classes best first: the topk most likely ones, or all 1000 when topk is not given. Each entry is:

interface Classification {
label: string; // the class name: 'minibus', 'golden retriever', ...
classId: number; // the same as a number, index into classifier.labels
confidence: number; // how sure the model is, 0 to 1
}

The confidences of all 1000 classes sum to 1. A low top score means the model is torn between a few similar classes: in the example above the next guesses are all vans and buses too.

function createImageClassifier(
model?: ImageClassifierModel,
options?: LoadOptions,
): Promise<ImageClassifier>;
interface ImageClassifier {
readonly labels: readonly string[];
readonly inputSize: number;
classify(
image: ImageBuffer,
options?: ClassifyOptions,
): Promise<(Classification & { label: string })[]>;
dispose(): void;
}
interface ClassifyOptions {
readonly topk?: number; // default: every class
}
  • classify(image, options?) returns the most likely classes, best first. image is an ImageBuffer of any size. Build one with imageBufferFromImageData(imageData) from a canvas, or imageBuffer(bytes, width, height, format?) from raw rgba, rgb, bgra, bgr or gray bytes. topk caps how many come back.
  • labels are the class names, by class index.
  • inputSize is the model input width and height, 224.
  • dispose() frees the GPU memory.

The first argument is the model config, a plain object with the fields below. Every field is optional. models.imageClassification has the ready-made config, so the quick start passes it as is.

interface ImageClassifierModel {
readonly modelPath?: ModelPath;
readonly labels?: readonly string[]; // 1000 names, ImageNet by default
readonly resizeMode?: 'crop' | 'stretch' | 'letterbox'; // default 'crop'
}
type ModelPath = string | RangeSource; // a URL, or bytes you already have

Examples:

// Weights hosted by you, class names in another language, no cropping.
await
function createImageClassifier(config?: ImageClassifierModel, opts?: LoadOptions): Promise<ImageClassifier>
createImageClassifier
({
ImageClassifierModel.modelPath?: ModelPath | undefined
modelPath
: '/models/mobilenetv4/model.safetensors',
ImageClassifierModel.labels?: readonly string[] | undefined
labels
:
const myLabels: string[]
myLabels
,
ImageClassifierModel.resizeMode?: ResizeMode | undefined
resizeMode
: 'stretch',
});
  • modelPath - the weights, a model.safetensors file of MobileNetV4 with the 1000 ImageNet classes. Default: the file on the Hugging Face Hub, so there is nothing to host. Point it at your own copy to serve the weights yourself.
  • labels - the class names, one per class, 1000 of them. Default: the ImageNet names in English.
  • resizeMode - how the image fits the model input. crop (default) cuts the middle 87.5% of the centered square, the crop the model was evaluated with; stretch distorts, letterbox pads.

The second argument controls the download. Every field is optional.

interface LoadOptions {
cache?: WeightCache;
cacheId?: string;
onProgress?: (name: string, doneBytes: number, totalBytes: number) => void;
onBytes?: (chunkBytes: number) => void;
signal?: AbortSignal;
}

Example:

import {
function createImageClassifier(config?: ImageClassifierModel, opts?: LoadOptions): Promise<ImageClassifier>
createImageClassifier
,
function createOpfsCache(namespace: string): Promise<WeightCache | undefined>
createOpfsCache
,
const models: {
readonly embedding: {
readonly EMBEDDINGGEMMA_2: {
DEFAULT: {
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F16: {
dtype: "f16";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F32: {
dtype: "f32";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
};
readonly ALL_MINILM_L6_V2: {
DEFAULT: {
tokenizerPath: string;
} & {
modelPath: string;
};
F16: {
tokenizerPath: string;
} & {
dtype: "f16";
modelPath: string;
};
F32: {
tokenizerPath: string;
} & {
modelPath: string;
dtype: "f32";
};
};
};
... 6 more ...;
readonly imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
};
}
models
} from 'runntime/zoo';
const
const classifier: ImageClassifier
classifier
= await
function createImageClassifier(config?: ImageClassifierModel, opts?: LoadOptions): Promise<ImageClassifier>
createImageClassifier
(
const models: {
readonly embedding: {
readonly EMBEDDINGGEMMA_2: {
DEFAULT: {
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F16: {
dtype: "f16";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
F32: {
dtype: "f32";
modelPath: string;
visionPath: string;
audioPath: string;
tokenizerPath: string;
};
};
readonly ALL_MINILM_L6_V2: {
DEFAULT: {
tokenizerPath: string;
} & {
modelPath: string;
};
F16: {
tokenizerPath: string;
} & {
dtype: "f16";
modelPath: string;
};
F32: {
tokenizerPath: string;
} & {
modelPath: string;
dtype: "f32";
};
};
};
... 6 more ...;
readonly imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
};
}
models
.
imageClassification: {
readonly MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
};
}
imageClassification
.
type MOBILENETV4: {
readonly DEFAULT: {
modelPath: string;
};
readonly SMALL: {
modelPath: string;
};
}
MOBILENETV4
.
type DEFAULT: {
modelPath: string;
}
DEFAULT
, {
LoadOptions.cache?: WeightCache | undefined
cache
: await
function createOpfsCache(namespace: string): Promise<WeightCache | undefined>
createOpfsCache
('my-app'),
LoadOptions.onProgress?: ((name: string, doneBytes: number, totalBytes: number) => void) | undefined
onProgress
: (
name: string
name
,
done: number
done
,
total: number
total
) =>
var console: Console
console
.
Console.log(...data: any[]): void
log
(`${
var Math: Math
Math
.
Math.round(x: number): number
round
((100 *
done: number
done
) /
total: number
total
)}%`),
});
  • cache - keeps the downloaded weights in the browser, so the next visit loads them without the network. createOpfsCache(name) is the built-in one.
  • onProgress - called while the weights upload to the GPU, with the tensor name, bytes done and bytes total. Use it for a progress bar.
  • signal - an AbortSignal. Abort it to stop the load between steps. A step already running, like the download or the GPU upload, finishes first.
ModelConfigWeightsClassesInput
MobileNetV4-Conv-Smodels.imageClassification.MOBILENETV4f16, 10.3 MBImageNet-1k224×224