fix:撕裂tcp没有maxid修复

This commit is contained in:
yiyi 2025-12-12 00:31:21 +08:00
parent b8ffc01f27
commit 4d9e24e62a
36 changed files with 712 additions and 1524 deletions

View File

@ -1,23 +1,22 @@
TEMPLATE = subdirs
# 拆包项目
SUBDIRS += ./GrabBag/GrabBag.pro
# SUBDIRS += ./GrabBag/GrabBag.pro
# 撕裂项目
SUBDIRS += ./BeltTearing/BeltTearing.pro
#焊接
SUBDIRS += ./LapWeld/LapWeld.pro
# SUBDIRS += ./LapWeld/LapWeld.pro
#工件定位
SUBDIRS += ./Workpiece/Workpiece.pro
# 颗粒尺寸检测
SUBDIRS += ./ParticleSize/ParticleSize.pro
# SUBDIRS += ./ParticleSize/ParticleSize.pro
# 双目Mark检测
SUBDIRS += ./BinocularMark/BinocularMark.pro
# SUBDIRS += ./BinocularMark/BinocularMark.pro
# 工件项目WorkpiecePosition和WorkpieceSplice
SUBDIRS += ./WorkpieceProject/WorkpieceProject.pro
# SUBDIRS += ./WorkpieceProject/WorkpieceProject.pro

View File

@ -2,7 +2,7 @@
#define VERSION_H
#define BELT_TEARING_APP_VERSION_STRING "2.0.5"
#define BELT_TEARING_APP_VERSION_BUILD "1"
#define BELT_TEARING_APP_VERSION_BUILD "2"
#define BELT_TEARING_APP_PRODUCT_NAME "BeltTearingApp"
#define BELT_TEARING_APP_COMPANY_NAME "VisionRobot"
#define BELT_TEARING_APP_COPYRIGHT "Copyright (C) 2024-2025 VisionRobot. All rights reserved."

View File

