智能视觉货架库存盘点机器人

举报
DS小龙哥 发表于 2025/12/25 11:44:26 2025/12/25
【摘要】 项目开发背景在零售和仓储管理中,库存盘点是一项关键但繁琐的任务。传统方法主要依赖人工进行盘点,不仅过程耗时耗力,还容易因疲劳或疏忽导致数据错误,从而影响库存准确性、运营效率和客户满意度。随着业务规模的扩大和智能化需求的提升,实现实时、精准的库存管理已成为行业迫切需求,推动自动化盘点解决方案的发展。得益于计算机视觉和嵌入式技术的快速发展,智能机器人逐渐成为解决库存盘点难题的有效工具。通过集成...

项目开发背景

在零售和仓储管理中,库存盘点是一项关键但繁琐的任务。传统方法主要依赖人工进行盘点,不仅过程耗时耗力,还容易因疲劳或疏忽导致数据错误,从而影响库存准确性、运营效率和客户满意度。随着业务规模的扩大和智能化需求的提升,实现实时、精准的库存管理已成为行业迫切需求,推动自动化盘点解决方案的发展。

得益于计算机视觉和嵌入式技术的快速发展,智能机器人逐渐成为解决库存盘点难题的有效工具。通过集成摄像头和轻量级目标检测算法,如YOLO或SSD,机器人能够自动识别货架上的商品种类并统计数量,同时结合移动底盘与传感器实现自主循迹导航和避障,从而完成全自动、高精度的盘点工作,显著提升作业效率并降低人工成本。

本项目“智能视觉货架库存盘点机器人”基于上述技术背景,旨在设计一个实用且经济的自动化系统。它采用树莓派Pico W作为处理核心,搭配OpenMV摄像头进行实时图像采集与识别,并通过Wi-Fi实时传输盘点数据至后台服务器。其循迹移动、防跌落和障碍物避障功能确保了机器人在复杂环境中的安全稳定运行,为零售、仓库等场景提供了一种高效、可靠的库存管理解决方案。

设计实现的功能

(1)基于红外或超声传感器实现沿预设磁轨或色带的循迹移动。
(2)通过OpenMV或OV2640摄像头,配合补光LED,对货架各层进行图像采集。
(3)运用YOLO或SSD等轻量级目标检测算法,识别并统计图像中的商品数量与种类。
(4)通过Wi-Fi将盘点结果(商品ID、数量、位置)实时发送至后台服务器。
(5)具备防跌落检测和障碍物避障功能,异常时自动停车并报警。

项目硬件模块组成

(1)主控模块:采用树莓派Pico W作为移动与视觉处理核心,平衡性能与功耗。
(2)视觉处理模块:采用OpenMV Cam H7 Plus摄像头,内置高性能处理器可独立运行识别算法。
(3)移动底盘模块:包括TT减速电机车轮、L298N电机驱动板及循迹/避障传感器模块。
(4)通信模块:利用树莓派Pico W的板载Wi-Fi进行数据传输。
(5)结构供电模块:定制亚克力板车身,采用两节18650锂电池与XL6009升压模块供电。

设计意义

该智能视觉货架库存盘点机器人的设计意义在于显著提升仓储管理的自动化水平,通过集成循迹移动与视觉识别技术,实现货架库存的自主盘点,减少对人工巡检的依赖,从而降低运营成本并提高盘点效率。其应用有助于缩短库存检查周期,确保数据及时更新,为仓储优化和补货决策提供有力支持。

基于轻量级目标检测算法如YOLO或SSD,机器人能够准确识别商品种类与数量,大幅降低人为盘点中常见的错误率,提升库存数据的可靠性。这种精准识别能力可适应多样化商品环境,增强系统在实际场景中的适用性,为库存管理的精细化和智能化奠定基础。

通过Wi-Fi实时传输盘点结果至后台服务器,机器人实现了库存数据的即时同步,便于管理人员远程监控和动态调整。这种实时通信功能强化了仓储系统的响应能力,支持快速处理库存异常,从而提升整体供应链的运作效率。

机器人的防跌落检测与障碍物避障功能确保了其在复杂环境中的安全运行,避免因意外碰撞或跌落造成设备损坏或数据中断。这一设计增强了系统的稳定性和耐用性,保障盘点任务的连续性,减少维护需求。

在硬件设计上,采用树莓派Pico W与OpenMV摄像头等模块,平衡了性能与功耗,使得机器人结构紧凑、续航能力较强。定制化车身与高效供电方案进一步优化了移动性和部署便利性,体现了成本效益与实用性的结合,为中小型仓储场景提供了可行的自动化解决方案。

设计思路

智能视觉货架库存盘点机器人的设计以树莓派Pico W作为核心主控模块,它负责协调移动、视觉处理和通信任务,以实现高性能与低功耗的平衡。整体设计强调模块化集成,通过定制亚克力板车身构建坚固底盘,容纳所有硬件组件,确保机器人能够稳定地在货架环境中运行。视觉处理模块采用OpenMV Cam H7 Plus摄像头,其内置高性能处理器可独立运行轻量级目标检测算法,如YOLO或SSD,配合补光LED在货架各层进行图像采集,从而准确识别商品种类与数量。

移动底盘模块基于TT减速电机车轮和L298N电机驱动板,实现精确的循迹移动功能。机器人通过红外或超声传感器沿预设磁轨或色带自主导航,同时集成防跌落检测和障碍物避障传感器,在遇到异常情况时自动停车并报警,保障操作安全。这种设计确保了机器人在复杂货架环境中能稳定行进,避免碰撞或跌落风险。

