belt server build & pkg

This commit is contained in:
yiyi 2025-09-10 00:31:27 +08:00
parent e66a0cfc1e
commit 551fbc575a
25 changed files with 1961 additions and 1463 deletions

View File

@ -5,10 +5,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TEMPLATE = app
CONFIG += c++17
QMAKE_CXXFLAGS += /utf-8
SUBDIRS += ../BeltTearingConfig/BeltTearingConfig.pro \
../VrNet/VrTcpClient.pro
# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# Include paths
INCLUDEPATH += $$PWD/Presenter/Inc
@ -26,9 +26,9 @@ win32:CONFIG(debug, debug|release) {
LIBS += -L../VrNets/release -lVrTcpClient
LIBS += -L../VrUtils/release -lVrUtils
}else:unix:!macx {
LIBS += -L../VrUtils -lVrUtils
LIBS += -L../BeltTearingConfig -lBeltTearingConfig
LIBS += -L../VrNets -lVrTcpClient
LIBS += -L../VrUtils -lVrUtils
}
# You can make your code fail to compile if it uses deprecated APIs.
@ -46,10 +46,10 @@ SOURCES += \
widgets/ImageGridWidget.cpp \
widgets/ImageGridWithTableWidget.cpp \
widgets/ImageTileWidget.cpp \
Presenter/Src/PathManager.cpp \
Presenter/Src/BeltTearingPresenter.cpp \
widgets/TearingDataTableWidget.cpp \
widgets/StyledMessageBox.cpp \
widgets/TearingDataTableWidget.cpp
Presenter/Src/PathManager.cpp \
Presenter/Src/BeltTearingPresenter.cpp
HEADERS += \
IStatusUpdate.h \
@ -61,10 +61,10 @@ HEADERS += \
widgets/ImageGridWidget.h \
widgets/ImageGridWithTableWidget.h \
widgets/ImageTileWidget.h \
Presenter/Inc/PathManager.h \
Presenter/Inc/BeltTearingPresenter.h \
widgets/TearingDataTableWidget.h \
widgets/StyledMessageBox.h \
widgets/TearingDataTableWidget.h
Presenter/Inc/PathManager.h \
Presenter/Inc/BeltTearingPresenter.h
FORMS += \
dialognetconfig.ui \

View File

@ -16,7 +16,6 @@ CONFIG += staticlib
# 头文件路径
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/../VrUtils/tinyxml2
# 源文件
HEADERS += \

View File

