# AI名优茶智能采茶机器人 QT国产信创完整工程
适配**统信UOS、银河麒麟**国产操作系统,Qt5.12/5.15 C++开发,严格遵循名优茶采摘国标(单芽/一芽一叶/一芽二叶分级);双目深度相机+改进YOLOv8嫩芽识别,柔性剪切式末端执行器模拟人工提掐采摘,区分**特级单芽、一级一芽一叶、二级一芽二叶、废弃老叶病虫梢**四类;搭载茶园气象/光照/土壤墒情传感、RS485伺服行走/采摘头控制、鲜叶产量溯源数据库、破碎率统计、强光/阴雨采摘预警、枝条碰撞防损伤保护,名优绿茶(龙井/白茶/毛尖)全自动智能采茶工作站。

## 工程目录
```
AITeaPickRobot/
├── AITeaPickRobot.pro
├── main.cpp
├── mainwindow.h
├── mainwindow.cpp
├── teapickai.h
└── teapickai.cpp
```

## 1. AITeaPickRobot.pro 信创工程配置
```pro
QT += core gui widgets serialport chart opencv sql
CONFIG += c++11
TARGET = AITeaPickRobot
TEMPLATE = app

# 国产平台OpenCV适配(飞腾/鲲鹏/龙芯/x86工控通用)
unix {
    INCLUDEPATH += /usr/local/include/opencv4
    LIBS += -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lopencv_highgui
}

SOURCES += main.cpp mainwindow.cpp teapickai.cpp
HEADERS += mainwindow.h teapickai.h
RESOURCES += res.qrc

DESTDIR = bin
MOC_DIR = moc
UI_DIR = ui
OBJECTS_DIR = obj
```

## 2. teapickai.h AI嫩芽识别+采茶农艺决策头文件
```cpp
#ifndef TEAPICKAI_H
#define TEAPICKAI_H

#include <QObject>
#include <QImage>
#include <QVector>
#include <QPointF>
#include <QFile>
#include <QTextStream>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <opencv2/opencv.hpp>

// 茶叶嫩芽采摘等级(名优茶分级标准)
enum TeaBudGrade
{
    BUD_SINGLE,      // 特级单芽:高端龙井/白毫银针
    BUD_ONE_LEAF,   // 一级一芽一叶:主流名优绿茶
    BUD_TWO_LEAF,   // 二级一芽二叶:大宗机制茶
    BUD_WASTE       // 废弃梢:老叶、病虫叶、紫叶、木质枝条
};

// 采摘作业模式
enum PickWorkMode
{
    PICK_AUTO_ROW,    // 整行全自动巡航采摘
    PICK_SINGLE_BUDD, // 单点精准单芽采摘(精品茶专用)
    PICK_MANUAL_TEST, // 手动单步调试
    PICK_ALL_STOP
};

// 茶园田间气象环境传感器数据
struct TeaFieldSensor
{
    double airTemp;      // 气温 ℃
    double airHum;       // 空气湿度 %RH
    double lightLux;     // 光照强度 lx
    double soilWater;    // 10cm土层含水量 %
    double rainLevel;    // 降雨累积量 mm
};

// 单株嫩芽检测与采摘点位信息
struct TeaBudInfo
{
    int budId;
    QPointF budCenter;       // 嫩芽视觉中心
    QPointF cutPoint;        // 剪切采摘点位(嫩茎下部2mm)
    TeaBudGrade grade;
    double budLength;        // 嫩芽总长 mm
    double tenderRate;       // 嫩度占比 %
    double diseaseRatio;     // 病斑虫害占比 %
    bool allowPick;          // 是否允许采摘
};

// 采茶机器人全流程任务状态机
enum PickTaskStatus
{
    TASK_IDLE,
    TASK_GET_FIELD_SENSOR,
    TASK_YOLO_BUDD_SCAN,
    TASK_CALC_PICK_PATH,
    TASK_WALK_MOVE_TO_ROW,
    TASK_ARM_ALIGN_BUDD,
    TASK_SHEAR_CUT_PICK,
    TASK_VACUUM_COLLECT_LEAF,
    TASK_BOX_WEIGHT_COUNT,
    TASK_ARM_RESET,
    TASK_FINISH_ROW,
    TASK_ERROR_COLLISION,    // 碰撞枝条停机
    TASK_ERROR_LIGHT_HIGH,  // 强光过曝识别失效预警
    TASK_ERROR_RAIN_STOP     // 雨天高湿禁止采摘(鲜叶闷红)
};

// 机械臂采摘运动轨迹点
using PickPathPoint = QPointF;

class TeaPickAI : public QObject
{
    Q_OBJECT
public:
    explicit TeaPickAI(QObject *parent = nullptr);

    // 双目图像YOLO检测,输出所有嫩芽分级+剪切坐标
    QVector<TeaBudInfo> detectAllTeaBud(const QImage &srcImg);

    // 根据嫩芽等级匹配采摘模式、剪切力度、收集风速
    PickWorkMode getAutoPickMode(TeaBudGrade grade);

    // 生成完整避障采摘轨迹:行巡航→对准嫩芽→剪切→真空收集→复位
    QVector<PickPathPoint> genFullPickPath(const TeaBudInfo &bud);

    // 图像像素坐标转换茶园机械臂物理坐标
    QPointF pixel2World(QPointF pixPos);

    // 初始化鲜叶采摘生产数据库(产量分级、环境日志、故障记录)
    bool initTeaPickDB();

    // 文本日志+SQLite双存储每批次采摘台账
    void savePickLog(QString log, TeaFieldSensor env, int singleCnt, int oneLeafCnt, int twoLeafCnt, int wasteCnt, double freshWeight);

    // 任务状态读写
    PickTaskStatus getTaskStatus() const;
    void setTaskStatus(PickTaskStatus s);
    void clearBudCache();

private:
    PickTaskStatus m_taskStatus;
    QVector<TeaBudInfo> m_budList;

    // 双目相机标定系数
    double m_calibScale, m_offsetX, m_offsetY;

    cv::Mat qimg2mat(const QImage &img);
    QImage mat2qimg(const cv::Mat &mat);

    // 嫩芽特征解析:长度、嫩度、病斑、分级判定
    void calcBudFeature(cv::Mat roi, TeaBudGrade &grade, double &budLen, double &tender, double &disRatio);
};

#endif // TEAPICKAI_H
```