在视觉处理方面,OpenMV摄像头采集的图像直接在设备上运行目标检测算法,减少了对外部计算的依赖。算法通过训练模型识别货架上的商品,实时统计库存数据,并将结果结构化处理。通信模块利用树莓派Pico W的板载Wi-Fi功能,将盘点结果(包括商品ID、数量和位置)无线传输至后台服务器,实现数据的实时更新和远程监控,提升了库存管理的效率。

结构供电模块采用两节18650锂电池结合XL6009升压模块,为整个系统提供稳定电力支持,确保长时间运行需求。亚克力板车身不仅轻便耐用,还优化了传感器和摄像头的布局,使机器人能适应货架多层扫描任务。整体设计注重实用性和可靠性,所有功能均基于现有硬件实现,无需额外添加,从而在低成本下完成自动化库存盘点。

框架图

远程服务端
动力与结构
控制与处理核心
感知与执行层
循迹/障碍信号
图像流/识别结果
跌落信号
电机控制信号
驱动动力
盘点数据: 商品ID/数量/位置
Wi-Fi传输
供电
供电
供电
供电
后台服务器
两节18650锂电池
XL6009升压模块
系统电源总线
TT减速电机
定制亚克力底盘
Wi-Fi通信
树莓派Pico W主控
L298N电机驱动板
循迹/避障传感器
补光LED
OpenMV Cam H7 Plus
防跌落传感器

系统总体设计

该系统以树莓派Pico W作为核心主控模块,负责协调移动、视觉处理和数据通信等任务,确保机器人在货架环境中高效运行。移动底盘模块包括TT减速电机车轮和L298N电机驱动板,通过红外或超声传感器沿预设磁轨或色带进行循迹移动,同时集成防跌落检测和障碍物避障功能,当检测到异常时自动停车并触发报警,以保障运行安全。

视觉处理模块采用OpenMV Cam H7 Plus摄像头,配合补光LED对货架各层进行图像采集。该摄像头内置高性能处理器,可独立运行YOLO或SSD等轻量级目标检测算法,识别并统计图像中的商品数量与种类,减轻主控模块的计算负担,提升盘点效率。

通信模块利用树莓派Pico W的板载Wi-Fi功能,将盘点结果包括商品ID、数量和位置实时发送至后台服务器,实现数据同步与远程监控。结构供电模块基于定制亚克力板车身,由两节18650锂电池和XL6009升压模块提供稳定电力,支持整个系统的长时间工作。整体设计注重模块化协同,确保机器人稳定完成库存盘点任务。

系统功能总结

序号 功能名称 功能描述 关键硬件模块
1 循迹移动 基于红外或超声传感器,沿预设磁轨或色带实现自主移动,确保机器人按路径行驶。 移动底盘模块(TT减速电机、L298N驱动板、循迹传感器)
2 图像采集 通过OpenMV或OV2640摄像头配合补光LED,对货架各层进行高清图像采集,以获取商品视觉数据。 视觉处理模块(OpenMV Cam H7 Plus摄像头、补光LED)
3 目标识别与统计 运用YOLO或SSD等轻量级目标检测算法,实时识别图像中的商品种类并统计数量,完成库存盘点。 视觉处理模块(摄像头内置处理器运行算法)
4 数据传输 通过Wi-Fi将盘点结果(商品ID、数量、位置)实时发送至后台服务器,实现数据同步与监控。 通信模块(树莓派Pico W板载Wi-Fi)
5 安全防护与报警 具备防跌落检测和障碍物避障功能,异常时自动停车并通过报警机制提醒,确保运行安全。 移动底盘模块(避障传感器)、主控模块(树莓派Pico W处理报警)

设计的各个功能模块描述

主控模块采用树莓派Pico W作为移动与视觉处理核心,负责协调机器人的整体运作,平衡性能与功耗,处理传感器数据并控制电机驱动,实现智能决策与任务调度。

视觉处理模块基于OpenMV Cam H7 Plus摄像头,配合补光LED对货架各层进行图像采集,内置高性能处理器可独立运行YOLO或SSD等轻量级目标检测算法,识别并统计图像中的商品数量与种类。

移动底盘模块包括TT减速电机车轮和L298N电机驱动板,实现机器人的基础移动功能,结合循迹传感器(如红外或超声传感器)沿预设磁轨或色带循迹移动,并集成防跌落检测和障碍物避障功能,在异常情况下自动停车并报警。

通信模块利用树莓派Pico W的板载Wi-Fi功能,将盘点结果(包括商品ID、数量、位置)实时发送至后台服务器,确保数据传输的及时性和可靠性。

结构供电模块采用定制亚克力板车身提供机械支撑,使用两节18650锂电池与XL6009升压模块供电,为整个系统提供稳定可靠的电源,保障机器人长时间运行。

上位机代码设计

以下是采用C++开发的智能视觉货架库存盘点机器人上位机服务器代码,包含完整的TCP服务器实现和数据管理功能:

// main.cpp - 智能视觉货架库存盘点系统上位机
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <thread>
#include <mutex>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <cstring>
#include <cstdlib>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

// 商品信息结构体
struct InventoryItem {
    std::string productID;      // 商品ID
    int quantity;              // 数量
    std::string location;      // 位置(货架层数+位置)
    std::string timestamp;     // 时间戳
    std::string imagePath;     // 对应图像路径
};

// 系统配置
struct SystemConfig {
    int serverPort = 8888;     // 服务器端口
    int maxConnections = 10;   // 最大连接数
    std::string dataPath = "data/";  // 数据存储路径
    std::string logPath = "logs/";   // 日志存储路径
    bool saveImages = true;    // 是否保存图像
};