@ -91,6 +91,8 @@ struct BeltTearingConfigResult
DebugParam debugParam; // 调试参数
BeltTearingProjectType projectType; // 项目类型
int serverPort = 5900; // 服务端端口
};
/**

View File

@ -61,12 +61,11 @@ BeltTearingConfigResult VrBeltTearingConfig::LoadConfig(const std::string& fileP
xml.readNext();
// 解析服务器配置
if (xml.isStartElement() && xml.name() == "Servers") {
if (xml.isStartElement() && xml.name() == "ClientServers") {
while (xml.readNextStartElement()) {
if (xml.name() == "Server") {
ServerInfo server;
server.name = xml.attributes().value("name").toString().toStdString();
server.ip = xml.attributes().value("ip").toString().toStdString();
server.name = xml.attributes().value("name").toString().toStdString(); server.ip = xml.attributes().value("ip").toString().toStdString();
server.port = xml.attributes().value("port").toInt();
result.servers.push_back(server);
xml.skipCurrentElement();
@ -134,6 +133,16 @@ BeltTearingConfigResult VrBeltTearingConfig::LoadConfig(const std::string& fileP
}
xml.skipCurrentElement();
}
// 解析服务端配置
else if (xml.isStartElement() && xml.name() == "LocalServerConfig") {
while (xml.readNextStartElement()) {
if (xml.name() == "ServerPort") {
result.serverPort = xml.attributes().value("port").toInt();
xml.skipCurrentElement();
}
}
}
}
file.close();
@ -169,7 +178,7 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon
// 保存服务器配置
if (!configResult.servers.empty()) {
xml.writeStartElement("Servers");
xml.writeStartElement("ClientServers");
for (const auto& server : configResult.servers) {
xml.writeStartElement("Server");
xml.writeAttribute("name", QString::fromStdString(server.name));
@ -177,7 +186,7 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon
xml.writeAttribute("port", QString::number(server.port));
xml.writeEndElement(); // Server
}
xml.writeEndElement(); // Servers
xml.writeEndElement(); // ClientServers
}
// 保存算法参数
@ -221,6 +230,13 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon
xml.writeAttribute("type", typeStr);
xml.writeEndElement(); // ProjectType
// 保存服务端配置
xml.writeStartElement("LocalServerConfig");
xml.writeStartElement("ServerPort");
xml.writeAttribute("port", QString::number(configResult.serverPort));
xml.writeEndElement(); // ServerPort
xml.writeEndElement(); // LocalServerConfig
xml.writeEndElement(); // BeltTearingConfig
xml.writeEndDocument();

View File

@ -1,11 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<BeltTearingConfig>
<!-- 网络服务器配置 -->
<Servers>
<!-- 客户端连接服务端的配置 -->
<ClientServers>
<Server name="主服务器" ip="192.168.1.100" port="8080" />
<Server name="备用服务器1" ip="192.168.1.101" port="8081" />
<Server name="备用服务器2" ip="192.168.1.102" port="8082" />
</Servers>
</ClientServers>
<!-- 本地服务端配置 -->
<LocalServerConfig>
<ServerPort port="5900" />
</LocalServerConfig>
<!-- 算法参数 -->
<AlgorithmParams>

View File

@ -93,13 +93,7 @@ std::vector<SSG_beltTearingInfo> BeltTearingAlgo::Predit(std::vector<SVzNL3DLase
beltTearings_unknown, //未判明,应用无需处理。
algoParam);
lineIndex += 1;
// if (beltTearings_unknown.size() > 0) {
// std::cout << "未知的撕裂数量: " << beltTearings_unknown.size() << std::endl;
// }
// if (beltTearings_growing.size() > 0) {
// std::cout << "进行中的撕裂数量: " << beltTearings_growing.size() << std::endl;
// }
if (beltTearings_new.size() > 0) //收集撕裂点
{
@ -116,6 +110,5 @@ std::vector<SSG_beltTearingInfo> BeltTearingAlgo::Predit(std::vector<SVzNL3DLase
}
}
// TODO: 返回值有可能为空,也有可能为当前检测的撕裂
return total_tearings;
}

View File

@ -1,29 +1,378 @@
#include "BeltTearingPresenter.h"
#include "PathManager.h"
#include "version.h"
#include <QUuid>
#include <QDataStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QBuffer>
#include <QByteArray>
#include <QDateTime>
#include <iostream>
#include <QRandomGenerator>
#include "VrLog.h"
BeltTearingPresenter::BeltTearingPresenter(QObject *parent)
: QObject(parent)
, m_tcpServer(new QTcpServer(this))
, m_dataTimer(new QTimer(this))
, m_cameraInitTimer(new QTimer(this))
, m_port(0)
, m_config(nullptr)
, m_eyeDevice(nullptr)
, m_beltTearingAlgo(new BeltTearingAlgo())
, m_cameraInitialized(false)
, m_cameraDetecting(false)
{
// 打印版本信息
LOG_INFO("Initializing %s %s\n", getProductName().toStdString().c_str(), getVersionString().toStdString().c_str());
LOG_INFO("Build: %s\n", getBuildInfo().toStdString().c_str());
// 连接信号槽
connect(m_tcpServer, &QTcpServer::newConnection, this, &BeltTearingPresenter::onNewConnection);
connect(m_dataTimer, &QTimer::timeout, this, &BeltTearingPresenter::onSendSimulatedData);
connect(m_cameraInitTimer, &QTimer::timeout, this, &BeltTearingPresenter::onCameraInitTimer);
// 创建配置实例
IVrBeltTearingConfig::CreateInstance(&m_config);
if (m_config) {
m_config->SetConfigChangeNotify(this);
}
// 初始化相机
initializeCamera();
}
BeltTearingPresenter::~BeltTearingPresenter()
{
stopServer();
stopCamera();
if (m_beltTearingAlgo) {
delete m_beltTearingAlgo;
m_beltTearingAlgo = nullptr;
}
if (m_config) {
delete m_config;
m_config = nullptr;
}
}
bool BeltTearingPresenter::loadConfiguration(const QString& configFilePath)
{
if (!m_config) {
LOG_WARNING("Config instance not created\n");
return false;
}
m_configFilePath = configFilePath;
try {
// 加载配置文件
m_configResult = m_config->LoadConfig(configFilePath.toStdString());
LOG_INFO("Configuration loaded successfully:\n");
LOG_INFO(" Server Port: %d\n", m_configResult.serverPort);
LOG_INFO(" Project Type: %d\n", static_cast<int>(m_configResult.projectType));
LOG_INFO(" Servers count: %d\n", m_configResult.servers.size());
// 应用算法参数
applyAlgorithmParameters(m_configResult.algorithmParams);
// 输出算法参数信息
const auto& params = m_configResult.algorithmParams;
LOG_INFO("Algorithm Parameters:\n");
LOG_INFO(" Tear Threshold: %f\n", params.beltTearingParam.tearThreshold);
LOG_INFO(" Min Tear Length: %f\n", params.beltTearingParam.minTearLength);
LOG_INFO(" Max Tear Width: %f\n", params.beltTearingParam.maxTearWidth);
LOG_INFO(" Blur Size: %d\n", params.imageProcessingParam.blurSize);
LOG_INFO(" Check Interval: %d\n", params.monitoringParam.checkInterval);
return true;
} catch (const std::exception& e) {
LOG_ERROR("Error loading configuration: %s\n", e.what());
return false;
}
}
void BeltTearingPresenter::applyAlgorithmParameters(const BeltTearingAlgorithmParams& params)
{
if (!m_beltTearingAlgo) {
return;
}
// 重置算法参数
m_beltTearingAlgo->ResetParameter();
// 应用算法参数
LOG_DEBUG("Applying algorithm parameters...\n");
LOG_DEBUG(" Tear Threshold: %f\n", params.beltTearingParam.tearThreshold);
LOG_DEBUG(" Min Tear Length: %f\n", params.beltTearingParam.minTearLength);
LOG_DEBUG(" Max Tear Width: %f\n", params.beltTearingParam.maxTearWidth);
LOG_DEBUG(" Blur Size: %d\n", params.imageProcessingParam.blurSize);
LOG_DEBUG(" Canny Threshold 1: %f\n", params.imageProcessingParam.cannyThreshold1);
LOG_DEBUG(" Canny Threshold 2: %f\n", params.imageProcessingParam.cannyThreshold2);
LOG_DEBUG(" Check Interval: %d ms\n", params.monitoringParam.checkInterval);
LOG_DEBUG(" Alert Threshold: %f\n", params.monitoringParam.alertThreshold);
// 调试参数
if (m_configResult.debugParam.enableDebug) {
LOG_DEBUG("Debug mode enabled:\n");
LOG_DEBUG(" Save debug images: %s\n", (m_configResult.debugParam.saveDebugImage ? "Yes" : "No"));
LOG_DEBUG(" Print detail log: %s\n", (m_configResult.debugParam.printDetailLog ? "Yes" : "No"));
}
// TODO: 根据BeltTearingAlgo的实际接口设置参数
// 例如m_beltTearingAlgo->SetTearThreshold(params.beltTearingParam.tearThreshold);
}
void BeltTearingPresenter::OnConfigChanged(const BeltTearingConfigResult& configResult)
{
LOG_INFO("Configuration changed notification received\n");
// 更新配置结果
m_configResult = configResult;
// 重新应用算法参数
applyAlgorithmParameters(configResult.algorithmParams);
// 如果服务器端口改变,可能需要重启服务器
if (m_tcpServer->isListening() && m_tcpServer->serverPort() != configResult.serverPort) {
LOG_INFO("Server port changed, restarting server...\n");
stopServer();
startServer(configResult.serverPort);
}
}
bool BeltTearingPresenter::initializeCamera()
{
if (m_eyeDevice) {
return true; // 已经初始化过
}
int result = IVrEyeDevice::CreateObject(&m_eyeDevice);
if (result != 0 || !m_eyeDevice) {
LOG_ERROR("Failed to create VrEyeDevice object, error code: %d\n", result);
return false;
}
result = m_eyeDevice->InitDevice();
if (result != 0) {
LOG_ERROR("Failed to initialize VrEyeDevice, error code: %d\n", result);
delete m_eyeDevice;
m_eyeDevice = nullptr;
return false;
}
// 设置状态回调
result = m_eyeDevice->SetStatusCallback(OnStatusCallback, this);
if (result != 0) {
LOG_WARNING("Failed to set status callback, error code: %d\n", result);
}
LOG_INFO("VrEyeDevice initialized successfully\n");
return true;
}
void BeltTearingPresenter::startCamera()
{
if (!m_eyeDevice) {
if (!initializeCamera()) {
// 启动定时器持续重试初始化
LOG_WARNING("Camera initialization failed, starting retry timer...\n");
m_cameraInitTimer->start(5000); // 每5秒重试一次
return;
}
}
// 尝试打开设备
int result = m_eyeDevice->OpenDevice(nullptr);
if (result != 0) {
LOG_WARNING("Failed to open camera device, error code: %d, retrying...\n", result);
// 启动定时器持续重试连接
m_cameraInitTimer->start(3000); // 每3秒重试一次
return;
}
// 停止重试定时器
m_cameraInitTimer->stop();
m_cameraInitialized = true;
// 开始检测
result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this);
if (result != 0) {
LOG_ERROR("Failed to start detection, error code: %d\n", result);
return;
}
m_cameraDetecting = true;
}
void BeltTearingPresenter::stopCamera()
{
m_cameraInitTimer->stop();
if (m_eyeDevice) {
if (m_cameraDetecting) {
m_eyeDevice->StopDetect();
m_cameraDetecting = false;
}
if (m_cameraInitialized) {
m_eyeDevice->CloseDevice();
m_cameraInitialized = false;
}
delete m_eyeDevice;
m_eyeDevice = nullptr;
}
LOG_INFO("Camera stopped\n");
}
void BeltTearingPresenter::onCameraInitTimer()
{
LOG_DEBUG("Retrying camera initialization...\n");
// 尝试重新初始化和连接相机
if (!m_eyeDevice) {
if (!initializeCamera()) {
return; // 继续重试
}
}
// 尝试连接相机
int result = m_eyeDevice->OpenDevice(nullptr);
if (result == 0) {
// 连接成功,开始检测
m_cameraInitTimer->stop();
m_cameraInitialized = true;
result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this);
if (result == 0) {
m_cameraDetecting = true;
LOG_INFO("Camera connection restored successfully!\n");
} else {
LOG_ERROR("Failed to start detection after reconnection, error code: %d\n", result);
}
} else {
LOG_WARNING("Still unable to connect to camera, continuing retry...\n");
}
}
void BeltTearingPresenter::OnStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam)
{
BeltTearingPresenter* presenter = static_cast<BeltTearingPresenter*>(pInfoParam);
if (!presenter) return;
LOG_DEBUG("Camera status changed: %d\n", static_cast<int>(eStatus));
// 处理相机状态变化
switch (eStatus) {
case keDeviceWorkStatus_Eye_Comming:
LOG_INFO("Camera connected\n");
break;
case keDeviceWorkStatus_Offline:
LOG_WARNING("Camera disconnected, attempting to reconnect...\n");
presenter->m_cameraInitialized = false;
presenter->m_cameraDetecting = false;
// 开始重连尝试
if (!presenter->m_cameraInitTimer->isActive()) {
presenter->m_cameraInitTimer->start(3000);
}
break;
default:
break;
}
}
void BeltTearingPresenter::OnPointCloudCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam)
{
if(keResultDataType_Position != eDataType) return;
BeltTearingPresenter* presenter = static_cast<BeltTearingPresenter*>(pParam);
if (!presenter || !pLaserLinePoint) return;
// 处理点云数据
presenter->processPointCloudData(pLaserLinePoint);
}
void BeltTearingPresenter::processPointCloudData(const SVzLaserLineData* pLaserLine)
{
if (!m_beltTearingAlgo || !pLaserLine) return;
try {
SVzNL3DLaserLine sNL3DLaserLine;
sNL3DLaserLine.nTimeStamp = pLaserLine->llTimeStamp;
sNL3DLaserLine.p3DPosition = static_cast<SVzNL3DPosition*>(pLaserLine->p3DPoint);
sNL3DLaserLine.nPositionCnt = pLaserLine->nPointCount;
// 调用算法进行检测
std::vector<SSG_beltTearingInfo> results = m_beltTearingAlgo->Predit(&sNL3DLaserLine, pLaserLine->llFrameIdx);
// 如果有检测结果,发送到网络客户端
if (!results.empty()) {
LOG_INFO("Detected %d belt tearing(s)\n", results.size());
sendTearingResults(results);
}
} catch (const std::exception& e) {
LOG_ERROR("Error in algorithm processing: %s\n", e.what());
}
}
void BeltTearingPresenter::sendTearingResults(const std::vector<SSG_beltTearingInfo>& results)
{
if (results.empty() || m_clients.isEmpty()) {
return;
}
// 将检测结果转换为JSON格式
QJsonArray tearingArray;
for (const auto& result : results) {
QJsonObject tearingObj;
// tearingObj["level"] = QString::number(result.level);
tearingObj["id"] = QString::number(result.tearID);
tearingObj["tearStatus"] = QString::number(static_cast<int>(result.tearStatus));
tearingObj["tearType"] = QString::number(result.tearType);
tearingObj["statLineIdx"] = QString::number(result.statLineIdx);
tearingObj["endLineIdx"] = QString::number(result.endLineIdx);
tearingObj["tearDepth"] = QString::number(result.tearDepth);
tearingObj["tearWidth"] = QString::number(result.tearWidth);
// tearingObj["tearLength"] = QString::number(result.tearLength);
tearingObj["roiLeft"] = QString::number(result.roi.left);
tearingObj["roiRight"] = QString::number(result.roi.right);
tearingObj["roiTop"] = QString::number(result.roi.top);
tearingObj["roiBottom"] = QString::number(result.roi.bottom);
// tearingObj["imageFile"] = QString::fromStdString(result.imageFile);
// tearingObj["older"] = QString::fromStdString(result.older);
tearingObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
tearingArray.append(tearingObj);
}
// 转换为JSON字符串
QJsonDocument doc(tearingArray);
QByteArray message = doc.toJson(QJsonDocument::Compact);
// 创建数据包
QByteArray package;
QDataStream stream(&package, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(message.size());
package.append(message);
package.append("___END___\r\n");
// 发送到所有连接的客户端
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
QTcpSocket *socket = it.value();
if (socket->state() == QAbstractSocket::ConnectedState) {
socket->write(package);
}
}
LOG_INFO("Sent tearing detection results to %d client(s)\n", m_clients.size());
}
bool BeltTearingPresenter::startServer(quint16 port)
@ -36,9 +385,9 @@ bool BeltTearingPresenter::startServer(quint16 port)
bool result = m_tcpServer->listen(QHostAddress::Any, port);
if (result) {
std::cout << "TCP server started on port " << port << std::endl;
LOG_INFO("TCP server started on port %d\n", port);
} else {
std::cout << "Failed to start TCP server: " << m_tcpServer->errorString().toStdString() << std::endl;
LOG_ERROR("Failed to start TCP server: %s\n", m_tcpServer->errorString().toStdString().c_str());
}
return result;
@ -57,25 +406,9 @@ void BeltTearingPresenter::stopServer()
m_tcpServer->close();
}
// 停止数据发送定时器
stopSendingSimulatedData();
m_port = 0;
}
void BeltTearingPresenter::startSendingSimulatedData()
{
// 启动定时器每2秒发送一次模拟数据
m_dataTimer->start(1000);
}
void BeltTearingPresenter::stopSendingSimulatedData()
{
if (m_dataTimer->isActive()) {
m_dataTimer->stop();
}
}
void BeltTearingPresenter::onNewConnection()
{
while (m_tcpServer->hasPendingConnections()) {
@ -90,7 +423,7 @@ void BeltTearingPresenter::onNewConnection()
// 连接客户端信号
connect(socket, &QTcpSocket::disconnected, this, &BeltTearingPresenter::onClientDisconnected);
std::cout << "Client connected: " << clientId.toStdString() << std::endl;
LOG_INFO("Client connected: %s\n", clientId.toStdString().c_str());
// 发送欢迎消息
QByteArray welcomeMsg = "Welcome to BeltTearing Server!";
@ -116,164 +449,34 @@ void BeltTearingPresenter::onClientDisconnected()
if (!clientId.isEmpty()) {
m_clients.remove(clientId);
std::cout << "Client disconnected: " << clientId.toStdString() << std::endl;
LOG_INFO("Client disconnected: %s\n", clientId.toStdString().c_str());
}
socket->deleteLater();
}
void BeltTearingPresenter::onSendSimulatedData()
{
static unsigned int nCount = 0;
nCount++;
// 向所有连接的客户端发送模拟数据
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
QTcpSocket *socket = it.value();
if (socket->state() == QAbstractSocket::ConnectedState) {
if(nCount % 2 == 0){
sendSimulatedImage(socket);
}else{
sendSimulatedTearingData(socket);
}
// 随机选择发送图像或日志记录
// sendSimulatedImage(socket);
// sendSimulatedLogRecords(socket);
// sendSimulatedTearingData(socket);
}
}
}
void BeltTearingPresenter::sendSimulatedImage(QTcpSocket *socket)
{
// 创建一个模拟的图像
cv::Mat image(480, 640, CV_8UC3, cv::Scalar(128, 128, 128));
// 在图像上绘制一些随机内容
cv::circle(image, cv::Point(320, 240), 50, cv::Scalar(0, 0, 255), -1);
cv::rectangle(image, cv::Rect(100, 100, 200, 150), cv::Scalar(0, 255, 0), 2);
static unsigned int s_imageCount = 0;
// 在图像上叠加数字
QString numberText = QString::number(++s_imageCount); // 生成一个随机数
cv::putText(image, numberText.toStdString(), cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
// 编码图像
std::vector<uchar> imgBuffer;
std::vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 3};
cv::imencode(".png", image, imgBuffer, params);
QByteArray byteArray(reinterpret_cast<const char*>(imgBuffer.data()), imgBuffer.size());
// 创建数据包
QByteArray package;
QDataStream stream(&package, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint8(static_cast<quint8>(ByteDataType::Image)) << quint32(byteArray.size());
package.append(byteArray);
package.append("___END___\r\n");
// 发送数据
socket->write(package);
std::cout << "Sent simulated image data to client" << std::endl;
}
void BeltTearingPresenter::sendSimulatedLogRecords(QTcpSocket *socket)
{
// 创建模拟日志记录
QJsonArray logRecords;
for (int i = 0; i < 3; i++) {
QJsonObject record;
record["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
record["level"] = QString("INFO");
record["message"] = QString("Simulated log message %1").arg(i + 1);
record["source"] = QString("BeltTearingDetector");
logRecords.append(record);
}
// 将日志记录转换为JSON字符串
QJsonDocument doc(logRecords);
QByteArray message = doc.toJson(QJsonDocument::Compact);
// 创建数据包
QByteArray package;
QDataStream stream(&package, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(message.size());
package.append(message);
package.append("___END___\r\n");
// 发送数据
socket->write(package);
std::cout << "Sent simulated log records to client" << std::endl;
}
void BeltTearingPresenter::sendSimulatedTearingData(QTcpSocket *socket)
{
// 创建模拟撕裂数据
QJsonArray tearingDataArray;
// 生成随机数量的撕裂数据1-3个
int tearingCount = QRandomGenerator::global()->bounded(1, 4);
for (int i = 0; i < tearingCount; i++) {
QJsonObject tearingInfo;
// 随机生成撕裂数据
int level = QRandomGenerator::global()->bounded(1, 4); // 1-3级
int tearStatus = QRandomGenerator::global()->bounded(1, 4); // 1-3状态
QString statusStr;
switch (tearStatus) {
case 1: statusStr = "新的撕裂"; break;
case 2: statusStr = "进行中"; break;
case 3: statusStr = "已完成"; break;
default: statusStr = "无效撕裂"; break;
}
// 填充撕裂信息
tearingInfo.insert("level", QString::number(level));
tearingInfo.insert("id", QString::number(QRandomGenerator::global()->bounded(1000, 9999)));
tearingInfo.insert("tearStatus", statusStr);
tearingInfo.insert("tearType", QString::number(QRandomGenerator::global()->bounded(1, 5)));
tearingInfo.insert("statLineIdx", QString::number(QRandomGenerator::global()->bounded(1, 100)));
tearingInfo.insert("endLineIdx", QString::number(QRandomGenerator::global()->bounded(1, 100)));
tearingInfo.insert("tearDepth", QString::number(QRandomGenerator::global()->bounded(1, 1000)));
tearingInfo.insert("tearWidth", QString::number(QRandomGenerator::global()->bounded(1, 500)));
tearingInfo.insert("tearLength", QString::number(QRandomGenerator::global()->bounded(1, 1000)));
tearingInfo.insert("roiLeft", QString::number(QRandomGenerator::global()->bounded(1, 200)));
tearingInfo.insert("roiRight", QString::number(QRandomGenerator::global()->bounded(200, 400)));
tearingInfo.insert("roiTop", QString::number(QRandomGenerator::global()->bounded(1, 100)));
tearingInfo.insert("roiBottom", QString::number(QRandomGenerator::global()->bounded(100, 300)));
tearingInfo.insert("imageFile", QString("/Image/Tearing/image_%1__.jpg").arg(QRandomGenerator::global()->bounded(1, 100)));
tearingInfo.insert("older", (QRandomGenerator::global()->bounded(0, 2) == 0) ? "-" : "旧伤");
tearingDataArray.append(tearingInfo);
}
// 将撕裂数据转换为JSON字符串
QJsonDocument doc(tearingDataArray);
QByteArray message = doc.toJson(QJsonDocument::Compact);
// 创建数据包
QByteArray package;
QDataStream stream(&package, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::BigEndian);
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(message.size());
package.append(message);
package.append("___END___\r\n");
// 发送数据
socket->write(package);
std::cout << "Sent simulated tearing data to client" << std::endl;
}
QString BeltTearingPresenter::generateClientId(QTcpSocket *socket)
{
// 使用UUID生成唯一的客户端ID
return QUuid::createUuid().toString();
}
QString BeltTearingPresenter::getVersionString()
{
return QString(BELT_TEARING_SERVER_VERSION_STRING);
}
QString BeltTearingPresenter::getProductName()
{
return QString(BELT_TEARING_SERVER_PRODUCT_NAME);
}
QString BeltTearingPresenter::getBuildInfo()
{
return QString("%1 %2 built on %3 %4 for %5")
.arg(BELT_TEARING_SERVER_BUILD_TYPE)
.arg(BELT_TEARING_SERVER_VERSION_STRING)
.arg(BELT_TEARING_SERVER_BUILD_DATE)
.arg(BELT_TEARING_SERVER_BUILD_TIME)
.arg(BELT_TEARING_SERVER_PLATFORM);
}

View File

@ -7,6 +7,13 @@
#include <QTimer>
#include <QMap>
#include <opencv2/opencv.hpp>
#include "IVrEyeDevice.h"
#include "BeltTearingAlgo.h"
#include "beltTearingDetection_Export.h"
#include "IVrBeltTearingConfig.h"
#include "VZNL_Common.h"
#include "VZNL_Types.h"
#include "VrLog.h"
// 假设ByteDataType枚举定义
enum class ByteDataType : quint8 {
@ -14,7 +21,7 @@ enum class ByteDataType : quint8 {
Image = 2
};
class BeltTearingPresenter : public QObject
class BeltTearingPresenter : public QObject, public IVrBeltTearingConfigChangeNotify
{
Q_OBJECT
@ -22,29 +29,70 @@ public:
explicit BeltTearingPresenter(QObject *parent = nullptr);
~BeltTearingPresenter();
// 配置相关方法
bool loadConfiguration(const QString& configFilePath);
void applyAlgorithmParameters(const BeltTearingAlgorithmParams& params);
// 初始化TCP服务器
bool startServer(quint16 port);
bool startServer(quint16 port = 0); // port = 0 表示使用配置文件中的端口
void stopServer();
// 模拟数据发送
void startSendingSimulatedData();
void stopSendingSimulatedData();
// 相机相关方法
bool initializeCamera();
void startCamera();
void stopCamera();
// 获取配置信息
quint16 getServerPort() const { return m_configResult.serverPort; }
const BeltTearingConfigResult& getConfig() const { return m_configResult; }
// 获取版本信息
static QString getVersionString();
static QString getProductName();
static QString getBuildInfo();
// IVrBeltTearingConfigChangeNotify接口实现
void OnConfigChanged(const BeltTearingConfigResult& configResult) override;
private slots:
void onNewConnection();
void onClientDisconnected();
void onSendSimulatedData();
void onCameraInitTimer();
private:
// TCP服务器相关
QTcpServer *m_tcpServer;
QMap<QString, QTcpSocket*> m_clients;
QTimer *m_dataTimer;
quint16 m_port;
// 模拟数据发送方法
void sendSimulatedImage(QTcpSocket *socket);
void sendSimulatedLogRecords(QTcpSocket *socket);
void sendSimulatedTearingData(QTcpSocket *socket);
// 配置相关
IVrBeltTearingConfig *m_config;
BeltTearingConfigResult m_configResult;
QString m_configFilePath;
// 相机相关
IVrEyeDevice *m_eyeDevice;
QTimer *m_cameraInitTimer;
QString m_cameraIP;
bool m_cameraInitialized;
bool m_cameraDetecting;
// 算法相关
BeltTearingAlgo *m_beltTearingAlgo;
// 相机状态回调函数
static void OnStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam);
// 点云数据回调函数
static void OnPointCloudCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam);
// 处理点云数据
void processPointCloudData(const SVzLaserLineData* pLaserLine);
// 发送算法结果
void sendTearingResults(const std::vector<SSG_beltTearingInfo>& results);
// 工具方法
QString generateClientId(QTcpSocket *socket);
};

View File

@ -1,5 +1,4 @@
QT += core gui
QT += serialport
QT += core
QT += network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
@ -7,7 +6,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TEMPLATE = app
CONFIG += c++11 console
QMAKE_CXXFLAGS += /utf-8
# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
@ -20,12 +22,14 @@ INCLUDEPATH += $$PWD/Utils/Inc
INCLUDEPATH += $$PWD/../VrConfig/Inc
INCLUDEPATH += $$PWD/../VrEyeDevice/Inc
INCLUDEPATH += $$PWD/../BeltTearingConfig/Inc
SOURCES += \
BeltTearingAlgo.cpp \
beltTearingDetection.cpp \
main.cpp \
BeltTearingPresenter.cpp
BeltTearingPresenter.cpp \
PathManager.cpp
HEADERS += \
SG_baseAlgo_Export.h \
@ -33,20 +37,25 @@ HEADERS += \
SG_errCode.h \
beltTearingDetection_Export.h \
BeltTearingAlgo.h \
BeltTearingPresenter.h
BeltTearingPresenter.h \
PathManager.h \
Version.h
FORMS +=
win32:CONFIG(debug, debug|release) {
LIBS += -L../VrUtils/debug -lVrUtils
LIBS += -L../VrEyeDevice/debug -lVrEyeDevice
LIBS += -L../BeltTearingConfig/debug -lBeltTearingConfig
LIBS += -L../VrEyeDevice/debug -lVrEyeDevice
LIBS += -L../VrUtils/debug -lVrUtils
}else:win32:CONFIG(release, debug|release){
LIBS += -L../VrUtils/release -lVrUtils
LIBS += -L../VrEyeDevice/release -lVrEyeDevice
LIBS += -L../BeltTearingConfig/release -lBeltTearingConfig
LIBS += -L../VrEyeDevice/release -lVrEyeDevice
LIBS += -L../VrUtils/release -lVrUtils
}else:unix:!macx {
# Unix/Linux平台库链接(包括交叉编译)
# 注意链接顺序:依赖关系从高到低
LIBS += -L../VrUtils -lVrUtils
LIBS += -L../BeltTearingConfig -lBeltTearingConfig
LIBS += -L../VrEyeDevice -lVrEyeDevice
# 添加系统库依赖
@ -84,22 +93,10 @@ else:unix:!macx: {
LIBS += -L$$PWD/../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui
}
# 添加libmodbus依赖
# 添加Windows系统库依赖
win32 {
LIBS += -lshell32
LIBS += -lws2_32
LIBS += Advapi32.lib
LIBS += -lAdvapi32
}
# Default rules for deployment.
unix {
target.path = /usr/lib
# Link real-time library for POSIX shared memory functions
LIBS += -lrt
}
!isEmpty(target.path): INSTALLS += target
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

View File

@ -0,0 +1,59 @@
#include "PathManager.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
#include <QtCore/QFile>
#include <iostream>
QString PathManager::GetConfigFilePath()
{
// 确保目标目录存在
EnsureConfigDirectoryExists();
return GetAppConfigDirectory() + "/config.xml";
}
QString PathManager::GetAppConfigDirectory()
{
QString configDir = "";
#ifdef _WIN32
// Windows系统使用程序目录
configDir = GetProgramDirectory() + "/BeltTearingServer/Config";
#else
// Linux系统使用用户配置目录
configDir = GetUserConfigDirectory() + "/../BeltTearingServer/Config";
#endif
return configDir;
}
bool PathManager::EnsureConfigDirectoryExists()
{
QString configDir = GetAppConfigDirectory();
if (QDir().exists(configDir)) {
return true;
}
std::cout << "Creating configuration directory: " << configDir.toStdString() << std::endl;
bool success = QDir().mkpath(configDir);
if (success) {
std::cout << "Configuration directory created successfully" << std::endl;
} else {
std::cout << "Failed to create configuration directory: " << configDir.toStdString() << std::endl;
}
return success;
}
QString PathManager::GetProgramDirectory()
{
QString exePath = QCoreApplication::applicationFilePath();
return QFileInfo(exePath).absoluteDir().path();
}
QString PathManager::GetUserConfigDirectory()
{
return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
}

View File

@ -0,0 +1,48 @@
#ifndef PATHMANAGER_H
#define PATHMANAGER_H
#include <QString>
/**
* @brief
*
*
* - Windows:
* - Linux: (~/.config/BeltTearingServer/)
*/
class PathManager
{
public:
/**
* @brief (BeltTearingConfig.xml)
* @return
*/
static QString GetConfigFilePath();
private:
/**
* @brief
* @return truefalse
*/
static bool EnsureConfigDirectoryExists();
/**
* @brief
* @return
*/
static QString GetAppConfigDirectory();
/**
* @brief
* @return
*/
static QString GetProgramDirectory();
/**
* @brief Linux系统
* @return
*/
static QString GetUserConfigDirectory();
};
#endif // PATHMANAGER_H