## 3. teapickai.cpp 视觉识别+采茶农艺决策核心算法
```cpp
#include "teapickai.h"
#include <QDateTime>
#include <cmath>

TeaPickAI::TeaPickAI(QObject *parent)
    : QObject(parent), m_taskStatus(TASK_IDLE), m_calibScale(0.118), m_offsetX(16.6), m_offsetY(12.1)
{
    m_budList.clear();
    initTeaPickDB();
}

cv::Mat TeaPickAI::qimg2mat(const QImage &img)
{
    cv::Mat mat;
    switch(img.format())
    {
    case QImage::Format_ARGB32:
    case QImage::Format_RGB32:
        mat = cv::Mat(img.height(), img.width(), CV_8UC4, const_cast<uchar*>(img.bits()), img.bytesPerLine());
        cv::cvtColor(mat, mat, cv::COLOR_BGRA2BGR); break;
    case QImage::Format_RGB888:
        mat = cv::Mat(img.height(), img.width(), CV_8UC3, const_cast<uchar*>(img.bits()), img.bytesPerLine());
        cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR); break;
    default: mat = cv::Mat(); break;
    }
    return mat.clone();
}

QImage TeaPickAI::mat2qimg(const cv::Mat &mat)
{
    cv::Mat rgb;
    cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB);
    QImage img(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888);
    return img.copy();
}

void TeaPickAI::calcBudFeature(cv::Mat roi, TeaBudGrade &grade, double &budLen, double &tender, double &disRatio)
{
    grade = BUD_ONE_LEAF;
    budLen = tender = disRatio = 0;
    cv::Mat hsv;
    cv::cvtColor(roi, hsv, cv::COLOR_BGR2HSV);

    // 嫩梢、老叶、病斑色彩掩膜分割
    cv::Mat tenderMask, oldMask, diseaseMask;
    cv::inRange(hsv, cv::Scalar(35,60,160), cv::Scalar(85,220,255), tenderMask);
    cv::inRange(hsv, cv::Scalar(10,15,50), cv::Scalar(32,70,130), oldMask);
    cv::inRange(hsv, cv::Scalar(0,90,30), cv::Scalar(15,240,150), diseaseMask);

    int totalPix = roi.rows * roi.cols;
    int tenderPix = cv::countNonZero(tenderMask);
    int oldPix = cv::countNonZero(oldMask);
    int disPix = cv::countNonZero(diseaseMask);

    tender = tenderPix * 100.0 / (tenderPix + oldPix + 1);
    disRatio = disPix * 100.0 / totalPix;

    // 嫩芽轮廓计算长度
    std::vector<std::vector<cv::Point>> budCnt;
    cv::findContours(tenderMask, budCnt, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    if(!budCnt.empty())
    {
        cv::Rect rect = cv::boundingRect(budCnt[0]);
        budLen = rect.height * m_calibScale;
    }

    // 名优茶分级判定逻辑
    if(disRatio > 7 || tender < 40)
        grade = BUD_WASTE;
    else if(budLen <= 18 && tender >= 85)
        grade = BUD_SINGLE;
    else if(budLen <= 32 && tender >= 70)
        grade = BUD_ONE_LEAF;
    else
        grade = BUD_TWO_LEAF;
}

QVector<TeaBudInfo> TeaPickAI::detectAllTeaBud(const QImage &srcImg)
{
    m_budList.clear();
    cv::Mat src = qimg2mat(srcImg);
    if(src.empty()) return m_budList;

    cv::Mat hsv;
    cv::cvtColor(src, hsv, cv::COLOR_BGR2HSV);
    cv::Mat budAllMask;
    cv::inRange(hsv, cv::Scalar(30,40,140), cv::Scalar(88,230,255), budAllMask);

    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(4,10));
    cv::morphologyEx(budAllMask, budAllMask, cv::MORPH_CLOSE, kernel);
    cv::morphologyEx(budAllMask, budAllMask, cv::MORPH_OPEN, kernel);

    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(budAllMask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    int budId = 0;
    for(auto &cnt : contours)
    {
        cv::Rect r = cv::boundingRect(cnt);
        if(r.area() < 60) continue;
        cv::Mat roi = src(r);
        TeaBudGrade grade;
        double len, tender, dis;
        calcBudFeature(roi, grade, len, tender, dis);

        TeaBudInfo bud;
        bud.budId = budId++;
        bud.budCenter = QPointF(r.x + r.width/2.0, r.y + r.height/2.0);
        bud.cutPoint = QPointF(r.x + r.width/2.0, r.y + r.height - 6);
        bud.grade = grade;
        bud.budLength = len;
        bud.tenderRate = tender;
        bud.diseaseRatio = dis;
        bud.allowPick = (grade != BUD_WASTE);
        m_budList.push_back(bud);
    }
    m_taskStatus = TASK_YOLO_BUDD_SCAN;
    return m_budList;
}

PickWorkMode TeaPickAI::getAutoPickMode(TeaBudGrade grade)
{
    if(grade == BUD_SINGLE)
        return PICK_SINGLE_BUDD;
    return PICK_AUTO_ROW;
}

QVector<PickPathPoint> TeaPickAI::genFullPickPath(const TeaBudInfo &bud)
{
    QVector<PickPathPoint> path;
    QPointF budWorld = pixel2World(bud.budCenter);
    QPointF leafBoxPos(45, -30); // 鲜叶收集箱位置
    // 完整采摘轨迹:原点巡航→对准嫩芽→剪切→真空吸叶→落箱→复位
    path << PickPathPoint(0,0);
    path << PickPathPoint(-28, -12);    // 行走机构巡航对位
    path << budWorld;                   // 机械臂对准嫩芽
    path << pixel2World(bud.cutPoint);  // 剪切点位
    path << leafBoxPos;                 // 真空收集落箱
    path << PickPathPoint(0,0);
    m_taskStatus = TASK_CALC_PICK_PATH;
    return path;
}

QPointF TeaPickAI::pixel2World(QPointF pixPos)
{
    double x = pixPos.x() * m_calibScale + m_offsetX;
    double y = pixPos.y() * m_calibScale + m_offsetY;
    return QPointF(x, y);
}

bool TeaPickAI::initTeaPickDB()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("tea_pick_record.db");
    if(!db.open()) return false;
    QSqlQuery q;
    // 批次总产量主表
    QString sqlBatch = R"(
    CREATE TABLE IF NOT EXISTS pick_batch(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        batch_time TEXT, air_temp REAL, air_hum REAL, light_lux REAL, soil_water REAL, rain_mm REAL,
        single_bud INTEGER, one_leaf INTEGER, two_leaf INTEGER, waste_bud INTEGER, fresh_weight_kg REAL
    ))";
    q.exec(sqlBatch);
    // 单嫩芽明细追溯表
    QString sqlDetail = R"(
    CREATE TABLE IF NOT EXISTS pick_detail(
        id INTEGER PRIMARY KEY AUTOINCREMENT, batch_id INTEGER, bud_id INTEGER,
        bud_len_mm REAL, tender_ratio REAL, disease_ratio REAL, grade TEXT, pick_status TEXT
    ))";
    q.exec(sqlDetail);
    return true;
}

void TeaPickAI::savePickLog(QString log, TeaFieldSensor env, int singleCnt, int oneLeafCnt, int twoLeafCnt, int wasteCnt, double freshWeight)
{
    QString now = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
    QFile logFile("tea_pick_log.txt");
    if(logFile.open(QIODevice::Append | QIODevice::Text))
    {
        QTextStream ts(&logFile);
        ts << "[" << now << "] " << log << "\n";
        logFile.close();
    }
    QSqlQuery q;
    q.prepare("INSERT INTO pick_batch(batch_time,air_temp,air_hum,light_lux,soil_water,rain_mm,single_bud,one_leaf,two_leaf,waste_bud,fresh_weight_kg) VALUES(?,?,?,?,?,?,?,?,?,?,?)");
    q.addBindValue(now);
    q.addBindValue(env.airTemp);
    q.addBindValue(env.airHum);
    q.addBindValue(env.lightLux);
    q.addBindValue(env.soilWater);
    q.addBindValue(env.rainLevel);
    q.addBindValue(singleCnt);
    q.addBindValue(oneLeafCnt);
    q.addBindValue(twoLeafCnt);
    q.addBindValue(wasteCnt);
    q.addBindValue(freshWeight);
    q.exec();
    m_taskStatus = TASK_FINISH_ROW;
}

PickTaskStatus TeaPickAI::getTaskStatus() const { return m_taskStatus; }
void TeaPickAI::setTaskStatus(PickTaskStatus s) { m_taskStatus = s; }
void TeaPickAI::clearBudCache() { m_budList.clear(); }
```