class InventoryServer {
private:
    SOCKET serverSocket;
    SystemConfig config;
    std::vector<InventoryItem> inventoryData;
    std::map<std::string, int> inventorySummary;  // 商品ID->总数量
    std::mutex dataMutex;
    std::ofstream logFile;
    bool running = false;
    
public:
    InventoryServer() {
        WSADATA wsaData;
        WSAStartup(MAKEWORD(2, 2), &wsaData);
        serverSocket = INVALID_SOCKET;
    }
    
    ~InventoryServer() {
        stop();
        WSACleanup();
        if (logFile.is_open()) {
            logFile.close();
        }
    }
    
    bool initialize() {
        // 创建必要的目录
        system(("mkdir " + config.dataPath).c_str());
        system(("mkdir " + config.logPath).c_str());
        system(("mkdir " + config.dataPath + "images/").c_str());
        
        // 初始化日志文件
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::ostringstream logFileName;
        logFileName << config.logPath << "inventory_"
                   << (localTime->tm_year + 1900)
                   << std::setw(2) << std::setfill('0') << (localTime->tm_mon + 1)
                   << std::setw(2) << std::setfill('0') << localTime->tm_mday
                   << ".log";
        
        logFile.open(logFileName.str(), std::ios::app);
        if (!logFile.is_open()) {
            std::cerr << "无法创建日志文件" << std::endl;
            return false;
        }
        
        log("系统初始化完成");
        return true;
    }
    
    bool start() {
        // 创建socket
        serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (serverSocket == INVALID_SOCKET) {
            log("创建socket失败: " + std::to_string(WSAGetLastError()));
            return false;
        }
        
        // 绑定地址和端口
        sockaddr_in serverAddr;
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = INADDR_ANY;
        serverAddr.sin_port = htons(config.serverPort);
        
        if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
            log("绑定端口失败: " + std::to_string(WSAGetLastError()));
            closesocket(serverSocket);
            return false;
        }
        
        // 监听
        if (listen(serverSocket, config.maxConnections) == SOCKET_ERROR) {
            log("监听失败: " + std::to_string(WSAGetLastError()));
            closesocket(serverSocket);
            return false;
        }
        
        running = true;
        log("服务器启动,监听端口: " + std::to_string(config.serverPort));
        
        // 启动连接处理线程
        std::thread connectionThread(&InventoryServer::handleConnections, this);
        connectionThread.detach();
        
