Bilder in einer Webanwendung mit ONNX Runtime Web klassifizieren
In diesem Tutorial verwenden wir eine GitHub-Repository-Vorlage, um eine Bildklassifizierungs-Web-App mit ONNX Runtime Web zu erstellen. Wir führen die Inferenz in JavaScript im Browser für ein Computer-Vision-Modell durch.
Einer der schwierigsten Teile bei der Bereitstellung und Inferenz in Sprachen, die nicht üblicherweise für Data Science verwendet werden, ist die Lösung der Datenverarbeitung und Inferenz. Wir haben die ganze harte Arbeit für Sie mit dieser Vorlage erledigt!
Unten sehen Sie, wie die Website aus der Vorlage aussieht. Sie durchläuft eine Liste von Beispielbildern, ruft die Inferenz-Sitzung mit einem SqueezeNet-Modell auf und gibt dann den Score und das Label aus der Inferenz zurück.
Beispielvorlagenausgabe

Inhalt
- Inferenz auf dem Gerät
- SqueezeNet Machine Learning Modell
- Erstellen einer statischen Website mit NextJS (einem ReactJS-Framework) zum Bereitstellen von Modellen im Browser
- Der Ordner
data - ImageCanvas FSX Element Web Component
- next.config.js
- package.json
- Projekt lokal ausführen
- Bereitstellen auf Azure Static Web Apps
- TypeScript Notebook
- Weitere Ressourcen
Inferenz auf dem Gerät
Diese Anwendung führt Inferenz auf dem Gerät durch, im Browser unter Verwendung der onnxruntime-web JavaScript-Bibliothek.
SqueezeNet Machine Learning Modell
Wir werden SqueezeNet aus dem ONNX Model Zoo verwenden. SqueezeNet-Modelle führen Bildklassifizierungen durch – sie nehmen Bilder als Eingabe und klassifizieren das Hauptobjekt im Bild in eine Reihe vordefinierter Klassen. Sie werden auf dem ImageNet-Datensatz trainiert, der Bilder aus 1000 verschiedenen Klassen enthält. SqueezeNet-Modelle sind sehr effizient in Bezug auf Größe und Geschwindigkeit und bieten dennoch gute Genauigkeiten. Dies macht sie ideal für Plattformen mit strengen Größenbeschränkungen, wie z. B. Client-seitige Inferenz.
Wenn Sie noch mehr Modellspeicher und Festplatteneffizienz benötigen, können Sie das ONNX-Modell in das ORT-Format konvertieren und ein ORT-Modell in Ihrer Anwendung anstelle des ONNX-Modells verwenden. Sie können auch die Größe der ONNX Runtime selbst reduzieren, um nur die Unterstützung für die spezifischen Modelle in Ihrer Anwendung einzuschließen.
Erstellen einer statischen Website mit NextJS (einem ReactJS-Framework) zum Bereitstellen von Modellen im Browser
Die Vorlage
Das Ziel dieser Vorlage ist es, einen Ausgangspunkt für Ihre beschleunigte ML-Webanwendung zu bieten. Die Vorlage generiert eine Computer-Vision-Anwendung unter Verwendung des NextJS-Frameworks, geschrieben in TypeScript und mit Webpack erstellt. Tauchen wir in die Vorlage ein und zerlegen den Code.
Der Ordner utils
Es gibt drei Dateien im Utils-Ordner: imageHelper.ts, modelHelper.ts und predict.ts. Predict ist der Einstiegspunkt von der Webkomponente, um die Inferenz zu starten. Hier importieren wir die Helfer und rufen die Standardfunktionen auf, um den Bild-Tensor zu erhalten und unsere Modellinferenz auszuführen.
predict.ts
// Language: typescript
// Path: react-next\utils\predict.ts
import { getImageTensorFromPath } from './imageHelper';
import { runSqueezenetModel } from './modelHelper';
export async function inferenceSqueezenet(path: string): Promise<[any,number]> {
// 1. Convert image to tensor
const imageTensor = await getImageTensorFromPath(path);
// 2. Run model
const [predictions, inferenceTime] = await runSqueezenetModel(imageTensor);
// 3. Return predictions and the amount of time it took to inference.
return [predictions, inferenceTime];
}
imageHelper.ts
Zuerst müssen wir unser Bild von einer lokalen Datei oder URL abrufen und es in einen Tensor konvertieren. Die Funktion getImageTensorFromPath in imageHelper.ts verwendet JIMP, um die Datei zu lesen, zu skalieren und die imageData zurückzugeben. JIMP ist eine JavaScript-Bibliothek zur Bildmanipulation. Sie verfügt über viele eingebaute Funktionen zur Arbeit mit Bilddaten wie Skalieren, Graustufen, Schreiben und mehr. In diesem Beispiel müssen wir nur skalieren, in Ihrem Code benötigen Sie jedoch möglicherweise zusätzliche Bilddatenverarbeitung.
import * as Jimp from 'jimp';
import { Tensor } from 'onnxruntime-web';
export async function getImageTensorFromPath(path: string, dims: number[] = [1, 3, 224, 224]): Promise<Tensor> {
// 1. load the image
var image = await loadImagefromPath(path, dims[2], dims[3]);
// 2. convert to tensor
var imageTensor = imageDataToTensor(image, dims);
// 3. return the tensor
return imageTensor;
}
async function loadImagefromPath(path: string, width: number = 224, height: number= 224): Promise<Jimp> {
// Use Jimp to load the image and resize it.
var imageData = await Jimp.default.read(path).then((imageBuffer: Jimp) => {
return imageBuffer.resize(width, height);
});
return imageData;
}
Sobald wir die imageData haben, senden wir sie in die Funktion imageDataToTensor, um sie für die Inferenz in einen ORT-Tensor zu konvertieren. Um ein Bild in JavaScript in einen Tensor zu konvertieren, müssen wir die RGB-Werte (Rot, Grün, Blau) in Arrays umwandeln. Dazu schleifen wir durch den imageBufferData durch die 4 Kanäle von RGBA jedes Pixels. Sobald wir die RGB-Pixelkanäle für das Bild haben, erstellen wir den Float32Array aus den transposedData und dividieren durch 255, um den Wert zu normalisieren. Warum normalisiert 255 den Pixelwert? Nun, Normalisierung ist eine Technik, die verwendet wird, um Werte auf eine gemeinsame Skala zu ändern, ohne die Unterschiede zu verzerren. 255 ist die maximale Zahl für einen RGB-Wert, daher normalisiert das Teilen durch 255 unsere Werte auf einen Bereich zwischen 0 und 1, ohne die statistischen Unterschiede zu verlieren. Jetzt, da wir die Float32Array-Darstellung des Bildes haben, können wir den ORT-Tensor erstellen, indem wir den Typ, die Daten und die Abmessungen übergeben. Dann geben wir den inputTensor für die Inferenz zurück.
function imageDataToTensor(image: Jimp, dims: number[]): Tensor {
// 1. Get buffer data from image and create R, G, and B arrays.
var imageBufferData = image.bitmap.data;
const [redArray, greenArray, blueArray] = new Array(new Array<number>(), new Array<number>(), new Array<number>());
// 2. Loop through the image buffer and extract the R, G, and B channels
for (let i = 0; i < imageBufferData.length; i += 4) {
redArray.push(imageBufferData[i]);
greenArray.push(imageBufferData[i + 1]);
blueArray.push(imageBufferData[i + 2]);
// skip data[i + 3] to filter out the alpha channel
}
// 3. Concatenate RGB to transpose [224, 224, 3] -> [3, 224, 224] to a number array
const transposedData = redArray.concat(greenArray).concat(blueArray);
// 4. convert to float32
let i, l = transposedData.length; // length, we need this for the loop
// create the Float32Array size 3 * 224 * 224 for these dimensions output
const float32Data = new Float32Array(dims[1] * dims[2] * dims[3]);
for (i = 0; i < l; i++) {
float32Data[i] = transposedData[i] / 255.0; // convert to float
}
// 5. create the tensor object from onnxruntime-web.
const inputTensor = new Tensor("float32", float32Data, dims);
return inputTensor;
}
modelHelper.ts
Der inputTensor ist bereit für die Inferenz. Rufen wir die Standardfunktion modelHelper.ts auf und gehen die Logik durch. Zuerst erstellen wir die ort.InferenceSession, indem wir den Pfad zum Modell und die SessionOptions übergeben. Für die executionProviders können Sie entweder webgl zur Verwendung der GPU oder wasm zur Verwendung der CPU verwenden. Weitere Informationen zu den für die Inferenzkonfiguration verfügbaren SessionOptions finden Sie hier in der Dokumentation: hier.
import * as ort from 'onnxruntime-web';
import _ from 'lodash';
import { imagenetClasses } from '../data/imagenet';
export async function runSqueezenetModel(preprocessedData: any): Promise<[any, number]> {
// Create session and set options. See the docs here for more options:
//https://onnxruntime.de/docs/api/js/interfaces/InferenceSession.SessionOptions.html#graphOptimizationLevel
const session = await ort.InferenceSession
.create('./_next/static/chunks/pages/squeezenet1_1.onnx',
{ executionProviders: ['webgl'], graphOptimizationLevel: 'all' });
console.log('Inference session created');
// Run inference and get results.
var [results, inferenceTime] = await runInference(session, preprocessedData);
return [results, inferenceTime];
}
Rufen wir dann die Funktion runInference auf, indem wir die session und unseren Eingabe-Tensor preprocessedData übergeben.
async function runInference(session: ort.InferenceSession, preprocessedData: any): Promise<[any, number]> {
// Get start time to calculate inference time.
const start = new Date();
// create feeds with the input name from model export and the preprocessed data.
const feeds: Record<string, ort.Tensor> = {};
feeds[session.inputNames[0]] = preprocessedData;
// Run the session inference.
const outputData = await session.run(feeds);
// Get the end time to calculate inference time.
const end = new Date();
// Convert to seconds.
const inferenceTime = (end.getTime() - start.getTime())/1000;
// Get output results with the output name from the model export.
const output = outputData[session.outputNames[0]];
//Get the softmax of the output data. The softmax transforms values to be between 0 and 1
var outputSoftmax = softmax(Array.prototype.slice.call(output.data));
//Get the top 5 results.
var results = imagenetClassesTopK(outputSoftmax, 5);
console.log('results: ', results);
return [results, inferenceTime];
}
Nach Abschluss der Inferenz geben wir die Top 5 Ergebnisse und die Inferenzdauer zurück. Dies wird dann auf der ImageCanvas Web Component angezeigt.
Der Ordner data
Der Datenordner in dieser Vorlage enthält imagenetClasses, die verwendet werden, um das Label basierend auf dem Index des Inferenzresults zuzuweisen. Zusätzlich gibt es eine sample-image-urls.ts zur Testung der Anwendung.
ImageCanvas FSX Element Web Component
Die ImageCanvas.tsx Web Component enthält die Schaltflächen- und Anzeigeelemente. Nachfolgend die Logik für die Web Component.
import { useRef, useState } from 'react';
import { IMAGE_URLS } from '../data/sample-image-urls';
import { inferenceSqueezenet } from '../utils/predict';
import styles from '../styles/Home.module.css';
interface Props {
height: number;
width: number;
}
const ImageCanvas = (props: Props) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
var image: HTMLImageElement;
const [topResultLabel, setLabel] = useState("");
const [topResultConfidence, setConfidence] = useState("");
const [inferenceTime, setInferenceTime] = useState("");
// Load the image from the IMAGE_URLS array
const getImage = () => {
var sampleImageUrls: Array<{ text: string; value: string }> = IMAGE_URLS;
var random = Math.floor(Math.random() * (9 - 0 + 1) + 0);
return sampleImageUrls[random];
}
// Draw image and other UI elements then run inference
const displayImageAndRunInference = () => {
// Get the image
image = new Image();
var sampleImage = getImage();
image.src = sampleImage.value;
// Clear out previous values.
setLabel(`Inferencing...`);
setConfidence("");
setInferenceTime("");
// Draw the image on the canvas
const canvas = canvasRef.current;
const ctx = canvas!.getContext('2d');
image.onload = () => {
ctx!.drawImage(image, 0, 0, props.width, props.height);
}
// Run the inference
submitInference();
};
const submitInference = async () => {
// Get the image data from the canvas and submit inference.
var [inferenceResult,inferenceTime] = await inferenceSqueezenet(image.src);
// Get the highest confidence.
var topResult = inferenceResult[0];
// Update the label and confidence
setLabel(topResult.name.toUpperCase());
setConfidence(topResult.probability);
setInferenceTime(`Inference speed: ${inferenceTime} seconds`);
};
return (
<>
<button
className={styles.grid}
onClick={displayImageAndRunInference} >
Run Squeezenet inference
</button>
<br/>
<canvas ref={canvasRef} width={props.width} height={props.height} />
<span>{topResultLabel} {topResultConfidence}</span>
<span>{inferenceTime}</span>
</>
)
};
export default ImageCanvas;
Dieses Web Component-Element wird dann in der index.tsx importiert.
<ImageCanvas width={240} height={240}/>
next.config.js
Wir müssen ein paar Plugins in der next.config.js hinzufügen. Dies ist die Webpack-Konfiguration, die im NextJS-Framework implementiert ist. Das CopyPlugin wird verwendet, um die wasm-Dateien und die Modelldateien in den out-Ordner für die Bereitstellung zu kopieren.
/** @type {import('next').NextConfig} */
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
reactStrictMode: true,
//distDir: 'build',
webpack: (config, { }) => {
config.resolve.extensions.push(".ts", ".tsx");
config.resolve.fallback = { fs: false };
config.plugins.push(
new NodePolyfillPlugin(),
new CopyPlugin({
patterns: [
{
from: './node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.wasm',
to: 'static/chunks/pages',
}, {
from: './node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.mjs',
to: 'static/chunks/pages',
},
{
from: './model',
to: 'static/chunks/pages',
},
],
}),
);
return config;
}
}
package.json
Da wir dies als statische Website bereitstellen möchten, müssen wir den Build-Befehl in der package.json auf next build && next export aktualisieren, um unsere statische Site-Ausgabe zu generieren. Dies generiert alle Assets, die für die Bereitstellung der statischen Website benötigt werden, und platziert sie im out-Ordner.
{
"name": "ort-web-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"fs": "^0.0.1-security",
"jimp": "^0.16.1",
"lodash": "^4.17.21",
"ndarray": "^1.0.19",
"ndarray-ops": "^1.2.2",
"next": "^11.1.2",
"onnxruntime-web": "^1.9.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"node-polyfill-webpack-plugin": "^1.1.4",
"copy-webpack-plugin": "^9.0.1",
"@types/lodash": "^4.14.176",
"@types/react": "17.0.19",
"eslint": "7.32.0",
"eslint-config-next": "11.1.0",
"typescript": "4.4.2"
}
}
Projekt lokal ausführen
Wir sind bereit, das Projekt auszuführen. Führen Sie den Befehl aus, je nachdem, ob Sie mit dem Debugging starten, den out-Ordner erstellen oder ohne Debugging starten möchten.
// to run with debugging
npm run dev
// to build the project
npm run build
// to run without debugging
npm run start
Bereitstellen auf Azure Static Web Apps
Nachdem wir die Website erstellt haben, können wir sie auf Azure Static Web Apps bereitstellen. Lesen Sie die Dokumentation, um zu erfahren, wie Sie mit Azure hier bereitstellen.
TypeScript Notebook
Wir haben den Umgang mit dieser Vorlage erläutert, aber hier gibt es einen Bonus! Unter dem Ordner „notebook“ in der Vorlage befindet sich ein Notebook mit diesem Code, mit dem Sie experimentieren und Änderungen ausprobieren können, die Sie möglicherweise benötigen. So können Sie es ganz einfach machen, wenn Sie ein anderes Modell oder Bild ausprobieren möchten. Um das TypeScript Jupyter Notebook zu verwenden, laden Sie die VS Code Jupyter Notebooks-Erweiterung herunter.
Weitere Ressourcen
-
Beginnen Sie jetzt mit der Verwendung der Vorlage, indem Sie zum GitHub NextJS ORT-Web Template Repository gehen.
-
Schauen Sie sich den Release-Blog hier an
-
Die Vorlage verwendet NextJS, ein Framework zum Erstellen von Anwendungen mit ReactJS.
-
Schauen Sie sich ONNX Runtime Web Demo für weitere Modelle an. ONNX Runtime Web Demo ist ein interaktives Demo-Portal, das reale Anwendungsfälle für die Ausführung von ONNX Runtime Web in VueJS zeigt. Es unterstützt derzeit vier Beispiele, mit denen Sie die Leistung von ONNX Runtime Web schnell erleben können.
-
Dieser Blogbeitrag zeigt, wie ORT Web mit Python verwendet wird, um ein vortrainiertes AlexNet-Modell im Browser bereitzustellen.
-
Weitere ONNX Runtime JS Beispiele ansehen