673 lines
20 KiB
Markdown
673 lines
20 KiB
Markdown
|
|
# 工件角点检测功能 - 完整实现代码
|
|||
|
|
|
|||
|
|
## 已完成的工作 ✅
|
|||
|
|
|
|||
|
|
### 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. 常见问题排查
|
|||
|
|
|
|||
|
|
#### 问题1:SDK库找不到
|
|||
|
|
**症状**:编译或运行时提示找不到 `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通信完善。参考本文档中的代码示例即可快速完成剩余实现。
|