        return true;
    }
    
    void stop() {
        running = false;
        if (serverSocket != INVALID_SOCKET) {
            closesocket(serverSocket);
            serverSocket = INVALID_SOCKET;
        }
        log("服务器已停止");
    }
    
    void handleConnections() {
        while (running) {
            sockaddr_in clientAddr;
            int clientAddrLen = sizeof(clientAddr);
            
            SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);
            if (clientSocket == INVALID_SOCKET) {
                if (running) {
                    log("接受连接失败: " + std::to_string(WSAGetLastError()));
                }
                continue;
            }
            
            char clientIP[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
            log("新的客户端连接: " + std::string(clientIP));
            
            // 为每个客户端创建处理线程
            std::thread clientThread(&InventoryServer::handleClient, this, clientSocket, std::string(clientIP));
            clientThread.detach();
        }
    }
    
    void handleClient(SOCKET clientSocket, std::string clientIP) {
        char buffer[4096];
        int bytesReceived;
        
        while (running) {
            memset(buffer, 0, sizeof(buffer));
            bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
            
            if (bytesReceived == SOCKET_ERROR) {
                log("接收数据错误: " + std::to_string(WSAGetLastError()));
                break;
            } else if (bytesReceived == 0) {
                log("客户端断开连接: " + clientIP);
                break;
            }
            
            std::string receivedData(buffer, bytesReceived);
            log("收到数据: " + receivedData);
            
            // 处理接收到的数据
            if (processInventoryData(receivedData, clientIP)) {
                send(clientSocket, "OK", 2, 0);
            } else {
                send(clientSocket, "ERROR", 5, 0);
            }
        }
        
        closesocket(clientSocket);
    }
    
    bool processInventoryData(const std::string& data, const std::string& source) {
        std::lock_guard<std::mutex> lock(dataMutex);
        
        // 解析数据格式: "PRODUCT_ID,QUANTITY,LOCATION,IMAGE_DATA"
        std::vector<std::string> tokens;
        std::string token;
        std::istringstream tokenStream(data);
        
        while (std::getline(tokenStream, token, ',')) {
            tokens.push_back(token);
        }
        
        if (tokens.size() < 3) {
            log("数据格式错误: " + data);
            return false;
        }
        
        // 创建库存记录
        InventoryItem item;
        item.productID = tokens[0];
        item.quantity = std::stoi(tokens[1]);
        item.location = tokens[2];
        
        // 生成时间戳
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        std::stringstream timestamp;
        timestamp << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
        item.timestamp = timestamp.str();
        
        // 保存图像数据(如果存在)
        if (tokens.size() > 3 && config.saveImages) {
            std::string imageFilename = config.dataPath + "images/" + 
                                       item.productID + "_" + 
                                       std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(
                                           now.time_since_epoch()).count()) + 
                                       ".jpg";
            
            // 这里简化为保存Base64数据,实际需要解码
            std::ofstream imageFile(imageFilename, std::ios::binary);
            if (imageFile.is_open()) {
                imageFile << tokens[3];
                imageFile.close();
                item.imagePath = imageFilename;
            }
        }
        
        // 添加到库存数据
        inventoryData.push_back(item);
        
        // 更新库存汇总
        inventorySummary[item.productID] += item.quantity;
        
        // 保存到文件
        saveInventoryToFile();
        
        log("库存记录添加: " + item.productID + " x" + std::to_string(item.quantity) + 
            " @ " + item.location);
        
        return true;
    }
    
    void saveInventoryToFile() {
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::ostringstream filename;
        filename << config.dataPath << "inventory_"
                << (localTime->tm_year + 1900)
                << std::setw(2) << std::setfill('0') << (localTime->tm_mon + 1)
                << std::setw(2) << std::setfill('0') << localTime->tm_mday
                << ".csv";
        
        std::ofstream file(filename.str());
        if (!file.is_open()) {
            log("无法打开库存文件: " + filename.str());
            return;
        }
        
        // 写入CSV标题
        file << "时间戳,商品ID,数量,位置,图像路径\n";
        
        // 写入数据
        for (const auto& item : inventoryData) {
            file << item.timestamp << ","
                 << item.productID << ","
                 << item.quantity << ","
                 << item.location << ","
                 << item.imagePath << "\n";
        }
        
        file.close();
    }
    
    void generateReport() {
        std::lock_guard<std::mutex> lock(dataMutex);
        
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        std::ostringstream filename;
        filename << config.dataPath << "report_"
                << (localTime->tm_year + 1900)
                << std::setw(2) << std::setfill('0') << (localTime->tm_mon + 1)
                << std::setw(2) << std::setfill('0') << localTime->tm_mday
                << "_" << std::setw(2) << std::setfill('0') << localTime->tm_hour
                << std::setw(2) << std::setfill('0') << localTime->tm_min
                << ".txt";
        
        std::ofstream report(filename.str());
        if (!report.is_open()) {
            log("无法创建报告文件");
            return;
        }
        
        report << "==================== 库存盘点报告 ====================\n";
        report << "生成时间: " << std::put_time(localTime, "%Y-%m-%d %H:%M:%S") << "\n";
        report << "总记录数: " << inventoryData.size() << "\n";
        report << "------------------------------------------------------\n";
        
        for (const auto& summary : inventorySummary) {
            report << "商品ID: " << std::setw(10) << std::left << summary.first
                   << " 数量: " << std::setw(6) << std::right << summary.second << "\n";
        }
        
        report << "==================== 详细记录 ====================\n";
        for (const auto& item : inventoryData) {
            report << "时间: " << item.timestamp
                   << " | 商品: " << item.productID
                   << " | 数量: " << item.quantity
                   << " | 位置: " << item.location << "\n";
        }
        
        report.close();
        log("报告生成完成: " + filename.str());
    }
    
    void log(const std::string& message) {
        std::time_t now = std::time(nullptr);
        std::tm* localTime = std::localtime(&now);
        
        std::string logMessage = "[" + std::string(std::put_time(localTime, "%Y-%m-%d %H:%M:%S")) + 
                                "] " + message;
        
        std::cout << logMessage << std::endl;
        
        if (logFile.is_open()) {
            logFile << logMessage << std::endl;
        }
    }
    
    void printInventory() {
        std::lock_guard<std::mutex> lock(dataMutex);
        
        std::cout << "\n========== 当前库存 ==========\n";
        std::cout << "总记录数: " << inventoryData.size() << "\n";
        
        for (const auto& summary : inventorySummary) {
            std::cout << "商品ID: " << summary.first 
                     << " 总数量: " << summary.second << "\n";
        }
        std::cout << "==============================\n\n";
    }
};

int main() {
    std::cout << "智能视觉货架库存盘点系统 - 上位机服务器\n";
    std::cout << "=====================================\n";
    
    InventoryServer server;
    
    if (!server.initialize()) {
        std::cerr << "系统初始化失败" << std::endl;
        return 1;
    }
    
    if (!server.start()) {
        std::cerr << "服务器启动失败" << std::endl;
        return 1;
    }
    
    // 主循环
    bool exit = false;
    while (!exit) {
        std::cout << "\n命令选项:\n";
        std::cout << "1. 显示当前库存\n";
        std::cout << "2. 生成报告\n";
        std::cout << "3. 退出系统\n";
        std::cout << "请选择: ";
        
        int choice;
        std::cin >> choice;
        
        switch (choice) {
            case 1:
                server.printInventory();
                break;
            case 2:
                server.generateReport();
                std::cout << "报告已生成\n";
                break;
            case 3:
                exit = true;
                break;
            default:
                std::cout << "无效选择\n";
                break;
        }
    }
    
    server.stop();
    std::cout << "系统已关闭\n";
    
    return 0;
}
// CMakeLists.txt - 构建配置文件
cmake_minimum_required(VERSION 3.10)
project(InventoryServer)

set(CMAKE_CXX_STANDARD 17)

# Windows下需要链接Winsock库
if(WIN32)
    target_link_libraries(InventoryServer ws2_32)
endif()

add_executable(InventoryServer main.cpp)
// config.h - 配置文件
#ifndef CONFIG_H
#define CONFIG_H

#include <string>

// 系统配置
struct SystemConfig {
    int serverPort = 8888;           // 服务器端口
    std::string serverIP = "0.0.0.0"; // 服务器IP
    int bufferSize = 4096;           // 缓冲区大小
    std::string dataPath = "data/";  // 数据存储路径
    std::string logPath = "logs/";   // 日志路径
    bool debugMode = true;           // 调试模式
    int maxInventoryItems = 10000;   // 最大库存记录数
};

