Deteksi Objek dengan YOLO: Menemukan Objek dalam Gambar dan Video
Deteksi objek menemukan objek apa saja yang ada dalam gambar dan di mana letaknya. Keluarannya berupa kotak pembatas dengan nama kelas dan skor keyakinan. Keluarga model YOLO cukup cepat untuk video. Artikel ini menjelaskan gagasan di baliknya: koordinat kotak, IoU, NMS, dekode sel grid, pengubahan ukuran letterbox, dan pengukuran kualitas dengan precision, recall, dan AP. Setiap contoh adalah JavaScript yang hanya memakai pustaka standar, sehingga bisa langsung dijalankan tanpa mengunduh model.
Yang Anda butuhkan
Anda memerlukan Node.js 18 atau yang lebih baru dan pengetahuan dasar JavaScript. Jalankan setiap contoh dengan node namaberkas.js. Tidak diperlukan paket tambahan. Menjalankan model YOLO sungguhan pada gambar memerlukan Python dan pustaka ultralytics. Itu tidak dijalankan di mesin uji untuk artikel ini, jadi hanya muncul pada bagian langkah berikutnya.
Ilustrasi
Contoh 1: Gambar adalah larik angka
Gambar digital adalah grid nilai piksel, jadi model bekerja dengan angka. Contoh ini membuat gambar hitam putih 4x4 dan menghitung piksel yang terang.
// Gambar hitam putih 4x4 disimpan sebagai larik nilai kecerahan 0-255
const image = [
[0, 0, 255, 255],
[0, 0, 255, 255],
[0, 0, 0, 0],
[0, 0, 0, 0],
];
const height = image.length;
const width = image[0].length;
console.log(`Ukuran gambar: lebar ${width} x tinggi ${height}`);
let bright = 0;
for (const row of image) for (const p of row) if (p > 127) bright++;
console.log("Jumlah piksel terang:", bright);
console.log("Nilai piksel pojok kiri atas:", image[0][0]);
Keluaran sebenarnya
Ukuran gambar: lebar 4 x tinggi 4
Jumlah piksel terang: 4
Nilai piksel pojok kiri atas: 0
Contoh 2: Mengubah format kotak
YOLO mengembalikan titik tengah dengan lebar dan tinggi (cx, cy, w, h). Untuk menggambar dan menghitung IoU biasanya diperlukan sudut kiri atas dan kanan bawah (x1, y1, x2, y2).
// Model YOLO mengembalikan kotak sebagai (cx, cy, w, h), tetapi menggambar dan menghitung biasanya memakai (x1, y1, x2, y2)
function xywhToXyxy([cx, cy, w, h]) {
return [cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2];
}
const box = [320, 240, 100, 50];
const [x1, y1, x2, y2] = xywhToXyxy(box);
console.log("Pojok kiri atas:", x1, y1);
console.log("Pojok kanan bawah:", x2, y2);
console.log("lebar:", x2 - x1, "tinggi:", y2 - y1);
Keluaran sebenarnya
Pojok kiri atas: 270 215
Pojok kanan bawah: 370 265
lebar: 100 tinggi: 50
Contoh 3: IoU mengukur tumpang tindih kotak
IoU adalah area yang bertumpang tindih dibagi area gabungan. Nilainya berkisar dari 0 sampai 1, dan 1 berarti kedua kotak identik.
// IoU = area yang bertumpang tindih / area gabungan kedua kotak
function area([x1, y1, x2, y2]) {
return Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
}
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]);
const iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]);
const iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
return inter / (area(a) + area(b) - inter);
}
const a = [0, 0, 100, 100];
console.log("Kotak sama:", iou(a, a).toFixed(2));
console.log("Tumpang tindih sepertiga:", iou(a, [50, 0, 150, 100]).toFixed(2));
console.log("Tidak tumpang tindih:", iou(a, [200, 200, 300, 300]).toFixed(2));
Keluaran sebenarnya
Kotak sama: 1.00
Tumpang tindih sepertiga: 0.33
Tidak tumpang tindih: 0.00
Contoh 4: Menyaring berdasarkan keyakinan
Model memprediksi banyak kotak. Kotak dengan keyakinan di bawah ambang akan dibuang sebelum langkah berikutnya.
// Buang kotak yang keyakinannya rendah lebih dulu, dengan ambang keyakinan (confidence threshold)
const detections = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "car", score: 0.31, box: [200, 70, 260, 130] },
{ label: "person", score: 0.88, box: [300, 50, 340, 170] },
{ label: "dog", score: 0.12, box: [10, 150, 60, 200] },
];
const THRESHOLD = 0.5;
const kept = detections.filter(d => d.score >= THRESHOLD);
console.log(`Tersisa ${kept.length} dari ${detections.length} kotak (ambang ${THRESHOLD})`);
for (const d of kept) console.log(d.label, d.score.toFixed(2));
Keluaran sebenarnya
Tersisa 2 dari 4 kotak (ambang 0.5)
car 0.94
person 0.88
Contoh 5: Non-Maximum Suppression (NMS)
NMS mengurutkan kotak berdasarkan keyakinan, menyimpan yang terbaik, dan membuang kotak lain yang IoU-nya tinggi terhadapnya. Hasilnya adalah satu kotak untuk setiap objek.
// Non-Maximum Suppression: simpan kotak yang paling yakin, dan buang kotak yang sangat bertumpang tindih dengannya
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]), iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]), iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
const areaA = (a[2] - a[0]) * (a[3] - a[1]);
const areaB = (b[2] - b[0]) * (b[3] - b[1]);
return inter / (areaA + areaB - inter);
}
function nms(dets, iouThreshold) {
const sorted = [...dets].sort((p, q) => q.score - p.score);
const keep = [];
for (const d of sorted) {
if (keep.every(k => iou(k.box, d.box) < iouThreshold)) keep.push(d);
}
return keep;
}
const dets = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "car", score: 0.81, box: [45, 62, 185, 142] },
{ label: "car", score: 0.67, box: [200, 70, 260, 130] },
{ label: "person", score: 0.88, box: [300, 50, 340, 170] },
];
const result = nms(dets, 0.5);
console.log(`Sebelum NMS ${dets.length} kotak -> sesudah NMS ${result.length} kotak`);
for (const d of result) console.log(d.label, d.score.toFixed(2));
Keluaran sebenarnya
Sebelum NMS 4 kotak -> sesudah NMS 3 kotak
car 0.94
person 0.88
car 0.67
Contoh 6: NMS per kelas
Jika NMS mencampur semua kelas, mobil dan anjing yang bertumpang tindih bisa kehilangan salah satu kotaknya. Jalankan NMS secara terpisah untuk setiap kelas.
// NMS per kelas: kotak dari kelas yang berbeda yang bertumpang tindih tetap disimpan keduanya
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]), iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]), iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter);
}
function nms(dets, iouThreshold) {
const sorted = [...dets].sort((p, q) => q.score - p.score);
const keep = [];
for (const d of sorted) if (keep.every(k => iou(k.box, d.box) < iouThreshold)) keep.push(d);
return keep;
}
function nmsPerClass(dets, iouThreshold) {
const groups = {};
for (const d of dets) (groups[d.label] = groups[d.label] || []).push(d);
return Object.values(groups).flatMap(g => nms(g, iouThreshold));
}
const dets = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "dog", score: 0.90, box: [50, 65, 170, 150] },
{ label: "car", score: 0.85, box: [45, 62, 185, 142] },
];
const result = nmsPerClass(dets, 0.5);
console.log("Sesudah NMS per kelas:");
for (const d of result) console.log(d.label, d.score.toFixed(2));
Keluaran sebenarnya
Sesudah NMS per kelas:
car 0.94
dog 0.90
Contoh 7: Mendekode sel grid
YOLO membagi gambar menjadi grid, dan setiap sel memprediksi nilai mentah. Terapkan sigmoid pada titik tengah dan exp pada lebar serta tinggi untuk mendapatkan koordinat piksel.
// YOLO membagi gambar menjadi grid, dan setiap sel menghasilkan kotak dalam nilai mentah. Nilai itu harus diubah dengan sigmoid dan exp
const sigmoid = x => 1 / (1 + Math.exp(-x));
const STRIDE = 100; // gambar 400x400 dibagi menjadi 4x4 sel, setiap sel 100 piksel
const raw = [
{ name: "Sel A", row: 1, col: 2, tx: 0.2, ty: -0.4, tw: 0.0, th: 0.5, obj: 3.0 },
{ name: "Sel B", row: 3, col: 0, tx: -1.5, ty: 1.0, tw: -0.3, th: 0.1, obj: -2.0 },
];
for (const r of raw) {
const cx = (r.col + sigmoid(r.tx)) * STRIDE;
const cy = (r.row + sigmoid(r.ty)) * STRIDE;
const w = Math.exp(r.tw) * STRIDE;
const h = Math.exp(r.th) * STRIDE;
const conf = sigmoid(r.obj);
console.log(`${r.name}: cx=${cx.toFixed(1)} cy=${cy.toFixed(1)} w=${w.toFixed(1)} h=${h.toFixed(1)} conf=${conf.toFixed(2)}`);
}
Keluaran sebenarnya
Sel A: cx=255.0 cy=140.1 w=100.0 h=164.9 conf=0.95
Sel B: cx=18.2 cy=373.1 w=74.1 h=110.5 conf=0.12
Contoh 8: Softmax memilih kelas
Skor kelas mentah diubah menjadi probabilitas yang jumlahnya 100 persen. Yang tertinggi adalah prediksinya.
// Ubah skor mentah setiap kelas menjadi probabilitas dengan softmax, lalu pilih kelas tertinggi
const names = ["person", "car", "dog"];
const logits = [1.2, 3.1, 0.4];
const exps = logits.map(Math.exp);
const sum = exps.reduce((a, b) => a + b, 0);
const probs = exps.map(e => e / sum);
probs.forEach((p, i) => console.log(names[i].padEnd(8), (p * 100).toFixed(1) + "%"));
const best = probs.indexOf(Math.max(...probs));
console.log("Prediksi:", names[best]);
Keluaran sebenarnya
person 12.3%
car 82.2%
dog 5.5%
Prediksi: car
Contoh 9: Letterbox dan pemetaan kembali
Model menerima ukuran tetap seperti 640x640, jadi gambar diperkecil dengan mempertahankan rasio sisi, lalu diberi bantalan. Setelah deteksi, buang bantalan dan bagi dengan skala untuk kembali ke ukuran asli.
// Model menerima gambar ukuran tetap 640x640, jadi gambar diperkecil dengan mempertahankan rasio lalu diberi tepi hitam (letterbox)
// Koordinat kotak juga harus dikonversi kembali ke ukuran gambar asli
const orig = { w: 1280, h: 720 };
const INPUT = 640;
const scale = Math.min(INPUT / orig.w, INPUT / orig.h);
const newW = Math.round(orig.w * scale);
const newH = Math.round(orig.h * scale);
const padX = (INPUT - newW) / 2;
const padY = (INPUT - newH) / 2;
console.log(`scale=${scale} ukuran baru=${newW}x${newH} padX=${padX} padY=${padY}`);
// kotak yang diprediksi model dalam area 640x640
const boxInInput = [100, 200, 300, 400];
const back = [
(boxInInput[0] - padX) / scale,
(boxInInput[1] - padY) / scale,
(boxInInput[2] - padX) / scale,
(boxInInput[3] - padY) / scale,
];
console.log("Kotak di gambar asli:", back.map(v => Math.round(v)));
Keluaran sebenarnya
scale=0.5 ukuran baru=640x360 padX=0 padY=140
Kotak di gambar asli: [ 200, 120, 600, 520 ]
Contoh 10: Menggambar kotak dengan karakter
Contoh ini menggambar kotak pada kanvas karakter 40x12 sehingga Anda bisa melihat letak objek tanpa pustaka grafis.
// Gambar kotak di atas kanvas karakter untuk melihat hasil deteksi tanpa pustaka grafis
// Gambar asli 400x240 piksel diperkecil menjadi grid 40x12 sel (setiap sel 10x20 piksel)
const COLS = 40, ROWS = 12, SX = 10, SY = 20;
const canvas = Array.from({ length: ROWS }, () => Array(COLS).fill("."));
const dets = [
{ label: "car", box: [40, 60, 180, 140] },
{ label: "person", box: [300, 50, 340, 170] },
];
for (const d of dets) {
const [x1, y1, x2, y2] = d.box;
const c1 = Math.floor(x1 / SX), c2 = Math.floor(x2 / SX);
const r1 = Math.floor(y1 / SY), r2 = Math.floor(y2 / SY);
for (let c = c1; c <= c2; c++) { canvas[r1][c] = "-"; canvas[r2][c] = "-"; }
for (let r = r1; r <= r2; r++) { canvas[r][c1] = "|"; canvas[r][c2] = "|"; }
canvas[r1][c1] = d.label[0].toUpperCase();
}
console.log(canvas.map(row => row.join("")).join("\n"));
Keluaran sebenarnya
........................................
........................................
..............................P---|.....
....C-------------|...........|...|.....
....|.............|...........|...|.....
....|.............|...........|...|.....
....|.............|...........|...|.....
....|-------------|...........|...|.....
..............................|---|.....
........................................
........................................
........................................
Contoh 11: Menulis hasil sebagai SVG
SVG adalah format teks yang bisa dihasilkan oleh kode. Gunakan untuk menggambar kotak dan label di atas gambar. Ia bisa dibuka di browser mana pun.
// Buat berkas SVG yang menumpuk kotak di atas gambar untuk ditampilkan atau disimpan sebagai hasil deteksi
const dets = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "person", score: 0.88, box: [300, 50, 340, 170] },
];
const lines = ['<svg xmlns="http://www.w3.org/2000/svg" width="400" height="240">'];
lines.push('<rect width="400" height="240" fill="#1b1f2a"/>');
for (const d of dets) {
const [x1, y1, x2, y2] = d.box;
lines.push(`<rect x="${x1}" y="${y1}" width="${x2 - x1}" height="${y2 - y1}" fill="none" stroke="#ffb020" stroke-width="2"/>`);
lines.push(`<text x="${x1}" y="${y1 - 4}" font-size="12" fill="#ffb020">${d.label} ${d.score.toFixed(2)}</text>`);
}
lines.push("</svg>");
const svg = lines.join("\n");
console.log(svg);
console.log("Jumlah baris:", lines.length, "panjang:", svg.length, "karakter");
Keluaran sebenarnya
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="240">
<rect width="400" height="240" fill="#1b1f2a"/>
<rect x="40" y="60" width="140" height="80" fill="none" stroke="#ffb020" stroke-width="2"/>
<text x="40" y="56" font-size="12" fill="#ffb020">car 0.94</text>
<rect x="300" y="50" width="40" height="120" fill="none" stroke="#ffb020" stroke-width="2"/>
<text x="300" y="46" font-size="12" fill="#ffb020">person 0.88</text>
</svg>
Jumlah baris: 7 panjang: 441 karakter
Contoh 12: Precision dan recall
Bandingkan prediksi dengan kebenaran (ground truth). TP adalah deteksi yang benar, FP adalah alarm palsu, dan FN adalah objek yang terlewat. Precision adalah bagian prediksi yang benar. Recall adalah bagian objek nyata yang berhasil ditemukan.
// Bandingkan prediksi dengan jawaban yang benar: kotak dengan IoU >= 0.5 dan kelas yang sama adalah TP, sisanya FP
// Jawaban benar yang belum ditebak adalah FN
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]), iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]), iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter);
}
const gts = [
{ label: "car", box: [40, 60, 180, 140] },
{ label: "person", box: [300, 50, 340, 170] },
{ label: "dog", box: [10, 150, 60, 200] },
];
const preds = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "person", score: 0.88, box: [305, 55, 345, 175] },
{ label: "car", score: 0.40, box: [200, 70, 260, 130] },
];
const used = new Array(gts.length).fill(false);
let tp = 0, fp = 0;
for (const p of [...preds].sort((a, b) => b.score - a.score)) {
let best = -1, bestIou = 0.5;
gts.forEach((g, i) => {
const v = iou(p.box, g.box);
if (!used[i] && g.label === p.label && v >= bestIou) { bestIou = v; best = i; }
});
if (best >= 0) { used[best] = true; tp++; } else fp++;
console.log(`${p.label.padEnd(7)} ${p.score.toFixed(2)} ${best >= 0 ? "TP" : "FP"}`);
}
const fn = gts.length - tp;
console.log(`TP=${tp} FP=${fp} FN=${fn}`);
console.log("precision =", (tp / (tp + fp)).toFixed(2));
console.log("recall =", (tp / (tp + fn)).toFixed(2));
Keluaran sebenarnya
car 0.94 TP
person 0.88 TP
car 0.40 FP
TP=2 FP=1 FN=1
precision = 0.67
recall = 0.67
Contoh 13: Average Precision (AP)
AP adalah area di bawah kurva precision-recall, dengan prediksi diurutkan berdasarkan keyakinan. Ini memberikan satu angka untuk membandingkan model.
// Average Precision (AP): ringkas kurva precision-recall menjadi satu angka
// Urutkan kotak berdasarkan keyakinan, lalu hitung precision dan recall kumulatif satu per satu
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]), iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]), iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter);
}
const gts = [
{ label: "car", box: [40, 60, 180, 140] },
{ label: "person", box: [300, 50, 340, 170] },
{ label: "dog", box: [10, 150, 60, 200] },
];
const preds = [
{ label: "car", score: 0.94, box: [40, 60, 180, 140] },
{ label: "person", score: 0.88, box: [305, 55, 345, 175] },
{ label: "car", score: 0.40, box: [200, 70, 260, 130] },
{ label: "dog", score: 0.30, box: [12, 152, 62, 202] },
];
const sorted = [...preds].sort((a, b) => b.score - a.score);
const used = new Array(gts.length).fill(false);
const flags = sorted.map(p => {
let best = -1, bestIou = 0.5;
gts.forEach((g, i) => {
const v = iou(p.box, g.box);
if (!used[i] && g.label === p.label && v >= bestIou) { bestIou = v; best = i; }
});
if (best >= 0) { used[best] = true; return 1; }
return 0;
});
let tp = 0, fp = 0;
const precision = [], recall = [];
flags.forEach(f => {
if (f) tp++; else fp++;
precision.push(tp / (tp + fp));
recall.push(tp / gts.length);
});
for (let i = precision.length - 2; i >= 0; i--) precision[i] = Math.max(precision[i], precision[i + 1]);
let ap = 0, prevRecall = 0;
recall.forEach((r, i) => { ap += (r - prevRecall) * precision[i]; prevRecall = r; });
sorted.forEach((p, i) => console.log(`${p.score.toFixed(2)} ${flags[i] ? "TP" : "FP"} recall=${recall[i].toFixed(2)} precision=${precision[i].toFixed(2)}`));
console.log("AP =", ap.toFixed(3));
Keluaran sebenarnya
0.94 TP recall=0.33 precision=1.00
0.88 TP recall=0.67 precision=1.00
0.40 FP recall=0.67 precision=0.75
0.30 TP recall=1.00 precision=0.75
AP = 0.917
Contoh 14: Alur lengkap
Contoh ini menggabungkan langkah-langkahnya: membaca keluaran mentah, menyaring dengan ambang, NMS, dan memetakan kotak kembali ke gambar asli.
// Contoh gabungan: baca keluaran mentah -> saring dengan ambang -> NMS -> ubah koordinat kembali ke gambar asli
const sigmoid = x => 1 / (1 + Math.exp(-x));
const NAMES = ["person", "car", "dog"];
const STRIDE = 100, INPUT = 400;
const ORIG = { w: 1280, h: 720 };
const scale = Math.min(INPUT / ORIG.w, INPUT / ORIG.h);
const padX = (INPUT - ORIG.w * scale) / 2;
const padY = (INPUT - ORIG.h * scale) / 2;
const raw = [
{ row: 1, col: 1, tx: 0.1, ty: -0.2, tw: 0.2, th: 0.0, obj: 2.5, cls: [-1.0, 3.0, 0.2] },
{ row: 1, col: 2, tx: 0.0, ty: 0.1, tw: 0.1, th: 0.2, obj: 2.0, cls: [-0.5, 2.8, 0.0] },
{ row: 2, col: 0, tx: 0.3, ty: 0.0, tw: -0.4, th: -0.2, obj: 1.5, cls: [2.5, -1.0, 0.1] },
{ row: 3, col: 3, tx: -0.5, ty: 0.2, tw: 0.0, th: 0.1, obj: -2.0, cls: [0.0, 0.0, 3.0] },
];
const dets = raw.map(r => {
const cls = r.cls.map(sigmoid);
const best = cls.indexOf(Math.max(...cls));
const cx = (r.col + sigmoid(r.tx)) * STRIDE;
const cy = (r.row + sigmoid(r.ty)) * STRIDE;
const w = Math.exp(r.tw) * STRIDE;
const h = Math.exp(r.th) * STRIDE;
const score = sigmoid(r.obj) * cls[best];
return { label: NAMES[best], score, box: [cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2] };
});
const kept = dets.filter(d => d.score >= 0.5);
console.log("Sesudah filter ambang 0.5:", kept.length, "kotak");
function iou(a, b) {
const ix1 = Math.max(a[0], b[0]), iy1 = Math.max(a[1], b[1]);
const ix2 = Math.min(a[2], b[2]), iy2 = Math.min(a[3], b[3]);
const inter = Math.max(0, ix2 - ix1) * Math.max(0, iy2 - iy1);
return inter / ((a[2] - a[0]) * (a[3] - a[1]) + (b[2] - b[0]) * (b[3] - b[1]) - inter);
}
const final = [];
for (const d of [...kept].sort((a, b) => b.score - a.score)) {
if (final.every(f => f.label !== d.label || iou(f.box, d.box) < 0.5)) final.push(d);
}
console.log("Sesudah NMS:", final.length, "kotak");
for (const d of final) {
const [x1, y1, x2, y2] = d.box.map((v, i) => (v - (i % 2 === 0 ? padX : padY)) / scale);
console.log(`${d.label.padEnd(7)} ${d.score.toFixed(2)} [${[x1, y1, x2, y2].map(v => Math.round(v)).join(", ")}]`);
}
Keluaran sebenarnya
Sesudah filter ambang 0.5: 3 kotak
Sesudah NMS: 3 kotak
car 0.88 [293, 24, 683, 344]
car 0.83 [623, 13, 977, 403]
person 0.76 [77, 389, 291, 651]
Contoh 15: Menghitung objek dalam video
Video adalah banyak frame yang berurutan. Contoh ini menghitung kelas di setiap frame, lalu memakai kelas yang paling sering muncul dalam tiga frame terakhir untuk mengurangi kedipan.
// Video adalah banyak gambar yang berurutan. Periksa setiap frame lalu hitung kelasnya
// Melihat nilai dari beberapa frame yang berdekatan membantu mengurangi kedipan hasil prediksi
const frames = [
["car", "car", "person"],
["car", "person", "person"],
["car", "car", "person"],
["car", "car"],
["person", "car", "car"],
];
frames.forEach((objs, i) => {
const counts = {};
for (const o of objs) counts[o] = (counts[o] || 0) + 1;
console.log(`Frame ${i + 1}:`, JSON.stringify(counts));
});
// Gunakan kelas yang paling sering muncul dalam jendela 3 frame
function majority(window) {
const c = {};
for (const x of window) c[x] = (c[x] || 0) + 1;
return Object.entries(c).sort((a, b) => b[1] - a[1])[0][0];
}
const labels = frames.map(f => f[0]);
const smoothed = labels.map((_, i) => majority(labels.slice(Math.max(0, i - 2), i + 1)));
console.log("Label per frame:", labels.join(" "));
console.log("Setelah melihat 3 frame ke belakang:", smoothed.join(" "));
Keluaran sebenarnya
Frame 1: {"car":2,"person":1}
Frame 2: {"car":1,"person":2}
Frame 3: {"car":2,"person":1}
Frame 4: {"car":2}
Frame 5: {"person":1,"car":2}
Label per frame: car car car car person
Setelah melihat 3 frame ke belakang: car car car car car
Langkah berikutnya
Untuk menjalankan model sungguhan, pasang Python 3.12, jalankan pip install ultralytics, lalu muat model terlatih seperti YOLO('yolo11n.pt'). Berikan gambar untuk mendapatkan kotak, kelas, dan skor keyakinan. Keluarannya memiliki bentuk yang sama dengan contoh di sini, sehingga logika NMS dan koordinat tetap berlaku.
Ringkasan
Deteksi objek menemukan kotak dan kelas objek dalam gambar. Langkah utamanya adalah mendekode keluaran grid, menyaring berdasarkan keyakinan, menerapkan NMS, dan memetakan kotak kembali ke gambar asli. Kualitasnya diukur dengan precision, recall, dan AP. Setiap keluaran dalam artikel ini berasal dari proses yang benar-benar dijalankan.