Robotic Solutions

AI Vision Inventory Robot

Vision-powered autonomous inventory management.

 

The AI Vision Inventory Robot is an autonomous mobile robot that counts stock using AI cameras and computer vision instead of RFID tags. It drives your aisles on its own, recognises products, books, labels and shelf locations in real time, validates what it sees on board, and syncs the result to a cloud platform — running 24/7 and completing a stock take up to 90% faster than counting by hand.

AI Vision

Advanced AI cameras recognise products, boxes, labels and shelf locations with precision.

Autonomous & Smart

SLAM navigation, obstacle avoidance and autonomous charging for 24/7 operation.

Inventory Intelligence

Real-time data collection and analytics for accurate inventory and reports.

Cloud Connected

Secure cloud platform for remote monitoring, reporting and system management.

Items scanned0
Misplaced0
Missing0
StatusScanning
Illustration of scanning behaviour — not live data
/* ---------- palette + sizing (edit these) ---------- */ .stockbot{ --sb-height: 320px; --sb-bg: #F6F5F1; /* aisle / page ground */ --sb-floor: #E7E4DC; /* floor band */ --sb-line: #DEDBD1; /* borders */ --sb-shelf: #BEBAAC; /* shelving steel */ --sb-shelf-2: #A9A497; /* shelf edge / uprights */ --sb-teal: #013E49; /* HKC petrol — robot detail, counted marks */ --sb-teal-soft: #7FA7B0; --sb-gold: #D69D46; /* HKC gold — scan band, recognition boxes */ --sb-gold-deep: #8A6420; --sb-ink: #0B2027; --sb-ink-2: #4B5A61; --sb-ink-3: #7D8B90; --sb-surface: #FFFFFF; /* stats bar, robot body */ /* stock item tones */ --sb-i1: #C9B99B; --sb-i2: #B4A488; --sb-i3: #D8CDB6; --sb-i4: #9FA9A4; --sb-i5: #C2CBC9; --sb-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --sb-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace; } @media (prefers-color-scheme: dark){ .stockbot[data-scheme="auto"]{ --sb-bg: #06171C; --sb-floor: #0B2229; --sb-line: #17343C; --sb-shelf: #2C4A52; --sb-shelf-2: #3A5C65; --sb-teal: #4FB8C9; --sb-teal-soft: #2A6270; --sb-gold: #E8B765; --sb-gold-deep: #E8B765; --sb-ink: #E7EFF0; --sb-ink-2: #9DB2B7; --sb-ink-3: #74898F; --sb-surface: #0C232A; --sb-i1: #7A6D55; --sb-i2: #8B7C61; --sb-i3: #665C48; --sb-i4: #52605E; --sb-i5: #45514F; } } .stockbot[data-scheme="dark"]{ --sb-bg: #06171C; --sb-floor: #0B2229; --sb-line: #17343C; --sb-shelf: #2C4A52; --sb-shelf-2: #3A5C65; --sb-teal: #4FB8C9; --sb-teal-soft: #2A6270; --sb-gold: #E8B765; --sb-gold-deep: #E8B765; --sb-ink: #E7EFF0; --sb-ink-2: #9DB2B7; --sb-ink-3: #74898F; --sb-surface: #0C232A; --sb-i1: #7A6D55; --sb-i2: #8B7C61; --sb-i3: #665C48; --sb-i4: #52605E; --sb-i5: #45514F; } /* ---------- structure ---------- */ .stockbot, .stockbot *{ box-sizing: border-box; } .stockbot{ width: 100%; font-family: var(--sb-font); } .stockbot__frame{ border: 1px solid var(--sb-line); border-radius: 14px; background: var(--sb-bg); overflow: hidden; } .stockbot__canvas{ display: block; width: 100%; height: var(--sb-height); max-width: 100%; } .stockbot__bar{ display: flex; flex-wrap: wrap; align-items: center; border-top: 1px solid var(--sb-line); background: var(--sb-surface); } .stockbot__stat{ display: flex; align-items: baseline; gap: 9px; padding: 11px 20px; border-right: 1px solid var(--sb-line); font-family: var(--sb-mono); font-size: 11px; line-height: 1.4; letter-spacing: .11em; text-transform: uppercase; color: var(--sb-ink-3); } .stockbot__stat b{ font-size: 14px; font-weight: 600; letter-spacing: .02em; color: var(--sb-ink); font-variant-numeric: tabular-nums; } .stockbot__stat--live b{ color: var(--sb-gold-deep); } .stockbot__note{ margin-left: auto; padding: 11px 20px; font-family: var(--sb-mono); font-size: 10px; line-height: 1.4; letter-spacing: .08em; text-transform: uppercase; color: var(--sb-ink-3); } @media (max-width: 820px){ .stockbot__note{ margin-left: 0; width: 100%; border-top: 1px solid var(--sb-line); } .stockbot__stat{ padding: 10px 14px; } } (function () { 'use strict'; function initAll(){ var n = document.querySelectorAll('.stockbot'); for (var i = 0; i < n.length; i++) init(n[i]); } function init(root){ if (root.getAttribute('data-sb-ready') === '1') return; root.setAttribute('data-sb-ready', '1'); var cv = root.querySelector('.stockbot__canvas'); if (!cv || !cv.getContext) return; var ctx = cv.getContext('2d'); var elScanned = root.querySelector('[data-sb="scanned"]'); var elFlagged = root.querySelector('[data-sb="flagged"]'); var elMissing = root.querySelector('[data-sb="missing"]'); var elStatus = root.querySelector('[data-sb="status"]'); var motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); var W = 0, H = 0, dpr = 1; var floorY = 0, shelfTop = 0, rows = 3, rowH = 0; var items = [], uprights = []; var bot = null; var scanned = 0, flagged = 0, missing = 0; var rafId = null, last = 0, running = false; var C = {}, MODE = 'vision'; function readTokens(){ var s = getComputedStyle(root); function g(n){ return s.getPropertyValue(n).trim(); } C = { bg:g('--sb-bg'), floor:g('--sb-floor'), line:g('--sb-line'), shelf:g('--sb-shelf'), shelf2:g('--sb-shelf-2'), teal:g('--sb-teal'), tealSoft:g('--sb-teal-soft'), gold:g('--sb-gold'), goldDeep:g('--sb-gold-deep'), ink:g('--sb-ink'), ink2:g('--sb-ink-2'), ink3:g('--sb-ink-3'), surface:g('--sb-surface'), items:[g('--sb-i1'),g('--sb-i2'),g('--sb-i3'),g('--sb-i4'),g('--sb-i5')], mono:g('--sb-mono') || 'monospace' }; MODE = (root.getAttribute('data-mode') || 'vision').toLowerCase(); if (MODE !== 'rfid') MODE = 'vision'; } function rr(x,y,w,h,r){ r = Math.min(r, Math.abs(w)/2, Math.abs(h)/2); ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); } function build(){ floorY = H - Math.max(20, H * 0.08); shelfTop = Math.max(14, H * 0.06); rows = H < 240 ? 3 : 4; rowH = (floorY - shelfTop) / rows; // uprights every ~170px uprights = []; var step = Math.max(150, W / Math.max(2, Math.round(W / 190))); for (var ux = 0; ux <= W + step; ux += step) uprights.push(ux); items = []; for (var r = 0; r < rows; r++){ var boardY = shelfTop + (r + 1) * rowH; // shelf board the row sits on var x = 8; while (x W - 8) break; var gap = 2 + Math.random() * 3; // ~7% of slots are empty — these become "missing" when scanned var empty = Math.random() < 0.07; var h = rowH * (0.52 + Math.random() * 0.34); items.push({ x: x, w: w, h: h, row: r, top: boardY - 3 - h, col: C.items[(Math.random() * C.items.length) | 0], empty: empty, // ~6% of present items are misplaced misplaced: (!empty && Math.random() scan -> done t: 0 }); x += w + gap; } } bot = { x: -60, speed: 52 + Math.random() * 10, alpha: 0, h: (floorY - shelfTop) * 0.92 }; } function reset(){ build(); if (elStatus) elStatus.textContent = 'Scanning'; } function scanX(){ return bot.x + 26; } function update(dt){ bot.x += bot.speed * dt; bot.alpha = Math.min(1, bot.alpha + dt * 2); var sx = scanX(); for (var i = 0; i = it.x + it.w * 0.5){ it.state = 'scan'; it.t = 0; if (it.empty){ missing++; } else { scanned++; if (it.misplaced) flagged++; } } else if (it.state === 'scan'){ it.t += dt / 0.55; if (it.t >= 1){ it.t = 1; it.state = 'done'; } } } if (bot.x > W + 70){ scanned = 0; flagged = 0; missing = 0; reset(); } } function drawShelving(){ // floor ctx.fillStyle = C.floor; ctx.fillRect(0, floorY, W, H - floorY); ctx.strokeStyle = C.shelf2; ctx.globalAlpha = .5; ctx.beginPath(); ctx.moveTo(0, floorY + .5); ctx.lineTo(W, floorY + .5); ctx.stroke(); ctx.globalAlpha = 1; // shelf boards ctx.fillStyle = C.shelf; for (var r = 0; r <= rows; r++){ var y = shelfTop + r * rowH; ctx.fillRect(0, y, W, 3); } // uprights ctx.fillStyle = C.shelf2; for (var i = 0; i < uprights.length; i++){ ctx.fillRect(uprights[i], shelfTop, 4, floorY - shelfTop); } // bay labels ctx.fillStyle = C.ink3; ctx.font = '600 8px ' + C.mono; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; for (var j = 0; j < uprights.length - 1; j++){ ctx.fillText('A' + (j + 1), uprights[j] + 8, shelfTop + 5); } } function drawItems(){ for (var i = 0; i < items.length; i++){ var it = items[i]; if (!it.empty){ ctx.fillStyle = it.col; rr(it.x, it.top, it.w, it.h, 1.5); ctx.fill(); // spine highlight ctx.fillStyle = C.surface; ctx.globalAlpha = .22; ctx.fillRect(it.x + 1.5, it.top + 2, Math.max(1, it.w * 0.22), it.h - 4); ctx.globalAlpha = 1; } // recognition annotation if (it.state === 'scan'){ var e = 1 - Math.pow(1 - it.t, 2); ctx.globalAlpha = 0.35 + 0.65 * (1 - it.t); ctx.strokeStyle = it.empty ? C.ink3 : C.gold; ctx.lineWidth = 1.4; if (it.empty) ctx.setLineDash([3, 3]); var pad = 2.5 * (1 - e) + 1.5; rr(it.x - pad, it.top - pad, it.w + pad * 2, it.h + pad * 2, 2); ctx.stroke(); ctx.setLineDash([]); ctx.globalAlpha = 1; } if (it.state === 'done'){ if (it.empty){ // missing slot ctx.strokeStyle = C.ink3; ctx.globalAlpha = .55; ctx.lineWidth = 1; ctx.setLineDash([3, 3]); rr(it.x, it.top, it.w, it.h, 2); ctx.stroke(); ctx.setLineDash([]); ctx.globalAlpha = 1; } else if (it.misplaced){ // flagged: persistent gold outline + marker dot ctx.strokeStyle = C.gold; ctx.lineWidth = 1.3; rr(it.x - 1.5, it.top - 1.5, it.w + 3, it.h + 3, 2); ctx.stroke(); ctx.fillStyle = C.gold; ctx.beginPath(); ctx.arc(it.x + it.w / 2, it.top - 5, 2, 0, Math.PI * 2); ctx.fill(); } else { // counted: small teal tick under the item ctx.fillStyle = C.teal; ctx.globalAlpha = .8; ctx.fillRect(it.x, it.top + it.h + 1.5, it.w, 1.4); ctx.globalAlpha = 1; } } } } function drawScan(){ var sx = scanX(); if (sx W + 30) return; var bandW = MODE === 'rfid' ? 96 : 62; var top = shelfTop, bot2 = floorY; var grad = ctx.createLinearGradient(sx - bandW / 2, 0, sx + bandW / 2, 0); grad.addColorStop(0, 'rgba(0,0,0,0)'); grad.addColorStop(0.5, C.gold); grad.addColorStop(1, 'rgba(0,0,0,0)'); ctx.globalAlpha = MODE === 'rfid' ? .13 : .18; ctx.fillStyle = grad; ctx.fillRect(sx - bandW / 2, top, bandW, bot2 - top); ctx.globalAlpha = 1; // leading edge ctx.strokeStyle = C.gold; ctx.globalAlpha = .75; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.moveTo(sx, top); ctx.lineTo(sx, bot2); ctx.stroke(); ctx.globalAlpha = 1; } function drawBot(){ var baseW = 34, baseH = 13; var x = bot.x; var y = floorY; var topY = y - bot.h; ctx.save(); ctx.globalAlpha = bot.alpha; // ground glow ctx.fillStyle = C.teal; ctx.globalAlpha = bot.alpha * .12; ctx.beginPath(); ctx.ellipse(x, y + 2, baseW * 0.85, 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = bot.alpha; // sensing rays from the head toward the shelves var headY = topY + 10; ctx.strokeStyle = MODE === 'rfid' ? C.gold : C.teal; ctx.globalAlpha = bot.alpha * .45; ctx.lineWidth = 1; if (MODE === 'rfid'){ for (var k = 1; k <= 3; k++){ ctx.beginPath(); ctx.arc(x + 8, y - bot.h * 0.5, 12 + k * 11, -Math.PI * 0.42, Math.PI * 0.42); ctx.stroke(); } } else { ctx.beginPath(); ctx.moveTo(x + 6, headY); ctx.lineTo(scanX() + 22, shelfTop + 6); ctx.moveTo(x + 6, headY); ctx.lineTo(scanX() + 22, floorY - 6); ctx.stroke(); } ctx.globalAlpha = bot.alpha; // mast ctx.strokeStyle = C.ink2; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(x, topY + 26); ctx.lineTo(x, y - baseH - 26); ctx.stroke(); // camera head ctx.fillStyle = C.ink; rr(x - 5, topY, 10, 18, 2.5); ctx.fill(); ctx.fillStyle = MODE === 'rfid' ? C.gold : C.teal; ctx.beginPath(); ctx.arc(x, topY + 9, 2.4, 0, Math.PI * 2); ctx.fill(); // gold collar ctx.fillStyle = C.surface; ctx.strokeStyle = C.gold; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(x, topY + 34, 15, 8, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); // tapered body var bodyTop = topY + 40; var bodyBot = y - baseH - 1; ctx.beginPath(); ctx.moveTo(x - 8, bodyTop); ctx.quadraticCurveTo(x - 10, (bodyTop + bodyBot) / 2, x - 19, bodyBot); ctx.lineTo(x + 19, bodyBot); ctx.quadraticCurveTo(x + 10, (bodyTop + bodyBot) / 2, x + 8, bodyTop); ctx.closePath(); ctx.fillStyle = C.surface; ctx.fill(); ctx.strokeStyle = C.shelf2; ctx.lineWidth = 1; ctx.stroke(); // base + gold ring ctx.fillStyle = C.surface; rr(x - baseW / 2, y - baseH, baseW, baseH, 5); ctx.fill(); ctx.strokeStyle = C.gold; ctx.lineWidth = 2; rr(x - baseW / 2, y - baseH, baseW, baseH, 5); ctx.stroke(); ctx.fillStyle = C.teal; ctx.fillRect(x - 7, y - baseH / 2 - 1, 14, 2); ctx.restore(); } function draw(){ ctx.clearRect(0, 0, W, H); ctx.fillStyle = C.bg; ctx.fillRect(0, 0, W, H); drawShelving(); drawItems(); drawScan(); drawBot(); } function syncStats(){ if (elScanned && elScanned.textContent !== String(scanned)) elScanned.textContent = scanned.toLocaleString('en-US'); if (elFlagged && elFlagged.textContent !== String(flagged)) elFlagged.textContent = String(flagged); if (elMissing && elMissing.textContent !== String(missing)) elMissing.textContent = String(missing); } function frame(now){ if (!running) return; var dt = Math.min(0.05, (now - last) / 1000 || 0); last = now; update(dt); draw(); syncStats(); rafId = requestAnimationFrame(frame); } function start(){ if (running || motionQuery.matches || !W) return; running = true; last = performance.now(); rafId = requestAnimationFrame(frame); } function stop(){ running = false; if (rafId) cancelAnimationFrame(rafId); rafId = null; } function staticFrame(){ // pose a representative still: robot mid-run, shelves part-scanned bot.x = W * 0.46; bot.alpha = 1; var sx = scanX(); scanned = 0; flagged = 0; missing = 0; for (var i = 0; i < items.length; i++){ var it = items[i]; if (it.x + it.w * 0.5 < sx){ it.state = 'done'; it.t = 1; if (it.empty) missing++; else { scanned++; if (it.misplaced) flagged++; } } } draw(); syncStats(); if (elStatus) elStatus.textContent = 'Scanning'; } function resize(){ var r = cv.getBoundingClientRect(); if (!r.width) return; dpr = Math.min(window.devicePixelRatio || 1, 2); cv.width = Math.round(r.width * dpr); cv.height = Math.round(r.height * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); W = r.width; H = r.height; readTokens(); scanned = 0; flagged = 0; missing = 0; build(); if (motionQuery.matches) staticFrame(); else draw(); } var rt; window.addEventListener('resize', function(){ clearTimeout(rt); rt = setTimeout(resize, 140); }); var darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); function repaint(){ readTokens(); if (!running) draw(); } if (darkQuery.addEventListener) darkQuery.addEventListener('change', repaint); if (window.MutationObserver){ new MutationObserver(function(){ readTokens(); if (!running) draw(); }) .observe(root, { attributes: true, attributeFilter: ['data-scheme', 'data-mode'] }); } if (motionQuery.addEventListener){ motionQuery.addEventListener('change', function(){ if (motionQuery.matches){ stop(); staticFrame(); } else { start(); } }); } document.addEventListener('visibilitychange', function(){ if (document.hidden) stop(); else start(); }); if ('IntersectionObserver' in window){ new IntersectionObserver(function(entries){ if (entries[0].isIntersecting) start(); else stop(); }, { threshold: 0.02 }).observe(cv); } function boot(){ resize(); if (!motionQuery.matches) start(); } if (document.fonts && document.fonts.ready) document.fonts.ready.then(boot).catch(boot); else boot(); } if (document.readyState === 'loading'){ document.addEventListener('DOMContentLoaded', initAll); } else { initAll(); } })();

What is an AI vision inventory robot?

An AI vision inventory robot is an autonomous mobile robot that counts and verifies stock using cameras and computer vision, rather than by reading RFID tags or by a person walking the aisles with a scanner.

Traditional stocktaking has three problems that never go away: it is manual, it is slow, and it is inconsistent. Counts happen quarterly rather than continuously because each one costs a shift of labour, and by the time the numbers are compiled they are already out of date.

The AI Vision Inventory Robot removes the labour from the loop. It runs a scheduled task overnight or between shifts, recognises what is on each shelf, flags what is missing or misplaced, and has a report waiting before the floor opens.

The key difference is that it needs no RFID infrastructure. RFID stocktaking is fast and accurate, but only after every item carries a tag and the estate is maintained. For collections that are not tagged — or where tagging every item is not economic — that is a hard barrier to entry.

Computer vision reads what is already there: the product itself, the box, the printed label, the barcode and the shelf position. Nothing has to be applied to the stock before the robot can start counting it.

Robot Scanning Library Shelves
HOW

How the AI Vision Inventory Robot works

Every stock take runs the same six-step cycle, from a scheduled task to a finished report — with no person in the aisle at any point.

1.

Mission setup

Set the stock take task, select the areas or scan groups to cover, and schedule when the robot should run.

3.

AI scanning & recognition

The AI vision system scans the shelves and recognises items, barcodes and labels in real time as it travels.

5.

Cloud sync in real time

Validated inventory data is uploaded instantly to the cloud platform, so results are available while the robot is still working.

2.

Autonomous navigation

The robot navigates the environment on its own using SLAM, avoiding obstacles and optimising its path through the aisles.

4.

Data validation

Captured data is validated on board the robot to ensure accuracy and completeness before anything is sent onward.

6.

Reports & insights

The platform turns the scan into inventory reports, missing and misplaced item lists, item-level detail and Excel exports.

Benefits

Key Benefits

What changes on the floor once the robot is running.

High accuracy

AI vision ensures precise identification and verification, every pass.

Save time

Up to 90% faster than manual stock takes.

Reduce cost

Lower labour costs and minimise human error.

Safe & reliable

Multi-sensor protection for safe operation in dynamic environments.

Real-time visibility

Live inventory status, anytime, anywhere.

WHERE

Designed for modern facilities

Anywhere stock sits on shelves, needs counting often, and is not economically taggable.

Libraries & archives

Shelf-reading and collection audits without closing the floor. The platform includes item locating by barcode and floor-plan search, plus misplaced-item detection — the two jobs that consume most shelving time.

Warehouse & distribution

Cycle counting across racking without pulling staff off picking, and item-level reports exported straight to Excel or an upstream system.

Education & institutions

Campus libraries, resource centres and equipment stores with large collections and small teams.

Retail & supermarket

Continuous shelf counts instead of quarterly ones, with missing and misplaced stock flagged before a customer finds the gap.

Manufacturing stores

Component and consumable stores where accuracy drives line availability, and where a missed part costs more than the count.

Asset-heavy facilities

Patrol management doubles the robot as a scheduled floor-check platform between inventory tasks.

Let's Talk

Tell us the task, not the robot

Describe the work you want automated — what moves, how often, how far, and how many people it takes today. That is enough for HKC to say which robot fits, whether the numbers work, and what a pilot would involve.

image

Email

contact_hkcgroup@hkc.net

image

Call Us

+852 2527 8822

image

Office Address

14/F., Block B, Vita Tower, 29 Wong Chuk Hang Road, Hong Kong