View File

@ -0,0 +1,29 @@
#ifndef VERSION_H
#define VERSION_H
#define BELT_TEARING_SERVER_VERSION_STRING "1.0.0"
#define BELT_TEARING_SERVER_VERSION_BUILD "1"
#define BELT_TEARING_SERVER_PRODUCT_NAME "BeltTearingServer"
#define BELT_TEARING_SERVER_COMPANY_NAME "VisionRobot"
#define BELT_TEARING_SERVER_COPYRIGHT "Copyright (C) 2024 VisionRobot. All rights reserved."
#define BELT_TEARING_SERVER_DESCRIPTION "Belt Tearing Detection Server with Camera Integration"
// 构建信息
#define BELT_TEARING_SERVER_BUILD_DATE __DATE__
#define BELT_TEARING_SERVER_BUILD_TIME __TIME__
#ifdef _WIN32
#define BELT_TEARING_SERVER_PLATFORM "Windows"
#elif __linux__
#define BELT_TEARING_SERVER_PLATFORM "Linux"
#else
#define BELT_TEARING_SERVER_PLATFORM "Unknown"
#endif
#ifdef _DEBUG
#define BELT_TEARING_SERVER_BUILD_TYPE "Debug"
#else
#define BELT_TEARING_SERVER_BUILD_TYPE "Release"
#endif
#endif // VERSION_H