#endif
// inventory_manager.h - 库存管理类
#ifndef INVENTORY_MANAGER_H
#define INVENTORY_MANAGER_H

#include <string>
#include <vector>
#include <map>
#include <mutex>

struct InventoryItem {
    std::string id;
    std::string name;
    int quantity;
    std::string location;
    std::string timestamp;
    double confidence;  // 识别置信度
};

class InventoryManager {
private:
    std::vector<InventoryItem> items;
    std::map<std::string, int> summary;
    std::mutex mtx;
    
public:
    void addItem(const InventoryItem& item);
    void removeItem(const std::string& id);
    int getTotalQuantity(const std::string& productID);
    std::vector<InventoryItem> getItemsByLocation(const std::string& location);
    std::map<std::string, int> getSummary();
    void clear();
    int size() const;
};

#endif
// network_manager.h - 网络管理类
#ifndef NETWORK_MANAGER_H
#define NETWORK_MANAGER_H

#include <functional>
#include <string>

class NetworkManager {
public:
    using DataCallback = std::function<void(const std::string&)>;
    
    virtual bool start(int port) = 0;
    virtual void stop() = 0;
    virtual void sendData(const std::string& data) = 0;
    virtual void setDataCallback(DataCallback callback) = 0;
    
    virtual ~NetworkManager() = default;
};

#endif
// utils.cpp - 工具函数
#include <ctime>
#include <sstream>
#include <iomanip>

namespace Utils {
    std::string getCurrentTimestamp() {
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        std::stringstream ss;
        ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
        return ss.str();
    }
    
    std::string generateFilename(const std::string& prefix, const std::string& extension) {
        auto now = std::chrono::system_clock::now();
        auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(
            now.time_since_epoch()).count();
        return prefix + "_" + std::to_string(millis) + "." + extension;
    }
    
    std::vector<std::string> splitString(const std::string& str, char delimiter) {
        std::vector<std::string> tokens;
        std::string token;
        std::istringstream tokenStream(str);
        
        while (std::getline(tokenStream, token, delimiter)) {
            tokens.push_back(token);
        }
        
        return tokens;
    }
}

这个上位机系统包含以下核心功能:

  1. TCP服务器:监听指定端口,接收来自盘点机器人的数据
  2. 数据解析:解析机器人发送的商品信息(商品ID、数量、位置)
  3. 库存管理:维护内存中的库存数据,支持查询和汇总
  4. 数据持久化:将库存数据保存到CSV文件
  5. 日志记录:记录系统操作和错误信息
  6. 报告生成:生成详细的库存盘点报告
  7. 图像保存:可选保存商品识别图像

使用说明

  1. 编译代码(Windows需要安装Winsock2,Linux使用socket)
  2. 运行程序启动服务器
  3. 盘点机器人通过Wi-Fi连接到服务器IP和端口
  4. 通过控制台命令管理库存数据

数据格式
机器人发送的数据应采用CSV格式:产品ID,数量,位置,图像数据(可选)

扩展功能

  • 可以添加数据库支持(SQLite/MySQL)
  • 可扩展为Web界面显示
  • 添加用户身份验证
  • 支持多机器人同时连接
  • 实现库存预警功能

模块代码设计

#include <stdint.h>

// 寄存器定义(基于STM32F103C8T6)
#define RCC_BASE       0x40021000
#define GPIOA_BASE     0x40010800
#define GPIOB_BASE     0x40010C00
#define TIM1_BASE      0x40012C00
#define TIM2_BASE      0x40000000

// RCC寄存器
#define RCC_APB2ENR    *(volatile uint32_t *)(RCC_BASE + 0x18)

// GPIOA寄存器
#define GPIOA_CRL      *(volatile uint32_t *)(GPIOA_BASE + 0x00)
#define GPIOA_CRH      *(volatile uint32_t *)(GPIOA_BASE + 0x04)
#define GPIOA_IDR      *(volatile uint32_t *)(GPIOA_BASE + 0x08)
#define GPIOA_ODR      *(volatile uint32_t *)(GPIOA_BASE + 0x0C)

// GPIOB寄存器
#define GPIOB_CRL      *(volatile uint32_t *)(GPIOB_BASE + 0x00)
#define GPIOB_CRH      *(volatile uint32_t *)(GPIOB_BASE + 0x04)
#define GPIOB_ODR      *(volatile uint32_t *)(GPIOB_BASE + 0x0C)

// TIM1寄存器(用于PWM电机控制)
#define TIM1_CR1       *(volatile uint32_t *)(TIM1_BASE + 0x00)
#define TIM1_CCMR1     *(volatile uint32_t *)(TIM1_BASE + 0x18)
#define TIM1_CCER      *(volatile uint32_t *)(TIM1_BASE + 0x20)
#define TIM1_PSC       *(volatile uint32_t *)(TIM1_BASE + 0x28)
#define TIM1_ARR       *(volatile uint32_t *)(TIM1_BASE + 0x2C)
#define TIM1_CCR1      *(volatile uint32_t *)(TIM1_BASE + 0x34)
#define TIM1_CCR2      *(volatile uint32_t *)(TIM1_BASE + 0x38)

// TIM2寄存器(可选用于超声波测距,此处用循环延时简化)
#define TIM2_CR1       *(volatile uint32_t *)(TIM2_BASE + 0x00)
#define TIM2_CNT       *(volatile uint32_t *)(TIM2_BASE + 0x24)