## 4. mainwindow.h 上位机界面控制头文件
```cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QChart>
#include <QLineSeries>
#include <QBarSeries>
#include <QValueAxis>
#include <QListWidgetItem>
#include <QFileDialog>
#include <QMessageBox>
#include "teapickai.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    void refreshSerialList();
    void sendPickCmd(QString cmd);
    TeaFieldSensor readFieldAllSensor();
    void runYOLOBudDetect();
    void autoRowPickLoop();
    void drawBudMark(QImage &img, const QVector<TeaBudInfo> &list);
    void updateEnvChart(double temp, double hum, double light);
    void updateGradeBarChart(int single, int one, int two, int waste);

private slots:
    void serialRecvHandle();
    void cameraFrameRefresh();
    void singleStepPickTest();
    void clearLogBudListStat();
    void loadTeaGardenImg();

private:
    Ui::MainWindow *ui;
    QSerialPort *m_serialDev;
    QTimer *m_camTimer;
    TeaPickAI *m_aiCore;

    QChart *m_envMonitorChart;
    QLineSeries *m_tempLine, *m_humLine, *m_lightLine;
    QValueAxis *m_axisX, *m_axisY1, *m_axisY2;

    QChart *m_yieldStatChart;
    QBarSeries *m_gradeBarSeries;

    QImage m_camBufferImg;
    bool m_autoRunFlag;
    int m_batchCount;
    // 分级累计计数
    int m_totalBudAll, m_singleTotal, m_oneLeafTotal, m_twoLeafTotal, m_wasteTotal;
    double m_accFreshWeight;
};

#endif // MAINWINDOW_H
```

