Розробка AI-системи автоматичної інвентаризації складу через камери дрони

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Розробка AI-системи автоматичної інвентаризації складу через камери дрони
Складний
~2-4 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

Розроблення AI для інвентаризації складу (камери та дрони)

Ручна інвентаризація складу займає 2–5 днів простою з 1–3% помилок підрахунку. AI-системи на основі нерухомих камер або дронів скорочують час до кількох годин із точністю підрахунку 98–99%. Дрони особливо ефективні для високих стелажів (10+ метрів), недоступних ручним сканерам. Завдання: підрахунок піддонів, контроль заповнення комірок, ідентифікація артикулів за штрихкодами та зоровими ознаками.

Система підрахунку піддонів через камери

import numpy as np
import cv2
from ultralytics import YOLO
import sqlite3
from datetime import datetime

class WarehouseInventorySystem:
    def __init__(self, detector_path: str,
                  db_path: str = 'warehouse.db',
                  device: str = 'cuda'):
        self.detector = YOLO(detector_path)
        self.device = device
        self.db = sqlite3.connect(db_path, check_same_thread=False)
        self._init_db()

    def _init_db(self):
        self.db.execute('''
            CREATE TABLE IF NOT EXISTS inventory_snapshots (
                id INTEGER PRIMARY KEY,
                camera_id TEXT,
                zone_id TEXT,
                timestamp TIMESTAMP,
                pallet_count INTEGER,
                occupancy_pct REAL,
                detections TEXT
            )
        ''')
        self.db.commit()

    def count_pallets(self, image: np.ndarray,
                       camera_id: str,
                       zone_id: str,
                       total_slots: int = None) -> dict:
        """
        Підрахунок піддонів у зоні сховища.
        total_slots: відоме загальне число комірок для % заповнення.
        """
        results = self.detector(image, conf=0.45)
        detections = []

        for box in results[0].boxes:
            cls_name = self.detector.model.names[int(box.cls)]
            detections.append({
                'type': cls_name,
                'bbox': box.xyxy[0].tolist(),
                'conf': float(box.conf)
            })

        pallet_count = sum(
            1 for d in detections
            if d['type'] in ['pallet', 'loaded_pallet', 'empty_pallet']
        )
        loaded = sum(1 for d in detections if d['type'] == 'loaded_pallet')
        empty = sum(1 for d in detections if d['type'] == 'empty_pallet')

        occupancy = (pallet_count / total_slots * 100) if total_slots else None

        result = {
            'camera_id': camera_id,
            'zone_id': zone_id,
            'pallet_count': pallet_count,
            'loaded_pallets': loaded,
            'empty_pallets': empty,
            'occupancy_pct': occupancy,
            'timestamp': datetime.now().isoformat()
        }

        # Збереження в базу даних
        self.db.execute(
            'INSERT INTO inventory_snapshots VALUES (NULL, ?, ?, ?, ?, ?, ?)',
            (camera_id, zone_id, result['timestamp'],
             pallet_count, occupancy, str(detections))
        )
        self.db.commit()

        return result

    def get_zone_history(self, zone_id: str,
                          hours: int = 24) -> list[dict]:
        """Історія змін заповнення зони"""
        rows = self.db.execute('''
            SELECT timestamp, pallet_count, occupancy_pct
            FROM inventory_snapshots
            WHERE zone_id = ?
            AND timestamp > datetime('now', ?)
            ORDER BY timestamp DESC
        ''', (zone_id, f'-{hours} hours')).fetchall()
        return [
            {'timestamp': r[0], 'pallet_count': r[1], 'occupancy': r[2]}
            for r in rows
        ]

Дронова система для високих стелажів