// 引脚宏定义
#define PA1            (1 << 1)
#define PA2            (1 << 2)
#define PA3            (1 << 3)
#define PA4            (1 << 4)
#define PA5            (1 << 5)
#define PA8            (1 << 8)
#define PA9            (1 << 9)
#define PB0            (1 << 0)
#define PB1            (1 << 1)
#define PB2            (1 << 2)
#define PB3            (1 << 3)

// 函数声明
void SystemInit(void);
void GPIO_Init(void);
void TIM1_Init(void);
void Motor_Control(int left_speed, int right_speed);
void Ultrasonic_Trigger(void);
uint32_t Ultrasonic_Measure(void);
void Track_Sensor_Read(void);
void Obstacle_Avoidance(void);
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);

// 全局变量用于存储传感器状态
uint8_t left_track = 0;
uint8_t right_track = 0;
uint32_t obstacle_distance = 0;

int main(void) {
    SystemInit();
    GPIO_Init();
    TIM1_Init();
    
    // 开启补光LED(PA5输出高电平)
    GPIOA_ODR |= PA5;
    
    while(1) {
        Track_Sensor_Read();       // 循迹传感器读取与控制
        Obstacle_Avoidance();      // 避障检测与响应
        // 主循环中可加入其他功能,如Wi-Fi通信或与OpenMV串口通信
    }
}

// 系统初始化:启用时钟
void SystemInit(void) {
    // 启用GPIOA、GPIOB、TIM1时钟(APB2外设)
    RCC_APB2ENR |= (1 << 2) | (1 << 3) | (1 << 11); // IOPAEN, IOPBEN, TIM1EN
}

// GPIO初始化:配置所有传感器和电机控制引脚
void GPIO_Init(void) {
    // 配置GPIOA引脚
    // PA1, PA2: 循迹传感器(红外数字输入,浮空输入模式)
    GPIOA_CRL &= ~(0xF << 4);  // 清除PA1配置(位4-7)
    GPIOA_CRL |= (0x04 << 4);   // CNF=01(浮空输入), MODE=00 -> 0x4
    GPIOA_CRL &= ~(0xF << 8);  // 清除PA2配置(位8-11)
    GPIOA_CRL |= (0x04 << 8);   // 同上
    
    // PA3: 超声波触发引脚(推挽输出)
    GPIOA_CRL &= ~(0xF << 12); // 清除PA3配置(位12-15)
    GPIOA_CRL |= (0x03 << 12);  // CNF=00(推挽输出), MODE=11(50MHz)-> 0x3
    
    // PA4: 超声波回波引脚(浮空输入)
    GPIOA_CRL &= ~(0xF << 16); // 清除PA4配置(位16-19)
    GPIOA_CRL |= (0x04 << 16);  // 浮空输入
    
    // PA5: 补光LED(推挽输出)
    GPIOA_CRL &= ~(0xF << 20); // 清除PA5配置(位20-23)
    GPIOA_CRL |= (0x03 << 20);  // 推挽输出
    
    // PA8, PA9: PWM输出(复用推挽输出,用于电机速度控制)
    GPIOA_CRH &= ~(0xF << 0);   // 清除PA8配置(CRH位0-3)
    GPIOA_CRH |= (0x0B << 0);    // CNF=10(复用推挽), MODE=11 -> 0xB
    GPIOA_CRH &= ~(0xF << 4);   // 清除PA9配置(CRH位4-7)
    GPIOA_CRH |= (0x0B << 4);    // 同上
    
    // 配置GPIOB引脚:电机方向控制(推挽输出)
    GPIOB_CRL &= ~(0xF << 0);   // 清除PB0配置
    GPIOB_CRL |= (0x03 << 0);    // 推挽输出
    GPIOB_CRL &= ~(0xF << 4);   // 清除PB1配置
    GPIOB_CRL |= (0x03 << 4);    // 推挽输出
    GPIOB_CRL &= ~(0xF << 8);   // 清除PB2配置
    GPIOB_CRL |= (0x03 << 8);    // 推挽输出
    GPIOB_CRL &= ~(0xF << 12);  // 清除PB3配置
    GPIOB_CRL |= (0x03 << 12);   // 推挽输出
}

// TIM1初始化:用于生成PWM控制电机速度
void TIM1_Init(void) {
    // 预分频器设置:系统时钟72MHz,分频至1MHz(PWM频率1kHz)
    TIM1_PSC = 71;               // 72MHz / (71+1) = 1MHz
    TIM1_ARR = 999;              // 自动重载值,PWM周期 = 1000计数
    
    // 配置通道1和通道2为PWM模式1
    TIM1_CCMR1 |= (0x6 << 4);    // OC1M = 110(PWM模式1)
    TIM1_CCMR1 |= (0x6 << 12);   // OC2M = 110(PWM模式1)
    
    // 启用通道1和通道2输出
    TIM1_CCER |= (1 << 0);       // CC1E使能
    TIM1_CCER |= (1 << 4);       // CC2E使能
    
    // 初始占空比设置为50%
    TIM1_CCR1 = 500;
    TIM1_CCR2 = 500;
    
    // 启用TIM1计数器
    TIM1_CR1 |= (1 << 0);        // CEN=1
}