View File

@ -1,25 +1,60 @@
#include <iostream>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include "BeltTearingPresenter.h"
#include "PathManager.h"
#include "version.h"
#include "VrLog.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// 设置应用程序信息
app.setApplicationName(BELT_TEARING_SERVER_PRODUCT_NAME);
app.setApplicationVersion(BELT_TEARING_SERVER_VERSION_STRING);
app.setOrganizationName(BELT_TEARING_SERVER_COMPANY_NAME);
// 打印启动横幅
LOG_DEBUG("===========================================\n");
LOG_INFO(" %s\n", BELT_TEARING_SERVER_PRODUCT_NAME);
LOG_INFO(" %s\n", BELT_TEARING_SERVER_DESCRIPTION);
LOG_INFO(" %s\n", BELT_TEARING_SERVER_COPYRIGHT);
LOG_DEBUG("===========================================\n");
// 创建并初始化Presenter
BeltTearingPresenter presenter;
// 启动TCP服务器监听端口12345
if (!presenter.startServer(12345)) {
std::cout << "Failed to start server" << std::endl;
// 获取配置文件路径
QString configFilePath = PathManager::GetConfigFilePath();
// 加载配置文件
if (!presenter.loadConfiguration(configFilePath)) {
LOG_WARN("Warning: Failed to load configuration file. Using default settings.\n");
LOG_INFO("Config file location: %s\n", configFilePath.toStdString().c_str());
}
// 启动TCP服务器使用配置文件中的端口如果配置加载失败则使用默认端口
quint16 serverPort = presenter.getServerPort();
if (serverPort == 0) {
serverPort = 5900; // 默认端口
}
if (!presenter.startServer(serverPort)) {
LOG_ERROR("Failed to start server on port %d\n", serverPort);
return -1;
}
// 开始发送模拟数据
presenter.startSendingSimulatedData();
// 启动相机
presenter.startCamera();
std::cout << "BeltTearing Server started. Press Ctrl+C to exit." << std::endl;
// 输出系统信息
LOG_INFO("=== BeltTearing Server Started ===\n");
LOG_DEBUG("===================================\n");
LOG_INFO("Server is running. Press Ctrl+C to exit.\n");
return app.exec();
}