class DroneInventorySystem:
    """
    Автономний об'їзд стелажів із камерою:
    1. Планування маршруту за відомою картою складу
    2. Знімання кожної комірки
    3. OCR штрихкодів та QR-кодів
    4. Перевірка з WMS (Warehouse Management System)
    """
    def __init__(self, barcode_reader, wms_client,
                  rack_detector: YOLO):
        self.barcode_reader = barcode_reader
        self.wms = wms_client
        self.rack_detector = rack_detector

    def inventory_rack(self, rack_images: list[dict]) -> dict:
        """
        rack_images: [{'image': np.ndarray, 'rack_id': str, 'shelf': int, 'slot': int}]
        Аналіз усіх фото конкретного стелажа.
        """
        rack_inventory = []
        discrepancies = []

        for item in rack_images:
            image = item['image']
            location = f"{item['rack_id']}-{item['shelf']}-{item['slot']}"

            # Детекція заповнення комірки
            results = self.rack_detector(image, conf=0.5)
            is_occupied = len(results[0].boxes) > 0

            # OCR штрихкода/QR-кода
            barcodes = self._read_barcodes(image)

            slot_data = {
                'location': location,
                'occupied': is_occupied,
                'barcodes': barcodes,
                'sku_ids': [self._barcode_to_sku(b) for b in barcodes]
            }

            rack_inventory.append(slot_data)

            # Перевірка з WMS
            if barcodes:
                for sku_id in slot_data['sku_ids']:
                    wms_expected = self.wms.get_expected_sku(location)
                    if sku_id != wms_expected:
                        discrepancies.append({
                            'location': location,
                            'found_sku': sku_id,
                            'expected_sku': wms_expected,
                            'discrepancy_type': 'wrong_sku' if wms_expected else 'unexpected_item'
                        })

        return {
            'total_slots_checked': len(rack_inventory),
            'occupied_slots': sum(1 for s in rack_inventory if s['occupied']),
            'empty_slots': sum(1 for s in rack_inventory if not s['occupied']),
            'barcodes_read': sum(len(s['barcodes']) for s in rack_inventory),
            'discrepancies': discrepancies,
            'discrepancy_rate': len(discrepancies) / len(rack_inventory) if rack_inventory else 0,
            'inventory': rack_inventory
        }

    def _read_barcodes(self, image: np.ndarray) -> list[str]:
        """Читання штрихкодів через pyzbar + QR через cv2"""
        from pyzbar import pyzbar
        decoded = pyzbar.decode(image)
        barcodes = [d.data.decode('utf-8') for d in decoded]

        # Спроба QR через OpenCV
        qr = cv2.QRCodeDetector()
        data, _, _ = qr.detectAndDecode(image)
        if data:
            barcodes.append(data)

        return barcodes

    def _barcode_to_sku(self, barcode: str) -> str:
        """Маппінг штрихкода → SKU через WMS"""
        return self.wms.lookup_barcode(barcode) or barcode

Генерування звіту інвентаризації

def generate_inventory_report(inventory_data: list[dict],
                                wms_data: dict,
                                output_format: str = 'json') -> dict:
    """Зведений звіт із розбіжностями для бухгалтерії/логістики"""
    total_items = sum(s.get('quantity', 1) for s in inventory_data)
    occupied = sum(1 for s in inventory_data if s.get('occupied'))

    all_discrepancies = []
    for slot in inventory_data:
        for disc in slot.get('discrepancies', []):
            all_discrepancies.append({**disc, 'location': slot['location']})

    return {
        'report_date': datetime.now().isoformat(),
        'summary': {
            'total_slots_scanned': len(inventory_data),
            'occupied': occupied,
            'empty': len(inventory_data) - occupied,
            'occupancy_rate_pct': occupied / len(inventory_data) * 100,
            'total_items': total_items,
            'discrepancy_count': len(all_discrepancies)
        },
        'discrepancies': all_discrepancies,
        'accuracy_rate': 1 - len(all_discrepancies) / max(len(inventory_data), 1)
    }
Метод Точність підрахунку Швидкість Покриття
Нерухомі камери 95–98% Реал-тайм Поля зору камер
Мобільний робот-AGV 97–99% 2–4 км/год Цілий поверх
Дрон-квадрокоптер 96–99% 5–8 км/год 3D, високі стелажі
RFID (без CV) 99%+ Реал-тайм Лише RFID-мітки
Завдання Строк виконання
Підрахунок піддонів на основі існуючих камер 4–6 тижнів
Дронова інвентаризація стелажів + інтеграція WMS 12–20 тижнів
Повна автономна система інвентаризації (AGV + AI) 24–40 тижнів