<div class="container">
<div class="img-container">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Material_Design.svg/640px-Material_Design.svg.png" crossorigin="anonymous">
<div class="boxes-container"></div>
</div>
<div class="buttons">
<div class="model tflite-custom">
<div class="btn disabled">TFLite (custom)</div>
<div class="result"></div>
</div>
</div>
</div>
body {
font-family: 'Google Sans';
}
.container {
display: flex;
}
.img-container {
position: relative;
}
img {
height: 250px;
border-radius: 8px;
}
.boxes-container {
top: 0;
left: 0;
height: 100%;
width: 100%;
position: absolute;
}
.buttons {
margin-left: 20px;
}
.model {
display: flex;
align-items: center;
}
.result {
margin-left: 20px;
}
.btn {
border: 1px solid #888;
cursor: pointer;
width: 150px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
margin: 10px 0;
}
.btn.disabled {
color: #bbb;
border-color: #bbb;
pointer-events: none;
}
.btn:hover {
background-color: #eee;
}
.box-container {
position: absolute;
}
.box {
border: 2px solid red;
}
.label {
background-color: red;
color: white;
height: 18px;
line-height: 18px;
position: absolute;
top: -16px;
white-space: nowrap;
padding: 0 5px;
font-size: 12px;
display: none;
}
.box-container:hover .label {
display: block;
}
.msg {
visibility: hidden;
font-size: 14px;
}
.msg.show {
visibility: visible;
}
const img = document.querySelector("img");
let lastModel;
let lastClassName;
let warmedUp = false;
setupButton(
"tflite-custom",
async () =>
await tfTask.ObjectDetection.CustomModel.TFLite.load({
model: "https://raw.githack.com/dusskapark/zeplin-ml/main/public/models/rico.tflite"
})
);
async function setupButton(className, modelCreateFn, needWarmup) {
document
.querySelector(`.model.${className} .btn`)
.classList.remove("disabled");
const resultEle = document.querySelector(`.model.${className} .result`);
document
.querySelector(`.model.${className} .btn`)
.addEventListener("click", async () => {
let model;
// Create the model when user clicks on a button.
if (lastClassName !== className) {
// Clean up the previous model if existed.
if (lastModel) {
lastModel.cleanUp();
}
// Create the new model and save it.
resultEle.textContent = "Loading...";
model = await modelCreateFn();
lastModel = model;
lastClassName = className;
}
// Reuse the model if user clicks on the same button.
else {
model = lastModel;
}
// Warm up if needed.
if (needWarmup && !warmedUp) {
await model.predict(img);
warmedUp = true;
}
// Run inference and update result.
const start = Date.now();
const result = await model.predict(img);
const latency = Date.now() - start;
renderDetectionResult(result);
resultEle.textContent = `Latency: ${latency}ms`;
});
}
/** Render detection results. */
function renderDetectionResult(result) {
const boxesContainer = document.querySelector(".boxes-container");
boxesContainer.innerHTML = "";
const objects = result.objects;
for (let i = 0; i < Math.min(5, objects.length); i++) {
const curObject = objects[i];
const boundingBox = curObject.boundingBox;
const name = curObject.className;
const score = curObject.score;
const boxContainer = createDetectionResultBox(
boundingBox.originX,
boundingBox.originY,
boundingBox.width,
boundingBox.height,
name,
score
);
boxesContainer.appendChild(boxContainer);
}
}
/** Create a single detection result box. */
function createDetectionResultBox(left, top, width, height, name, score) {
const container = document.createElement("div");
container.classList.add("box-container");
const box = document.createElement("div");
box.classList.add("box");
container.appendChild(box);
const label = document.createElement("div");
label.classList.add("label");
label.textContent = `${name} (${score.toFixed(2)})`;
container.appendChild(label);
container.style.left = `${left - 1}px`;
container.style.top = `${top - 1}px`;
box.style.width = `${width + 1}px`;
box.style.height = `${height + 1}px`;
return container;
}
This Pen doesn't use any external CSS resources.