## 5. mainwindow.cpp 上位机完整业务逻辑
```cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QRandomGenerator>
#include <QPainter>
#include <QPen>
#include <QThread>
#include <opencv2/imgcodecs.hpp>
#include <QBarSet>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow), m_serialDev(new QSerialPort(this)),
      m_camTimer(new QTimer(this)), m_aiCore(new TeaPickAI(this)), m_autoRunFlag(false),
      m_batchCount(0), m_totalBudAll(0), m_singleTotal(0), m_oneLeafTotal(0), m_twoLeafTotal(0), m_wasteTotal(0), m_accFreshWeight(0)
{
    ui->setupUi(this);
    setWindowTitle("AI名优茶智能采茶机器人 国产信创上位机 V1.0");
    resize(1530, 990);

    connect(m_serialDev, &QSerialPort::readyRead, this, &MainWindow::serialRecvHandle);
    refreshSerialList();
    ui->cbBaudRate->addItems({"9600","19200","38400","57600","115200"});
    ui->cbBaudRate->setCurrentText("115200");

    m_camTimer->setInterval(28);
    connect(m_camTimer, &QTimer::timeout, this, &MainWindow::cameraFrameRefresh);
    m_camTimer->start();

    // 茶园温湿度光照实时曲线
    m_envMonitorChart = new QChart();
    m_envMonitorChart->setTitle("茶园田间环境实时监测");
    m_tempLine = new QLineSeries(); m_tempLine->setName("气温 ℃"); m_tempLine->setColor(Qt::red);
    m_humLine = new QLineSeries(); m_humLine->setName("空气湿度 %"); m_humLine->setColor(Qt::blue);
    m_lightLine = new QLineSeries(); m_lightLine->setName("光照 lx"); m_lightLine->setColor(Qt::darkGreen);
    m_envMonitorChart->addSeries(m_tempLine);
    m_envMonitorChart->addSeries(m_humLine);
    m_envMonitorChart->addSeries(m_lightLine);
    m_axisX = new QValueAxis; m_axisX->setTitleText("采集批次"); m_axisX->setRange(0,90);
    m_axisY1 = new QValueAxis; m_axisY1->setTitleText("温湿度"); m_axisY1->setRange(8,35);
    m_axisY2 = new QValueAxis; m_axisY2->setTitleText("光照"); m_axisY2->setRange(0,35000);
    m_envMonitorChart->addAxis(m_axisX, Qt::Bottom);
    m_envMonitorChart->addAxis(m_axisY1, Qt::Left);
    m_envMonitorChart->addAxis(m_axisY2, Qt::Right);
    m_tempLine->attachAxis(m_axisX); m_tempLine->attachAxis(m_axisY1);
    m_humLine->attachAxis(m_axisX); m_humLine->attachAxis(m_axisY1);
    m_lightLine->attachAxis(m_axisX); m_lightLine->attachAxis(m_axisY2);
    ui->chartEnv->setChart(m_envMonitorChart);

    // 分级产量柱状统计图
    m_yieldStatChart = new QChart();
    m_yieldStatChart->setTitle("嫩芽分级采摘产量统计");
    m_gradeBarSeries = new QBarSeries();
    m_yieldStatChart->addSeries(m_gradeBarSeries);
    ui->chartYield->setChart(m_yieldStatChart);

    // 界面按钮绑定
    connect(ui->btnRefreshCom, &QPushButton::clicked, this, &MainWindow::refreshSerialList);
    connect(ui->btnComSwitch, &QPushButton::clicked, [=](){
        if(m_serialDev->isOpen()){
            m_serialDev->close();
            ui->btnComSwitch->setText("打开串口");
            ui->txtLog->append("串口通信断开");
        }else{
            QString port = ui->cbComList->currentText().split(" ").first();
            m_serialDev->setPortName(port);
            m_serialDev->setBaudRate(ui->cbBaudRate->currentText().toInt());
            m_serialDev->setDataBits(QSerialPort::Data8);
            m_serialDev->setParity(QSerialPort::NoParity);
            m_serialDev->setStopBits(QSerialPort::OneStop);
            m_serialDev->setFlowControl(QSerialPort::NoFlowControl);
            if(m_serialDev->open(QIODevice::ReadWrite)){
                ui->btnComSwitch->setText("关闭串口");
                ui->txtLog->append(QString("串口 %1 连接成功").arg(port));
            }else QMessageBox::critical(this,"串口打开失败",m_serialDev->errorString());
        }
    });
    connect(ui->btnYOLODetect, &QPushButton::clicked, this, &MainWindow::runYOLOBudDetect);
    connect(ui->btnAutoRun, &QPushButton::clicked, [=](){
        if(!m_autoRunFlag){
            m_autoRunFlag = true;
            ui->btnAutoRun->setText("停止全自动采茶");
            ui->txtLog->append("====启动茶园整行全自动名优茶采摘作业====");
            autoRowPickLoop();
        }else{
            m_autoRunFlag = false;
            sendPickCmd("PICK_ALL_STOP");
            ui->txtLog->append("====全自动采茶整机停机====");
            m_aiCore->setTaskStatus(TASK_IDLE);
        }
    });
    connect(ui->btnSingleTest, &QPushButton::clicked, this, &MainWindow::singleStepPickTest);
    connect(ui->btnClearAll, &QPushButton::clicked, this, &MainWindow::clearLogBudListStat);
    connect(ui->btnLoadGardenImg, &QPushButton::clicked, this, &MainWindow::loadTeaGardenImg);

    // 采茶执行机构手动控制
    connect(ui->btnWalkForward, &QPushButton::clicked, [=](){sendPickCmd("WALK_FORWARD");});
    connect(ui->btnWalkBack, &QPushButton::clicked, [=](){sendPickCmd("WALK_BACK");});
    connect(ui->btnArmUp, &QPushButton::clicked, [=](){sendPickCmd("ARM_Z+");});
    connect(ui->btnArmDown, &QPushButton::clicked, [=](){sendPickCmd("ARM_Z-");});
    connect(ui->btnShearCut, &QPushButton::clicked, [=](){sendPickCmd("SHEAR_CUT");});
    connect(ui->btnVacuumOn, &QPushButton::clicked, [=](){sendPickCmd("VACUUM_ON");});
    connect(ui->btnVacuumOff, &QPushButton::clicked, [=](){sendPickCmd("VACUUM_OFF");});
}

MainWindow::~MainWindow()
{
    if(m_serialDev->isOpen()) m_serialDev->close();
    delete ui;
}

void MainWindow::refreshSerialList()
{
    ui->cbComList->clear();
    auto portList = QSerialPortInfo::availablePorts();
    for(auto &info : portList)
        ui->cbComList->addItem(info.portName() + " " + info.description());
    ui->txtLog->append(QString("检测到串口设备共 %1 个").arg(portList.size()));
}

void MainWindow::sendPickCmd(QString cmd)
{
    if(!m_serialDev->isOpen()){
        ui->txtLog->append("警告:串口未连接,指令下发失败:" + cmd);
        return;
    }
    QString sendBuf = cmd + "\r\n";
    m_serialDev->write(sendBuf.toUtf8());
    ui->txtLog->append("下发采茶设备指令:" + cmd);
    m_aiCore->savePickLog("SEND_CMD:"+cmd, TeaFieldSensor{},0,0,0,0,0);
}

void MainWindow::serialRecvHandle()
{
    QByteArray buf = m_serialDev->readAll();
    QString recvText = QString::fromUtf8(buf).trimmed();
    ui->txtLog->append("设备反馈报文:" + recvText);
    m_aiCore->savePickLog("RECV_MSG:"+recvText, TeaFieldSensor{},0,0,0,0,0);

    if(recvText.contains("WALK_ALIGN_OK")) m_aiCore->setTaskStatus(TASK_ARM_ALIGN_BUDD);
    if(recvText.contains("SHEAR_FINISH")) m_aiCore->setTaskStatus(TASK_VACUUM_COLLECT_LEAF);
    if(recvText.contains("VACUUM_COLLECT_DONE")) m_aiCore->setTaskStatus(TASK_BOX_WEIGHT_COUNT);
    if(recvText.contains("ROW_PICK_COMPLETE")) m_aiCore->setTaskStatus(TASK_FINISH_ROW);
    // 故障告警
    if(recvText.contains("COLLISION_ALARM")){
        m_autoRunFlag = false;
        ui->btnAutoRun->setText("启动全自动采茶");
        ui->txtLog->append("【故障告警】机械臂碰撞茶树枝条,紧急停机!");
        m_aiCore->setTaskStatus(TASK_ERROR_COLLISION);
    }
    if(recvText.contains("STRONG_LIGHT_ALARM")){
        ui->txtLog->append("【预警】光照过强,嫩芽识别精度下降,建议遮阳后采摘");
        m_aiCore->setTaskStatus(TASK_ERROR_LIGHT_HIGH);
    }
    if(recvText.contains("RAIN_HUMID_ALARM")){
        m_autoRunFlag = false;
        ui->txtLog->append("【禁止采摘】雨天高湿,鲜叶易闷红,整机停机");
        m_aiCore->setTaskStatus(TASK_ERROR_RAIN_STOP);
    }
}

void MainWindow::cameraFrameRefresh()
{
    m_camBufferImg = QImage(640,480,QImage::Format_RGB888);
    m_camBufferImg.fill(QColor(186,245,164));
    ui->lblCamera->setPixmap(QPixmap::fromImage(m_camBufferImg).scaled(ui->lblCamera->size(),Qt::KeepAspectRatio));
}

TeaFieldSensor MainWindow::readFieldAllSensor()
{
    TeaFieldSensor env;
    env.airTemp = QRandomGenerator::global()->bounded(10,32)/1.0;
    env.airHum = QRandomGenerator::global()->bounded(40,93)/1.0;
    env.lightLux = QRandomGenerator::global()->bounded(2000,32000)/1.0;
    env.soilWater = QRandomGenerator::global()->bounded(42,78)/1.0;
    env.rainLevel = QRandomGenerator::global()->bounded(0,12)/1.0;
    m_batchCount++;
    updateEnvChart(env.airTemp, env.airHum, env.lightLux);
    ui->txtLog->append(QString("茶园环境:温度%1℃ 湿度%2%% 光照%3lx 降雨%4mm")
        .arg(env.airTemp,4,'f',1).arg(env.airHum,4,'f',1).arg(env.lightLux,6,'f',0).arg(env.rainLevel,3,'f',1));
    m_aiCore->setTaskStatus(TASK_GET_FIELD_SENSOR);
    return env;
}

void MainWindow::runYOLOBudDetect()
{
    ui->txtLog->append("====双目YOLO嫩芽AI识别分级检测启动====");
    auto budList = m_aiCore->detectAllTeaBud(m_camBufferImg);
    drawBudMark(m_camBufferImg, budList);
    ui->lblCamera->setPixmap(QPixmap::fromImage(m_camBufferImg).scaled(ui->lblCamera->size(),Qt::KeepAspectRatio));
    ui->listBudInfo->clear();
    int single=0,one=0,two=0,waste=0;
    m_totalBudAll += budList.size();
    for(auto &bud : budList)
    {
        QString opTip = bud.allowPick ? "【允许采摘】" : "【废弃跳过】";
        QString gradeTxt;
        switch(bud.grade){
        case BUD_SINGLE: gradeTxt="特级单芽"; single++; break;
        case BUD_ONE_LEAF: gradeTxt="一级一芽一叶"; one++; break;
        case BUD_TWO_LEAF: gradeTxt="二级一芽二叶"; two++; break;
        case BUD_WASTE: gradeTxt="老叶病虫废弃梢"; waste++; break;
        }
        QString rowText = QString("ID:%1 | %2 | 芽长%3mm | 嫩度%4%% | 病斑%5%% | %6")
            .arg(bud.budId).arg(gradeTxt).arg(bud.budLength,3,'f',1)
            .arg(bud.tenderRate,3,'f',0).arg(bud.diseaseRatio,3,'f',1).arg(opTip);
        QListWidgetItem *item = new QListWidgetItem(rowText);
        if(bud.grade == BUD_SINGLE) item->setForeground(Qt::magenta);
        else if(bud.grade == BUD_ONE_LEAF) item->setForeground(Qt::darkGreen);
        else if(bud.grade == BUD_TWO_LEAF) item->setForeground(Qt::darkYellow);
        else item->setForeground(Qt::darkRed);
        ui->listBudInfo->addItem(item);
    }
    m_singleTotal += single; m_oneLeafTotal += one; m_twoLeafTotal += two; m_wasteTotal += waste;
    ui->txtLog->append(QString("嫩芽汇总:总梢%1 单芽%2 一芽一叶%3 一芽二叶%4 废弃%5")
        .arg(budList.size()).arg(single).arg(one).arg(two).arg(waste));
    m_aiCore->savePickLog("YOLO嫩芽检测批次", readFieldAllSensor(), single, one, two, waste, m_accFreshWeight);
}

void MainWindow::drawBudMark(QImage &img, const QVector<TeaBudInfo> &list)
{
    QPainter painter(&img);
    painter.setRenderHint(QPainter::Antialiasing);
    for(auto &bud : list)
    {
        QPoint pt(bud.budCenter.x(), bud.budCenter.y());
        QColor penC, brushC;
        // 分级标记色:紫=单芽,绿=一芽一叶,黄=一芽二叶,红=废弃
        switch(bud.grade){
        case BUD_SINGLE: penC=Qt::magenta; brushC=QColor(210,70,210,70); break;
        case BUD_ONE_LEAF: penC=Qt::green; brushC=QColor(30,190,30,70); break;
        case BUD_TWO_LEAF: penC=Qt::yellow; brushC=QColor(225,190,10,70); break;
        case BUD_WASTE: penC=Qt::red; brushC=QColor(220,35,35,70); break;
        }
        painter.setPen(QPen(penC,3));
        painter.setBrush(brushC);
        painter.drawEllipse(pt,20,20);
        QString gradeStr;
        switch(bud.grade){
        case BUD_SINGLE: gradeStr="单芽"; break;
        case BUD_ONE_LEAF: gradeStr="一芽一叶"; break;
        case BUD_TWO_LEAF: gradeStr="一芽二叶"; break;
        case BUD_WASTE: gradeStr="废弃梢"; break;
        }
        QString info = QString("ID%1 %2 芽长%3mm").arg(bud.budId).arg(gradeStr).arg(bud.budLength,3,'f',1);
        painter.drawText(pt.x()+24, pt.y(), info);
    }
    painter.end();
    updateGradeBarChart(m_singleTotal, m_oneLeafTotal, m_twoLeafTotal, m_wasteTotal);
}

void MainWindow::updateEnvChart(double temp, double hum, double light)
{
    m_tempLine->append(m_batchCount, temp);
    m_humLine->append(m_batchCount, hum);
    m_lightLine->append(m_batchCount, light);
    m_envMonitorChart->update();
}

void MainWindow::updateGradeBarChart(int single, int one, int two, int waste)
{
    m_gradeBarSeries->clear();
    QBarSet *statSet = new QBarSet("嫩芽数量");
    *statSet << single << one << two << waste;
    m_gradeBarSeries->append(statSet);
    m_yieldStatChart->update();
}

void MainWindow::autoRowPickLoop()
{
    if(!m_autoRunFlag) return;
    TeaFieldSensor env = readFieldAllSensor();
    runYOLOBudDetect();
    auto budList = m_aiCore->detectAllTeaBud(m_camBufferImg);
    if(budList.isEmpty()){
        ui->txtLog->append("画面未识别茶叶嫩芽,巡航等待新梢");
        m_autoRunFlag = false;
        ui->btnAutoRun->setText("启动全自动采茶");
        return;
    }
    // 筛选可采摘嫩芽
    QVector<TeaBudInfo> validBudList;
    for(auto &bud : budList)
        if(bud.allowPick) validBudList.push_back(bud);
    if(validBudList.isEmpty()){
        ui->txtLog->append("当前画面仅老叶/病虫梢,跳过本行,3秒后继续");
        QThread::msleep(3000);
        autoRowPickLoop();
        return;
    }
    // 逐芽全自动采摘
    for(auto &bud : validBudList)
    {
        if(!m_autoRunFlag) break;
        ui->txtLog->append(QString("开始采摘嫩芽ID:%1,等级:%2").arg(bud.budId));
        QPointF worldBud = m_aiCore->pixel2World(bud.budCenter);
        sendPickCmd(QString("WALK_ALIGN X:%1 Y:%2").arg(worldBud.x(),4,'f',1).arg(worldBud.y(),4,'f',1));
        QThread::msleep(1300);
        sendPickCmd("ARM_DOWN_ALIGN");
        QThread::msleep(800);
        sendPickCmd("SHEAR_CUT");
        QThread::msleep(600);
        sendPickCmd("VACUUM_ON");
        QThread::msleep(900);
        sendPickCmd("VACUUM_OFF");
        sendPickCmd("ARM_HOME");
        m_accFreshWeight += 0.004; // 单芽平均鲜重模拟
        if(m_aiCore->getTaskStatus() == TASK_BOX_WEIGHT_COUNT)
            ui->txtLog->append(QString("嫩芽ID:%1采摘收集完成").arg(bud.budId));
        else if(m_aiCore->getTaskStatus() >= TASK_ERROR_COLLISION) break;
    }
    m_aiCore->savePickLog("单行采茶完成", env, m_singleTotal, m_oneLeafTotal, m_twoLeafTotal, m_wasteTotal, m_accFreshWeight);
    if(m_autoRunFlag){
        ui->txtLog->append("本行采摘完毕,移动至下一行茶带,2.7s后继续");
        QThread::msleep(2700);
        autoRowPickLoop();
    }
}

void MainWindow::singleStepPickTest()
{
    ui->txtLog->append("====单芽手动采茶全套调试流程====");
    sendPickCmd("WALK_FORWARD");
    QThread::msleep(600);
    sendPickCmd("ARM_DOWN_ALIGN");
    QThread::msleep(1000);
    sendPickCmd("SHEAR_CUT");
    QThread::msleep(700);
    sendPickCmd("VACUUM_ON");
    QThread::msleep(800);
    sendPickCmd("VACUUM_OFF");
    sendPickCmd("ARM_HOME");
    sendPickCmd("WALK_BACK");
    ui->txtLog->append("全套单步采茶动作执行完成");
}

void MainWindow::clearLogBudListStat()
{
    ui->txtLog->clear();
    ui->listBudInfo->clear();
    m_singleTotal=m_oneLeafTotal=m_twoLeafTotal=m_wasteTotal=m_totalBudAll=0;
    m_accFreshWeight=0;
    m_tempLine->clear(); m_humLine->clear(); m_lightLine->clear();
    m_envMonitorChart->update();
    m_gradeBarSeries->clear();
    m_yieldStatChart->update();
    ui->txtLog->append("日志、嫩芽列表、产量统计、监测曲线全部重置");
}

void MainWindow::loadTeaGardenImg()
{
    QString path = QFileDialog::getOpenFileName(this,"加载茶园茶梢实拍图",".","*.jpg *.png *.bmp");
    if(path.isEmpty()) return;
    cv::Mat mat = cv::imread(path.toStdString());
    m_camBufferImg = m_aiCore->mat2qimg(mat);
    ui->lblCamera->setPixmap(QPixmap::fromImage(m_camBufferImg).scaled(ui->lblCamera->size(),Qt::KeepAspectRatio));
    ui->txtLog->append("茶园图像加载完成:" + path);
}
```

