GrabBag/App/Workpiece/Doc/工件角点检测完整实现代码.md

673 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 工件角点检测功能 - 完整实现代码
## 已完成的工作 ✅
### 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通信完善。参考本文档中的代码示例即可快速完成剩余实现。