View File

@ -6,6 +6,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TEMPLATE = app
CONFIG += c++17
# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# You can make your code fail to compile if it uses deprecated APIs.
@ -91,12 +95,12 @@ win32:CONFIG(debug, debug|release) {
}else:unix:!macx {
# Unix/Linux平台库链接(包括交叉编译)
# 注意链接顺序:依赖关系从高到低
LIBS += -L../GrabBagConfig -lGrabBagConfig
LIBS += -L../VrUtils -lVrUtils
LIBS += -L../VrEyeDevice -lVrEyeDevice
LIBS += -L../GrabBagConfig -lGrabBagConfig
LIBS += -L../VrEyeDevice -lVrEyeDevice
LIBS += -L../Module/ModbusTCPServer -lModbusTCPServer
LIBS += -L../Module/ShareMem -lShareMem
LIBS += -L../VrNets -lVrNets
LIBS += -L../VrNets -lVrModbus
LIBS += -L../Module/ShareMem -lShareMem
LIBS += -L../VrUtils -lVrUtils
# 添加系统库依赖
LIBS += -lpthread

View File

@ -2,7 +2,6 @@
#include "ui_dialogcamera.h"
#include <QMessageBox>
#include "VrLog.h"
#include "StyledMessageBox.h"
DialogCamera::DialogCamera(const std::vector<std::pair<std::string, IVrEyeDevice*>>& deviceList,
QWidget *parent) :
@ -72,16 +71,16 @@ void DialogCamera::on_camera_selection_changed(int index)
void DialogCamera::on_btn_camer_ok_clicked()
{
if (!m_currentDevice) {
StyledMessageBox::warning(this, "错误", "设备未初始化");
QMessageBox::warning(this, "错误", "设备未初始化");
return;
}
// 应用参数配置
if (ApplyCameraParameters()) {
StyledMessageBox::information(this, "成功", "相机参数配置成功!");
QMessageBox::information(this, "成功", "相机参数配置成功!");
accept(); // 关闭对话框并返回Accepted
} else {
StyledMessageBox::warning(this, "失败", "相机参数配置失败,请检查设备连接!");
QMessageBox::warning(this, "失败", "相机参数配置失败,请检查设备连接!");
}
}
@ -133,7 +132,7 @@ void DialogCamera::InitCameraParameters()
}
} catch (...) {
StyledMessageBox::critical(this, "错误", "读取相机参数时发生异常");
QMessageBox::critical(this, "错误", "读取相机参数时发生异常");
}
}
@ -193,7 +192,7 @@ bool DialogCamera::ApplyCameraParameters()
return success;
} catch (...) {
StyledMessageBox::critical(this, "错误", "应用相机参数时发生异常");
QMessageBox::critical(this, "错误", "应用相机参数时发生异常");
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@
#include <QtCore/QFile>
#include "VrLog.h"
#include "PathManager.h"
#include "StyledMessageBox.h"
DialogConfig::DialogConfig(IVrConfig* devConfig, QWidget *parent) :
QDialog(parent),
@ -71,7 +70,7 @@ void DialogConfig::LoadConfigToUI()
} catch (const std::exception& e) {
LOG_ERROR("Failed to load configuration: %s\n", e.what());
StyledMessageBox::critical(this, "错误", "加载配置文件失败!");
QMessageBox::critical(this, "错误", "加载配置文件失败!");
}
}
@ -127,10 +126,10 @@ bool DialogConfig::SaveConfigFromUI()
void DialogConfig::on_btn_apply_clicked()
{
if (SaveConfigFromUI()) {
StyledMessageBox::information(this, "成功", "配置保存成功!");
QMessageBox::information(this, "成功", "配置保存成功!");
accept(); // 关闭对话框并返回Accepted
} else {
StyledMessageBox::warning(this, "失败", "配置保存失败,请检查文件权限!");
QMessageBox::warning(this, "失败", "配置保存失败,请检查文件权限!");
}
}