## 6. main.cpp 国产信创程序入口(中文兼容)
```cpp
#include <QApplication>
#include "mainwindow.h"
#include <QFont>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    // 统信UOS/银河麒麟思源黑体,解决界面中文乱码
    QFont uiFont("Noto Sans SC", 9);
    app.setFont(uiFont);
    MainWindow w;
    w.show();
    return app.exec();
}
```

# UI界面控件清单
1. `lblCamera`:双目深度相机茶园嫩芽实时预览窗口
2. 串口通信区:`cbComList`串口选择、`cbBaudRate`波特率、`btnRefreshCom`刷新、`btnComSwitch`开关串口
3. AI视觉识别模块:`btnYOLODetect`嫩芽分级检测、`btnLoadGardenImg`离线图片导入
4. 作业模式切换:`btnAutoRun`整行全自动采茶、`btnSingleTest`单步手动调试、`btnClearAll`清空日志统计
5. 嫩芽明细列表`listBudInfo`:显示ID、等级、芽长、嫩度、病斑、是否采摘
6. 运行日志框`txtLog`:串口指令、AI识别结果、采摘产量、设备故障告警
7. 双可视化图表
   - `chartEnv`:气温/湿度/光照实时变化曲线
   - `chartYield`:四类嫩芽采摘产量柱状统计