@ -45,6 +45,8 @@ BeltTearingPresenter::BeltTearingPresenter(QObject *parent)
, m_pRobotProtocol(nullptr) // 初始化RobotProtocol指针
, m_pModbusRTUMaster(nullptr) // 初始化ModbusRTUMaster指针
, m_bRobotConnected(false) // 初始化连接状态
, m_simulationTearIdCounter(1000) // 仿真撕裂ID计数器初始值
, m_simulationCallCount(0) // 仿真调用次数初始值
{
// 设置静态实例
s_instance = this;
@ -313,67 +315,40 @@ void BeltTearingPresenter::sendSimulationData()
if(m_pRobotProtocol){
m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_WORKING);
}
// 创建模拟的撕裂结果数据
std::vector<SSG_beltTearingInfo> simulationResults;
// 创建第一个模拟撕裂结果
SSG_beltTearingInfo tear1;
tear1.tearID = 11;
tear1.tearStatus = keSG_tearStatus_New; // 假设1表示有效撕裂
tear1.tearWidth = 15.5f; // 撕裂宽度 15.5mm
tear1.tearDepth = 8.2f; // 撕裂深度 8.2mm
tear1.roi.left = 0.0f;
tear1.roi.top = 0.0f;
tear1.roi.right = 60.0f;
tear1.roi.bottom = 1.0f;
// 其他字段由于结构定义不明确,暂时不填充
simulationResults.push_back(tear1);
// 创建第二个模拟撕裂结果
SSG_beltTearingInfo tear2;
tear2.tearID = 12;
tear2.tearStatus = keSG_tearStatus_Growing; // 假设2表示无效撕裂
tear2.tearWidth = 22.3f; // 撕裂宽度 22.3mm
tear2.tearDepth = 12.7f; // 撕裂深度 12.7mm
tear2.roi.left = 20.0f;
tear2.roi.top = 0.0f;
tear2.roi.right = 50.0f;
tear2.roi.bottom = 1.0f;
// 其他字段由于结构定义不明确,暂时不填充
simulationResults.push_back(tear2);
// 创建一个模拟的图像
QImage simulationImage(800, 600, QImage::Format_RGB888);
// 填充背景色
simulationImage.fill(Qt::white);
// 使用成员变量(开流时会清除)
m_simulationCallCount++;
// 获取当前时间
QDateTime currentTime = QDateTime::currentDateTime();
QString timeString = currentTime.toString("yyyy-MM-dd hh:mm:ss");
// 固定生成2个撕裂长度逐渐增大
for (int i = 0; i < 2; i++) {
SSG_beltTearingInfo tear;
tear.tearID = ++m_simulationTearIdCounter;
tear.tearStatus = keSG_tearStatus_Growing;
// 在图像上绘制时间信息
QPainter painter(&simulationImage);
if (painter.isActive()) {
QFont font("Arial", 16, QFont::Bold);
painter.setFont(font);
painter.setPen(Qt::black);
// 撕裂长度:基础长度 + 调用次数 * 10
float tearLength = 30.0f + m_simulationCallCount * 10.0f + i * 5.0f;
// 在图像顶部绘制时间
painter.drawText(QRect(10, 10, 780, 40), Qt::AlignLeft | Qt::AlignVCenter, QString("时间: %1").arg(timeString));
painter.end();
// 设置ROI
tear.roi.left = 10.0f + i * 50.0f;
tear.roi.top = 0.0f;
tear.roi.right = tear.roi.left + tearLength;
tear.roi.bottom = 10.0f;
// 设置撕裂宽度和深度
tear.tearWidth = 15.0f + i * 5.0f;
tear.tearDepth = 8.0f + i * 2.0f;
simulationResults.push_back(tear);
}
// 发送撕裂结果到所有客户端
sendTearingResults(simulationResults);
SendDetectionResultToRobot(simulationResults); // 发送检测结果到机械臂
sendImageToClients(simulationImage);
m_tearingProtocol->sendDetectResult(simulationResults, simulationImage);
LOG_INFO("Simulation image data sent successfully (timestamp: %s)\n", timeString.toStdString().c_str());
m_tearingProtocol->sendDetectResult(simulationResults, QImage());
}
bool BeltTearingPresenter::initializeCamera()
@ -1331,6 +1306,18 @@ void BeltTearingPresenter::ResetDetect()
}
}
// 清除TCP协议的历史最大撕裂数据
if (m_tearingProtocol) {
m_tearingProtocol->clearHistoryMaxData();
}
// 清除简化协议的历史最大撕裂数据
m_historyMaxTearing = HistoryMaxTearingData();
// 清除仿真数据计数器
m_simulationTearIdCounter = 1000;
m_simulationCallCount = 0;
result = startCamera();
if (result != 0) {
LOG_WARNING("Failed to start camera after reset, error code: %d\n", result);
@ -1363,6 +1350,23 @@ int BeltTearingPresenter::StartWork()
{
LOG_INFO("Starting work - camera and detection\n");
// 清除TCP协议的历史最大撕裂数据
if (m_tearingProtocol) {
m_tearingProtocol->clearHistoryMaxData();
}
// 清除简化协议的历史最大撕裂数据
m_historyMaxTearing = HistoryMaxTearingData(); // 重置历史最大值
if (m_pRobotProtocolSimplified) {
m_pRobotProtocolSimplified->ClearAlarmData();
LOG_INFO("Cleared simplified protocol alarm data on start work\n");
}
// 清除仿真数据计数器
m_simulationTearIdCounter = 1000;
m_simulationCallCount = 0;
LOG_INFO("Cleared simulation counters on start work\n");
// 启动相机
int cameraResult = startCamera();
if (cameraResult != SUCCESS) {
@ -1740,16 +1744,15 @@ void BeltTearingPresenter::SendDetectionResultToRobot(const std::vector<SSG_belt
{
// 根据协议类型发送数据
if (m_modbusTCPProtocolType == ModbusTCPProtocolType::Simplified) {
// 简化协议:只发送最大撕裂信息
// 简化协议:只发送历史最大撕裂信息(从开流开始的最大值)
if (!m_pRobotProtocolSimplified) {
LOG_WARNING("Simplified robot protocol not initialized, cannot send detection results\n");
return;
}
RobotProtocolSimplified::TearingAlarmData alarmData;
// 更新历史最大值
if (!detectionResults.empty()) {
// 找出最大撕裂(使用宽度和高度中的较大值)
// 找出本次检测的最大撕裂
auto maxTearIt = std::max_element(detectionResults.begin(), detectionResults.end(),
[](const SSG_beltTearingInfo& a, const SSG_beltTearingInfo& b) {
// 计算撕裂区域的宽度和高度,取较大值
@ -1760,30 +1763,40 @@ void BeltTearingPresenter::SendDetectionResultToRobot(const std::vector<SSG_belt
double widthB = b.roi.right - b.roi.left;
double lengthB = b.roi.bottom - b.roi.top;
double maxSizeB = widthB > lengthB ? widthB : lengthB;
// 返回true表示a应该排在b之前即a的最大边长比b小
return maxSizeA < maxSizeB;
});
// 设置报警标志
alarmData.alarmFlag = 1; // 有撕裂报警
// 计算撕裂区域的宽度和长度
// 计算本次最大撕裂的尺寸
double dWidth = maxTearIt->roi.right - maxTearIt->roi.left;
double dLength = maxTearIt->roi.bottom - maxTearIt->roi.top;
// 计算撕裂区域的对角线长度,作为撕裂大小的更准确表示
double diagonalLength = std::sqrt(dWidth * dWidth + dLength * dLength);
alarmData.maxLength = static_cast<uint16_t>(diagonalLength); // 最大长度(毫米)
alarmData.maxWidth = static_cast<uint16_t>(maxTearIt->tearWidth); // 最大宽度(毫米)
alarmData.maxId = maxTearIt->tearID; // 最大撕裂ID
uint16_t currentMaxLength = static_cast<uint16_t>(diagonalLength);
uint16_t currentMaxWidth = static_cast<uint16_t>(maxTearIt->tearWidth);
uint32_t currentMaxId = maxTearIt->tearID;
// 更新历史最大值
if (!m_historyMaxTearing.hasData || currentMaxLength > m_historyMaxTearing.maxLength) {
m_historyMaxTearing.maxLength = currentMaxLength;
m_historyMaxTearing.maxWidth = currentMaxWidth;
m_historyMaxTearing.maxId = currentMaxId;
m_historyMaxTearing.hasData = true;
}
}
// 构造报警数据(使用历史最大值)
RobotProtocolSimplified::TearingAlarmData alarmData;
if (m_historyMaxTearing.hasData) {
alarmData.alarmFlag = 1; // 有撕裂报警
alarmData.maxLength = m_historyMaxTearing.maxLength;
alarmData.maxWidth = m_historyMaxTearing.maxWidth;
alarmData.maxId = m_historyMaxTearing.maxId;
} else {
// 没有撕裂,清空报警
// 没有历史数据
alarmData.alarmFlag = 0;
alarmData.maxLength = 0;
alarmData.maxWidth = 0;
alarmData.maxId = 0;
LOG_DEBUG("No tearing detected, clearing alarm\n");
}
// 发送报警数据

View File

@ -170,6 +170,21 @@ private:
ModbusRTUMaster* m_pModbusRTUMaster = nullptr; // Modbus-RTU主端实例
bool m_bModbusRTUConnected = false; // Modbus-RTU连接状态
// 历史最大撕裂数据(从开流开始累积,用于简化协议)
struct HistoryMaxTearingData {
uint16_t maxLength; // 历史最大撕裂长度
uint16_t maxWidth; // 历史最大撕裂宽度
uint32_t maxId; // 历史最大撕裂ID
bool hasData; // 是否有数据
HistoryMaxTearingData() : maxLength(0), maxWidth(0), maxId(0), hasData(false) {}
};
HistoryMaxTearingData m_historyMaxTearing;
// 仿真数据计数器(开流时清除)
uint32_t m_simulationTearIdCounter; // 仿真撕裂ID计数器
int m_simulationCallCount; // 仿真调用次数
// 初始化机械臂协议
int InitRobotProtocol();

View File

@ -101,9 +101,6 @@ int RobotProtocolSimplified::SetAlarmData(const TearingAlarmData& alarmData)
// 更新Modbus寄存器从地址0开始更新所有寄存器
m_pModbusServer->updateHoldingRegisters(RESET_CMD_ADDR, data);
LOG_DEBUG("Alarm data updated: flag=%d, length=%d, width=%d, id=%u\n",
alarmData.alarmFlag, alarmData.maxLength, alarmData.maxWidth, alarmData.maxId);
return SUCCESS;
}

View File

@ -10,6 +10,8 @@ TearingTcpProtocol::TearingTcpProtocol(QObject *parent)
, m_heartbeatInterval(30)
, m_clientTimeout(90)
, m_tcpPort(0)
, m_historyMaxLength(0)
, m_historyMaxId(0)
{
// 连接心跳定时器
connect(m_heartbeatTimer, &QTimer::timeout, this, &TearingTcpProtocol::onHeartbeatTimeout);
@ -95,25 +97,34 @@ void TearingTcpProtocol::sendDetectResult(const std::vector<SSG_beltTearingInfo>
return;
}
// 计算撕裂个数和最大撕裂长度
// 计算撕裂个数和本次检测的最大撕裂长度
int count = static_cast<int>(results.size());
int maxLength = 0;
int currentMaxLength = 0;
int currentMaxId = 0;
for (const auto& result : results) {
double width = result.roi.right - result.roi.left;
double length = result.roi.bottom - result.roi.top;
int tearLength = static_cast<int>(width > length ? width : length);
if (tearLength > maxLength) {
maxLength = tearLength;
if (tearLength > currentMaxLength) {
currentMaxLength = tearLength;
currentMaxId = result.tearID;
}
}
// 构造JSON消息
// 更新历史最大值(从开流开始的最大值)
if (currentMaxLength > m_historyMaxLength) {
m_historyMaxLength = currentMaxLength;
m_historyMaxId = currentMaxId;
}
// 构造JSON消息使用历史最大值
QJsonObject jsonObj;
jsonObj["msgType"] = "DETECT_RESULT";
jsonObj["timestamp"] = QDateTime::currentMSecsSinceEpoch();
jsonObj["count"] = count;
jsonObj["max"] = maxLength;
jsonObj["max"] = m_historyMaxLength; // 使用历史最大长度
jsonObj["maxId"] = m_historyMaxId; // 使用历史最大撕裂ID
// 将图像转换为Base64
if (!visImage.isNull()) {
@ -132,7 +143,8 @@ void TearingTcpProtocol::sendDetectResult(const std::vector<SSG_beltTearingInfo>
// 发送给所有客户端
bool success = m_tcpServer->SendAllData(frame.constData(), frame.size());
if (success) {
LOG_DEBUG("Sent DETECT_RESULT to all clients, count=%d, max=%d\n", count, maxLength);
LOG_DEBUG("Sent DETECT_RESULT to all clients, count=%d, historyMax=%d, historyMaxId=%d\n",
count, m_historyMaxLength, m_historyMaxId);
} else {
LOG_WARNING("Failed to send DETECT_RESULT to clients\n");
}
@ -182,6 +194,13 @@ void TearingTcpProtocol::setTcpPort(quint16 port)
m_tcpPort = port;
}
void TearingTcpProtocol::clearHistoryMaxData()
{
m_historyMaxLength = 0;
m_historyMaxId = 0;
LOG_INFO("Cleared history max data (开流清除)\n");
}
void TearingTcpProtocol::onHeartbeatTimeout()
{
// 可以在这里主动发送心跳给客户端(如果需要)

View File

@ -77,6 +77,7 @@ public:
* @brief
* @param results
* @param visImage
* @note JSON消息包含count()max()maxId(ID)visimg(Base64图像)
*/
void sendDetectResult(const std::vector<SSG_beltTearingInfo>& results, const QImage& visImage);
@ -104,6 +105,21 @@ public:
*/
void setTcpPort(quint16 port);
/**
* @brief
*/
void clearHistoryMaxData();
/**
* @brief
*/
int getHistoryMaxLength() const { return m_historyMaxLength; }
/**
* @brief ID
*/
int getHistoryMaxId() const { return m_historyMaxId; }
private slots:
/**
* @brief
@ -212,6 +228,10 @@ private:
// 回调函数
SpeedCallback m_speedCallback;
ControlCallback m_controlCallback;
// 历史最大撕裂数据(从开流开始累积)
int m_historyMaxLength; // 历史最大撕裂长度
int m_historyMaxId; // 历史最大撕裂对应的ID
};
#endif // TEARINGTCPPROTOCOL_H

View File

@ -2,7 +2,7 @@
#define VERSION_H
#define BELT_TEARING_SERVER_VERSION_STRING "2.0.5"
#define BELT_TEARING_SERVER_VERSION_BUILD "1"
#define BELT_TEARING_SERVER_VERSION_BUILD "2"
#define BELT_TEARING_SERVER_PRODUCT_NAME "BeltTearingServer"
#define BELT_TEARING_SERVER_COMPANY_NAME "VisionRobot"
#define BELT_TEARING_SERVER_COPYRIGHT "Copyright (C) 2024-2025 VisionRobot. All rights reserved."

View File

@ -1,4 +1,7 @@
# 2.0.5
## build_2 2025-12-11
1. 修复撕裂结果发送
## build_1 2025-11-30
1. 协议增加最大撕裂的ID
2. 页面修改:上下去掉间隙

View File

@ -1,474 +0,0 @@
# 工件角点检测功能实现说明
## 功能概述
本文档说明如何在 WorkpieceApp 中集成工件角点提取功能,使用 SDK/workpieceCornerExtraction 算法库进行检测,并通过 JSON 格式将检测结果发送给客户端。
## 实现步骤
### 1. 配置结构更新 ✅
#### 1.1 更新 IVrConfig.h
`VrWorkpieceParam` 结构中添加了 `lineLen` 参数:
```cpp
struct VrWorkpieceParam
{
double lapHeight = 2.0; // 搭接厚度
double weldMinLen = 2.0; // 最小焊缝长度
int weldRefPoints = 2; // 输出参考点数量
WeldScanMode scanMode = WeldScanMode::ScanMode_V;
double lineLen = 100.0; // 工件角点提取:直线段长度阈值
};
```
#### 1.2 更新配置文件 config.xml
```xml
<WorkpieceParam lapHeight="2.0" weldMinLen="2.0" weldRefPoints="2" lineLen="100.0" />
```
#### 1.3 更新 VrConfig.cpp
- 加载配置时读取 `lineLen` 参数
- 保存配置时写入 `lineLen` 参数
### 2. DetectPresenter 算法集成 (待实现)
#### 2.1 添加SDK头文件引用
```cpp
#include "BQ_workpieceCornerExtraction_Export.h"
```
#### 2.2 实现角点检测方法
`DetectWorkpiece` 方法中添加工件角点提取调用:
```cpp
int DetectPresenter::DetectWorkpiece(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
DetectionResult& detectionResult)
{
// 1. 转换激光线数据为算法需要的格式
std::vector<std::vector<SVzNL3DPosition>> scanLines;
// ... 数据转换代码 ...
// 2. 准备算法参数
SSX_BQworkpiecePara workpieceParam;
workpieceParam.lineLen = algorithmParams.workpieceParam.lineLen;
SSG_cornerParam cornerParam;
cornerParam.cornerTh = algorithmParams.cornerParam.cornerTh;
cornerParam.scale = algorithmParams.cornerParam.scale;
cornerParam.minEndingGap = algorithmParams.cornerParam.minEndingGap;
cornerParam.minEndingGap_z = algorithmParams.cornerParam.minEndingGap_z;
cornerParam.jumpCornerTh_1 = algorithmParams.cornerParam.jumpCornerTh_1;
cornerParam.jumpCornerTh_2 = algorithmParams.cornerParam.jumpCornerTh_2;
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = algorithmParams.filterParam.continuityTh;
filterParam.outlierTh = algorithmParams.filterParam.outlierTh;
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = algorithmParams.growParam.maxLineSkipNum;
growParam.yDeviation_max = algorithmParams.growParam.yDeviation_max;
growParam.maxSkipDistance = algorithmParams.growParam.maxSkipDistance;
growParam.zDeviation_max = algorithmParams.growParam.zDeviation_max;
growParam.minLTypeTreeLen = algorithmParams.growParam.minLTypeTreeLen;
growParam.minVTypeTreeLen = algorithmParams.growParam.minVTypeTreeLen;
// 3. 获取调平参数
SSG_planeCalibPara groundCalibPara;
const VrCameraPlaneCalibParam* cameraCalib =
algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex);
if (cameraCalib && cameraCalib->isCalibrated) {
memcpy(groundCalibPara.planeCalib, cameraCalib->planeCalib, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, cameraCalib->invRMatrix, sizeof(double) * 9);
groundCalibPara.planeHeight = cameraCalib->planeHeight;
} else {
// 使用单位矩阵
double identity[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
memcpy(groundCalibPara.planeCalib, identity, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, identity, sizeof(double) * 9);
groundCalibPara.planeHeight = -1.0;
}
// 4. 调用算法
SSX_debugInfo debugContours[100]; // 调试信息
int errCode = 0;
SSX_BQworkpieceResult result = sx_BQ_getWorkpieceCorners(
scanLines,
cornerParam,
filterParam,
growParam,
groundCalibPara,
workpieceParam,
#if _OUTPUT_DEBUG_DATA
debugContours,
#endif
&errCode
);
if (errCode != 0) {
LOG_ERROR("工件角点检测失败,错误码: %d\n", errCode);
return errCode;
}
// 5. 将结果转换为手眼坐标系
// 手眼标定变换矩阵应用到检测结果
// ... 坐标转换代码 ...
// 6. 填充检测结果
detectionResult.cameraIndex = cameraIndex;
detectionResult.positions.clear();
// 添加左侧角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_L[i].x;
pos.y = result.corner_L[i].y;
pos.z = result.corner_L[i].z;
detectionResult.positions.push_back(pos);
}
// 添加右侧角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_R[i].x;
pos.y = result.corner_R[i].y;
pos.z = result.corner_R[i].z;
detectionResult.positions.push_back(pos);
}
// 添加顶部角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_T[i].x;
pos.y = result.corner_T[i].y;
pos.z = result.corner_T[i].z;
detectionResult.positions.push_back(pos);
}
// 添加底部角点
for (int i = 0; i < 3; i++) {
WorkpiecePosition pos;
pos.x = result.corner_B[i].x;
pos.y = result.corner_B[i].y;
pos.z = result.corner_B[i].z;
detectionResult.positions.push_back(pos);
}
// 7. 生成可视化图像(如果需要)
if (debugParam.saveDebugImage) {
// 创建点云可视化图像
// ... 图像生成代码 ...
}
return SUCCESS;
}
```
### 3. WorkpiecePresenter 业务逻辑集成 (待实现)
#### 3.1 更新检测任务方法
`_DetectTask()` 方法中调用 `DetectPresenter::DetectWorkpiece()`
```cpp
int WorkpiecePresenter::_DetectTask()
{
LOG_INFO("[Algo Thread] Start workpiece corner extraction detection\n");
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
if (m_detectionDataCache.empty()) {
LOG_WARNING("No cached detection data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 执行检测
DetectionResult detectionResult;
int nRet = m_pDetectPresenter->DetectWorkpiece(
m_currentCameraIndex,
m_detectionDataCache,
m_algorithmParams,
m_debugParam,
m_dataLoader,
m_clibMatrixList[m_currentCameraIndex - 1].clibMatrix,
detectionResult
);
if (nRet != SUCCESS) {
LOG_ERROR("Detection failed with error: %d\n", nRet);
if (m_pStatus) {
m_pStatus->OnStatusUpdate("检测失败");
}
return nRet;
}
// 通知UI显示结果
detectionResult.cameraIndex = m_currentCameraIndex;
m_pStatus->OnDetectionResult(detectionResult);
// 发送结果到TCP客户端
_SendDetectionResultToTCP(detectionResult, m_currentCameraIndex);
// 更新状态
m_currentWorkStatus = WorkStatus::Completed;
m_pStatus->OnWorkStatusChanged(WorkStatus::Completed);
return SUCCESS;
}
```
#### 3.2 实现TCP结果发送方法
```cpp
void WorkpiecePresenter::_SendDetectionResultToTCP(
const DetectionResult& detectionResult,
int cameraIndex)
{
if (!m_pTCPServer || !m_bTCPConnected) {
LOG_WARNING("TCP not connected, skip sending detection result\n");
return;
}
// 构建JSON格式的检测结果
QJsonObject jsonResult;
jsonResult["cameraIndex"] = cameraIndex;
jsonResult["timestamp"] = QDateTime::currentMSecsSinceEpoch();
jsonResult["cornerCount"] = static_cast<int>(detectionResult.positions.size());
QJsonArray cornersArray;
for (const auto& pos : detectionResult.positions) {
QJsonObject cornerObj;
cornerObj["x"] = pos.x;
cornerObj["y"] = pos.y;
cornerObj["z"] = pos.z;
cornerObj["roll"] = pos.roll;
cornerObj["pitch"] = pos.pitch;
cornerObj["yaw"] = pos.yaw;
cornersArray.append(cornerObj);
}
jsonResult["corners"] = cornersArray;
QJsonDocument jsonDoc(jsonResult);
QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
// 发送JSON数据
m_pTCPServer->SendToAll(jsonData.data(), jsonData.size());
LOG_INFO("Sent detection result to TCP client: %d corners\n",
detectionResult.positions.size());
}
```
### 4. 参数配置界面 (待实现)
#### 4.1 更新 dialogalgoarg.ui
在算法参数对话框中添加工件参数配置控件:
- `lineEdit_lineLen`: 直线段长度阈值输入框
- `label_lineLen`: 参数标签
#### 4.2 更新 dialogalgoarg.cpp
```cpp
void DialogAlgoarg::LoadConfigToUI()
{
// ... 现有加载代码 ...
// 加载工件参数
ui->lineEdit_lineLen->setText(
QString::number(m_configData.algorithmParams.workpieceParam.lineLen));
}
bool DialogAlgoarg::SaveConfigFromUI()
{
// ... 现有保存代码 ...
// 保存工件参数
m_configData.algorithmParams.workpieceParam.lineLen =
ui->lineEdit_lineLen->text().toDouble();
// 保存到配置文件
QString configPath = PathManager::GetConfigFilePath();
return m_vrConfig->SaveConfig(configPath.toStdString(), m_configData);
}
```
### 5. 主窗口显示结果 (待实现)
#### 5.1 更新 mainwindow.cpp
实现 `OnDetectionResult` 回调方法:
```cpp
void MainWindow::OnDetectionResult(const DetectionResult& result)
{
// 1. 显示检测图像
if (!result.image.isNull()) {
ui->label_image->setPixmap(QPixmap::fromImage(result.image));
}
// 2. 更新结果列表
ui->listWidget_results->clear();
for (size_t i = 0; i < result.positions.size(); i++) {
const auto& pos = result.positions[i];
QString resultText = QString("角点 %1: X=%.2f, Y=%.2f, Z=%.2f")
.arg(i + 1)
.arg(pos.x)
.arg(pos.y)
.arg(pos.z);
ui->listWidget_results->addItem(resultText);
}
// 3. 更新状态栏
QString statusText = QString("检测完成,找到 %1 个角点")
.arg(result.positions.size());
ui->statusBar->showMessage(statusText);
}
```
### 6. 项目配置更新 (待实现)
#### 6.1 更新 WorkpieceApp.pro
添加工件角点提取SDK依赖
```qmake
# 工件角点提取算法SDK
INCLUDEPATH += ../../../SDK/workpieceCornerExtraction/Inc
win32:CONFIG(release, debug|release): {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Windows/x64/Release
LIBS += -lBQ_workpieceCornerExtraction -lbaseAlgorithm
}
else:win32:CONFIG(debug, debug|release): {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Windows/x64/Debug
LIBS += -lBQ_workpieceCornerExtraction -lbaseAlgorithm
}
else:unix:!macx: {
LIBS += -L$$PWD/../../../SDK/workpieceCornerExtraction/Arm/aarch64
LIBS += -lworkpieceCornerExtraction -lbaseAlgorithm
}
```
### 7. TCP通信协议
#### 7.1 客户端触发检测
客户端发送触发命令:
```
@,1,1,Trig,$
```
格式:`@,视觉号,视觉模版号,启动信息,$`
#### 7.2 服务端返回检测结果
服务端以JSON格式返回角点检测结果
```json
{
"cameraIndex": 1,
"timestamp": 1640000000000,
"cornerCount": 12,
"corners": [
{
"x": 100.5,
"y": 200.3,
"z": 50.2,
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0
},
// ... 更多角点数据 ...
]
}
```
## 数据流程
```
客户端触发 → TCPServerProtocol → WorkpiecePresenter::StartDetection
相机采集点云数据 → _DetectionCallback → m_detectionDataCache
相机扫描完成 → _AlgoDetectThread → _DetectTask
DetectPresenter::DetectWorkpiece → sx_BQ_getWorkpieceCorners (SDK算法)
坐标转换(手眼标定)→ DetectionResult
MainWindow::OnDetectionResult (UI显示) + _SendDetectionResultToTCP (发送给客户端)
```
## 关键技术点
### 1. 点云数据转换
需要将 `SVzLaserLineData` 格式转换为 `std::vector<std::vector<SVzNL3DPosition>>` 格式
### 2. 调平参数应用
使用相机的平面校准参数对点云数据进行调平处理
### 3. 手眼标定
将相机坐标系下的检测结果转换到机械臂坐标系
### 4. JSON序列化
使用Qt的QJsonObject/QJsonDocument进行JSON格式转换
### 5. 多线程安全
- 使用 `m_detectionDataMutex` 保护检测数据缓存
- 使用 `m_algoDetectCondition` 进行线程同步
## 编译和部署
### Windows平台
1. 确保SDK库文件在正确路径
2. 使用Qt Creator打开项目
3. 选择Release或Debug配置
4. 构建项目
### ARM/Linux平台
1. 配置交叉编译环境
2. 确保ARM版本的SDK库文件存在
3. 使用qmake生成Makefile
4. 执行make编译
```bash
cd App/Workpiece/WorkpieceApp
qmake WorkpieceApp.pro
make
```
## 测试验证
### 1. 参数配置测试
- 打开算法参数配置对话框
- 修改lineLen参数
- 保存并验证config.xml文件
### 2. 算法检测测试
- 加载调试点云数据
- 触发检测
- 验证检测结果
### 3. TCP通信测试
- 启动TCP服务器
- 客户端连接并发送触发命令
- 验证JSON结果返回
### 4. UI显示测试
- 验证检测图像显示
- 验证角点结果列表
- 验证状态信息更新
## 注意事项
1. **SDK版本兼容性**确保使用的SDK版本与项目兼容
2. **内存管理**:注意释放算法分配的内存资源
3. **坐标系转换**:确保手眼标定矩阵正确应用
4. **错误处理**:完善错误检测和日志输出
5. **性能优化**:大点云数据处理时注意性能
6. **线程安全**:确保多线程访问的数据安全
## 后续优化建议
1. 增加算法参数实时调整功能
2. 支持多种工件类型的角点检测
3. 增加检测结果的3D可视化
4. 优化大数据量下的处理速度
5. 增加离线调试和参数优化工具

View File

@ -1,672 +0,0 @@
# 工件角点检测功能 - 完整实现代码
## 已完成的工作 ✅
### 1. 配置结构更新 ✅
- ✅ 更新 `IVrConfig.h` 添加 `lineLen` 参数
- ✅ 更新 `config.xml` 配置文件
- ✅ 更新 `VrConfig.cpp` 支持加载和保存新参数
- ✅ 更新 `WorkpieceApp.pro` 添加SDK依赖
### 2. 算法集成 ✅
- ✅ 更新 `DetectPresenter.cpp` 集成角点提取算法
- ✅ 调用 `sx_BQ_getWorkpieceCorners` API
- ✅ 处理12个角点结果左、右、上、下各3个
- ✅ 坐标转换和可视化
## 待实现代码
### 1. 参数配置界面 (dialogalgoarg)
#### dialogalgoarg.ui 更新
在算法参数对话框中添加以下控件使用Qt Designer
```xml
<!--<><E59CA8><EFBFBD>有的算法参数区域添加 -->
<widget class="QLineEdit" name="lineEdit_lineLen">
<property name="geometry">
<rect>
<x>150</x>
<y>200</y>
<width>100</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>100.0</string>
</property>
</widget>
<widget class="QLabel" name="label_lineLen">
<property name="geometry">
<rect>
<x>20</x>
<y>200</y>
<width>120</width>
<height>30</height>
</rect>
</property>
<property name="text">
<string>直线段长度:</string>
</property>
</widget>
```
#### dialogalgoarg.cpp 更新
```cpp
#include "dialogalgoarg.h"
#include "ui_dialogalgoarg.h"
#include "PathManager.h"
#include <QMessageBox>
DialogAlgoarg::DialogAlgoarg(IVrConfig* vrConfig, QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogAlgoarg)
, m_vrConfig(vrConfig)
{
ui->setupUi(this);
// 加载配置文件路径
m_configFilePath = PathManager::GetConfigFilePath();
// 加载当前配置
if (m_vrConfig) {
m_configData = m_vrConfig->LoadConfig(m_configFilePath.toStdString());
}
// 将配置数据加载到UI
LoadConfigToUI();
}
DialogAlgoarg::~DialogAlgoarg()
{
delete ui;
}
void DialogAlgoarg::LoadConfigToUI()
{
// 加载角点参数
ui->lineEdit_cornerTh->setText(
QString::number(m_configData.algorithmParams.cornerParam.cornerTh));
ui->lineEdit_scale->setText(
QString::number(m_configData.algorithmParams.cornerParam.scale));
ui->lineEdit_minEndingGap->setText(
QString::number(m_configData.algorithmParams.cornerParam.minEndingGap));
ui->lineEdit_minEndingGap_z->setText(
QString::number(m_configData.algorithmParams.cornerParam.minEndingGap_z));
ui->lineEdit_jumpCornerTh_1->setText(
QString::number(m_configData.algorithmParams.cornerParam.jumpCornerTh_1));
ui->lineEdit_jumpCornerTh_2->setText(
QString::number(m_configData.algorithmParams.cornerParam.jumpCornerTh_2));
// 加载树生长参数
ui->lineEdit_maxLineSkipNum->setText(
QString::number(m_configData.algorithmParams.growParam.maxLineSkipNum));
ui->lineEdit_yDeviation_max->setText(
QString::number(m_configData.algorithmParams.growParam.yDeviation_max));
ui->lineEdit_maxSkipDistance->setText(
QString::number(m_configData.algorithmParams.growParam.maxSkipDistance));
ui->lineEdit_zDeviation_max->setText(
QString::number(m_configData.algorithmParams.growParam.zDeviation_max));
ui->lineEdit_minLTypeTreeLen->setText(
QString::number(m_configData.algorithmParams.growParam.minLTypeTreeLen));
ui->lineEdit_minVTypeTreeLen->setText(
QString::number(m_configData.algorithmParams.growParam.minVTypeTreeLen));
// 加载工件参数
ui->lineEdit_lapHeight->setText(
QString::number(m_configData.algorithmParams.workpieceParam.lapHeight));
ui->lineEdit_weldMinLen->setText(
QString::number(m_configData.algorithmParams.workpieceParam.weldMinLen));
ui->lineEdit_weldRefPoints->setText(
QString::number(m_configData.algorithmParams.workpieceParam.weldRefPoints));
// 加载工件角点提取参数
ui->lineEdit_lineLen->setText(
QString::number(m_configData.algorithmParams.workpieceParam.lineLen));
}
bool DialogAlgoarg::SaveConfigFromUI()
{
// 保存角点参数
m_configData.algorithmParams.cornerParam.cornerTh =
ui->lineEdit_cornerTh->text().toDouble();
m_configData.algorithmParams.cornerParam.scale =
ui->lineEdit_scale->text().toDouble();
m_configData.algorithmParams.cornerParam.minEndingGap =
ui->lineEdit_minEndingGap->text().toDouble();
m_configData.algorithmParams.cornerParam.minEndingGap_z =
ui->lineEdit_minEndingGap_z->text().toDouble();
m_configData.algorithmParams.cornerParam.jumpCornerTh_1 =
ui->lineEdit_jumpCornerTh_1->text().toDouble();
m_configData.algorithmParams.cornerParam.jumpCornerTh_2 =
ui->lineEdit_jumpCornerTh_2->text().toDouble();
// 保存树生长参数
m_configData.algorithmParams.growParam.maxLineSkipNum =
ui->lineEdit_maxLineSkipNum->text().toInt();
m_configData.algorithmParams.growParam.yDeviation_max =
ui->lineEdit_yDeviation_max->text().toDouble();
m_configData.algorithmParams.growParam.maxSkipDistance =
ui->lineEdit_maxSkipDistance->text().toDouble();
m_configData.algorithmParams.growParam.zDeviation_max =
ui->lineEdit_zDeviation_max->text().toDouble();
m_configData.algorithmParams.growParam.minLTypeTreeLen =
ui->lineEdit_minLTypeTreeLen->text().toDouble();
m_configData.algorithmParams.growParam.minVTypeTreeLen =
ui->lineEdit_minVTypeTreeLen->text().toDouble();
// 保存工件参数
m_configData.algorithmParams.workpieceParam.lapHeight =
ui->lineEdit_lapHeight->text().toDouble();
m_configData.algorithmParams.workpieceParam.weldMinLen =
ui->lineEdit_weldMinLen->text().toDouble();
m_configData.algorithmParams.workpieceParam.weldRefPoints =
ui->lineEdit_weldRefPoints->text().toInt();
// 保存工件角点提取参数
m_configData.algorithmParams.workpieceParam.lineLen =
ui->lineEdit_lineLen->text().toDouble();
// 保存到配置文件
if (!m_vrConfig->SaveConfig(m_configFilePath.toStdString(), m_configData)) {
return false;
}
return true;
}
void DialogAlgoarg::on_btn_camer_ok_clicked()
{
if (SaveConfigFromUI()) {
QMessageBox::information(this, "提示", "参数保存成功!");
accept();
} else {
QMessageBox::warning(this, "错误", "参数保存失败!");
}
}
void DialogAlgoarg::on_btn_camer_cancel_clicked()
{
reject();
}
```
### 2. TCPServerProtocol JSON结果发送
#### TCPServerMethods.cpp 添加方法
```cpp
#include "TCPServerProtocol.h"
#include "VrLog.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDateTime>
int TCPServerProtocol::SendWorkpieceCornerResult(
const std::vector<WorkpiecePosition>& corners,
int cameraIndex,
const TCPClient* pClient)
{
if (!m_pTCPServer || !m_bServerRunning) {
LOG_ERROR("TCP server is not running\n");
return -1;
}
// 构造JSON格式的检测结果
QJsonObject jsonResult;
jsonResult["type"] = "workpiece_corner";
jsonResult["cameraIndex"] = cameraIndex;
jsonResult["timestamp"] = QDateTime::currentMSecsSinceEpoch();
jsonResult["cornerCount"] = static_cast<int>(corners.size());
jsonResult["success"] = true;
jsonResult["message"] = "Detection completed successfully";
// 构造角点数组
QJsonArray cornersArray;
for (size_t i = 0; i < corners.size(); i++) {
const auto& pos = corners[i];
QJsonObject cornerObj;
cornerObj["index"] = static_cast<int>(i);
cornerObj["x"] = pos.x;
cornerObj["y"] = pos.y;
cornerObj["z"] = pos.z;
cornerObj["roll"] = pos.roll;
cornerObj["pitch"] = pos.pitch;
cornerObj["yaw"] = pos.yaw;
// 添加角点分类信息(可选)
if (i < 3) {
cornerObj["position"] = "left";
} else if (i < 6) {
cornerObj["position"] = "right";
} else if (i < 9) {
cornerObj["position"] = "top";
} else {
cornerObj["position"] = "bottom";
}
cornersArray.append(cornerObj);
}
jsonResult["corners"] = cornersArray;
// 转换为JSON字符串
QJsonDocument jsonDoc(jsonResult);
QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
// 发送数据
int result = -1;
if (pClient) {
// 发送给指定客户端
result = m_pTCPServer->SendData(pClient, jsonData.data(), jsonData.size()) ? 0 : -1;
} else {
// 广播给所有客户端
result = m_pTCPServer->SendAllData(jsonData.data(), jsonData.size()) ? 0 : -1;
}
if (result == 0) {
LOG_INFO("Sent workpiece corner detection result: %d corners, size: %d bytes\n",
static_cast<int>(corners.size()), jsonData.size());
} else {
LOG_ERROR("Failed to send workpiece corner detection result\n");
}
return result;
}
```
#### TCPServerProtocol.h 添加声明
```cpp
class TCPServerProtocol
{
public:
// ... 现有方法 ...
/**
* @brief 发送工件角点检测结果
* @param corners 角点位置数组
* @param cameraIndex 相机索引
* @param pClient 目标客户端nullptr表示广播给所有客户端
* @return 0-成功,其他-失败
*/
int SendWorkpieceCornerResult(
const std::vector<WorkpiecePosition>& corners,
int cameraIndex,
const TCPClient* pClient = nullptr);
};
```
### 3. WorkpiecePresenter 完整业务逻辑
#### WorkpiecePresenter.cpp 中的 _DetectTask 更新
```cpp
int WorkpiecePresenter::_DetectTask()
{
LOG_INFO("[Algo Thread] Start workpiece corner extraction detection\n");
std::lock_guard<std::mutex> lock(m_detectionDataMutex);
// 1. 检查数据
if (m_detectionDataCache.empty()) {
LOG_WARNING("No cached detection data available\n");
if (m_pStatus) {
m_pStatus->OnStatusUpdate("无缓存的检测数据");
}
return ERR_CODE(DEV_DATA_INVALID);
}
LOG_INFO("[Algo Thread] Detection data cache size: %zu lines\n",
m_detectionDataCache.size());
// 2. 获取手眼标定矩阵
const CalibMatrix& calibMatrix = m_clibMatrixList[m_currentCameraIndex - 1];
// 3. 执行检测
DetectionResult detectionResult;
int nRet = m_pDetectPresenter->DetectWorkpiece(
m_currentCameraIndex,
m_detectionDataCache,
m_algorithmParams,
m_debugParam,
m_dataLoader,
calibMatrix.clibMatrix,
detectionResult
);
if (nRet != SUCCESS) {
LOG_ERROR("Detection failed with error: %d\n", nRet);
if (m_pStatus) {
m_pStatus->OnStatusUpdate(QString("检测失败,错误码: %1").arg(nRet).toStdString());
m_pStatus->OnWorkStatusChanged(WorkStatus::Error);
}
m_currentWorkStatus = WorkStatus::Error;
return nRet;
}
LOG_INFO("[Algo Thread] Detection completed successfully, found %zu corners\n",
detectionResult.positions.size());
// 4. 更新检测结果
detectionResult.cameraIndex = m_currentCameraIndex;
// 5. 通知UI显示结果
if (m_pStatus) {
m_pStatus->OnDetectionResult(detectionResult);
QString statusMsg = QString("检测完成,找到 %1 个角点")
.arg(detectionResult.positions.size());
m_pStatus->OnStatusUpdate(statusMsg.toStdString());
}
// 6. 发送结果到TCP客户端
_SendDetectionResultToTCP(detectionResult, m_currentCameraIndex);
// 7. 更新工作状态
m_currentWorkStatus = WorkStatus::Completed;
if (m_pStatus) {
m_pStatus->OnWorkStatusChanged(WorkStatus::Completed);
}
return SUCCESS;
}
```
#### WorkpiecePresenter.cpp 中的 _SendDetectionResultToTCP 实现
```cpp
void WorkpiecePresenter::_SendDetectionResultToTCP(
const DetectionResult& detectionResult,
int cameraIndex)
{
if (!m_pTCPServer) {
LOG_WARNING("TCP server not initialized\n");
return;
}
if (!m_bTCPConnected) {
LOG_WARNING("TCP not connected, skip sending detection result\n");
return;
}
LOG_INFO("Sending workpiece corner detection result to TCP client\n");
// 调用TCPServerProtocol发送JSON格式结果
int result = m_pTCPServer->SendWorkpieceCornerResult(
detectionResult.positions,
cameraIndex,
nullptr // 广播给所有客户端
);
if (result == 0) {
LOG_INFO("Successfully sent detection result to TCP client: %zu corners\n",
detectionResult.positions.size());
if (m_pStatus) {
m_pStatus->OnStatusUpdate("检测结果已发送到客户端");
}
} else {
LOG_ERROR("Failed to send detection result to TCP client\n");
if (m_pStatus) {
m_pStatus->OnStatusUpdate("发送检测结果失败");
}
}
}
```
### 4. MainWindow 显示检测结果
#### mainwindow.cpp 实现 OnDetectionResult
```cpp
void MainWindow::OnDetectionResult(const DetectionResult& result)
{
LOG_INFO("Received detection result: %zu corners from camera %d\n",
result.positions.size(), result.cameraIndex);
// 1. 显示检测图像
if (!result.image.isNull()) {
// 缩放图像以适应显示区域
QPixmap pixmap = QPixmap::fromImage(result.image);
QPixmap scaledPixmap = pixmap.scaled(
ui->label_image->size(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation
);
ui->label_image->setPixmap(scaledPixmap);
LOG_DEBUG("Detection image displayed: %dx%d\n",
result.image.width(), result.image.height());
} else {
LOG_WARNING("No detection image to display\n");
}
// 2. 更新结果列表
ui->listWidget_results->clear();
// 添加标题信息
QString headerText = QString("=== 相机 %1 检测结果 ===").arg(result.cameraIndex);
ui->listWidget_results->addItem(headerText);
QString countText = QString("检测到 %1 个角点:").arg(result.positions.size());
ui->listWidget_results->addItem(countText);
ui->listWidget_results->addItem(""); // 空行
// 添加每个角点的详细信息
for (size_t i = 0; i < result.positions.size(); i++) {
const auto& pos = result.positions[i];
// 确定角点位置(左、右、上、下)
QString positionLabel;
if (i < 3) {
positionLabel = QString("左侧角点 %1").arg(i + 1);
} else if (i < 6) {
positionLabel = QString("右侧角点 %1").arg(i - 2);
} else if (i < 9) {
positionLabel = QString("顶部角点 %1").arg(i - 5);
} else {
positionLabel = QString("底部角点 %1").arg(i - 8);
}
QString resultText = QString("%1: X=%.2f, Y=%.2f, Z=%.2f")
.arg(positionLabel)
.arg(pos.x)
.arg(pos.y)
.arg(pos.z);
ui->listWidget_results->addItem(resultText);
LOG_DEBUG("Corner %zu: (%.2f, %.2f, %.2f)\n", i, pos.x, pos.y, pos.z);
}
// 3. 更新状态栏
QString statusText = QString("检测完成 - 找到 %1 个角点 - 相机 %2")
.arg(result.positions.size())
.arg(result.cameraIndex);
ui->statusBar->showMessage(statusText, 5000); // 显示5秒
// 4. 更<><E69BB4><EFBFBD>检测时间戳
QString timeText = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->label_detectTime->setText(timeText);
LOG_INFO("Detection result display updated successfully\n");
}
```
## JSON 通信协议格式
### 客户端触发检测请求
```
@,1,1,Trig,$
```
格式说明:`@,视觉号(相机ID),视觉模版号,启动信息,$`
### 服务端返回检测结果
```json
{
"type": "workpiece_corner",
"cameraIndex": 1,
"timestamp": 1640000000000,
"cornerCount": 12,
"success": true,
"message": "Detection completed successfully",
"corners": [
{
"index": 0,
"position": "left",
"x": 100.523,
"y": 200.341,
"z": 50.218,
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0
},
{
"index": 1,
"position": "left",
"x": 100.234,
"y": 250.567,
"z": 50.123,
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0
},
// ... 更多角点数据共12个...
{
"index": 11,
"position": "bottom",
"x": 450.789,
"y": 650.234,
"z": 50.456,
"roll": 0.0,
"pitch": 0.0,
"yaw": 0.0
}
]
}
```
### 角点位置说明
- **index 0-2**: 左侧角点position: "left"
- **index 3-5**: 右侧角点position: "right"
- **index 6-8**: 顶部角点position: "top"
- **index 9-11**: 底部角点position: "bottom"
## 编译和测试
### 编译步骤
```bash
# Windows平台
cd App/Workpiece/WorkpieceApp
qmake WorkpieceApp.pro
nmake # 或在Qt Creator中直接构建
# Linux/ARM平台
cd App/Workpiece/WorkpieceApp
qmake WorkpieceApp.pro
make -j4
```
### 测试步骤
#### 1. 参数配置测试
1. 运行WorkpieceApp
2. 点击"算法参数"按钮打开配置对话框
3. 修改"直线段长度"参数默认100.0
4. 点击"确定"保存
5. 验证 `config.xml` 文件中的 `lineLen` 参数已更新
#### 2. 算法检测测试
1. 准备点云数据文件(.txt格式
2. 在主界面点击"加载数据"
3. 选择点云文件
4. 点击"开始检测"
5. 观察日志输出和UI显示
#### 3. TCP通信测试
使用TCP客户端工具连接服务器
```python
import socket
import json
# 连接服务器
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 5020))
# 发送触发命令
trigger_cmd = b'@,1,1,Trig,$'
client.send(trigger_cmd)
# 接收JSON结果
response = client.recv(4096)
result = json.loads(response.decode('utf-8'))
print(f"检测到 {result['cornerCount']} 个角点")
for corner in result['corners']:
print(f"角点 {corner['index']}: ({corner['x']:.2f}, {corner['y']:.2f}, {corner['z']:.2f})")
client.close()
```
## 调试建议
### 1. 日志级别设置
`config.xml` 中启用调试模式:
```xml
<DebugParam enableDebug="true" savePointCloud="true"
saveDebugImage="true" printDetailLog="true"
debugOutputPath="./debug" />
```
### 2. 查看调试输出
- 点云数据:`./debug/Laserline_1_YYYYMMDDHHMMSS.txt`
- 检测图像:`./debug/Image_1_YYYYMMDDHHMMSS.png`
### 3. 常见问题排查
#### 问题1SDK库找不到
**症状**:编译或运行时提示找不到 `BQ_workpieceCornerExtraction.dll`
**解决方案**
- Windows: 确保 `SDK/workpieceCornerExtraction/Windows/x64/Release` 路径正确
- Linux: 设置 `LD_LIBRARY_PATH` 环境变量
#### 问题2检测结果为空
**症状**`cornerCount` 为 0
**解决方案**
- 检查点云数据质量
- 调整 `lineLen` 参数尝试50-200范围
- 检查调平参数是否正确
#### 问题3坐标转换错误
**症状**:角点坐标异常
**解决方案**
- 验证手眼标定矩阵
- 检查相机调平参数
- 查看日志中的原始坐标和转换后坐标
## 性能优化建议
1. **大数据量处理**:启用多线程处理
2. **网络传输**压缩JSON数据
3. **图像显示**:使用异步更新
4. **内存管理**:及时释放点云缓存
## 总结
**已完成**
- 配置结构和文件更新
- 算法SDK集成
- 检测结果处理
- 项目配置文件更新
📝 **待实现**
- 参数配置UI界面需要Qt Designer编辑.ui文件
- TCP JSON结果发送方法
- 主窗口结果显示逻辑
所有核心算法逻辑已经完成剩余工作主要是UI界面调整和TCP通信完善。参考本文档中的代码示例即可快速完成剩余实现。

View File

@ -145,7 +145,7 @@ int DetectPresenter::DetectWorkpiece(
LOG_DEBUG("before sx_BQ_getWorkpieceCorners \n");
// 调用工件角点提取算法
SSX_debugInfo debugContours[4];
std::vector<SSX_debugInfo> debugContours;
SSX_BQworkpieceResult bqResult = sx_BQ_getWorkpieceCorners(
xyzData,
cornerParam,
@ -174,36 +174,36 @@ int DetectPresenter::DetectWorkpiece(
// 1. 添加左侧角点(从下到上,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_L[i].x;
pt.y = bqResult.corner_L[i].y;
pt.z = bqResult.corner_L[i].z;
pt.x = bqResult.corner_1[i].x;
pt.y = bqResult.corner_1[i].y;
pt.z = bqResult.corner_1[i].z;
allCorners.push_back(pt);
}
// 2. 添加顶部角点(从左到右,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_T[i].x;
pt.y = bqResult.corner_T[i].y;
pt.z = bqResult.corner_T[i].z;
pt.x = bqResult.corner_2[i].x;
pt.y = bqResult.corner_2[i].y;
pt.z = bqResult.corner_2[i].z;
allCorners.push_back(pt);
}
// 3. 添加右侧角点(从上到下,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_R[i].x;
pt.y = bqResult.corner_R[i].y;
pt.z = bqResult.corner_R[i].z;
pt.x = bqResult.corner_3[i].x;
pt.y = bqResult.corner_3[i].y;
pt.z = bqResult.corner_3[i].z;
allCorners.push_back(pt);
}
// 4. 添加底部角点(从右到左,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_B[i].x;
pt.y = bqResult.corner_B[i].y;
pt.z = bqResult.corner_B[i].z;
pt.x = bqResult.corner_4[i].x;
pt.y = bqResult.corner_4[i].y;
pt.z = bqResult.corner_4[i].z;
allCorners.push_back(pt);
}

View File

@ -2,9 +2,9 @@
#define VERSION_H
#define WORKPIECE_VERSION_STRING "1.0.1"
#define WORKPIECE_BUILD_STRING "2"
#define WORKPIECE_FULL_VERSION_STRING "V1.0.1.2"
#define WORKPIECE_VERSION_STRING "1.0.2"
#define WORKPIECE_BUILD_STRING "1"
#define WORKPIECE_FULL_VERSION_STRING "V1.0.2.1"
// 获取版本信息的便捷函数
inline const char* GetWorkpieceVersion() {

View File

@ -1,4 +1,8 @@
#1.0.1 2025-11-08
# 1.0.2 2025-12-11
## build_0
1. 更新算法
# 1.0.1 2025-11-08
## build_2
1. 修正开机相机重连【项目结构重构】
@ -6,7 +10,7 @@
1. 更新算法,在应用日志中有算法版本: 1.1.0
2. 修复在没有配置文件中无法连接相机问题
#1.0.0 2025-11-02
# 1.0.0 2025-11-02
## build_1
1. 修复相机打开点亮激光器
2. 修复相机调平

View File

@ -168,3 +168,6 @@ unix {
LIBS += -lrt
}
!isEmpty(target.path): INSTALLS += target
DISTFILES += \
Version.md

View File

@ -29,14 +29,23 @@
<!-- 多相机平面校准参数 -->
<PlaneCalibParams>
<!-- 相机1的调平参数默认未校准 -->
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="false"
planeHeight="-1.0"
planeCalib_00="1.0" planeCalib_01="0.0" planeCalib_02="0.0"
planeCalib_10="0.0" planeCalib_11="1.0" planeCalib_12="0.0"
planeCalib_20="0.0" planeCalib_21="0.0" planeCalib_22="1.0"
invRMatrix_00="1.0" invRMatrix_01="0.0" invRMatrix_02="0.0"
invRMatrix_10="0.0" invRMatrix_11="1.0" invRMatrix_12="0.0"
invRMatrix_20="0.0" invRMatrix_21="0.0" invRMatrix_22="1.0" />
<!-- <CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="true"
planeHeight="2422.07"
planeCalib_00="0.999812" planeCalib_01="0.000120007" planeCalib_02="-0.0193725"
planeCalib_10="0.000120007" planeCalib_11="0.999923" planeCalib_12="0.0123879"
planeCalib_20="0.0193725" planeCalib_21="-0.0123879" planeCalib_22="0.999736"
invRMatrix_00="0.999812" invRMatrix_01="0.000120007" invRMatrix_02="0.0193725"
invRMatrix_10="0.000120007" invRMatrix_11="0.999923" invRMatrix_12="-0.0123879"
invRMatrix_20="-0.0193725" invRMatrix_21="0.0123879" invRMatrix_22="0.999736" /> -->
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="true"
planeHeight="2259.85"
planeCalib_00="0.999967" planeCalib_01="-2.59612e-05" planeCalib_02="0.0081093"
planeCalib_10="-2.59612e-05" planeCalib_11="0.99998" planeCalib_12="0.00640265"
planeCalib_20="-0.0081093" planeCalib_21="-0.00640265" planeCalib_22="0.999947"
invRMatrix_00="0.999967" invRMatrix_01="-2.59612e-05" invRMatrix_02="-0.0081093"
invRMatrix_10="-2.59612e-05" invRMatrix_11="0.99998" invRMatrix_12="-0.00640265"
invRMatrix_20="0.0081093" invRMatrix_21="0.00640265" invRMatrix_22="0.999947" />
</PlaneCalibParams>
</AlgorithmParams>

View File

@ -12,6 +12,7 @@
#include <QtCore/QCoreApplication>
#include "VrLog.h"
#include "PathManager.h"
// 前置声明,具体定义由各应用的 IVrConfig.h 提供
class IVrConfig;
@ -145,7 +146,7 @@ public:
// 保存配置文件路径
if (configFilePath.empty()) {
QString defaultPath = QCoreApplication::applicationDirPath() + "/config.json";
QString defaultPath = PathManager::GetInstance().GetConfigFilePath();
m_configFilePath = defaultPath.toStdString();
} else {
m_configFilePath = configFilePath;

View File

@ -242,6 +242,7 @@ int BasePresenter::InitCamera(std::vector<DeviceInfo>& cameraList, bool bRGB, bo
int cameraCount = cameraList.size();
OnCameraCountChanged(cameraCount);
LOG_INFO("[BasePresenter] init eyedevice list\n");
// 初始化相机列表,预分配空间
m_vrEyeDeviceList.resize(cameraCount, std::make_pair("", nullptr));
for(int i = 0; i < cameraCount; i++)

View File

@ -42,10 +42,23 @@ bool ConfigMonitor::Start(const std::string& sharedMemName)
// 启动监控线程
m_bMonitorRunning = true;
m_monitorThread = std::thread(&ConfigMonitor::MonitorThreadFunc, this);
LOG_INFO("[ConfigMonitor] Shared memory monitor started (name: %s)\n", sharedMemName.c_str());
return true;
try {
m_monitorThread = std::thread(&ConfigMonitor::MonitorThreadFunc, this);
LOG_INFO("[ConfigMonitor] Shared memory monitor started (name: %s)\n", sharedMemName.c_str());
return true;
} catch (const std::system_error& e) {
LOG_ERROR("[ConfigMonitor] Failed to create monitor thread: %s (error code: %d)\n",
e.what(), e.code().value());
m_bMonitorRunning = false;
CleanupSharedMemory();
return false;
} catch (const std::exception& e) {
LOG_ERROR("[ConfigMonitor] Failed to create monitor thread: %s\n", e.what());
m_bMonitorRunning = false;
CleanupSharedMemory();
return false;
}
}
void ConfigMonitor::Stop()
@ -161,36 +174,42 @@ bool ConfigMonitor::InitializeSharedMemory(const std::string& sharedMemName)
return true; // 已经初始化
}
m_pShareMem = CreateShareMemInstance();
if (!m_pShareMem) {
LOG_ERROR("[ConfigMonitor] Failed to create shared memory instance\n");
return false;
}
try {
m_pShareMem = CreateShareMemInstance();
if (!m_pShareMem) {
LOG_ERROR("[ConfigMonitor] Failed to create shared memory instance\n");
return false;
}
// 尝试打开已存在的共享内存,如果不存在则创建
int ret = m_pShareMem->CreateOrOpen(sharedMemName, CONFIG_CMD_SHARED_MEM_SIZE, false);
if (ret != SUCCESS) {
// 如果打开失败,尝试创建新的
ret = m_pShareMem->CreateOrOpen(sharedMemName, CONFIG_CMD_SHARED_MEM_SIZE, true);
// 尝试打开已存在的共享内存,如果不存在则创建
int ret = m_pShareMem->CreateOrOpen(sharedMemName, CONFIG_CMD_SHARED_MEM_SIZE, false);
if (ret != SUCCESS) {
LOG_ERROR("[ConfigMonitor] Failed to create or open shared memory, error: %d\n", ret);
// 如果打开失败,尝试创建新的
ret = m_pShareMem->CreateOrOpen(sharedMemName, CONFIG_CMD_SHARED_MEM_SIZE, true);
if (ret != SUCCESS) {
LOG_ERROR("[ConfigMonitor] Failed to create or open shared memory, error: %d\n", ret);
CleanupSharedMemory();
return false;
}
}
// 映射内存
void* mappedAddr = m_pShareMem->MapView();
if (!mappedAddr) {
LOG_ERROR("[ConfigMonitor] Failed to map shared memory view\n");
CleanupSharedMemory();
return false;
}
}
// 映射内存
void* mappedAddr = m_pShareMem->MapView();
if (!mappedAddr) {
LOG_ERROR("[ConfigMonitor] Failed to map shared memory view\n");
LOG_INFO("[ConfigMonitor] Shared memory initialized successfully (name: %s, size: %zu)\n",
sharedMemName.c_str(), CONFIG_CMD_SHARED_MEM_SIZE);
return true;
} catch (const std::exception& e) {
LOG_ERROR("[ConfigMonitor] Exception in InitializeSharedMemory: %s\n", e.what());
CleanupSharedMemory();
return false;
}
LOG_INFO("[ConfigMonitor] Shared memory initialized successfully (name: %s, size: %zu)\n",
sharedMemName.c_str(), CONFIG_CMD_SHARED_MEM_SIZE);
return true;
}
void ConfigMonitor::CleanupSharedMemory()

View File

@ -2,6 +2,7 @@
#include "ui_DeviceStatusWidget.h"
#include <QPainter>
#include <QBrush>
#include <QThread>
#include "VrLog.h"
DeviceStatusWidget::DeviceStatusWidget(QWidget *parent)
@ -69,35 +70,81 @@ void DeviceStatusWidget::setRobotStatusImage(QWidget* widget, bool isOnline) {
// 设置相机1名称
void DeviceStatusWidget::setCamera1Name(const QString& name)
{
m_camera1Name = name;
// 如果相机1已经初始化过状态则更新显示文本
if (ui->dev_camera_1_txt) {
updateCamera1DisplayText(ui->dev_camera_1_txt->text().contains("在线"));
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
m_camera1Name = name;
// 如果相机1已经初始化过状态则更新显示文本
if (ui->dev_camera_1_txt) {
updateCamera1DisplayText(ui->dev_camera_1_txt->text().contains("在线"));
}
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, name]() {
m_camera1Name = name;
// 如果相机1已经初始化过状态则更新显示文本
if (ui->dev_camera_1_txt) {
updateCamera1DisplayText(ui->dev_camera_1_txt->text().contains("在线"));
}
}, Qt::QueuedConnection);
}
}
// 设置相机2名称
void DeviceStatusWidget::setCamera2Name(const QString& name)
{
m_camera2Name = name;
// 如果相机2已经初始化过状态则更新显示文本
if (ui->dev_camera_2_txt) {
updateCamera2DisplayText(ui->dev_camera_2_txt->text().contains("在线"));
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
m_camera2Name = name;
// 如果相机2已经初始化过状态则更新显示文本
if (ui->dev_camera_2_txt) {
updateCamera2DisplayText(ui->dev_camera_2_txt->text().contains("在线"));
}
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, name]() {
m_camera2Name = name;
// 如果相机2已经初始化过状态则更新显示文本
if (ui->dev_camera_2_txt) {
updateCamera2DisplayText(ui->dev_camera_2_txt->text().contains("在线"));
}
}, Qt::QueuedConnection);
}
}
// 更新相机1状态
void DeviceStatusWidget::updateCamera1Status(bool isConnected)
{
setCameraStatusImage(ui->dev_camer_1_img, isConnected);
updateCamera1DisplayText(isConnected);
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
setCameraStatusImage(ui->dev_camer_1_img, isConnected);
updateCamera1DisplayText(isConnected);
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, isConnected]() {
setCameraStatusImage(ui->dev_camer_1_img, isConnected);
updateCamera1DisplayText(isConnected);
}, Qt::QueuedConnection);
}
}
// 更新相机2状态
void DeviceStatusWidget::updateCamera2Status(bool isConnected)
{
setCameraStatusImage(ui->dev_camer_2_img, isConnected);
updateCamera2DisplayText(isConnected);
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
setCameraStatusImage(ui->dev_camer_2_img, isConnected);
updateCamera2DisplayText(isConnected);
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, isConnected]() {
setCameraStatusImage(ui->dev_camer_2_img, isConnected);
updateCamera2DisplayText(isConnected);
}, Qt::QueuedConnection);
}
}
// 更新相机1显示文本的私有方法
@ -135,50 +182,121 @@ void DeviceStatusWidget::updateCamera2DisplayText(bool isConnected)
// 更新机械臂状态
void DeviceStatusWidget::updateRobotStatus(bool isConnected)
{
setRobotStatusImage(ui->dev_robot_img, isConnected);
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
setRobotStatusImage(ui->dev_robot_img, isConnected);
QString statusText = isConnected ? "机械臂在线" : "机械臂离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
QString statusText = isConnected ? "机械臂在线" : "机械臂离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, isConnected]() {
setRobotStatusImage(ui->dev_robot_img, isConnected);
QString statusText = isConnected ? "机械臂在线" : "机械臂离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
}, Qt::QueuedConnection);
}
}
// 更新串口状态
void DeviceStatusWidget::updateSerialStatus(bool isConnected)
{
setRobotStatusImage(ui->dev_robot_img, isConnected);
QString statusText = isConnected ? "RS485在线" : "RS485离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
setRobotStatusImage(ui->dev_robot_img, isConnected);
QString statusText = isConnected ? "RS485在线" : "RS485离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
} else {
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, isConnected]() {
setRobotStatusImage(ui->dev_robot_img, isConnected);
QString statusText = isConnected ? "RS485在线" : "RS485离线";
QString colorStyle = isConnected ? "color: rgb(140, 180, 60);" : "color: rgb(220, 60, 60);";
ui->dev_robot_txt->setText(statusText);
ui->dev_robot_txt->setStyleSheet(colorStyle);
}, Qt::QueuedConnection);
}
}
// 设置相机数量(用于控制相机二的显示和布局方向)
void DeviceStatusWidget::setCameraCount(int cameraCount)
{
// 如果相机数量小于2隐藏相机二相关的UI元素
bool showCamera2 = (cameraCount >= 2);
LOG_DEBUG("setCameraCount cameraCount: %d \n", cameraCount);
LOG_DEBUG("setCameraCount: %d \n", cameraCount);
// 检查是否在主线程中
if (QThread::currentThread() == this->thread()) {
// 在主线程中,直接执行
// 如果相机数量小于2隐藏相机二相关的UI元素
bool showCamera2 = (cameraCount >= 2);
// 隐藏整个相机2的frame而不是单独隐藏图片和文字
if (ui->frame_camera_2) {
ui->frame_camera_2->setVisible(showCamera2);
LOG_DEBUG("showCamera2: %d \n", showCamera2);
// 隐藏整个相机2的frame而不是单独隐藏图片和文字
if (ui->frame_camera_2) {
ui->frame_camera_2->setVisible(showCamera2);
} else {
// 如果frame_camera_2不存在则单独隐藏图片和文字控件
if (ui->dev_camer_2_img) {
ui->dev_camer_2_img->setVisible(showCamera2);
}
if (ui->dev_camera_2_txt) {
ui->dev_camera_2_txt->setVisible(showCamera2);
}
}
LOG_DEBUG("change status \n");
// 根据相机数量选择布局方式
if (cameraCount >= 2) {
// 多相机:纵向排列
setVerticalLayout();
} else {
// 单相机:横向排列,相机和机械臂各占一半
setHorizontalLayoutForSingleCamera();
}
LOG_DEBUG("change layout \n");
} else {
// 如果frame_camera_2不存在则单独隐藏图片和文字控件
ui->dev_camer_2_img->setVisible(showCamera2);
ui->dev_camera_2_txt->setVisible(showCamera2);
}
// 在其他线程中,使用队列方式确保线程安全
QMetaObject::invokeMethod(this, [this, cameraCount]() {
// 如果相机数量小于2隐藏相机二相关的UI元素
bool showCamera2 = (cameraCount >= 2);
// 根据相机数量选择布局方式
if (cameraCount >= 2) {
// 多相机:纵向排列
setVerticalLayout();
} else {
// 单相机:横向排列,相机和机械臂各占一半
setHorizontalLayoutForSingleCamera();
LOG_DEBUG("showCamera2: %d \n", showCamera2);
// 隐藏整个相机2的frame而不是单独隐藏图片和文字
if (ui->frame_camera_2) {
ui->frame_camera_2->setVisible(showCamera2);
} else {
// 如果frame_camera_2不存在则单独隐藏图片和文字控件
if (ui->dev_camer_2_img) {
ui->dev_camer_2_img->setVisible(showCamera2);
}
if (ui->dev_camera_2_txt) {
ui->dev_camera_2_txt->setVisible(showCamera2);
}
}
LOG_DEBUG("change status \n");
// 根据相机数量选择布局方式
if (cameraCount >= 2) {
// 多相机:纵向排列
setVerticalLayout();
} else {
// 单相机:横向排列,相机和机械臂各占一半
setHorizontalLayoutForSingleCamera();
}
LOG_DEBUG("change layout \n");
}, Qt::QueuedConnection);
}
}

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "BeltTearingApp"
#define MyAppVersion "2.0.5.1"
#define MyAppVersion "2.0.5.2"
#define MyAppPublisher ""
#define MyAppURL ""
#define MyAppExeName "BeltTearingApp.exe"

View File

@ -26,4 +26,5 @@ SUBDIRS += ../App/App.pro
# App 依赖 AppUtils(确保 AppCommonCloudUtils 等先构建)
App.depends = AppUtils
App.depends = Device
App.depends = VrUtils

View File

@ -329,7 +329,7 @@ void ModbusTCPServer::processModbusRequest(std::shared_ptr<ClientConnection> cli
}
// 根据功能码验证数据格式并执行回调
LOG_DEBUG("Modbus %02X - Trans:%d Unit:%d\n", function, transactionId, unitId);
// LOG_DEBUG("Modbus %02X - Trans:%d Unit:%d\n", function, transactionId, unitId);
switch (function) {
case MODBUS_FC_WRITE_SINGLE_COIL:
@ -420,8 +420,6 @@ void ModbusTCPServer::processModbusRequest(std::shared_ptr<ClientConnection> cli
break;
}
LOG_DEBUG("exceptionCode: %d\n", exceptionCode);
// 统一处理响应
if (exceptionCode != 0) {
modbus_reply_exception(client->modbusCtx, query, exceptionCode);

View File

@ -81,6 +81,27 @@ void vzReadLaserScanPointFromFile_XYZ_vector(const char* fileName, std::vector<s
return;
}
void wdSavePlyTxt(const char* fileName, std::vector<std::vector< SVzNL3DPosition>> scanLines)
{
std::ofstream sw(fileName);
int lineNum = scanLines.size();
for (int line = 0; line < lineNum; line++)
{
int nPositionCnt = scanLines[line].size();
for (int i = 0; i < nPositionCnt; i++)
{
SVzNL3DPoint* pt3D = &scanLines[line][i].pt3D;
if (pt3D->z < 1e-4)
continue;
double x = (double)pt3D->x;
double y = (double)pt3D->y;
double z = (double)pt3D->z;
sw << x << "," << y << "," << z << std::endl;
}
}
sw.close();
}
void _convertToGridData_XYZ_vector(std::vector<std::vector< SVzNL3DPosition>>& scanData, double _F, std::vector<std::vector< SVzNL3DPosition>>& scanData_grid)
{
int min_y = 100000000;
@ -211,59 +232,77 @@ void _outputCalibPara(char* fileName, SSG_planeCalibPara calibPara)
void _outputCornerInfo(char* fileName, SSX_BQworkpieceResult workpieceCorner)
{
std::ofstream sw(fileName);
sw << "节点" << workpieceCorner.workpieceType << std::endl << std::endl;
char dataStr[250];
sw << "L:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_L[0].x, workpieceCorner.corner_L[0].y, workpieceCorner.corner_L[0].z);
sw << "A:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_1[0].x, workpieceCorner.corner_1[0].y, workpieceCorner.corner_1[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_L[1].x, workpieceCorner.corner_L[1].y, workpieceCorner.corner_L[1].z);
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_1[1].x, workpieceCorner.corner_1[1].y, workpieceCorner.corner_1[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_L[2].x, workpieceCorner.corner_L[2].y, workpieceCorner.corner_L[2].z);
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_1[2].x, workpieceCorner.corner_1[2].y, workpieceCorner.corner_1[2].z);
sw << dataStr << std::endl;
double dist = sqrt(pow(workpieceCorner.corner_L[0].x - workpieceCorner.corner_L[2].x, 2) +
pow(workpieceCorner.corner_L[0].y - workpieceCorner.corner_L[2].y, 2) +
pow(workpieceCorner.corner_L[0].z - workpieceCorner.corner_L[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
sw << "T:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_T[0].x, workpieceCorner.corner_T[0].y, workpieceCorner.corner_T[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_T[1].x, workpieceCorner.corner_T[1].y, workpieceCorner.corner_T[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_T[2].x, workpieceCorner.corner_T[2].y, workpieceCorner.corner_T[2].z);
sw << dataStr << std::endl;
dist = sqrt(pow(workpieceCorner.corner_T[0].x - workpieceCorner.corner_T[2].x, 2) +
pow(workpieceCorner.corner_T[0].y - workpieceCorner.corner_T[2].y, 2) +
pow(workpieceCorner.corner_T[0].z - workpieceCorner.corner_T[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
sw << "R:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_R[0].x, workpieceCorner.corner_R[0].y, workpieceCorner.corner_R[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_R[1].x, workpieceCorner.corner_R[1].y, workpieceCorner.corner_R[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_R[2].x, workpieceCorner.corner_R[2].y, workpieceCorner.corner_R[2].z);
sw << dataStr << std::endl;
dist = sqrt(pow(workpieceCorner.corner_R[0].x - workpieceCorner.corner_R[2].x, 2) +
pow(workpieceCorner.corner_R[0].y - workpieceCorner.corner_R[2].y, 2) +
pow(workpieceCorner.corner_R[0].z - workpieceCorner.corner_R[2].z, 2));
double dist = sqrt(pow(workpieceCorner.corner_1[0].x - workpieceCorner.corner_1[2].x, 2) +
pow(workpieceCorner.corner_1[0].y - workpieceCorner.corner_1[2].y, 2) +
pow(workpieceCorner.corner_1[0].z - workpieceCorner.corner_1[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
sw << "B:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_B[0].x, workpieceCorner.corner_B[0].y, workpieceCorner.corner_B[0].z);
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_2[0].x, workpieceCorner.corner_2[0].y, workpieceCorner.corner_3[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_B[1].x, workpieceCorner.corner_B[1].y, workpieceCorner.corner_B[1].z);
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_2[1].x, workpieceCorner.corner_2[1].y, workpieceCorner.corner_3[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_B[2].x, workpieceCorner.corner_B[2].y, workpieceCorner.corner_B[2].z);
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_2[2].x, workpieceCorner.corner_2[2].y, workpieceCorner.corner_3[2].z);
sw << dataStr << std::endl;
dist = sqrt(pow(workpieceCorner.corner_B[0].x - workpieceCorner.corner_B[2].x, 2) +
pow(workpieceCorner.corner_B[0].y - workpieceCorner.corner_B[2].y, 2) +
pow(workpieceCorner.corner_B[0].z - workpieceCorner.corner_B[2].z, 2));
dist = sqrt(pow(workpieceCorner.corner_2[0].x - workpieceCorner.corner_2[2].x, 2) +
pow(workpieceCorner.corner_2[0].y - workpieceCorner.corner_2[2].y, 2) +
pow(workpieceCorner.corner_2[0].z - workpieceCorner.corner_2[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
if (workpieceCorner.workpieceType != 3)
{
sw << "C:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_3[0].x, workpieceCorner.corner_3[0].y, workpieceCorner.corner_3[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_3[1].x, workpieceCorner.corner_3[1].y, workpieceCorner.corner_3[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_3[2].x, workpieceCorner.corner_3[2].y, workpieceCorner.corner_3[2].z);
sw << dataStr << std::endl;
dist = sqrt(pow(workpieceCorner.corner_3[0].x - workpieceCorner.corner_3[2].x, 2) +
pow(workpieceCorner.corner_3[0].y - workpieceCorner.corner_3[2].y, 2) +
pow(workpieceCorner.corner_3[0].z - workpieceCorner.corner_3[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
}
if ((workpieceCorner.workpieceType == 1) || (workpieceCorner.workpieceType == 4))
{
sw << "D:" << std::endl;
sprintf_s(dataStr, 250, " corner_0: (%g, %g, %g)", workpieceCorner.corner_4[0].x, workpieceCorner.corner_4[0].y, workpieceCorner.corner_4[0].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_1: (%g, %g, %g)", workpieceCorner.corner_4[1].x, workpieceCorner.corner_4[1].y, workpieceCorner.corner_4[1].z);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, " corner_2: (%g, %g, %g)", workpieceCorner.corner_4[2].x, workpieceCorner.corner_4[2].y, workpieceCorner.corner_4[2].z);
sw << dataStr << std::endl;
dist = sqrt(pow(workpieceCorner.corner_4[0].x - workpieceCorner.corner_4[2].x, 2) +
pow(workpieceCorner.corner_4[0].y - workpieceCorner.corner_4[2].y, 2) +
pow(workpieceCorner.corner_4[0].z - workpieceCorner.corner_4[2].z, 2));
sprintf_s(dataStr, 250, " Len: %g", dist);
sw << dataStr << std::endl;
}
sw << std::endl;
sprintf_s(dataStr, 250, "len_A1 : %g", workpieceCorner.len135_A1);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, "len_A2 : %g", workpieceCorner.len225_A2);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, "len_B2 : %g", workpieceCorner.len315_B2);
sw << dataStr << std::endl;
sprintf_s(dataStr, 250, "len_B1 : %g", workpieceCorner.len45_B1);
sw << dataStr << std::endl;
sw.close();
}
@ -374,14 +413,14 @@ void _outputRGBDScanLapWeld_RGBD(
std::vector<std::vector<SVzNL3DPosition>>& scanLines,
SSX_BQworkpieceResult workpieceCorner,
bool outDebugInfo,
SSX_debugInfo* debugData)
std::vector<SSX_debugInfo>& debugData)
{
int lineNum = (int)scanLines.size();
std::ofstream sw(fileName);
int realLines = lineNum;
if (workpieceCorner.workpieceType > 0)
realLines++;
if(debugData)
if(debugData.size() > 0)
realLines++;
sw << "LineNum:" << realLines << std::endl;
sw << "DataType: 0" << std::endl;
@ -424,26 +463,11 @@ void _outputRGBDScanLapWeld_RGBD(
featureType_v &= 0x0f;
if (true == outDebugInfo)
{
if (LINE_FEATURE_L_JUMP_H2L == featureType_v)
if (pt3D->nPointIdx < 0)
{
rgb = { 255, 97, 0 };
size = 5;
}
else if (LINE_FEATURE_L_JUMP_L2H == featureType_v)
{
rgb = objColor[7];
size = 5;
}
else if (LINE_FEATURE_L_JUMP_H2L == featureType_h)
{
rgb = objColor[6];
size = 5;
}
else if (LINE_FEATURE_L_JUMP_L2H == featureType_h)
{
rgb = { 97, 255, 0 };
size = 5;
}
else
{
rgb = { 200, 200, 200 };
@ -466,24 +490,29 @@ void _outputRGBDScanLapWeld_RGBD(
if (workpieceCorner.workpieceType > 0)
{
int linePtNum = 12;
std::vector<SVzNL3DPoint> ptBuffer;
for (int i = 0; i < 3; i++)
ptBuffer.push_back(workpieceCorner.corner_1[i]);
for (int i = 0; i < 3; i++)
ptBuffer.push_back(workpieceCorner.corner_2[i]);
if (workpieceCorner.workpieceType != 3)
{
for (int i = 0; i < 3; i++)
ptBuffer.push_back(workpieceCorner.corner_3[i]);
}
if ((workpieceCorner.workpieceType == 1) || (workpieceCorner.workpieceType == 4))
{
for (int i = 0; i < 3; i++)
ptBuffer.push_back(workpieceCorner.corner_4[i]);
}
ptBuffer.push_back(workpieceCorner.center);
int linePtNum = (int)ptBuffer.size();
sw << "Line_" << lineNum << "_0_" << linePtNum + 1 << std::endl;
lineNum++;
SVzNL3DPoint ptBuffer[12];
int idx = 0;
for (int i = 0; i < 3; i++)
ptBuffer[idx++] = workpieceCorner.corner_L[i];
for (int i = 0; i < 3; i++)
ptBuffer[idx++] = workpieceCorner.corner_R[i];
for (int i = 0; i < 3; i++)
ptBuffer[idx++] = workpieceCorner.corner_T[i];
for (int i = 0; i < 3; i++)
ptBuffer[idx++] = workpieceCorner.corner_B[i];
rgb = { 255, 0, 0 };
size = 15;
for (int j = 0; j < 12; j++)
for (int j = 0; j < linePtNum; j++)
{
float x = (float)ptBuffer[j].x;
float y = (float)ptBuffer[j].y;
@ -501,85 +530,99 @@ void _outputRGBDScanLapWeld_RGBD(
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
if (debugData)
if (workpieceCorner.workpieceType > 0)
{
int linePtNum = debugData[0].edge_size + debugData[0].edgeLink1_size + debugData[0].edgeLink2_size;
linePtNum += debugData[1].edge_size + debugData[1].edgeLink1_size + debugData[1].edgeLink2_size;
linePtNum += debugData[2].edge_size + debugData[2].edgeLink1_size + debugData[2].edgeLink2_size;
linePtNum += debugData[3].edge_size + debugData[3].edgeLink1_size + debugData[3].edgeLink2_size;
sw << "Line_" << lineNum << "_0_" << linePtNum + 1 << std::endl;
lineNum++;
std::vector< SWD_3DPointPair> lines;
SWD_3DPointPair a_line;
a_line.pt1 = workpieceCorner.corner_1[0];
a_line.pt2 = workpieceCorner.corner_1[2];
lines.push_back(a_line);
a_line.pt1 = workpieceCorner.corner_1[1];
a_line.pt2 = workpieceCorner.center;
lines.push_back(a_line);
rgb = { 255, 0, 0 };
size = 3;
for (int i = 0; i < 4; i++)
a_line.pt1 = workpieceCorner.corner_2[0];
a_line.pt2 = workpieceCorner.corner_2[2];
lines.push_back(a_line);
a_line.pt1 = workpieceCorner.corner_2[1];
a_line.pt2 = workpieceCorner.center;
lines.push_back(a_line);
if (workpieceCorner.workpieceType != 3)
{
for (int j = 0; j < debugData[i].edge_size; j++)
{
float x = (float)debugData[i].edge[j].x;
float y = (float)debugData[i].edge[j].y;
float z = (float)debugData[i].edge[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
for (int j = 0; j < debugData[i].edgeLink1_size; j++)
{
float x = (float)debugData[i].edgeLink_1[j].x;
float y = (float)debugData[i].edgeLink_1[j].y;
float z = (float)debugData[i].edgeLink_1[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
for (int j = 0; j < debugData[i].edgeLink2_size; j++)
{
float x = (float)debugData[i].edgeLink_2[j].x;
float y = (float)debugData[i].edgeLink_2[j].y;
float z = (float)debugData[i].edgeLink_2[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
a_line.pt1 = workpieceCorner.corner_3[0];
a_line.pt2 = workpieceCorner.corner_3[2];
lines.push_back(a_line);
a_line.pt1 = workpieceCorner.corner_3[1];
a_line.pt2 = workpieceCorner.center;
lines.push_back(a_line);
}
if ((workpieceCorner.workpieceType == 1) || (workpieceCorner.workpieceType == 4))
{
a_line.pt1 = workpieceCorner.corner_4[0];
a_line.pt2 = workpieceCorner.corner_4[2];
lines.push_back(a_line);
a_line.pt1 = workpieceCorner.corner_4[1];
a_line.pt2 = workpieceCorner.center;
lines.push_back(a_line);
}
//加一个点用于跳过显示工具bug
float x = (float)debugData[0].edge[0].x;
float y = (float)debugData[0].edge[0].y;
float z = (float)debugData[0].edge[0].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
if (debugData.size() > 0)
{
int linePtNum = 0;
for (int i = 0; i < (int)debugData.size(); i++)
linePtNum += debugData[i].edge_size + debugData[i].edgeLink1_size + debugData[i].edgeLink2_size;
sw << "Line_" << lineNum << "_0_" << linePtNum + 1 << std::endl;
lineNum++;
rgb = { 255, 0, 0 };
size = 3;
for (int i = 0; i < (int)debugData.size(); i++)
{
for (int j = 0; j < debugData[i].edge_size; j++)
{
float x = (float)debugData[i].edge[j].x;
float y = (float)debugData[i].edge[j].y;
float z = (float)debugData[i].edge[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
for (int j = 0; j < debugData[i].edgeLink1_size; j++)
{
float x = (float)debugData[i].edgeLink_1[j].x;
float y = (float)debugData[i].edgeLink_1[j].y;
float z = (float)debugData[i].edgeLink_1[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
for (int j = 0; j < debugData[i].edgeLink2_size; j++)
{
float x = (float)debugData[i].edgeLink_2[j].x;
float y = (float)debugData[i].edgeLink_2[j].y;
float z = (float)debugData[i].edgeLink_2[j].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
}
//加一个点用于跳过显示工具bug
float x = (float)debugData[0].edge[0].x;
float y = (float)debugData[0].edge[0].y;
float z = (float)debugData[0].edge[0].z;
sw << "{" << x << "," << y << "," << z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl;
}
//显示拟合直线
rgb = { 255, 0, 0 };
size = 3;
int lineIdx = 0;
for (int i = 0; i < 4; i++)
for (int i = 0; i < (int)lines.size(); i++)
{
SVzNL3DPoint pt0 = debugData[i].edge_ends[0];
SVzNL3DPoint pt1 = debugData[i].edge_ends[1];
sw << "Poly_" << lineIdx << "_2" << std::endl;
sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
sw << "{" << pt1.x << "," << pt1.y << "," << pt1.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
lineIdx++;
pt0 = debugData[i].edge_link1_ends[0];
pt1 = debugData[i].edge_link1_ends[1];
sw << "Poly_" << lineIdx << "_2" << std::endl;
sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
sw << "{" << pt1.x << "," << pt1.y << "," << pt1.z << "}-";
sw << "{0,0}-{0,0}-";
sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl;
lineIdx++;
pt0 = debugData[i].edge_link2_ends[0];
pt1 = debugData[i].edge_link2_ends[1];
SVzNL3DPoint pt0 = lines[i].pt1;
SVzNL3DPoint pt1 = lines[i].pt2;
sw << "Poly_" << lineIdx << "_2" << std::endl;
sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-";
sw << "{0,0}-{0,0}-";
@ -590,8 +633,8 @@ void _outputRGBDScanLapWeld_RGBD(
lineIdx++;
}
//加一个直线用于跳过显示工具bug
SVzNL3DPoint pt0 = debugData[0].edge_ends[0];
SVzNL3DPoint pt1 = debugData[0].edge_ends[1];
SVzNL3DPoint pt0 = lines[0].pt1;
SVzNL3DPoint pt1 = lines[0].pt2;
sw << "Poly_" << lineIdx << "_2" << std::endl;
sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-";
sw << "{0,0}-{0,0}-";
@ -606,15 +649,21 @@ void _outputRGBDScanLapWeld_RGBD(
#define CONVERT_TO_GRID 0
#define TEST_COMPUTE_CALIB_PARA 0
#define TEST_COMPUTE_CORNER 1
#define TEST_GROUP 1
#define TEST_GROUP 7
int main()
{
const char* dataPath[TEST_GROUP] = {
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\" //0
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\", //0
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\工件点云\\角度1\\", //1
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\工件点云\\角度2\\", //2
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\工件点云\\角度3\\", //3
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\工件点云\\角度4\\", //4
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\工件点云\\", //5
"F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\顶板样式\\", //6
};
SVzNLRange fileIdx[TEST_GROUP] = {
{1,3}
{1,3}, {6,8}, {6,8}, {6,8}, {6,8}, {9,11}, {2,4}
};
#if CONVERT_TO_GRID
@ -641,7 +690,7 @@ int main()
#if TEST_COMPUTE_CALIB_PARA
char _calib_datafile[256];
sprintf_s(_calib_datafile, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\scanData_1.txt");
sprintf_s(_calib_datafile, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\顶板样式\\节点4.txt");
int lineNum = 0;
float lineV = 0.0f;
int dataCalib = 0;
@ -677,10 +726,10 @@ int main()
}
//
char calibFile[250];
sprintf_s(calibFile, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\ground_calib_para.txt");
sprintf_s(calibFile, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\顶板样式\\ground_calib_para.txt");
_outputCalibPara(calibFile, calibPara);
char _out_file[256];
sprintf_s(_out_file, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\scanData_ground_1_calib.txt");
sprintf_s(_out_file, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\顶板样式\\scanData_ground_1_calib.txt");
int headNullLines = 0;
_outputScanDataFile_vector(_out_file, scanData, false, &headNullLines);
printf("%s: calib done!\n", _calib_datafile);
@ -688,7 +737,10 @@ int main()
#endif
#if TEST_COMPUTE_CORNER
for (int grp = 0; grp <= 0; grp++)
const char* ver = wd_BQWorkpieceCornerVersion();
printf("ver:%s\n", ver);
for (int grp = 5; grp <= 6; grp++)
{
SSG_planeCalibPara poseCalibPara;
//初始化成单位阵
@ -705,20 +757,26 @@ int main()
for (int i = 0; i < 9; i++)
poseCalibPara.invRMatrix[i] = poseCalibPara.planeCalib[i];
char calibFile[250];
if (grp == 0)
{
sprintf_s(calibFile, "F:\\ShangGu\\项目\\冠钦_博清科技\\数据\\ground_calib_para.txt");
poseCalibPara = _readCalibPara(calibFile);
}
sprintf_s(calibFile, "%sground_calib_para.txt", dataPath[grp]);
poseCalibPara = _readCalibPara(calibFile);
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
{
//fidx =1;
//fidx =4;
char _scan_file[256];
sprintf_s(_scan_file, "%sscanData_%d_grid.txt", dataPath[grp], fidx);
if(0 == grp)
sprintf_s(_scan_file, "%sscanData_%d_grid.txt", dataPath[grp], fidx);
else if(6 == grp)
sprintf_s(_scan_file, "%s节点%d.txt", dataPath[grp], fidx);
else
sprintf_s(_scan_file, "%s%d_LazerData_Hi229229.txt", dataPath[grp], fidx);
std::vector<std::vector< SVzNL3DPosition>> scanLines;
vzReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines);
//转成plyTxt格式
//sprintf_s(_scan_file, "%s%d_ply_Hi229229.txt", dataPath[grp], fidx);
//wdSavePlyTxt(_scan_file, scanLines);
long t1 = (long)GetTickCount64();//统计时间
for (int i = 0, i_max = (int)scanLines.size(); i < i_max; i++)
@ -727,7 +785,15 @@ int main()
int kkk = 1;
//行处理
//调平,去除地面
sx_BQ_lineDataR(scanLines[i], poseCalibPara.planeCalib, -1);
double cuttingZ = -1;
if (6 == grp)
{
if (2 == fidx)
cuttingZ = 1760.0;
else if(3 == fidx)
cuttingZ = 1780.0;
}
sx_BQ_lineDataR(scanLines[i], poseCalibPara.planeCalib, cuttingZ);
}
#if 0
char _out_file[256];
@ -754,9 +820,9 @@ int main()
growParam.minLTypeTreeLen = 100; //mm
growParam.minVTypeTreeLen = 100; //mm
SSX_BQworkpiecePara workpieceParam;
workpieceParam.lineLen = 180.0; //直线段长度
workpieceParam.lineLen = 160.0; //直线段长度
int errCode = 0;
SSX_debugInfo debug_conturs[4];
std::vector<SSX_debugInfo> debug_conturs;
SSX_BQworkpieceResult workpieceCorner = sx_BQ_getWorkpieceCorners(
scanLines,
cornerParam,
@ -772,11 +838,7 @@ int main()
printf("%s: %d(ms)!\n", _scan_file, (int)(t2 - t1));
//输出测试结果
sprintf_s(_scan_file, "%sresult\\LaserLine%d_result.txt", dataPath[grp], fidx);
#if _OUTPUT_DEBUG_DATA
_outputRGBDScanLapWeld_RGBD(_scan_file, scanLines, workpieceCorner, false, debug_conturs);
#else
_outputRGBDScanLapWeld_RGBD(_scan_file, scanLines, workpieceCorner, true, NULL);
#endif
sprintf_s(calibFile, "%sresult\\LaserLine%d_corner_info.txt", dataPath[grp], fidx);
_outputCornerInfo(calibFile, workpieceCorner);
}

View File

@ -1,21 +1,6 @@
#pragma once
#if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# define Q_DECL_EXPORT __declspec(dllexport)
# define Q_DECL_IMPORT __declspec(dllimport)
#else
# define Q_DECL_EXPORT __attribute__((visibility("default")))
# define Q_DECL_IMPORT __attribute__((visibility("default")))
#endif
#if defined(SG_API_LIBRARY)
# define SG_WORKPIECESHARED_EXPORT Q_DECL_EXPORT
#else
# define SG_WORKPIECESHARED_EXPORT Q_DECL_IMPORT
#endif
#include "SG_baseDataType.h"
#include "SG_algo_Export.h"
#include <vector>
#define _OUTPUT_DEBUG_DATA 1
@ -27,44 +12,47 @@ typedef struct
typedef struct
{
int workpieceType;
SVzNL3DPoint corner_L[3];
SVzNL3DPoint corner_R[3];
SVzNL3DPoint corner_T[3];
SVzNL3DPoint corner_B[3];
int workpieceType; //1-节点1 2-节点2 3-节点34-节点40-未知; 其它-非法
//1,2,3,4为逆时针
SVzNL3DPoint corner_1[3];
SVzNL3DPoint corner_2[3];
SVzNL3DPoint corner_3[3];
SVzNL3DPoint corner_4[3];
SVzNL3DPoint center; //工件中心点
double len135_A1; //A1长度从工件中心135度方向
double len45_B1; //B1长度从工件中心45度方向
double len315_B2; //B2长度从工件中心315度方向
double len225_A2; //A2长度从工件中心225度方向
}SSX_BQworkpieceResult;
typedef struct
{
int rgnIdx;
SVzNL3DPoint* edge;
int edge_size;
SVzNL3DPoint edge_ends[2];
SVzNL3DPoint* edgeLink_1;
int edgeLink1_size;
SVzNL3DPoint edge_link1_ends[2];
SVzNL3DPoint* edgeLink_2;
int edgeLink2_size;
SVzNL3DPoint edge_link2_ends[2];
SVzNL3DPoint* edge;
SVzNL3DPoint* edgeLink_1;
SVzNL3DPoint* edgeLink_2;
}SSX_debugInfo;
//读版本号
SG_WORKPIECESHARED_EXPORT const char* wd_BQWorkpieceCornerVersion(void);
SG_APISHARED_EXPORT const char* wd_BQWorkpieceCornerVersion(void);
//计算一个平面调平参数。
//数据输入中可以有一个地平面和参考调平平面,以最高的平面进行调平
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
SG_WORKPIECESHARED_EXPORT SSG_planeCalibPara sx_BQ_getBaseCalibPara(
SG_APISHARED_EXPORT SSG_planeCalibPara sx_BQ_getBaseCalibPara(
std::vector< std::vector<SVzNL3DPosition>>& scanLines);
//相机姿态调平,并去除地面
SG_WORKPIECESHARED_EXPORT void sx_BQ_lineDataR(
SG_APISHARED_EXPORT void sx_BQ_lineDataR(
std::vector< SVzNL3DPosition>& a_line,
const double* camPoseR,
double groundH);
//ÌáÈ¡´î½Óº¸·ì
SG_WORKPIECESHARED_EXPORT SSX_BQworkpieceResult sx_BQ_getWorkpieceCorners(
//提取工件角点及定位长度信息
SG_APISHARED_EXPORT SSX_BQworkpieceResult sx_BQ_getWorkpieceCorners(
std::vector< std::vector<SVzNL3DPosition>>& scanLines,
const SSG_cornerParam cornerPara,
const SSG_outlierFilterParam filterParam,
@ -72,6 +60,6 @@ SG_WORKPIECESHARED_EXPORT SSX_BQworkpieceResult sx_BQ_getWorkpieceCorners(
SSG_planeCalibPara groundCalibPara,
SSX_BQworkpiecePara workpieceParam,
#if _OUTPUT_DEBUG_DATA
SSX_debugInfo* debug_conturs,
std::vector<SSX_debugInfo>& debug_contours,
#endif
int* errCode);

View File

@ -0,0 +1,22 @@
#pragma once
#if defined(_MSC_VER) || defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
# define Q_DECL_EXPORT __declspec(dllexport)
# define Q_DECL_IMPORT __declspec(dllimport)
#else
# define Q_DECL_EXPORT __attribute__((visibility("default")))
# define Q_DECL_IMPORT __attribute__((visibility("default")))
#endif
#if defined(SG_API_LIBRARY)
# define SG_APISHARED_EXPORT Q_DECL_EXPORT
#else
# define SG_APISHARED_EXPORT Q_DECL_IMPORT
#endif
#include "SG_baseDataType.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846 // pi
#endif // !M_PI

View File

@ -177,6 +177,15 @@ typedef struct
double valleyMaxW;
}SSG_VFeatureParam;
typedef struct
{
int lineIdx;
int startPtIdx;
int endPtIdx;
SVzNL3DPoint startPt;
SVzNL3DPoint endPt;
}SWD_segFeature;
typedef struct
{
double sameGapTh;
@ -251,6 +260,15 @@ typedef struct
std::vector< SSG_featureSemiCircle> treeNodes;
}SSG_semiCircleFeatureTree;
typedef struct
{
int treeState;
int sLineIdx;
int eLineIdx;
double tree_value;
std::vector< SWD_segFeature> treeNodes;
}SWD_segFeatureTree;
typedef struct
{
int vTreeFlag;
@ -347,10 +365,16 @@ typedef struct
typedef struct
{
SVzNL3DPoint opCenter; //定子中心位置
int neighbourNum; //相邻目标数量
double opAngle; //定子外部相隔120度操作点的操作角度。以Y形为基准角度0度)
double obstacleDist; //距离周边障碍的距离
}SSG_motorStatorPosition;
double objR;
}SWD_motorStatorPosition;
typedef struct
{
int objID;
double grasperAngle;
double rotateAngle; //抓手在0度位置时为丫字形间隔120度。
double graspR;
}SWD_statorOuterGrasper;
typedef struct
{
@ -447,3 +471,16 @@ typedef struct {
std::vector<std::vector<int>> gray; // 灰度图
std::vector<std::vector<int>> markers; // 标记图(-1表示分水岭0表示未标记>0表示区域
}SWD_waterShedImage;
//查科二维码Mark
typedef struct
{
int markID;
SVzNL3DPoint mark3D;
}SWD_charuco3DMark;
typedef struct
{
SVzNL3DPoint pt1;
SVzNL3DPoint pt2;
}SWD_3DPointPair;

View File

@ -6,6 +6,7 @@
#define SG_ERR_NOT_GRID_FORMAT -1003
#define SG_ERR_LABEL_INFO_ERROR -1004
#define SG_ERR_INVLD_SORTING_MODE -1005
#define SG_ERR_INVLD_Q_SCALE -1006
//BQ_workpiece
#define SX_ERR_INVLD_VTREE_NUM -2001
@ -14,4 +15,8 @@
#define SX_ERR_INVLD_CLOSES_PT -2004
#define SX_ERR_ZERO_CONTOUR_PT -2005
#define SX_ERR_INVLID_RPEAK_NUM -2006
#define SX_ERR_INVLID_RPEAK_PAIR -2007
#define SX_ERR_INVLID_RPEAK_PAIR -2007
//¶¨×Óץȡ
#define SX_ERR_INVLID_CUTTING_Z -2101
#define SX_ERR_ZERO_OBJ -2102