View File

@ -28,7 +28,7 @@ win32:CONFIG(debug, debug|release) {
}else:win32:CONFIG(release, debug|release){
LIBS += -L../VrUtils/release -lVrUtils
}else:unix:!macx {
# Unix/Linux平台库链接包括交叉编译
# Unix/Linux平台库链接including cross-compilation
LIBS += -L../VrUtils -lVrUtils
}
@ -36,4 +36,4 @@ win32:CONFIG(debug, debug|release) {
unix {
target.path = /usr/lib
}
!isEmpty(target.path): INSTALLS += target
!isEmpty(target.path): INSTALLS += target

View File

@ -1,339 +1,339 @@
#ifndef IVRCONFIG_H
#define IVRCONFIG_H
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
/**
* @brief
*/
enum class ProjectType
{
GrabBag = 0, // 抓包
DirectBag = 1, // 带方向的编织袋
};
/**
* @brief
*/
inline std::string ProjectTypeToString(ProjectType type)
{
switch (type) {
case ProjectType::GrabBag:
return "GrabBag";
case ProjectType::DirectBag:
return "DirectBag";
default:
return "Unknown";
}
}
/**
* @brief
*/
inline ProjectType StringToProjectType(const std::string& str)
{
if (str == "GrabBag" || str == "0") {
return ProjectType::GrabBag;
} else if (str == "DirectBag" || str == "1") {
return ProjectType::DirectBag;
} else {
return ProjectType::GrabBag; // 默认返回抓包
}
}
struct DeviceInfo
{
std::string name;
std::string ip;
};
/**
* @brief
*/
struct SerialConfig
{
#ifdef _WIN32
std::string portName = "COM6"; // 串口名称
#else
std::string portName = "/dev/ttyS3"; // 串口名称
#endif
int baudRate = 115200; // 波特率
int dataBits = 8; // 数据位
int stopBits = 1; // 停止位
int parity = 0; // 校验位 (0-无校验, 1-奇校验, 2-偶校验)
int flowControl = 0; // 流控制 (0-无, 1-硬件, 2-软件)
bool enabled = true; // 是否启用串口通信
};
/**
* @brief
*/
struct VrBagParam
{
double bagL = 650.0; // 长
double bagW = 450.0; // 宽
double bagH = 160.0; // 高
};
/**
* @brief
*/
struct VrPileParam
{
double pileL = 1300.0; // 垛长
double pileW = 900.0; // 垛宽
double pileH = 800.0; // 垛高
};
/**
* @brief
*/
struct VrOutlierFilterParam
{
double continuityTh = 20.0; // 连续性阈值
int outlierTh = 5; // 离群点判断阈值
};
/**
* @brief
*/
struct VrCornerParam
{
double minEndingGap = 20.0; // 最小结束间隙
double minEndingGap_z = 20.0; // z方向最小结束间隙
double scale = 15.0; // 计算方向角的窗口比例因子
double cornerTh = 30.0; // 角点阈值
double jumpCornerTh_1 = 60.0; // 判断角点是否为跳跃的第一阈值
double jumpCornerTh_2 = 15.0; // 判断角点是否为跳跃的第二阈值
};
/**
* @brief
*/
struct VrSlopeParam
{
double LSlopeZWin = 10.0; // 计算L型Slope特征高度计算的窗口长度
double validSlopeH = 10.0; // 有效斜率高度
double minLJumpH = 20.0; // 最小L跳跃高度
double minEndingGap = 20.0; // 最小结束间隙
};
/**
* @brief V特征参数
*/
struct VrVFeatureParam
{
double valleyMinH = 10.0; // 山谷最小高度
double valleyMaxW = 80.0; // 山谷最大宽度
};
/**
* @brief
*/
struct VrTreeGrowParam
{
double yDeviation_max = 20.0; // 生长时允许的最大Y偏差
double zDeviation_max = 80.0; // 生长时允许的最大Z偏差
int maxLineSkipNum = 5; // 生长时允许跳过的最大线条数
double maxSkipDistance = 20.0; // 最大跳跃距离
double minLTypeTreeLen = 50.0; // L型树的最小长度
double minVTypeTreeLen = 50.0; // V型树的最小长度
};
/**
* @brief HSV颜色比较参数
*/
struct VrHsvCmpParam
{
double hueTh = 15.0; // 色度阈值,小于门限为同一颜色
double saturateTh = 120.0; // 色饱和度阈值
double FBVldPtRatioTh = 0.075; // 正反两面有效颜色点的比例门限
bool frontVldPtGreater = true; // true有效颜色比例高的点的是正面false有效颜色比例高的点的是反面
bool front_upVldPtGreater = false; // true有效颜色比例高的点的是正面朝上false有效颜色比例高的点的是正面朝下
bool back_upVldPtGreater = true; // true有效颜色比例高的点的是反面朝上false有效颜色比例高的点的是反面朝下
};
/**
* @brief RGB颜色模式
*/
struct VrRgbColorPattern
{
int r = 36; // 红色分量
int g = 165; // 绿色分量
int b = 208; // 蓝色分量
};
/**
* @brief
*/
#define RGN_HIST_SIZE 16
struct VrColorTemplateParam
{
double frontColorTemplate[RGN_HIST_SIZE] = {0.0}; // 正面颜色模板
double backColorTemplate[RGN_HIST_SIZE] = {0.0}; // 反面颜色模板
};
/**
* @brief
*/
struct VrCameraPlaneCalibParam
{
int cameraIndex = 1; // 相机索引1-based
std::string cameraName = ""; // 相机名称
double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵,将数据调平(默认单位矩阵)
double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据
double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵,回到原坐标系(默认单位矩阵)
bool isCalibrated = false; // 是否已经校准
};
/**
* @brief
*/
struct VrPlaneCalibParam
{
std::vector<VrCameraPlaneCalibParam> cameraCalibParams; // 各个相机的校准参数
// 获取指定相机的校准参数
VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) {
for (auto& param : cameraCalibParams) {
if (param.cameraIndex == cameraIndex) {
return &param;
}
}
return nullptr;
}
// 获取指定相机的校准参数const版本
const VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) const {
for (const auto& param : cameraCalibParams) {
if (param.cameraIndex == cameraIndex) {
return &param;
}
}
return nullptr;
}
// 设置或更新指定相机的校准参数
void SetCameraCalibParam(const VrCameraPlaneCalibParam& param) {
for (auto& existingParam : cameraCalibParams) {
if (existingParam.cameraIndex == param.cameraIndex) {
existingParam = param;
return;
}
}
// 如果不存在,则添加新的
cameraCalibParams.push_back(param);
}
// 移除指定相机的校准参数
void RemoveCameraCalibParam(int cameraIndex) {
cameraCalibParams.erase(
std::remove_if(cameraCalibParams.begin(), cameraCalibParams.end(),
[cameraIndex](const VrCameraPlaneCalibParam& param) {
return param.cameraIndex == cameraIndex;
}),
cameraCalibParams.end());
}
};
/**
* @brief
*/
struct VrDebugParam
{
bool enableDebug = false; // 是否开启调试模式
bool savePointCloud = false; // 是否保存点云数据
bool saveDebugImage = false; // 是否保存调试图像
bool printDetailLog = false; // 是否打印详细日志
std::string debugOutputPath = ""; // 调试输出路径
};
/**
* @brief
*/
struct VrAlgorithmParams
{
VrBagParam bagParam; // 编织袋参数
VrPileParam pileParam; // 垛参数
VrOutlierFilterParam filterParam; // 滤波参数
VrCornerParam cornerParam; // 角点特征参数
VrSlopeParam slopeParam; // 斜率参数
VrVFeatureParam valleyParam; // 山谷参数
VrTreeGrowParam growParam; // 增长参数
VrPlaneCalibParam planeCalibParam; // 平面校准参数
VrHsvCmpParam hsvCmpParam; // HSV颜色比较参数
VrRgbColorPattern rgbColorPattern; // RGB颜色模式
VrColorTemplateParam colorTemplateParam; // 颜色模板参数
};
/**
* @brief
*/
struct ConfigResult
{
std::vector<DeviceInfo> cameraList;
std::vector<DeviceInfo> deviceList;
VrAlgorithmParams algorithmParams; // 算法参数
VrDebugParam debugParam; // 调试参数
SerialConfig serialConfig; // 串口配置
ProjectType projectType; // 项目类型
};
/**
* @brief
*/
class IVrConfigChangeNotify
{
public:
virtual ~IVrConfigChangeNotify() {}
/**
* @brief
* @param configResult
*/
virtual void OnConfigChanged(const ConfigResult& configResult) = 0;
};
/**
* @brief VrConfig接口类
*/
class IVrConfig
{
public:
/**
* @brief
*/
virtual ~IVrConfig() {}
/**
* @brief
* @return
*/
static bool CreateInstance(IVrConfig** ppVrConfig);
/**
* @brief
* @param filePath
* @return
*/
virtual ConfigResult LoadConfig(const std::string& filePath) = 0;
/**
* @brief
* @param filePath
* @param configResult
* @return
*/
virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) = 0;
/**
* @brief
* @param notify
*/
virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) = 0;
};
#endif // IVRCONFIG_H
#ifndef IVRCONFIG_H
#define IVRCONFIG_H
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
/**
* @brief
*/
enum class ProjectType
{
GrabBag = 0, // 抓包
DirectBag = 1, // 带方向的编织袋
};
/**
* @brief
*/
inline std::string ProjectTypeToString(ProjectType type)
{
switch (type) {
case ProjectType::GrabBag:
return "GrabBag";
case ProjectType::DirectBag:
return "DirectBag";
default:
return "Unknown";
}
}
/**
* @brief
*/
inline ProjectType StringToProjectType(const std::string& str)
{
if (str == "GrabBag" || str == "0") {
return ProjectType::GrabBag;
} else if (str == "DirectBag" || str == "1") {
return ProjectType::DirectBag;
} else {
return ProjectType::GrabBag; // 默认返回抓包
}
}
struct DeviceInfo
{
std::string name;
std::string ip;
};
/**
* @brief
*/
struct SerialConfig
{
#ifdef _WIN32
std::string portName = "COM6"; // 串口名称
#else
std::string portName = "/dev/ttyS3"; // 串口名称
#endif
int baudRate = 115200; // 波特率
int dataBits = 8; // 数据位
int stopBits = 1; // 停止位
int parity = 0; // 校验位 (0-无校验, 1-奇校验, 2-偶校验)
int flowControl = 0; // 流控制 (0-无, 1-硬件, 2-软件)
bool enabled = true; // 是否启用串口通信
};
/**
* @brief
*/
struct VrBagParam
{
double bagL = 650.0; // 长
double bagW = 450.0; // 宽
double bagH = 160.0; // 高
};
/**
* @brief
*/
struct VrPileParam
{
double pileL = 1300.0; // 垛长
double pileW = 900.0; // 垛宽
double pileH = 800.0; // 垛高
};
/**
* @brief
*/
struct VrOutlierFilterParam
{
double continuityTh = 20.0; // 连续性阈值
int outlierTh = 5; // 离群点判断阈值
};
/**
* @brief
*/
struct VrCornerParam
{
double minEndingGap = 20.0; // 最小结束间隙
double minEndingGap_z = 20.0; // z方向最小结束间隙
double scale = 15.0; // 计算方向角的窗口比例因子
double cornerTh = 30.0; // 角点阈值
double jumpCornerTh_1 = 60.0; // 判断角点是否为跳跃的第一阈值
double jumpCornerTh_2 = 15.0; // 判断角点是否为跳跃的第二阈值
};
/**
* @brief
*/
struct VrSlopeParam
{
double LSlopeZWin = 10.0; // 计算L型Slope特征高度计算的窗口长度
double validSlopeH = 10.0; // 有效斜率高度
double minLJumpH = 20.0; // 最小L跳跃高度
double minEndingGap = 20.0; // 最小结束间隙
};
/**
* @brief V特征参数
*/
struct VrVFeatureParam
{
double valleyMinH = 10.0; // 山谷最小高度
double valleyMaxW = 80.0; // 山谷最大宽度
};
/**
* @brief
*/
struct VrTreeGrowParam
{
double yDeviation_max = 20.0; // 生长时允许的最大Y偏差
double zDeviation_max = 80.0; // 生长时允许的最大Z偏差
int maxLineSkipNum = 5; // 生长时允许跳过的最大线条数
double maxSkipDistance = 20.0; // 最大跳跃距离
double minLTypeTreeLen = 50.0; // L型树的最小长度
double minVTypeTreeLen = 50.0; // V型树的最小长度
};
/**
* @brief HSV颜色比较参数
*/
struct VrHsvCmpParam
{
double hueTh = 15.0; // 色度阈值,小于门限为同一颜色
double saturateTh = 120.0; // 色饱和度阈值
double FBVldPtRatioTh = 0.075; // 正反两面有效颜色点的比例门限
bool frontVldPtGreater = true; // true有效颜色比例高的点的是正面false有效颜色比例高的点的是反面
bool front_upVldPtGreater = false; // true有效颜色比例高的点的是正面朝上false有效颜色比例高的点的是正面朝下
bool back_upVldPtGreater = true; // true有效颜色比例高的点的是反面朝上false有效颜色比例高的点的是反面朝下
};
/**
* @brief RGB颜色模式
*/
struct VrRgbColorPattern
{
int r = 36; // 红色分量
int g = 165; // 绿色分量
int b = 208; // 蓝色分量
};
/**
* @brief
*/
#define RGN_HIST_SIZE 16
struct VrColorTemplateParam
{
double frontColorTemplate[RGN_HIST_SIZE] = {0.0}; // 正面颜色模板
double backColorTemplate[RGN_HIST_SIZE] = {0.0}; // 反面颜色模板
};
/**
* @brief
*/
struct VrCameraPlaneCalibParam
{
int cameraIndex = 1; // 相机索引1-based
std::string cameraName = ""; // 相机名称
double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵,将数据调平(默认单位矩阵)
double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据
double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵,回到原坐标系(默认单位矩阵)
bool isCalibrated = false; // 是否已经校准
};
/**
* @brief
*/
struct VrPlaneCalibParam
{
std::vector<VrCameraPlaneCalibParam> cameraCalibParams; // 各个相机的校准参数
// 获取指定相机的校准参数
VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) {
for (auto& param : cameraCalibParams) {
if (param.cameraIndex == cameraIndex) {
return &param;
}
}
return nullptr;
}
// 获取指定相机的校准参数const版本
const VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) const {
for (const auto& param : cameraCalibParams) {
if (param.cameraIndex == cameraIndex) {
return &param;
}
}
return nullptr;
}
// 设置或更新指定相机的校准参数
void SetCameraCalibParam(const VrCameraPlaneCalibParam& param) {
for (auto& existingParam : cameraCalibParams) {
if (existingParam.cameraIndex == param.cameraIndex) {
existingParam = param;
return;
}
}
// 如果不存在,则添加新的
cameraCalibParams.push_back(param);
}
// 移除指定相机的校准参数
void RemoveCameraCalibParam(int cameraIndex) {
cameraCalibParams.erase(
std::remove_if(cameraCalibParams.begin(), cameraCalibParams.end(),
[cameraIndex](const VrCameraPlaneCalibParam& param) {
return param.cameraIndex == cameraIndex;
}),
cameraCalibParams.end());
}
};
/**
* @brief
*/
struct VrDebugParam
{
bool enableDebug = false; // 是否开启调试模式
bool savePointCloud = false; // 是否保存点云数据
bool saveDebugImage = false; // 是否保存调试图像
bool printDetailLog = false; // 是否打印详细日志
std::string debugOutputPath = ""; // 调试输出路径
};
/**
* @brief
*/
struct VrAlgorithmParams
{
VrBagParam bagParam; // 编织袋参数
VrPileParam pileParam; // 垛参数
VrOutlierFilterParam filterParam; // 滤波参数
VrCornerParam cornerParam; // 角点特征参数
VrSlopeParam slopeParam; // 斜率参数
VrVFeatureParam valleyParam; // 山谷参数
VrTreeGrowParam growParam; // 增长参数
VrPlaneCalibParam planeCalibParam; // 平面校准参数
VrHsvCmpParam hsvCmpParam; // HSV颜色比较参数
VrRgbColorPattern rgbColorPattern; // RGB颜色模式
VrColorTemplateParam colorTemplateParam; // 颜色模板参数
};
/**
* @brief
*/
struct ConfigResult
{
std::vector<DeviceInfo> cameraList;
std::vector<DeviceInfo> deviceList;
VrAlgorithmParams algorithmParams; // 算法参数
VrDebugParam debugParam; // 调试参数
SerialConfig serialConfig; // 串口配置
ProjectType projectType; // 项目类型
};
/**
* @brief
*/
class IVrConfigChangeNotify
{
public:
virtual ~IVrConfigChangeNotify() {}
/**
* @brief
* @param configResult
*/
virtual void OnConfigChanged(const ConfigResult& configResult) = 0;
};
/**
* @brief VrConfig接口类
*/
class IVrConfig
{
public:
/**
* @brief
*/
virtual ~IVrConfig() {}
/**
* @brief
* @return
*/
static bool CreateInstance(IVrConfig** ppVrConfig);
/**
* @brief
* @param filePath
* @return
*/
virtual ConfigResult LoadConfig(const std::string& filePath) = 0;
/**
* @brief
* @param filePath
* @param configResult
* @return
*/
virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) = 0;
/**
* @brief
* @param notify
*/
virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) = 0;
};
#endif // IVRCONFIG_H