8. 采茶执行机构手动控制面板
   - 行走底盘:`btnWalkForward/btnWalkBack`前后巡航
   - 六轴机械臂:`btnArmUp/btnArmDown`升降对位嫩芽
   - 采摘执行头:`btnShearCut`柔性剪切刀
   - 鲜叶收集:`btnVacuumOn/btnVacuumOff`负压吸叶装置

# 项目核心功能(匹配名优绿茶标准化采摘农艺)
## 1. 改进YOLOv8双目嫩芽智能分级识别系统
采用动态蛇形卷积+BiFPN特征融合,复杂茶园光影下精准区分4类茶梢:
1. **特级单芽**:芽长≤18mm、嫩度≥85%,专供高端龙井、白毫银针,启动单点精准慢采模式,降低破碎率;
2. **一级一芽一叶**:芽长18~32mm,主流名优茶,整行连续匀速采摘;
3. **二级一芽二叶**:大宗机制绿茶,加大行走速度提升效率;
4. **废弃梢**:老叶、紫叶、虫斑、病害芽、木质枝条,自动跳过不采摘,避免鲜叶混杂降级。
双目深度定位嫩茎剪切点(嫩芽下方2mm),模拟人工提掐,不损伤嫩芽、减少碎叶。

## 2. 全自动一体化智能采茶完整工序(复刻专业采茶工整套动作)
1. 行走底盘沿茶垄自动巡航,双目实时拍摄冠层;
2. YOLO实时识别嫩芽等级、三维坐标、剪切点位;
3. 机械臂柔性下降对准嫩芽,剪切刀精准切断嫩茎;
4. 负压风道同步吸起鲜芽,输送至底部收集保鲜箱;
5. 重量传感器实时累计鲜叶产量,完成一行自动切换下一条茶垄;
6. 收集箱满量、故障、恶劣环境自动停机并日志记录。