// 电机控制函数
void Motor_Control(int left_speed, int right_speed) {
    // 限制速度范围:-1000到1000(对应PWM占空比0-100%)
    if(left_speed > 1000) left_speed = 1000;
    if(left_speed < -1000) left_speed = -1000;
    if(right_speed > 1000) right_speed = 1000;
    if(right_speed < -1000) right_speed = -1000;
    
    // 左电机控制(电机A,PWM通道1,方向引脚PB0和PB1)
    if(left_speed >= 0) {
        GPIOB_ODR &= ~PB0;       // PB0低电平
        GPIOB_ODR |= PB1;        // PB1高电平,正转
    } else {
        GPIOB_ODR |= PB0;        // PB0高电平
        GPIOB_ODR &= ~PB1;       // PB1低电平,反转
        left_speed = -left_speed;
    }
    TIM1_CCR1 = left_speed;      // 设置左电机PWM占空比
    
    // 右电机控制(电机B,PWM通道2,方向引脚PB2和PB3)
    if(right_speed >= 0) {
        GPIOB_ODR &= ~PB2;       // PB2低电平
        GPIOB_ODR |= PB3;        // PB3高电平,正转
    } else {
        GPIOB_ODR |= PB2;        // PB2高电平
        GPIOB_ODR &= ~PB3;       // PB3低电平,反转
        right_speed = -right_speed;
    }
    TIM1_CCR2 = right_speed;     // 设置右电机PWM占空比
}

// 超声波触发:发送10us高脉冲
void Ultrasonic_Trigger(void) {
    GPIOA_ODR |= PA3;            // 触发引脚高电平
    Delay_us(10);                // 延时10us
    GPIOA_ODR &= ~PA3;           // 触发引脚低电平
}

// 超声波测距:返回距离(单位:厘米)
uint32_t Ultrasonic_Measure(void) {
    Ultrasonic_Trigger();
    
    // 等待回波引脚变高
    while(!(GPIOA_IDR & PA4));   // 阻塞等待高电平
    
    // 测量高电平持续时间(使用循环计数简化,实际建议用定时器)
    uint32_t echo_time = 0;
    while(GPIOA_IDR & PA4) {
        Delay_us(1);             // 延时1us
        echo_time++;
        if(echo_time > 30000) break; // 超时(约30ms)
    }
    
    // 计算距离:距离 = (时间 * 340) / (2 * 10000) cm,简化公式 time / 58
    uint32_t distance = echo_time / 58;
    return distance;
}

// 循迹传感器读取与控制逻辑
void Track_Sensor_Read(void) {
    left_track = (GPIOA_IDR & PA1) ? 1 : 0;   // 左传感器状态
    right_track = (GPIOA_IDR & PA2) ? 1 : 0;  // 右传感器状态
    
    // 基本循迹逻辑:PID控制可在此扩展
    if(left_track && right_track) {
        // 双传感器检测到线:直行
        Motor_Control(800, 800);
    } else if(left_track && !right_track) {
        // 仅左传感器检测到线:右转
        Motor_Control(800, 400);
    } else if(!left_track && right_track) {
        // 仅右传感器检测到线:左转
        Motor_Control(400, 800);
    } else {
        // 无线检测:停止
        Motor_Control(0, 0);
    }
}

// 避障检测与响应
void Obstacle_Avoidance(void) {
    obstacle_distance = Ultrasonic_Measure();
    if(obstacle_distance < 20) {     // 检测到20cm内障碍物
        Motor_Control(0, 0);         // 紧急停止
        // 可扩展报警功能,如闪烁LED或蜂鸣器
    }
}

// 简易延时函数(基于循环,72MHz系统时钟下近似)
void Delay_us(uint32_t us) {
    for(uint32_t i = 0; i < us * 8; i++); // 粗略校准
}

void Delay_ms(uint32_t ms) {
    for(uint32_t i = 0; i < ms * 8000; i++);
}

项目核心代码

#include "stm32f10x.h"

// 定义传感器状态宏
#define ON_TRACK 1
#define OFF_TRACK 0
#define DEVIATED_LEFT 2
#define DEVIATED_RIGHT 3
#define OBSTACLE_DETECTED 1
#define NO_OBSTACLE 0
#define DROP_DETECTED 1
#define NO_DROP 0

// 引脚定义
#define TRACK_SENSOR_PIN GPIO_Pin_0
#define TRACK_SENSOR_PORT GPIOA
#define OBSTACLE_SENSOR_PIN GPIO_Pin_1
#define OBSTACLE_SENSOR_PORT GPIOA
#define DROP_SENSOR_PIN GPIO_Pin_2
#define DROP_SENSOR_PORT GPIOA
#define MOTOR_LEFT_PWM_PIN GPIO_Pin_6
#define MOTOR_LEFT_PWM_PORT GPIOA
#define MOTOR_RIGHT_PWM_PIN GPIO_Pin_7
#define MOTOR_RIGHT_PWM_PORT GPIOA
#define LED_ALARM_PIN GPIO_Pin_8
#define LED_ALARM_PORT GPIOA

// UART定义
#define CAMERA_UART USART1
#define WIFI_UART USART2

// 函数声明
void SystemInit(void);
void GPIO_Init(void);
void UART_Init(void);
void TIM_Init(void);
void Motor_Control(int left_speed, int right_speed);
int Read_Track_Sensor(void);
int Read_Obstacle_Sensor(void);
int Read_Drop_Sensor(void);
void Send_Data_via_WiFi(char *data);
void Receive_Data_from_Camera(void);
void Delay(uint32_t count);