View File

@ -4,9 +4,37 @@
PKG_NAME="BeltTearingServer"
PKG_ARCH="arm64"
# 默认版本信息
PKG_VERSION="1.0.0"
BUILD_NUMBER="1"
# 从Version.h文件中读取版本信息
VERSION_FILE="../BeltTearingServer/Version.h"
if [ -f "${VERSION_FILE}" ]; then
# 读取版本号(从 BELT_TEARING_SERVER_VERSION_STRING 中提取)
PKG_VERSION=$(grep '#define BELT_TEARING_SERVER_VERSION_STRING' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/')
# 读取构建号(从 BELT_TEARING_SERVER_VERSION_BUILD 中提取)
BUILD_NUMBER=$(grep '#define BELT_TEARING_SERVER_VERSION_BUILD' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/')
echo "${VERSION_FILE} 读取版本信息:"
echo " 版本号: ${PKG_VERSION}"
echo " 构建号: ${BUILD_NUMBER}"
# 如果读取失败,使用默认值
if [ -z "${PKG_VERSION}" ]; then
PKG_VERSION="1.0.0"
echo "警告: 无法读取版本号,使用默认值: ${PKG_VERSION}"
fi
if [ -z "${BUILD_NUMBER}" ]; then
BUILD_NUMBER="1"
echo "警告: 无法读取构建号,使用默认值: ${BUILD_NUMBER}"
fi
else
# Version.h文件不存在时的默认值
PKG_VERSION="1.0.0"
BUILD_NUMBER="1"
echo "警告: ${VERSION_FILE} 文件不存在,使用默认版本信息"
echo " 版本号: ${PKG_VERSION}"
echo " 构建号: ${BUILD_NUMBER}"
fi
PKG_PATH=$HOME/BeltTearingServerPkg
CODE_PATH=../
@ -23,12 +51,32 @@ fi
echo "创建打包目录结构..."
mkdir -p ${PKG_PATH}/DEBIAN
mkdir -p ${PKG_PATH}/opt/onvm
mkdir -p ${PKG_PATH}/usr/lib
mkdir -p ${PKG_PATH}/etc/profile.d
mkdir -p ${PKG_PATH}/opt/belttearingserver
# 复制 BeltTearingServer/onvm 目录下的所有内容到 /opt
BELT_TEARING_SERVER_PATH=${CODE_PATH}/GrabBagPrj/BeltTearingServer/onvm
echo "复制 Qt 运行时环境..."
#QT depend
QT_PKG_PATH=/opt/firefly_qt5.15_arm64_20.04
cp -rfd ${QT_PKG_PATH}/ext ${PKG_PATH}/opt/firefly_qt5.15
cp ${QT_PKG_PATH}/target_qtEnv.sh ${PKG_PATH}/etc/profile.d/
echo "复制依赖库文件..."
#depend
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_core*.so* ${PKG_PATH}/usr/lib/
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_imgproc*.so* ${PKG_PATH}/usr/lib/
cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_highgui*.so* ${PKG_PATH}/usr/lib/
cp ${CODE_PATH}/SDK/bagPosition/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
cp ${CODE_PATH}/SDK/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/
# 复制 BeltTearingServer 目录下的所有内容到 /opt
echo "复制 BeltTearingServer 内容到 /opt/..."
cp -r ${BELT_TEARING_SERVER_PATH}/* ${PKG_PATH}/opt/onvm
#APP
cp ${CODE_PATH}/GrabBagPrj/buildarm/BeltTearingServer/BeltTearingServer ${PKG_PATH}/opt/belttearingserver/
echo "生成 control 文件..."
#control
@ -49,16 +97,16 @@ cat > ${POSTINST_PATH} << 'EOF'
echo "配置 BeltTearingServer 应用程序..."
# 安装并启用 onvm.service
if [ -f /opt/onvm/onvm.service ]; then
echo "安装 onvm.service..."
cp /opt/onvm/onvm.service /etc/systemd/system/
# 安装并启用 belttearingserver.service
if [ -f /opt/belttearingserver/belttearingserver.service ]; then
echo "安装 belttearingserver.service..."
cp /opt/belttearingserver/belttearingserver.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable onvm.service
systemctl start onvm.service
echo "onvm.service 已安装并设置为开机自启"
systemctl enable belttearingserver.service
systemctl start belttearingserver.service
echo "belttearingserver.service 已安装并设置为开机自启"
else
echo "错误: 未找到 onvm.service 文件"
echo "错误: 未找到 belttearingserver.service 文件"
fi
EOF
@ -73,14 +121,17 @@ cat > ${POSTRM_PATH} << 'EOF'
echo "卸载 BeltTearingServer 应用程序..."
# 停止并禁用 onvm.service
systemctl stop onvm.service 2>/dev/null || true
systemctl disable onvm.service 2>/dev/null || true
rm -f /etc/systemd/system/onvm.service
# 停止并禁用 belttearingserver.service
systemctl stop belttearingserver.service 2>/dev/null || true
systemctl disable belttearingserver.service 2>/dev/null || true
rm -f /etc/systemd/system/belttearingserver.service
# 重新加载systemd
systemctl daemon-reload
# 清理 /opt/belttearingserver 目录
rm -rf /opt/belttearingserver
echo "BeltTearingServer 应用程序卸载完成!"
EOF

View File

@ -1,7 +1,10 @@
QT -= gui
CONFIG += c++17 console
QMAKE_CXXFLAGS += /utf-8
# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings

View File

@ -7,6 +7,11 @@ DEFINES += DLLBUILD
CONFIG += c++17
# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
@ -34,4 +39,4 @@ SOURCES += \
unix {
target.path = $$[QT_INSTALL_PLUGINS]/generic
}
!isEmpty(target.path): INSTALLS += target
!isEmpty(target.path): INSTALLS += target

View File

@ -15,7 +15,11 @@
#include "../crc/checksum.h"
#include "../ini/SimpleIni.h"
// getopt只在Windows平台下包含其他平台使用系统自带的getopt
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// getopt只在Windows平台下包含其他平台使用系统自带的getopt
#ifdef _WIN32
#include "getopt.h"
#pragma comment(lib, "VrUtils.lib")

View File

@ -31,6 +31,7 @@
#define LOG_DEBUG(...) VrLogUtils::EchoLog(KELOGLEVEL_Debug, logfilename(__FILE__), __LINE__, "APPD", ##__VA_ARGS__)
#define LOG_INFO(...) VrLogUtils::EchoLog(KELOGLEVEL_Info, logfilename(__FILE__), __LINE__, "APPI", ##__VA_ARGS__)
#define LOG_WARNING(...) VrLogUtils::EchoLog(KELOGLEVEL_Warning, logfilename(__FILE__), __LINE__, "APPW", ##__VA_ARGS__)
#define LOG_WARN(...) VrLogUtils::EchoLog(KELOGLEVEL_Warning, logfilename(__FILE__), __LINE__, "APPW", ##__VA_ARGS__)
#define LOG_ERROR(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__)
#define LOG_ERRO(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__)
#define LOG_ERR(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__)