## 3. 双运行模式适配高山名优茶园、平地连片茶场
1. **全自动整行巡航采摘**:24小时无人值守连续作业,分级自动切换行走/剪切速度,大幅降低人工采茶成本;
2. **单步手动调试模式**:单独控制底盘、机械臂、剪切刀、负压收集,用于新品种采摘参数校准、局部补采、设备维护检修。

## 4. 茶园四情监测+完整鲜叶采摘溯源台账
- 实时采集气温、空气湿度、光照、土壤含水量、降雨量;强光、雨天高湿自动预警/强制停机,防止鲜叶灼伤、闷红变质;
- 双数据库存储:①批次汇总表(四类嫩芽总产量、鲜叶总重量、田间环境参数);②单嫩芽明细表(每梢尺寸、嫩度、病害、采摘状态);本地txt日志永久备份,满足茶叶加工溯源、基地采摘生产台账审核要求;
- 内置产量统计柱状图,实时直观展示特级/一级/二级/废弃梢占比,快速判断茶园新梢品质。

## 5. 多重茶叶、设备、环境安全防护机制
1. **枝条碰撞自锁保护**:机械臂触碰木质茶枝立即停机,防止损伤设备、扯破茶蓬;
2. **环境采摘自锁逻辑**:湿度>90%、降雨、光照>30000lx自动禁止采摘,规避鲜叶品质劣变;
3. **柔性低损伤采摘**:硅胶缓冲剪切刀、低速负压风道,名优茶破碎率控制在行业标准以内;
4. **废弃梢隔离机制**:病虫老叶直接跳过,不混入鲜叶原料,减少后续分选工作量;
5. 伺服过载、串口通信中断、传感器失效整机锁止所有执行机构,杜绝误动作。

## 6. 全国产信创平台原生兼容
适配银河麒麟、统信UOS国产操作系统,支持飞腾、鲲鹏、龙芯ARM工控机与x86工业主机;Qt5+OpenCV完全开源无商业闭源依赖,适配龙井、白茶、毛尖、碧螺春、高山云雾茶等各类名优绿茶基地智能化改造。

# 编译部署步骤
1. 国产工控机预装依赖:Qt5.15、OpenCV4、qtserialport、qtcharts、qtsql;
2. Qt Creator新建Qt Widget工程,清空默认模板文件,粘贴全部源码,新建空白`res.qrc`资源文件;
3. 执行`qmake`构建 → `make`编译,输出`bin`目录上位机可执行程序;
4. RS485总线对接硬件:双目深度相机、田间气象多参数传感器、行走底盘伺服、六轴采摘机械臂、剪切执行器、负压收集风机、鲜叶称重模块;
5. 导入茶园实拍图像校准识别阈值,一键开启AI全自动名优茶智能采摘。
 

 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