int main(void)
{
    SystemInit();
    GPIO_Init();
    UART_Init();
    TIM_Init();
    
    while(1)
    {
        int track_state = Read_Track_Sensor();
        switch(track_state)
        {
            case ON_TRACK:
                Motor_Control(100, 100);
                break;
            case DEVIATED_LEFT:
                Motor_Control(50, 100);
                break;
            case DEVIATED_RIGHT:
                Motor_Control(100, 50);
                break;
            default:
                Motor_Control(0, 0);
                break;
        }
        
        if(Read_Obstacle_Sensor() == OBSTACLE_DETECTED)
        {
            Motor_Control(0, 0);
            GPIOA->BSRR = LED_ALARM_PIN;
            Send_Data_via_WiFi("ALARM:Obstacle detected");
        }
        else
        {
            GPIOA->BRR = LED_ALARM_PIN;
        }
        
        if(Read_Drop_Sensor() == DROP_DETECTED)
        {
            Motor_Control(0, 0);
            GPIOA->BSRR = LED_ALARM_PIN;
            Send_Data_via_WiFi("ALARM:Drop detected");
        }
        
        Receive_Data_from_Camera();
        
        Delay(100000);
    }
}

void SystemInit(void)
{
    RCC->CR |= RCC_CR_HSION;
    while(!(RCC->CR & RCC_CR_HSIRDY));
    RCC->CFGR |= RCC_CFGR_SW_HSI;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI);
    
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_TIM1EN;
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
}

void GPIO_Init(void)
{
    GPIOA->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
    GPIOA->CRL |= GPIO_CRL_CNF0_0;
    
    GPIOA->CRL &= ~(GPIO_CRL_MODE1 | GPIO_CRL_CNF1);
    GPIOA->CRL |= GPIO_CRL_CNF1_0;
    
    GPIOA->CRL &= ~(GPIO_CRL_MODE2 | GPIO_CRL_CNF2);
    GPIOA->CRL |= GPIO_CRL_CNF2_0;
    
    GPIOA->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6);
    GPIOA->CRL |= GPIO_CRL_MODE6_1 | GPIO_CRL_MODE6_0 | GPIO_CRL_CNF6_0;
    
    GPIOA->CRL &= ~(GPIO_CRL_MODE7 | GPIO_CRL_CNF7);
    GPIOA->CRL |= GPIO_CRL_MODE7_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_CNF7_0;
    
    GPIOA->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_CNF8);
    GPIOA->CRH |= GPIO_CRH_MODE8_1 | GPIO_CRH_MODE8_0 | GPIO_CRH_CNF8_0;
    
    GPIOA->CRH &= ~(GPIO_CRH_MODE9 | GPIO_CRH_CNF9 | GPIO_CRH_MODE10 | GPIO_CRH_CNF10);
    GPIOA->CRH |= GPIO_CRH_MODE9_1 | GPIO_CRH_MODE9_0 | GPIO_CRH_CNF9_1;
    GPIOA->CRH |= GPIO_CRH_CNF10_0;
    
    GPIOA->CRL &= ~(GPIO_CRL_MODE3 | GPIO_CRL_CNF3 | GPIO_CRL_MODE4 | GPIO_CRL_CNF4);
    GPIOA->CRL |= GPIO_CRL_MODE3_1 | GPIO_CRL_MODE3_0 | GPIO_CRL_CNF3_1;
    GPIOA->CRL |= GPIO_CRL_CNF4_0;
}

void UART_Init(void)
{
    USART1->BRR = 0x1D4C;
    USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
    
    USART2->BRR = 0x1D4C;
    USART2->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}

void TIM_Init(void)
{
    TIM1->PSC = 79;
    TIM1->ARR = 999;
    TIM1->CCR1 = 500;
    TIM1->CCR2 = 500;
    TIM1->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1;
    TIM1->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E;
    TIM1->CR1 |= TIM_CR1_CEN;
}

void Motor_Control(int left_speed, int right_speed)
{
    TIM1->CCR1 = left_speed;
    TIM1->CCR2 = right_speed;
}

int Read_Track_Sensor(void)
{
    uint8_t sensor_value = (GPIOA->IDR & TRACK_SENSOR_PIN) ? 1 : 0;
    return sensor_value;
}

int Read_Obstacle_Sensor(void)
{
    uint8_t sensor_value = (GPIOA->IDR & OBSTACLE_SENSOR_PIN) ? 1 : 0;
    return sensor_value;
}

int Read_Drop_Sensor(void)
{
    uint8_t sensor_value = (GPIOA->IDR & DROP_SENSOR_PIN) ? 1 : 0;
    return sensor_value;
}

void Send_Data_via_WiFi(char *data)
{
    while(*data)
    {
        while(!(USART2->SR & USART_SR_TXE));
        USART2->DR = *data++;
    }
}

void Receive_Data_from_Camera(void)
{
    if(USART1->SR & USART_SR_RXNE)
    {
        char data = USART1->DR;
        if(data == '\n')
        {
            Send_Data_via_WiFi("DATA:Inventory updated");
        }
    }
}

void Delay(uint32_t count)
{
    for(uint32_t i = 0; i < count; i++);
}

总结

智能视觉货架库存盘点机器人是一款集自动化移动、视觉识别和实时通信于一体的智能设备,旨在高效完成货架库存的自动化盘点任务。它通过循迹移动和图像采集功能,实现对货架各层商品的全面覆盖,并利用先进的目标检测算法准确识别商品数量与种类。

该机器人的功能设计强调实用性与安全性,包括基于传感器的精确导航、实时图像处理与数据传输,以及防跌落和避障机制,确保在复杂环境中稳定运行。硬件配置以树莓派Pico W为核心,结合OpenMV摄像头、电机驱动和传感器模块,实现了性能与功耗的平衡,并通过Wi-Fi将盘点结果即时发送至后台服务器。

整体设计采用定制化结构和高效供电方案,提升了机器人的可靠性和续航能力。这款机器人不仅简化了库存管理流程,还降低了人工成本,展现了智能技术在仓储物流领域的广泛应用潜力。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。