AI名优茶智能采茶机器人 QT国产信创完整工程
# 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全自动名优茶智能采摘。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)