Merge branch 'master' of http://gitea.mnutil.com/YangOne/GrabBag
This commit is contained in:
commit
588b42932d
@ -8,11 +8,22 @@ ParkingSpaceGuide 使用一台平面相机、一台雷达和一块显示屏完
|
||||
- 雷达:`Device/RsLidarDevice`
|
||||
- 显示屏:`Device/LedDisplayDevice`
|
||||
- 组播:`Nets/UDPClient`
|
||||
- 2D 机型识别:`AppAlgo/AAPGS_model`(RK3588 Linux AArch64)
|
||||
|
||||
算法入口当前预留在:
|
||||
算法入口位于:
|
||||
|
||||
`ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp`
|
||||
|
||||
机型识别由雷达算法按距离触发,相机软触发取图后调用 AAPGS 分类模型,
|
||||
当前支持 `A320` 和 `B737`。应用默认从可执行文件同级的
|
||||
`AAPGS_model/models/model.yaml` 加载模型;也可通过环境变量覆盖:
|
||||
|
||||
- `AAPGS_MODEL_CONFIG`:模型 YAML 的完整路径
|
||||
- `AAPGS_MODEL_ROOT`:AAPGS SDK 根目录
|
||||
- `AAPGS_RUNTIME_LIBRARY`:`libyolo_runtime.so.2` 的完整路径
|
||||
|
||||
Windows 构建保留界面与业务流程,但不执行仅支持 RK3588 的 NPU 推理。
|
||||
|
||||
文档:
|
||||
|
||||
- 详细设计:`详细设计.md`
|
||||
|
||||
@ -37,6 +37,9 @@ INCLUDEPATH += $$PWD/../../../Nets/UDPClient/Inc
|
||||
INCLUDEPATH += $$PWD/../../../Utils/VrCommon/Inc
|
||||
INCLUDEPATH += $$PWD/../../../Utils/VrUtils/Inc
|
||||
|
||||
AAPGS_ROOT = $$clean_path($$PWD/../../../AppAlgo/AAPGS_model)
|
||||
INCLUDEPATH += $$AAPGS_ROOT/include
|
||||
|
||||
win32 {
|
||||
POCO_ROOT = $$PWD/../../../SDK/poco/Windows
|
||||
} else:unix:!macx {
|
||||
@ -61,6 +64,7 @@ win32 {
|
||||
SOURCES += \
|
||||
Presenter/Src/ConfigManager.cpp \
|
||||
Presenter/Src/ParkingSpaceGuidePresenter.cpp \
|
||||
Presenter/Src/AapgsModelClassifier.cpp \
|
||||
Presenter/Src/DetectPresenter.cpp \
|
||||
Presenter/Src/ParkingStatusHttpServer.cpp \
|
||||
main.cpp \
|
||||
@ -70,6 +74,7 @@ SOURCES += \
|
||||
HEADERS += \
|
||||
Presenter/Inc/ConfigManager.h \
|
||||
Presenter/Inc/ParkingSpaceGuidePresenter.h \
|
||||
Presenter/Inc/AapgsModelClassifier.h \
|
||||
Presenter/Inc/DetectPresenter.h \
|
||||
Presenter/Inc/ParkingStatusHttpServer.h \
|
||||
mainwindow.h \
|
||||
@ -156,6 +161,20 @@ win32 {
|
||||
|
||||
unix {
|
||||
target.path = /usr/lib
|
||||
|
||||
aapgsRuntime.path = /usr/lib/AAPGS_model/lib
|
||||
aapgsRuntime.files = \
|
||||
$$AAPGS_ROOT/lib/libyolo_runtime.so.2 \
|
||||
$$AAPGS_ROOT/lib/libyolo_backend_cls.so \
|
||||
$$AAPGS_ROOT/lib/librknnrt.so \
|
||||
$$AAPGS_ROOT/lib/librga.so
|
||||
|
||||
aapgsModels.path = /usr/lib/AAPGS_model/models
|
||||
aapgsModels.files = \
|
||||
$$AAPGS_ROOT/models/model.yaml \
|
||||
$$AAPGS_ROOT/models/model.rknn
|
||||
|
||||
INSTALLS += aapgsRuntime aapgsModels
|
||||
}
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#ifndef PARKINGSPACEGUIDE_DETECTPRESENTER_H
|
||||
#define PARKINGSPACEGUIDE_DETECTPRESENTER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QImage>
|
||||
#include <QString>
|
||||
@ -8,6 +10,8 @@
|
||||
#include "IRsLidarDevice.h"
|
||||
#include "IYParkingSpaceGuideStatus.h"
|
||||
|
||||
class AapgsModelClassifier;
|
||||
|
||||
enum class AirplanePresenceState
|
||||
{
|
||||
NotDetected,
|
||||
@ -51,8 +55,11 @@ struct ModelRecognitionResult
|
||||
class DetectPresenter
|
||||
{
|
||||
public:
|
||||
DetectPresenter() = default;
|
||||
~DetectPresenter() = default;
|
||||
DetectPresenter();
|
||||
~DetectPresenter();
|
||||
|
||||
DetectPresenter(const DetectPresenter&) = delete;
|
||||
DetectPresenter& operator=(const DetectPresenter&) = delete;
|
||||
|
||||
static QString GetAlgoVersion();
|
||||
|
||||
@ -69,6 +76,9 @@ public:
|
||||
int RecognizeModel2D(const QImage& frame,
|
||||
const QByteArray& recognitionContext,
|
||||
ModelRecognitionResult& result);
|
||||
|
||||
private:
|
||||
std::unique_ptr<AapgsModelClassifier> m_modelClassifier;
|
||||
};
|
||||
|
||||
#endif // PARKINGSPACEGUIDE_DETECTPRESENTER_H
|
||||
|
||||
@ -3,6 +3,15 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "AapgsModelClassifier.h"
|
||||
|
||||
DetectPresenter::DetectPresenter()
|
||||
: m_modelClassifier(std::make_unique<AapgsModelClassifier>())
|
||||
{
|
||||
}
|
||||
|
||||
DetectPresenter::~DetectPresenter() = default;
|
||||
|
||||
bool ModelRecognitionResult::IsVerified(double minimumConfidence) const
|
||||
{
|
||||
if (!std::isfinite(minimumConfidence)) {
|
||||
@ -22,7 +31,7 @@ bool ModelRecognitionResult::IsVerified(double minimumConfidence) const
|
||||
|
||||
QString DetectPresenter::GetAlgoVersion()
|
||||
{
|
||||
return QStringLiteral("停车引导预留算法接口 版本0.1");
|
||||
return QStringLiteral("停机引导算法接口 0.1 / AAPGS_model 1.0.0");
|
||||
}
|
||||
|
||||
int DetectPresenter::DetectAirplanePresence(const RsCloudData& cloud,
|
||||
@ -81,7 +90,7 @@ int DetectPresenter::DetectParkingSpaceGuide(const RsCloudData& cloud,
|
||||
info.guideStateCode = 0;
|
||||
info.guideText = QStringLiteral("预留算法接口");
|
||||
result.parkingSpaceInfoList.push_back(info);
|
||||
result.message = QStringLiteral("停车引导预留算法接口");
|
||||
result.message = QStringLiteral("停机引导预留算法接口");
|
||||
|
||||
const double modelVerifyDistance = algorithmParams.calibrationParam.modelVerifyDistance;
|
||||
const bool isInModelVerifyRange = modelVerifyDistance >= 0.0 &&
|
||||
@ -110,10 +119,25 @@ int DetectPresenter::RecognizeModel2D(const QImage& frame,
|
||||
const QByteArray& recognitionContext,
|
||||
ModelRecognitionResult& result)
|
||||
{
|
||||
Q_UNUSED(frame);
|
||||
Q_UNUSED(recognitionContext);
|
||||
|
||||
result = ModelRecognitionResult();
|
||||
result.message = QStringLiteral("机型识别算法接口未实现");
|
||||
return -1;
|
||||
if (!m_modelClassifier) {
|
||||
result.message = QStringLiteral("AAPGS机型识别器未初始化");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AapgsModelClassifier::Classification classification;
|
||||
QString errorMessage;
|
||||
if (!m_modelClassifier->Classify(frame, classification, errorMessage)) {
|
||||
result.message = errorMessage.trimmed().isEmpty()
|
||||
? QStringLiteral("AAPGS机型识别失败")
|
||||
: errorMessage.trimmed();
|
||||
return -1;
|
||||
}
|
||||
|
||||
result.modelType = classification.modelType;
|
||||
result.confidence = classification.confidence;
|
||||
result.verified = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ int ParkingSpaceGuidePresenter::Init()
|
||||
{
|
||||
m_reconfiguring.store(false);
|
||||
NotifyWorkStatus(WorkStatus::InitIng);
|
||||
NotifyStatus("停车引导初始化中");
|
||||
NotifyStatus("停机引导初始化中");
|
||||
|
||||
int ret = InitConfig();
|
||||
if (ret != 0) {
|
||||
@ -192,7 +192,7 @@ int ParkingSpaceGuidePresenter::Init()
|
||||
|
||||
m_initialized = true;
|
||||
NotifyWorkStatus(WorkStatus::Ready);
|
||||
NotifyStatus("停车引导初始化完成");
|
||||
NotifyStatus("停机引导初始化完成");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -229,7 +229,7 @@ bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex)
|
||||
}
|
||||
|
||||
if (m_detectionRunning.load()) {
|
||||
NotifyStatus("停车引导实时检测已在运行");
|
||||
NotifyStatus("停机引导实时检测已在运行");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -259,12 +259,12 @@ bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex)
|
||||
m_detectionRunning.store(false);
|
||||
m_stopDetectionRequested.store(true);
|
||||
NotifyWorkStatus(WorkStatus::Error);
|
||||
NotifyStatus("启动停车引导实时检测线程失败");
|
||||
NotifyStatus("启动停机引导实时检测线程失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
NotifyWorkStatus(WorkStatus::Working);
|
||||
NotifyStatus("停车引导实时检测已开始");
|
||||
NotifyStatus("停机引导实时检测已开始");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -298,19 +298,19 @@ bool ParkingSpaceGuidePresenter::TriggerDetection(int cameraIndex)
|
||||
}
|
||||
|
||||
NotifyWorkStatus(WorkStatus::Detecting);
|
||||
NotifyStatus("停车引导单次检测已开始");
|
||||
NotifyStatus("停机引导单次检测已开始");
|
||||
bool success = false;
|
||||
ParkingGuideAlgorithmState algorithmState;
|
||||
try {
|
||||
success = DetectOnce(cloudFrame->cloud, algorithmState);
|
||||
} catch (const std::exception& e) {
|
||||
NotifyStatus(std::string("停车引导单次检测异常:") + e.what());
|
||||
NotifyStatus(std::string("停机引导单次检测异常:") + e.what());
|
||||
} catch (...) {
|
||||
NotifyStatus("停车引导单次检测发生未知异常");
|
||||
NotifyStatus("停机引导单次检测发生未知异常");
|
||||
}
|
||||
NotifyWorkStatus(success ? WorkStatus::Completed : WorkStatus::Error);
|
||||
NotifyStatus(success ? "停车引导单次检测完成"
|
||||
: "停车引导单次检测失败");
|
||||
NotifyStatus(success ? "停机引导单次检测完成"
|
||||
: "停机引导单次检测失败");
|
||||
return success;
|
||||
}
|
||||
|
||||
@ -335,11 +335,11 @@ bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud,
|
||||
if (ret != 0) {
|
||||
result.errorCode = ret;
|
||||
if (result.message.isEmpty()) {
|
||||
result.message = QStringLiteral("停车引导检测失败");
|
||||
result.message = QStringLiteral("停机引导检测失败");
|
||||
}
|
||||
} else {
|
||||
if (result.parkingSpaceInfoList.empty() && result.message.isEmpty()) {
|
||||
result.message = QStringLiteral("无停车引导结果");
|
||||
result.message = QStringLiteral("无停机引导结果");
|
||||
}
|
||||
|
||||
if (control.needModelRecognition) {
|
||||
@ -385,9 +385,17 @@ bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud,
|
||||
info.confidence = control.nextState.modelConfidence;
|
||||
}
|
||||
} else {
|
||||
const QString reason = modelResult.message.trimmed().isEmpty()
|
||||
? QStringLiteral("机型识别结果未通过验证")
|
||||
: modelResult.message;
|
||||
QString reason = modelResult.message.trimmed();
|
||||
if (reason.isEmpty() && modelRet == 0) {
|
||||
reason = QStringLiteral(
|
||||
"机型识别置信度不足:%1,实际%2,阈值%3")
|
||||
.arg(modelResult.modelType)
|
||||
.arg(modelResult.confidence, 0, 'f', 3)
|
||||
.arg(minimumConfidence, 0, 'f', 3);
|
||||
}
|
||||
if (reason.isEmpty()) {
|
||||
reason = QStringLiteral("机型识别结果未通过验证");
|
||||
}
|
||||
appendRecognitionFailure(reason);
|
||||
}
|
||||
}
|
||||
@ -608,10 +616,10 @@ void ParkingSpaceGuidePresenter::DetectionLoop()
|
||||
DetectOnce(cloudFrame->cloud, algorithmState);
|
||||
} catch (const std::exception& e) {
|
||||
PublishErrorResult(-1,
|
||||
QStringLiteral("停车引导检测异常:%1")
|
||||
QStringLiteral("停机引导检测异常:%1")
|
||||
.arg(QString::fromUtf8(e.what())));
|
||||
} catch (...) {
|
||||
PublishErrorResult(-1, QStringLiteral("停车引导检测发生未知异常"));
|
||||
PublishErrorResult(-1, QStringLiteral("停机引导检测发生未知异常"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,7 +647,7 @@ int ParkingSpaceGuidePresenter::StopDetection()
|
||||
|
||||
if (wasRunning) {
|
||||
NotifyWorkStatus(WorkStatus::Ready);
|
||||
NotifyStatus("停车引导实时检测已停止");
|
||||
NotifyStatus("停机引导实时检测已停止");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#ifndef PARKINGSPACEGUIDE_VERSION_H
|
||||
#define PARKINGSPACEGUIDE_VERSION_H
|
||||
|
||||
#define PARKINGSPACEGUIDE_APP_NAME "停车引导"
|
||||
#define PARKINGSPACEGUIDE_APP_NAME "停机引导"
|
||||
#define PARKINGSPACEGUIDE_VERSION_STRING "0.1.0"
|
||||
#define PARKINGSPACEGUIDE_BUILD_STRING "1"
|
||||
#define PARKINGSPACEGUIDE_FULL_VERSION_STRING "版本" PARKINGSPACEGUIDE_VERSION_STRING "_" PARKINGSPACEGUIDE_BUILD_STRING
|
||||
|
||||
@ -23,9 +23,9 @@ DialogAlgoArg::DialogAlgoArg(QWidget* parent)
|
||||
, ui(new Ui::DialogAlgoArg)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(QStringLiteral("停车引导参数设置"));
|
||||
setWindowTitle(QStringLiteral("停机引导参数设置"));
|
||||
if (ui->label_title) {
|
||||
ui->label_title->setText(QStringLiteral("停车引导参数设置"));
|
||||
ui->label_title->setText(QStringLiteral("停机引导参数设置"));
|
||||
}
|
||||
BuildUi();
|
||||
connect(ui->tabWidget, &QTabWidget::currentChanged, this, [this](int index) {
|
||||
@ -101,7 +101,7 @@ void DialogAlgoArg::ShowTabGroup(ConfigPage page)
|
||||
|
||||
void DialogAlgoArg::UpdateTitle(ConfigPage page)
|
||||
{
|
||||
QString title = QStringLiteral("停车引导参数设置");
|
||||
QString title = QStringLiteral("停机引导参数设置");
|
||||
switch (page) {
|
||||
case ConfigPage::Detection:
|
||||
title = QStringLiteral("检测参数设置");
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<string notr="true">color: rgb(221, 225, 233);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>停车引导参数设置</string>
|
||||
<string>停机引导参数设置</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
|
||||
@ -17,7 +17,7 @@ int main(int argc, char* argv[])
|
||||
if (instanceManager.isAnotherInstanceRunning()) {
|
||||
QMessageBox messageBox;
|
||||
messageBox.setWindowTitle(QObject::tr("应用已运行"));
|
||||
messageBox.setText(QObject::tr("停车引导程序已在运行。"));
|
||||
messageBox.setText(QObject::tr("停机引导程序已在运行。"));
|
||||
messageBox.addButton(QObject::tr("确定"), QMessageBox::AcceptRole);
|
||||
messageBox.exec();
|
||||
return 0;
|
||||
|
||||
@ -8,7 +8,9 @@
|
||||
#include <QFileDialog>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMetaObject>
|
||||
#include <QPixmap>
|
||||
@ -107,6 +109,39 @@ QString FormatNumber(double value, const QString& unit, int precision = 2)
|
||||
return QStringLiteral("%1%2").arg(value, 0, 'f', precision).arg(unit);
|
||||
}
|
||||
|
||||
void ConfigureResultWidget(SingleTargetResultWidget* resultWidget)
|
||||
{
|
||||
if (!resultWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep this presentation local to ParkingSpaceGuide; UICommon defaults are shared by other apps.
|
||||
auto* mainLayout = qobject_cast<QVBoxLayout*>(resultWidget->layout());
|
||||
if (mainLayout && mainLayout->count() > 0) {
|
||||
auto* headerLayout = qobject_cast<QHBoxLayout*>(mainLayout->itemAt(0)->layout());
|
||||
if (headerLayout && headerLayout->count() == 2) {
|
||||
headerLayout->setSpacing(12);
|
||||
headerLayout->setStretch(0, 0);
|
||||
headerLayout->setStretch(1, 0);
|
||||
headerLayout->addStretch(1);
|
||||
}
|
||||
}
|
||||
|
||||
resultWidget->setStyleSheet(
|
||||
resultWidget->styleSheet()
|
||||
+ QStringLiteral(
|
||||
"#singleTargetTitle { font-size: 28px; }"
|
||||
"#singleTargetStatus { font-size: 34px; font-weight: 700; padding: 0; }"
|
||||
"#singleTargetEmpty { font-size: 28px; }"
|
||||
"#singleTargetFieldName { font-size: 24px; }"
|
||||
"#singleTargetFieldValue { font-size: 26px; }"));
|
||||
|
||||
auto* statusLabel = resultWidget->findChild<QLabel*>(QStringLiteral("singleTargetStatus"));
|
||||
if (statusLabel) {
|
||||
statusLabel->setStyleSheet(QStringLiteral("background-color: transparent;"));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
@ -114,9 +149,9 @@ MainWindow::MainWindow(QWidget* parent)
|
||||
, ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(QStringLiteral("停车引导"));
|
||||
setWindowTitle(QStringLiteral("停机引导"));
|
||||
if (ui->label) {
|
||||
ui->label->setText(QStringLiteral("停车引导"));
|
||||
ui->label->setText(QStringLiteral("停机引导"));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -288,6 +323,7 @@ void MainWindow::addDetectionResult(const DetectionResult& result)
|
||||
resultWidget->setTitle(tr("当前状态"));
|
||||
resultWidget->setStatus(GuideStateName(ParkingGuideState::Unknown), false);
|
||||
resultWidget->setEmptyText(tr("暂无停机状态结果"));
|
||||
ConfigureResultWidget(resultWidget);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -305,6 +341,7 @@ void MainWindow::addDetectionResult(const DetectionResult& result)
|
||||
{tr("偏转角"), FormatNumber(info.angle, tr("度"))},
|
||||
{tr("速度"), FormatNumber(info.aircraftSpeed, tr("米/秒"), 2)}
|
||||
});
|
||||
ConfigureResultWidget(resultWidget);
|
||||
}
|
||||
|
||||
void MainWindow::OnStatusUpdate(const std::string& statusMessage)
|
||||
@ -452,7 +489,7 @@ void MainWindow::on_btn_start_clicked()
|
||||
}
|
||||
|
||||
clearDetectionLog();
|
||||
updateStatusLog(tr("开始停车引导检测"));
|
||||
updateStatusLog(tr("开始停机引导检测"));
|
||||
if (!m_presenter->StartDetection(-1)) {
|
||||
updateStatusLog(tr("启动检测失败"));
|
||||
}
|
||||
@ -686,7 +723,7 @@ void MainWindow::onSaveDetectionData()
|
||||
}
|
||||
|
||||
const std::string timeStamp = CVrDateUtils::GetNowTime();
|
||||
const QString fileName = QString("停车引导_%1_%2.txt")
|
||||
const QString fileName = QString("停机引导_%1_%2.txt")
|
||||
.arg(m_presenter->GetDetectIndex())
|
||||
.arg(QString::fromStdString(timeStamp));
|
||||
const QString fullPath = QDir(dirPath).filePath(fileName);
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>停车引导</string>
|
||||
<string>停机引导</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
@ -75,7 +75,7 @@ color: rgb(239, 241, 245);</string>
|
||||
background-color: rgba(255, 255, 255, 0);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>停车引导</string>
|
||||
<string>停机引导</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListWidget" name="detect_result_list">
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 89 KiB |
@ -5,6 +5,7 @@
|
||||
<COM Name="COM11" Remarks="" Baund="9600" Parity="0" StopBit="0" DataBit="0" FlowCtrl="0" TransMode="0" CharType="0" BreakTime="10" />
|
||||
<COM Name="COM13" Remarks="" Baund="9600" Parity="0" StopBit="0" DataBit="0" FlowCtrl="0" TransMode="0" CharType="0" BreakTime="10" />
|
||||
<COM Name="COM9" Remarks="" Baund="9600" Parity="0" StopBit="0" DataBit="0" FlowCtrl="0" TransMode="0" CharType="0" BreakTime="10" />
|
||||
<COM Name="COM1" Remarks="" Baund="9600" Parity="0" StopBit="0" DataBit="0" FlowCtrl="0" TransMode="0" CharType="0" BreakTime="10" />
|
||||
<NET Name="PLC_NET" Remarks="" DesIP="127.0.0.1" LocalPortID="502" DesPortID="0" LinkMode="1" LinkResetTime="0" LinkHoldTime="6000" TransMode="2" CharType="0" MaxAsynNum="15" />
|
||||
</PORT_LIST>
|
||||
<DEVICE_LIST>
|
||||
@ -13,7 +14,7 @@
|
||||
<DATA Name="拍照" BLOCK="2" PrtcTYPE="1" Unit="--" ShowType="0" Addr="1001" Gain="1" Size="1" BitOffset="0" BitNum="16" Range="--" Color="" ByteOder="0" WordOder="0" IntervalTime="0" IsShow="1" IsBRead="1" IsBWrite="0">
|
||||
<TIMEOUT Time="2000" ResendCount="0" />
|
||||
<CURVE IsShow="0" />
|
||||
<VALUE Stategy="0" TimeBngrWay="0" Coef_a="101" Coef_b="0" Coef_c="0" Coef_d="0" Coef_e="0" ResetStep="0" XInvl="1000" MaxRadom="0" MinRadom="0" MaxValue="0" MinValue="0" />
|
||||
<VALUE Stategy="0" TimeBngrWay="0" Coef_a="201" Coef_b="0" Coef_c="0" Coef_d="0" Coef_e="0" ResetStep="0" XInvl="1000" MaxRadom="0" MinRadom="0" MaxValue="0" MinValue="0" />
|
||||
</DATA>
|
||||
<DATA Name="完成" BLOCK="2" PrtcTYPE="1" Unit="--" ShowType="0" Addr="1003" Gain="1" Size="1" BitOffset="0" BitNum="16" Range="--" Color="" ByteOder="0" WordOder="0" IntervalTime="0" IsShow="1" IsBRead="1" IsBWrite="0">
|
||||
<TIMEOUT Time="2000" ResendCount="0" />
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@ -37,34 +37,36 @@
|
||||
|
||||
| PLC寄存器 | 代码协议地址 | 类型 | 读/写 | 说明 |
|
||||
|-----------|------------|------|-------|------|
|
||||
| D1000 | 1001 | uint16 | R/W | 产品型号/相机触发值(见下表) |
|
||||
| D1002 | 1003 | uint16 | W | 检测结果状态码(见下表) |
|
||||
|
||||
### 4.2 产品型号与拍照触发值(D1000)
|
||||
|
||||
| 序号 | 产品型号 | 触发相机1 | 触发相机2 | 当前状态 |
|
||||
|------|----------|-----------|-----------|----------|
|
||||
| 1 | 4WE6 | 1 | 2 | 已实现 |
|
||||
| 2 | Z2S6 | 101 | 102 | 已实现 |
|
||||
| 3 | Z2FS6_30_2 | 201 | 202 | 已实现 |
|
||||
| 4 | DB10 | 301 | 302 | 已实现 |
|
||||
| 5 | DB20 | 401 | 402 | 已实现 |
|
||||
|
||||
五种工件分别读取各自独立配置的检测参数。
|
||||
|
||||
### 4.3 检测结果状态码(D1002)
|
||||
| D1000 | 1001 | uint16 | R/W | 产品型号/相机触发值(见下表) |
|
||||
| D1002 | 1003 | uint16 | W | 检测结果状态码(见下表) |
|
||||
|
||||
### 4.2 产品型号与拍照触发值(D1000)
|
||||
|
||||
|序号| 产品型号 | 算法接口 | 触发相机1 | 触发相机2 | 高度 |
|
||||
|----|------|--------------|-----------|-----------|------|
|
||||
| 1 | 4WE6 | `API_1` | 1 | 2 | 47 |
|
||||
| 2 | Z2S6 | `API_2` | 101 | 102 | 47 |
|
||||
| 3 | Z2FS6_30_2 | `API_2` | 201 | 202 | 28 |
|
||||
| 4 | DB10 | `API_3` | 301 | 302 | 60 |
|
||||
| 5 | DB20 | `API_3` | 401 | 402 | 60 |
|
||||
|
||||
> **编号说明**:产品序号即软件工件编号,配置、界面、触发协议和算法分派统一使用 1-5,不再转换为算法 test 的历史工件编号。`API_1`、`API_2`、`API_3` 分别对应三套工件孔定位接口。
|
||||
|
||||
五种工件分别读取其软件工件编号对应的独立检测参数和工具参数。
|
||||
|
||||
### 4.3 检测结果状态码(D1002)
|
||||
|
||||
| 状态码 | 含义 | 说明 |
|
||||
|--------|------|------|
|
||||
| 1 | 检测成功 | 检测正确完成且有结果,坐标数据已写入D2000 |
|
||||
| 2 | 异常物料 | 算法返回异常物料错误码(`SX_ERR_UNKNOWN_OBJECT = -2501`) |
|
||||
| 3 | 无产品 | 算法返回无产品错误码(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) |
|
||||
| 11 | 检测失败 | 其他算法或系统异常 |
|
||||
| 12 | 相机失败 | 相机打开或连接失败 |
|
||||
| 1 | 检测成功 | 检测正确完成且有结果,坐标数据已写入D2000 |
|
||||
| 2 | 异常物料 | 算法返回异常物料错误码(`SX_ERR_UNKNOWN_OBJECT = -2501`) |
|
||||
| 3 | 无产品 | 算法返回无产品错误码(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) |
|
||||
| 11 | 检测失败 | 其他算法或系统异常 |
|
||||
| 12 | 相机失败 | 相机打开或连接失败 |
|
||||
|
||||
### 4.4 坐标数据寄存器(D2000起,1个点)
|
||||
### 4.4 坐标数据寄存器(D2000起,1个点)
|
||||
|
||||
检测成功时,将最后一个工件的坐标写入 D2000-D2011,包含6个float值(X, Y, Z, Pitch, Roll, Yaw),每个float占用2个寄存器(大端序),共12个寄存器。
|
||||
检测成功时,将最后一个工件的坐标写入 D2000-D2011,包含6个float值(X, Y, Z, Pitch, Roll, Yaw),每个float占用2个寄存器(大端序),共12个寄存器。
|
||||
|
||||
| PLC寄存器 | 代码协议地址 | 类型 | 说明 |
|
||||
|-----------|------------|------|------|
|
||||
@ -78,7 +80,7 @@
|
||||
> **注意**:
|
||||
> - 每个float值占用2个寄存器,采用IEEE 754单精度浮点数格式
|
||||
> - 大端序(Big-Endian):高位字在低地址寄存器,低位字在高地址寄存器
|
||||
> - 仅写入最后一个工件的坐标数据
|
||||
> - 仅写入最后一个工件的坐标数据
|
||||
|
||||
## 5. 通信流程
|
||||
|
||||
@ -90,9 +92,9 @@
|
||||
│ 轮询读取D1000(地址1001) │
|
||||
│◄─────────────────────────────│
|
||||
│ │
|
||||
│ D1000=触发值 (拍照请求) │
|
||||
│◄─────────────────────────────│
|
||||
│ (按产品型号/相机触发表解码) │
|
||||
│ D1000=触发值 (拍照请求) │
|
||||
│◄─────────────────────────────│
|
||||
│ (按产品型号/相机触发表解码) │
|
||||
│ │
|
||||
│ 写0到D1000(地址1001) │
|
||||
│─────────────────────────────►│
|
||||
@ -103,7 +105,7 @@
|
||||
│─────────────────────────────►│ (仅检测成功时)
|
||||
│ │
|
||||
│ 写状态码到D1002(地址1003) │
|
||||
│─────────────────────────────►│ (1/2/3/11/12)
|
||||
│─────────────────────────────►│ (1/2/3/11/12)
|
||||
│ │
|
||||
```
|
||||
|
||||
@ -113,12 +115,12 @@
|
||||
- 3D系统持续轮询读取PLC的D1000寄存器(代码协议地址1001)
|
||||
- 轮询间隔:100ms(可配置)
|
||||
- 使用边沿检测:只在0→非零变化时触发
|
||||
- 根据4.2节触发表同时确定产品型号和相机
|
||||
- 根据4.2节触发表同时确定产品型号和相机
|
||||
|
||||
2. **拍照执行**
|
||||
- 检测到D1000为非零后,立即写0到D1000清除请求
|
||||
- 暂停轮询,根据D1000的值选择对应产品和相机
|
||||
- 触发相机拍照,执行该产品对应的工件孔定位检测
|
||||
- 暂停轮询,根据D1000的值选择对应产品和相机
|
||||
- 触发相机拍照,执行该产品对应的工件孔定位检测
|
||||
|
||||
3. **坐标输出**(仅检测成功时)
|
||||
- 算法处理完成后,将坐标数据写入PLC的D2000开始的寄存器
|
||||
@ -127,7 +129,7 @@
|
||||
|
||||
4. **结果通知**
|
||||
- 无论成功还是失败,均写状态码到D1002(代码协议地址1003)
|
||||
- 状态码:1=成功有结果,2=异常物料,3=无产品,11=检测失败,12=相机失败
|
||||
- 状态码:1=成功有结果,2=异常物料,3=无产品,11=检测失败,12=相机失败
|
||||
- 写入状态码后恢复拍照请求轮询
|
||||
|
||||
## 6. 连接管理
|
||||
@ -150,12 +152,12 @@
|
||||
|
||||
| 错误类型 | 处理方式 | D1002状态码 |
|
||||
|----------|----------|------------|
|
||||
| 相机连接失败 | 通知PLC,恢复轮询 | 12 |
|
||||
| 异常物料(`SX_ERR_UNKNOWN_OBJECT = -2501`) | 通知PLC,恢复轮询 | 2 |
|
||||
| 无产品(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | 通知PLC,恢复轮询 | 3 |
|
||||
| 其他算法检测失败 | 通知PLC,恢复轮询 | 11 |
|
||||
| D1000触发值无效 | 清除请求,通知PLC,恢复轮询 | 11 |
|
||||
| 检测成功有结果 | 发送坐标,通知PLC,恢复轮询 | 1 |
|
||||
| 相机连接失败 | 通知PLC,恢复轮询 | 12 |
|
||||
| 异常物料(`SX_ERR_UNKNOWN_OBJECT = -2501`) | 通知PLC,恢复轮询 | 2 |
|
||||
| 无产品(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | 通知PLC,恢复轮询 | 3 |
|
||||
| 其他算法检测失败 | 通知PLC,恢复轮询 | 11 |
|
||||
| D1000触发值无效 | 清除请求,通知PLC,恢复轮询 | 11 |
|
||||
| 检测成功有结果 | 发送坐标,通知PLC,恢复轮询 | 1 |
|
||||
| 连接断开 | 自动重连,发送错误信号 | - |
|
||||
| 读取失败 | 记录日志,主动断开触发重连 | - |
|
||||
| 写入失败 | 记录日志,返回失败状态 | - |
|
||||
|
||||
@ -4,14 +4,24 @@
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <QImage>
|
||||
#include <QMetaType>
|
||||
|
||||
// 使用 AppCommon 提供的通用接口和类型
|
||||
#include "IVisionApplicationStatus.h"
|
||||
|
||||
// 算法 workpieceType 的高16位表示方向:1=反向,0=正向。
|
||||
inline bool IsReversedWorkpieceType(int workpieceType)
|
||||
{
|
||||
const std::uint32_t packedType = static_cast<std::uint32_t>(workpieceType);
|
||||
return ((packedType >> 16) & 0xFFFFu) == 1u;
|
||||
}
|
||||
|
||||
// 孔位置结构体
|
||||
struct HolePosition : public PositionData<double> {
|
||||
int workpieceType = 0; // 算法原始工件类型:高16位为方向,1=反向,0=正向
|
||||
|
||||
// 默认构造函数
|
||||
HolePosition() : PositionData<double>() {}
|
||||
|
||||
@ -30,7 +40,7 @@ struct HolePosition : public PositionData<double> {
|
||||
|
||||
// 工件孔检测结果结构体
|
||||
struct WorkpieceHoleDetectionResult : public DetectionResultData<HolePosition> {
|
||||
int workpieceType = 0; // 工件类型
|
||||
int workpieceType = 0; // 第一个工件的算法原始类型(包含高16位方向)
|
||||
|
||||
// 默认构造函数
|
||||
WorkpieceHoleDetectionResult() = default;
|
||||
|
||||
@ -33,7 +33,6 @@ public:
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
int eulerOrder,
|
||||
int dirVectorInvert,
|
||||
int poseOutputOrder,
|
||||
WorkpieceHoleDetectionResult& detectionResult);
|
||||
|
||||
@ -4,21 +4,30 @@
|
||||
namespace WorkpieceProduct
|
||||
{
|
||||
|
||||
enum class AlgorithmApi
|
||||
{
|
||||
Api1,
|
||||
Api2,
|
||||
Api3
|
||||
};
|
||||
|
||||
struct ProductInfo
|
||||
{
|
||||
int number;
|
||||
const char* model;
|
||||
bool detectionSupported;
|
||||
AlgorithmApi algorithmApi;
|
||||
int triggerBase; // D1000 触发值 = triggerBase + cameraIndex
|
||||
};
|
||||
|
||||
inline const ProductInfo* Products()
|
||||
{
|
||||
static const ProductInfo products[] = {
|
||||
{1, "4WE6", true},
|
||||
{2, "Z2S6", true},
|
||||
{3, "Z2FS6_30_2", true},
|
||||
{4, "DB10", true},
|
||||
{5, "DB20", true}
|
||||
{1, "4WE6", true, AlgorithmApi::Api1, 0},
|
||||
{2, "Z2S6", true, AlgorithmApi::Api2, 100},
|
||||
{3, "Z2FS6_30_2", true, AlgorithmApi::Api2, 200},
|
||||
{4, "DB10", true, AlgorithmApi::Api3, 300},
|
||||
{5, "DB20", true, AlgorithmApi::Api3, 400}
|
||||
};
|
||||
return products;
|
||||
}
|
||||
@ -41,7 +50,11 @@ inline const ProductInfo* Find(int workpieceNumber)
|
||||
|
||||
inline int TriggerValue(int workpieceNumber, int cameraIndex)
|
||||
{
|
||||
return (workpieceNumber - 1) * 100 + cameraIndex;
|
||||
const ProductInfo* product = Find(workpieceNumber);
|
||||
if (!product || cameraIndex < 1 || cameraIndex > 2) {
|
||||
return -1;
|
||||
}
|
||||
return product->triggerBase + cameraIndex;
|
||||
}
|
||||
|
||||
inline bool DecodeTriggerValue(int triggerValue, int& workpieceNumber, int& cameraIndex)
|
||||
|
||||
@ -1,13 +1,57 @@
|
||||
#include "DetectPresenter.h"
|
||||
#include "workpieceHolePositioning_Export.h"
|
||||
#include "SG_baseAlgo_Export.h"
|
||||
#include "SG_errCode.h"
|
||||
#include "AlgoParamConverter.h"
|
||||
#include "IHandEyeCalib.h"
|
||||
#include "WorkpieceProduct.h"
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
HECEulerOrder ToHandEyeEulerOrder(int eulerOrder)
|
||||
{
|
||||
switch (eulerOrder) {
|
||||
case 10: return HECEulerOrder::XYZ;
|
||||
case 11: return HECEulerOrder::ZYX;
|
||||
case 12: return HECEulerOrder::ZXY;
|
||||
case 13: return HECEulerOrder::YXZ;
|
||||
case 14: return HECEulerOrder::YZX;
|
||||
case 15: return HECEulerOrder::XZY;
|
||||
default:
|
||||
LOG_WARNING("Unsupported tool euler order %d, fallback to 11 (ZYX)\n", eulerOrder);
|
||||
return HECEulerOrder::ZYX;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyToolRotationToEyeAxes(IHandEyeCalib& handEyeCalib,
|
||||
HECEulerOrder eulerOrder,
|
||||
const VrToolParam& toolParam,
|
||||
std::vector<HECPoint3D>& axes)
|
||||
{
|
||||
if (axes.size() != 3 ||
|
||||
(std::fabs(toolParam.rotX) < 1e-9 &&
|
||||
std::fabs(toolParam.rotY) < 1e-9 &&
|
||||
std::fabs(toolParam.rotZ) < 1e-9)) {
|
||||
return;
|
||||
}
|
||||
|
||||
HECRotationMatrix toolRotation;
|
||||
handEyeCalib.EulerToRotationMatrix(
|
||||
HECEulerAngles::fromDegrees(toolParam.rotX, toolParam.rotY, toolParam.rotZ),
|
||||
eulerOrder,
|
||||
toolRotation);
|
||||
|
||||
const std::vector<HECPoint3D> originalAxes = axes;
|
||||
for (int column = 0; column < 3; ++column) {
|
||||
axes[column] = (originalAxes[0] * toolRotation.at(0, column)
|
||||
+ originalAxes[1] * toolRotation.at(1, column)
|
||||
+ originalAxes[2] * toolRotation.at(2, column)).normalized();
|
||||
}
|
||||
}
|
||||
|
||||
bool HasValidZPoint(const std::vector<SVzNL3DPosition>& line)
|
||||
{
|
||||
for (const SVzNL3DPosition& point : line) {
|
||||
@ -64,7 +108,6 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
const VrDebugParam& debugParam,
|
||||
LaserDataLoader& dataLoader,
|
||||
const double clibMatrix[16],
|
||||
int eulerOrder,
|
||||
int dirVectorInvert,
|
||||
int poseOutputOrder,
|
||||
WorkpieceHoleDetectionResult& detectionResult)
|
||||
@ -80,6 +123,8 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
LOG_ERROR("Algorithm profile is missing for workpiece %d\n", workpieceNumber);
|
||||
return ERR_CODE(DEV_CONFIG_ERR);
|
||||
}
|
||||
const VrToolParam& toolParam = profile->toolParam;
|
||||
const HECEulerOrder toolEulerOrder = ToHandEyeEulerOrder(toolParam.eulerOrder);
|
||||
|
||||
// 获取当前相机的校准参数
|
||||
VrCameraPlaneCalibParam cameraCalibParamValue;
|
||||
@ -136,6 +181,10 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
growParam.maxLineSkipNum, growParam.yDeviation_max,
|
||||
growParam.maxSkipDistance, growParam.zDeviation_max,
|
||||
growParam.minLTypeTreeLen, growParam.minVTypeTreeLen);
|
||||
LOG_INFO("Workpiece %d tool params: eulerOrder=%d, rot=(%.3f,%.3f,%.3f), offset=(%.3f,%.3f,%.3f)\n",
|
||||
workpieceNumber, toolParam.eulerOrder,
|
||||
toolParam.rotX, toolParam.rotY, toolParam.rotZ,
|
||||
toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ);
|
||||
|
||||
// 准备平面校准参数
|
||||
SSG_planeCalibPara groundCalibPara = AlgoParamConverter::ToAlgoPlaneCalibParam(cameraCalibParam);
|
||||
@ -162,22 +211,26 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
|
||||
std::vector<WD_workpieceInfo> workpiecePositioning;
|
||||
const char* algorithmName = nullptr;
|
||||
switch (workpieceNumber) {
|
||||
case 1:
|
||||
const WorkpieceProduct::ProductInfo* product = WorkpieceProduct::Find(workpieceNumber);
|
||||
if (!product || !product->detectionSupported) {
|
||||
LOG_ERROR("No product or detection algorithm for software workpiece %d\n", workpieceNumber);
|
||||
return ERR_CODE(FUN_UNSUPPORT);
|
||||
}
|
||||
|
||||
switch (product->algorithmApi) {
|
||||
case WorkpieceProduct::AlgorithmApi::Api1:
|
||||
algorithmName = "wd_workpieceHolePositioning";
|
||||
wd_workpieceHolePositioning(
|
||||
algorithmData, workpiecePara, lineSegPara, filterParam, growParam,
|
||||
groundCalibPara, workpiecePositioning, &errCode);
|
||||
break;
|
||||
case 2:
|
||||
case 5:
|
||||
case WorkpieceProduct::AlgorithmApi::Api2:
|
||||
algorithmName = "wd_workpieceHolePositioning_2";
|
||||
wd_workpieceHolePositioning_2(
|
||||
algorithmData, workpiecePara, lineSegPara, filterParam, growParam,
|
||||
groundCalibPara, workpiecePositioning, &errCode);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
case WorkpieceProduct::AlgorithmApi::Api3:
|
||||
algorithmName = "wd_workpieceHolePositioning_3";
|
||||
wd_workpieceHolePositioning_3(
|
||||
algorithmData, workpiecePara, workpieceParam.bigHoleDiameter,
|
||||
@ -185,7 +238,7 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
workpiecePositioning, &errCode);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("No detection algorithm for workpiece %d\n", workpieceNumber);
|
||||
LOG_ERROR("Unsupported algorithm API for software workpiece %d\n", workpieceNumber);
|
||||
return ERR_CODE(FUN_UNSUPPORT);
|
||||
}
|
||||
|
||||
@ -213,6 +266,12 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
if (i == 0) {
|
||||
detectionResult.workpieceType = workpiece.workpieceType;
|
||||
}
|
||||
if (IsReversedWorkpieceType(workpiece.workpieceType)) {
|
||||
// 算法执行成功,但反向工件属于异常物料;保留结果继续生成可视化图片。
|
||||
detectionResult.errorCode = SX_ERR_UNKNOWN_OBJECT;
|
||||
LOG_WARNING("Detected reversed workpiece %zu (workpieceType=%d)\n",
|
||||
i + 1, workpiece.workpieceType);
|
||||
}
|
||||
|
||||
// 六轴转换:位置+姿态(转换到机械臂坐标系)
|
||||
HECPoint3D eyeCenter(workpiece.center.x, workpiece.center.y, workpiece.center.z);
|
||||
@ -242,6 +301,9 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
break;
|
||||
}
|
||||
|
||||
// 工具姿态补偿在 Eye 坐标系内合成,再进入手眼旋转链。
|
||||
ApplyToolRotationToEyeAxes(*handEyeCalib, toolEulerOrder, toolParam, dirVectors_eye);
|
||||
|
||||
// 1. 位置转换:R * P_eye + T
|
||||
HECPoint3D ptRobot;
|
||||
handEyeCalib->TransformPoint(calibResult.R, calibResult.T, eyeCenter, ptRobot);
|
||||
@ -264,15 +326,17 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
R_pose[2][1] = dirVectors_robot[1].z;
|
||||
R_pose[2][2] = dirVectors_robot[2].z;
|
||||
|
||||
// 4. 使用算法库的 rotationMatrixToEulerZYX 转换为欧拉角(返回角度)
|
||||
SSG_EulerAngles robotRpy = rotationMatrixToEulerZYX(R_pose);
|
||||
// 4. 保持既有 ZYX 输出分解约定,确保零工具补偿时输出完全兼容。
|
||||
// toolParam.eulerOrder 只控制上面的工具补偿旋转合成顺序。
|
||||
const SSG_EulerAngles robotRpy = rotationMatrixToEulerZYX(R_pose);
|
||||
|
||||
// 将机器人坐标系下的位姿添加到positions列表
|
||||
// 根据 poseOutputOrder 重映射欧拉角输出顺序
|
||||
HolePosition centerRobotPos;
|
||||
centerRobotPos.x = ptRobot.x;
|
||||
centerRobotPos.y = ptRobot.y;
|
||||
centerRobotPos.z = ptRobot.z;
|
||||
centerRobotPos.workpieceType = workpiece.workpieceType;
|
||||
centerRobotPos.x = ptRobot.x + toolParam.offsetX;
|
||||
centerRobotPos.y = ptRobot.y + toolParam.offsetY;
|
||||
centerRobotPos.z = ptRobot.z + toolParam.offsetZ;
|
||||
switch (poseOutputOrder) {
|
||||
case POSE_ORDER_RPY:
|
||||
centerRobotPos.roll = robotRpy.roll;
|
||||
@ -315,9 +379,9 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
|
||||
if(debugParam.enableDebug && debugParam.printDetailLog){
|
||||
LOG_INFO("[Algo Thread] Direction vectors (eye): X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n",
|
||||
workpiece.x_dir.x, workpiece.x_dir.y, workpiece.x_dir.z,
|
||||
workpiece.y_dir.x, workpiece.y_dir.y, workpiece.y_dir.z,
|
||||
workpiece.z_dir.x, workpiece.z_dir.y, workpiece.z_dir.z);
|
||||
dirVectors_eye[0].x, dirVectors_eye[0].y, dirVectors_eye[0].z,
|
||||
dirVectors_eye[1].x, dirVectors_eye[1].y, dirVectors_eye[1].z,
|
||||
dirVectors_eye[2].x, dirVectors_eye[2].y, dirVectors_eye[2].z);
|
||||
LOG_INFO("[Algo Thread] Direction vectors (robot): X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n",
|
||||
dirVectors_robot[0].x, dirVectors_robot[0].y, dirVectors_robot[0].z,
|
||||
dirVectors_robot[1].x, dirVectors_robot[1].y, dirVectors_robot[1].z,
|
||||
@ -325,22 +389,25 @@ int DetectPresenter::DetectWorkpieceHole(
|
||||
LOG_INFO("[Algo Thread] Center Eye Coords: X=%.3f, Y=%.3f, Z=%.3f\n",
|
||||
workpiece.center.x, workpiece.center.y, workpiece.center.z);
|
||||
LOG_INFO("[Algo Thread] Center Robot Coords: X=%.3f, Y=%.3f, Z=%.3f, R=%.4f, P=%.4f, Y=%.4f\n",
|
||||
ptRobot.x, ptRobot.y, ptRobot.z,
|
||||
centerRobotPos.x, centerRobotPos.y, centerRobotPos.z,
|
||||
centerRobotPos.roll, centerRobotPos.pitch, centerRobotPos.yaw);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 PointCloudCanvas 生成点云图像(灰色底图 + 红色孔位标记 + 中心点编号)
|
||||
// 使用 PointCloudCanvas 生成点云图像(正向红点、反向蓝点 + 中心点编号)
|
||||
{
|
||||
PointCloudCanvas canvas = PointCloudCanvas::Create(xyzData);
|
||||
for (size_t i = 0; i < workpiecePositioning.size(); i++) {
|
||||
const WD_workpieceInfo& workpiece = workpiecePositioning[i];
|
||||
// 红色小圆点标记每个孔
|
||||
const QColor markerColor = IsReversedWorkpieceType(workpiece.workpieceType)
|
||||
? QColor(0, 0, 255)
|
||||
: QColor(255, 0, 0);
|
||||
// 小圆点标记每个孔
|
||||
for (size_t j = 0; j < workpiece.holes.size(); j++) {
|
||||
canvas.drawPoint(workpiece.holes[j].x, workpiece.holes[j].y, QColor(255, 0, 0), 4);
|
||||
canvas.drawPoint(workpiece.holes[j].x, workpiece.holes[j].y, markerColor, 4);
|
||||
}
|
||||
// 中心点标记 + 白色编号
|
||||
canvas.drawPoint(workpiece.center.x, workpiece.center.y, QColor(255, 0, 0), 4);
|
||||
canvas.drawPoint(workpiece.center.x, workpiece.center.y, markerColor, 4);
|
||||
canvas.drawText(workpiece.center.x, workpiece.center.y,
|
||||
QString("%1").arg(i + 1), Qt::white, 14);
|
||||
}
|
||||
|
||||
@ -62,6 +62,14 @@ const char* GetPLCStatusDescription(int statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainsReversedWorkpiece(const WorkpieceHoleDetectionResult& result)
|
||||
{
|
||||
return std::any_of(result.positions.begin(), result.positions.end(),
|
||||
[](const HolePosition& position) {
|
||||
return IsReversedWorkpieceType(position.workpieceType);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConfigChangeListenerProxy::OnSystemConfigChanged(const SystemConfig& config)
|
||||
@ -390,16 +398,6 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vector<std::pair<EVzResult
|
||||
ConfigResult configResult = m_pConfigManager->GetConfigResult();
|
||||
VrDebugParam debugParam = configResult.debugParam;
|
||||
|
||||
// 获取旋转顺序配置(从当前相机对应的标定矩阵获取)
|
||||
int eulerOrder = 11; // 默认外旋ZYX
|
||||
int matrixIdx = m_currentCameraIndex - 1;
|
||||
if (matrixIdx >= 0 && matrixIdx < static_cast<int>(configResult.handEyeCalibMatrixList.size())) {
|
||||
eulerOrder = configResult.handEyeCalibMatrixList[matrixIdx].eulerOrder;
|
||||
} else if (!configResult.handEyeCalibMatrixList.empty()) {
|
||||
eulerOrder = configResult.handEyeCalibMatrixList[0].eulerOrder;
|
||||
}
|
||||
LOG_INFO("[Algo Thread] Using euler order: %d\n", eulerOrder);
|
||||
|
||||
// 获取方向向量反向配置
|
||||
int dirVectorInvert = configResult.plcRobotServerConfig.dirVectorInvert;
|
||||
LOG_INFO("[Algo Thread] Using dir vector invert: %d\n", dirVectorInvert);
|
||||
@ -412,12 +410,22 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vector<std::pair<EVzResult
|
||||
|
||||
int nRet = m_pDetectPresenter->DetectWorkpieceHole(workpieceNumber, m_currentCameraIndex, detectionDataCache,
|
||||
algorithmParams, debugParam, m_dataLoader,
|
||||
currentClibMatrix.clibMatrix, eulerOrder, dirVectorInvert,
|
||||
currentClibMatrix.clibMatrix, dirVectorInvert,
|
||||
poseOutputOrder, detectionResult);
|
||||
const bool hasReversedWorkpiece = ContainsReversedWorkpiece(detectionResult);
|
||||
// 根据项目类型选择处理方式
|
||||
if (GetStatusCallback<IYWorkpieceHoleStatus>()) {
|
||||
QString err = QString("错误:%1").arg(nRet);
|
||||
if (auto pStatus = GetStatusCallback<IYWorkpieceHoleStatus>()) pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString());
|
||||
QString detectionStatus;
|
||||
if (nRet != SUCCESS) {
|
||||
detectionStatus = QString("检测错误:%1").arg(nRet);
|
||||
} else if (hasReversedWorkpiece) {
|
||||
detectionStatus = "检测异常:工件反向";
|
||||
} else {
|
||||
detectionStatus = "检测成功";
|
||||
}
|
||||
if (auto pStatus = GetStatusCallback<IYWorkpieceHoleStatus>()) {
|
||||
pStatus->OnStatusUpdate(detectionStatus.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("[Algo Thread] wd_workpieceHolePositioning detected %zu workpieces time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec());
|
||||
@ -435,7 +443,9 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vector<std::pair<EVzResult
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
QString statusMsg = QString("检测完成,发现%1个工件").arg(detectionResult.positions.size());
|
||||
QString statusMsg = hasReversedWorkpiece
|
||||
? QString("检测完成,发现%1个工件,其中包含反向工件").arg(detectionResult.positions.size())
|
||||
: QString("检测完成,发现%1个工件").arg(detectionResult.positions.size());
|
||||
if (auto pStatus = GetStatusCallback<IYWorkpieceHoleStatus>()) pStatus->OnStatusUpdate(statusMsg.toStdString());
|
||||
|
||||
// 发送检测结果给TCP客户端
|
||||
@ -895,7 +905,9 @@ void WorkpieceHolePresenter::SendCoordinateDataToPLC(const WorkpieceHoleDetectio
|
||||
if (auto pStatus = GetStatusCallback<IYWorkpieceHoleStatus>()) {
|
||||
switch (plcStatus) {
|
||||
case PLCModbusClient::RESULT_ABNORMAL_MATERIAL:
|
||||
pStatus->OnStatusUpdate("检测结果为异常物料,已通知PLC");
|
||||
pStatus->OnStatusUpdate(ContainsReversedWorkpiece(result)
|
||||
? "检测到反向工件,已按异常物料通知PLC"
|
||||
: "检测结果为异常物料,已通知PLC");
|
||||
break;
|
||||
case PLCModbusClient::RESULT_NO_PRODUCT:
|
||||
pStatus->OnStatusUpdate("检测结果为无产品,已通知PLC");
|
||||
@ -926,7 +938,7 @@ void WorkpieceHolePresenter::SendCoordinateDataToPLC(const WorkpieceHoleDetectio
|
||||
int poseOutputOrder = configResult.plcRobotServerConfig.poseOutputOrder;
|
||||
LOG_INFO("Using pose output order: %d\n", poseOutputOrder);
|
||||
|
||||
// 协议单点输出改为最后一个工件。
|
||||
// 协议单点输出使用最后一个工件。
|
||||
const auto& pos = result.positions.back();
|
||||
PLCModbusClient::CoordinateData coord;
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
#define WORKPIECEHOLE_APP_NAME "工件孔定位"
|
||||
|
||||
// 版本字符串
|
||||
#define WORKPIECEHOLE_VERSION_STRING "1.2.1"
|
||||
#define WORKPIECEHOLE_BUILD_STRING "1"
|
||||
#define WORKPIECEHOLE_VERSION_STRING "1.2.2"
|
||||
#define WORKPIECEHOLE_BUILD_STRING "2"
|
||||
#define WORKPIECEHOLE_FULL_VERSION_STRING "V" WORKPIECEHOLE_VERSION_STRING "_" WORKPIECEHOLE_BUILD_STRING
|
||||
|
||||
// 构建日期
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
# 1.2.2
|
||||
## build_1 2026-07-14
|
||||
1. 工件3和工件5 反了。
|
||||
2. 每个工件有独立的工具参数调整
|
||||
|
||||
# 1.2.1
|
||||
## build_1 2026-07-14
|
||||
1. 更新算法包含1-5工件算法
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
#include "PathManager.h"
|
||||
#include "StyledMessageBox.h"
|
||||
#include "HandEyeCalibWidget.h"
|
||||
#include "ToolExtrinsicWidget.h"
|
||||
#include "WorkpieceProduct.h"
|
||||
|
||||
#include "VrLog.h"
|
||||
@ -24,6 +25,7 @@ DialogAlgoarg::DialogAlgoarg(ConfigManager* configManager, QWidget *parent)
|
||||
, m_currentWorkpieceNumber(1)
|
||||
, m_isSwitchingWorkpieceProfile(true)
|
||||
, m_handEyeCalibWidget(nullptr)
|
||||
, m_toolExtrinsicWidget(nullptr)
|
||||
{
|
||||
try {
|
||||
ui->setupUi(this);
|
||||
@ -52,6 +54,9 @@ DialogAlgoarg::DialogAlgoarg(ConfigManager* configManager, QWidget *parent)
|
||||
// 初始化工件参数组下拉框
|
||||
InitWorkpieceProfileComboBox();
|
||||
|
||||
// 初始化工具参数 tab;工具参数由顶部工件选择器驱动
|
||||
InitToolExtrinsicTab();
|
||||
|
||||
// 从配置文件加载数据到界面
|
||||
LoadConfigToUI();
|
||||
m_isSwitchingWorkpieceProfile = false;
|
||||
@ -156,17 +161,39 @@ void DialogAlgoarg::LoadCurrentWorkpieceProfileToUI()
|
||||
LoadLineSegParamToUI(profile.lineSegParam);
|
||||
LoadFilterParamToUI(profile.filterParam);
|
||||
LoadGrowParamToUI(profile.growParam);
|
||||
|
||||
if (m_toolExtrinsicWidget) {
|
||||
m_toolExtrinsicWidget->setData(
|
||||
profile.toolParam.eulerOrder,
|
||||
profile.toolParam.rotX, profile.toolParam.rotY, profile.toolParam.rotZ,
|
||||
profile.toolParam.offsetX, profile.toolParam.offsetY, profile.toolParam.offsetZ);
|
||||
}
|
||||
}
|
||||
|
||||
bool DialogAlgoarg::SaveCurrentWorkpieceProfileFromUI()
|
||||
{
|
||||
if (m_toolExtrinsicWidget && !m_toolExtrinsicWidget->hasAcceptableInput()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VrWorkpieceAlgorithmProfile& profile =
|
||||
m_algorithmParams.GetOrCreateWorkpieceProfile(m_currentWorkpieceNumber);
|
||||
|
||||
return SaveWorkpieceHoleParamFromUI(profile.workpieceHoleParam) &&
|
||||
SaveLineSegParamFromUI(profile.lineSegParam) &&
|
||||
SaveFilterParamFromUI(profile.filterParam) &&
|
||||
SaveGrowParamFromUI(profile.growParam);
|
||||
if (!SaveWorkpieceHoleParamFromUI(profile.workpieceHoleParam) ||
|
||||
!SaveLineSegParamFromUI(profile.lineSegParam) ||
|
||||
!SaveFilterParamFromUI(profile.filterParam) ||
|
||||
!SaveGrowParamFromUI(profile.growParam)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_toolExtrinsicWidget) {
|
||||
m_toolExtrinsicWidget->getData(
|
||||
profile.toolParam.eulerOrder,
|
||||
profile.toolParam.rotX, profile.toolParam.rotY, profile.toolParam.rotZ,
|
||||
profile.toolParam.offsetX, profile.toolParam.offsetY, profile.toolParam.offsetZ);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DialogAlgoarg::on_comboBox_workpieceProfile_currentIndexChanged(int index)
|
||||
@ -243,8 +270,9 @@ bool DialogAlgoarg::SaveConfigFromUI()
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取当前配置
|
||||
SystemConfig systemConfig = m_pConfigManager->GetConfig();
|
||||
// 基于当前配置构造候选值;落盘失败时恢复运行时配置。
|
||||
const SystemConfig previousSystemConfig = m_pConfigManager->GetConfig();
|
||||
SystemConfig systemConfig = previousSystemConfig;
|
||||
VrAlgorithmParams& algoParams = systemConfig.configResult.algorithmParams;
|
||||
|
||||
// 先暂存当前工件,再一次性保存所有工件参数组。
|
||||
@ -300,7 +328,12 @@ bool DialogAlgoarg::SaveConfigFromUI()
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_pConfigManager->SaveConfigToFile(m_configFilePath.toStdString());
|
||||
if (!m_pConfigManager->SaveConfigToFile(m_configFilePath.toStdString())) {
|
||||
m_pConfigManager->UpdateFullConfig(previousSystemConfig);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
return false;
|
||||
@ -407,6 +440,21 @@ void DialogAlgoarg::on_btn_camer_cancel_clicked()
|
||||
reject();
|
||||
}
|
||||
|
||||
// ========== 工具参数相关实现 ==========
|
||||
|
||||
void DialogAlgoarg::InitToolExtrinsicTab()
|
||||
{
|
||||
if (!ui || !ui->verticalLayout_toolExtrinsicHost || m_toolExtrinsicWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_toolExtrinsicWidget = new ToolExtrinsicWidget(this);
|
||||
m_toolExtrinsicWidget->setCameraSelectorVisible(false);
|
||||
m_toolExtrinsicWidget->setTargetOffsetVisible(true);
|
||||
m_toolExtrinsicWidget->setAxialAngleRangeVisible(false);
|
||||
ui->verticalLayout_toolExtrinsicHost->addWidget(m_toolExtrinsicWidget);
|
||||
}
|
||||
|
||||
// ========== 手眼标定相关实现 ==========
|
||||
|
||||
void DialogAlgoarg::InitHandEyeCalibTab()
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "ConfigManager.h"
|
||||
|
||||
class HandEyeCalibWidget;
|
||||
class ToolExtrinsicWidget;
|
||||
|
||||
namespace Ui {
|
||||
class DialogAlgoarg;
|
||||
@ -59,6 +60,9 @@ private:
|
||||
void InitHandEyeCalibTab();
|
||||
void InitEulerOrderComboBox();
|
||||
|
||||
// 工具参数
|
||||
void InitToolExtrinsicTab();
|
||||
|
||||
private:
|
||||
Ui::DialogAlgoarg *ui;
|
||||
ConfigManager* m_pConfigManager;
|
||||
@ -69,6 +73,9 @@ private:
|
||||
|
||||
// 手眼标定共享控件
|
||||
HandEyeCalibWidget* m_handEyeCalibWidget;
|
||||
|
||||
// 工具参数共享控件
|
||||
ToolExtrinsicWidget* m_toolExtrinsicWidget;
|
||||
};
|
||||
|
||||
#endif // DIALOGALGOARG_H
|
||||
|
||||
@ -774,6 +774,12 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_toolExtrinsic">
|
||||
<attribute name="title">
|
||||
<string>工具参数</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_toolExtrinsicHost"/>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_network">
|
||||
<attribute name="title">
|
||||
<string>网络配置</string>
|
||||
|
||||
@ -434,7 +434,7 @@ int MainWindow::selectWorkpieceForDetection()
|
||||
layout.setContentsMargins(20, 20, 20, 20);
|
||||
layout.setSpacing(12);
|
||||
|
||||
QLabel header(tr("序号 产品型号"), &dialog);
|
||||
QLabel header(tr("显示序号 - 产品型号"), &dialog);
|
||||
layout.addWidget(&header);
|
||||
|
||||
QListWidget productList(&dialog);
|
||||
@ -446,7 +446,7 @@ int MainWindow::selectWorkpieceForDetection()
|
||||
const WorkpieceProduct::ProductInfo* products = WorkpieceProduct::Products();
|
||||
for (int i = 0; i < WorkpieceProduct::ProductCount(); ++i) {
|
||||
const WorkpieceProduct::ProductInfo& product = products[i];
|
||||
QString text = QString("%1 %2")
|
||||
QString text = tr("%1 - %2")
|
||||
.arg(product.number)
|
||||
.arg(QString::fromLatin1(product.model));
|
||||
if (!product.detectionSupported) {
|
||||
|
||||
@ -26,7 +26,7 @@ struct VrOutlierFilterParam
|
||||
*/
|
||||
struct VrLineSegParam
|
||||
{
|
||||
double distScale = 3.0; // 计算方向角的窗口比例因子
|
||||
double distScale = 10.0; // 计算方向角的窗口比例因子
|
||||
double segGapTh_y = 15.0; // Y方向间隔阈值,大于此值认为是分段
|
||||
double segGapTh_z = 0.0; // Z方向间隔阈值,大于此值认为是分段
|
||||
};
|
||||
@ -51,7 +51,7 @@ struct VrWorkpieceHoleParam
|
||||
{
|
||||
int workpieceType = 0; // 工件类型
|
||||
double holeDiameter = 6.0; // 孔直径 (mm)
|
||||
double bigHoleDiameter = 33.0; // 大孔直径 (mm),工件3算法使用
|
||||
double bigHoleDiameter = 33.0; // 大孔直径 (mm),API_3 算法使用
|
||||
double holeDist_L = 40.0; // 孔间距_长 (mm)
|
||||
double holeDist_W = 32.0; // 孔间距_宽 (mm)
|
||||
double xLen = 44.0; // x轴方向长度,用于绘制平面 (mm)
|
||||
@ -59,6 +59,22 @@ struct VrWorkpieceHoleParam
|
||||
double H = 47.0; // 高度 (mm)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 工具参数
|
||||
*
|
||||
* 每个工件独立保存一套工具姿态补偿和目标点偏移参数。
|
||||
*/
|
||||
struct VrToolParam
|
||||
{
|
||||
int eulerOrder = 11; // 欧拉角旋转顺序,默认外旋 ZYX
|
||||
double rotX = 0.0; // 工具姿态 X 轴补偿 (deg)
|
||||
double rotY = 0.0; // 工具姿态 Y 轴补偿 (deg)
|
||||
double rotZ = 0.0; // 工具姿态 Z 轴补偿 (deg)
|
||||
double offsetX = 0.0; // 目标点 X 方向偏移 (mm)
|
||||
double offsetY = 0.0; // 目标点 Y 方向偏移 (mm)
|
||||
double offsetZ = 0.0; // 目标点 Z 方向偏移 (mm)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PLC Modbus 寄存器地址配置
|
||||
*
|
||||
@ -198,6 +214,7 @@ struct VrWorkpieceAlgorithmProfile
|
||||
VrLineSegParam lineSegParam;
|
||||
VrOutlierFilterParam filterParam;
|
||||
VrTreeGrowParam growParam;
|
||||
VrToolParam toolParam;
|
||||
|
||||
VrWorkpieceAlgorithmProfile() = default;
|
||||
|
||||
@ -220,7 +237,7 @@ struct VrWorkpieceAlgorithmProfile
|
||||
workpieceHoleParam.yLen = 70.0;
|
||||
workpieceHoleParam.H = 47.0;
|
||||
|
||||
lineSegParam.distScale = 3.0;
|
||||
lineSegParam.distScale = 10.0;
|
||||
lineSegParam.segGapTh_y = 15.0;
|
||||
lineSegParam.segGapTh_z = 0.0;
|
||||
|
||||
@ -234,13 +251,29 @@ struct VrWorkpieceAlgorithmProfile
|
||||
growParam.minLTypeTreeLen = 2.0;
|
||||
growParam.minVTypeTreeLen = 2.0;
|
||||
|
||||
if (number == 3 || number == 4) {
|
||||
toolParam = VrToolParam();
|
||||
|
||||
// 参数编号始终采用软件产品顺序,不依赖算法 test 的历史工件编号。
|
||||
switch (number) {
|
||||
case 1: // 4WE6 / API_1 / 原 test1
|
||||
break;
|
||||
case 2: // Z2S6 / API_2 / 原 test2
|
||||
workpieceHoleParam.holeDiameter = 8.0;
|
||||
break;
|
||||
case 3: // Z2FS6_30_2 / API_2 / 原 test5
|
||||
workpieceHoleParam.H = 28.0;
|
||||
lineSegParam.distScale = 14.0;
|
||||
growParam.minLTypeTreeLen = 1.5;
|
||||
growParam.minVTypeTreeLen = 1.5;
|
||||
break;
|
||||
case 4: // DB10 / API_3 / 原 test3
|
||||
case 5: // DB20 / API_3 / 原 test4
|
||||
workpieceHoleParam.H = 60.0;
|
||||
lineSegParam.distScale = 30.0;
|
||||
lineSegParam.segGapTh_y = 40.0;
|
||||
} else if (number == 5) {
|
||||
workpieceHoleParam.holeDiameter = 10.0;
|
||||
workpieceHoleParam.H = 28.0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -10,6 +10,23 @@ using namespace tinyxml2;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kSoftwareProductNumberingVersion = 2;
|
||||
|
||||
int ToSoftwareWorkpieceNumber(int storedNumber, bool usesSoftwareProductNumbering)
|
||||
{
|
||||
if (usesSoftwareProductNumbering) {
|
||||
return storedNumber;
|
||||
}
|
||||
|
||||
// 旧配置按算法 test 编号保存;运行时统一转换为软件产品编号。
|
||||
switch (storedNumber) {
|
||||
case 3: return 4;
|
||||
case 4: return 5;
|
||||
case 5: return 3;
|
||||
default: return storedNumber;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadWorkpieceProfileParams(
|
||||
XMLElement* parentElement,
|
||||
VrWorkpieceAlgorithmProfile& profile)
|
||||
@ -75,6 +92,25 @@ void LoadWorkpieceProfileParams(
|
||||
if (growParamElement->Attribute("minVTypeTreeLen"))
|
||||
profile.growParam.minVTypeTreeLen = growParamElement->DoubleAttribute("minVTypeTreeLen");
|
||||
}
|
||||
|
||||
XMLElement* toolParamElement = parentElement->FirstChildElement("ToolParam");
|
||||
if (toolParamElement)
|
||||
{
|
||||
if (toolParamElement->Attribute("eulerOrder"))
|
||||
profile.toolParam.eulerOrder = toolParamElement->IntAttribute("eulerOrder");
|
||||
if (toolParamElement->Attribute("rotX"))
|
||||
profile.toolParam.rotX = toolParamElement->DoubleAttribute("rotX");
|
||||
if (toolParamElement->Attribute("rotY"))
|
||||
profile.toolParam.rotY = toolParamElement->DoubleAttribute("rotY");
|
||||
if (toolParamElement->Attribute("rotZ"))
|
||||
profile.toolParam.rotZ = toolParamElement->DoubleAttribute("rotZ");
|
||||
if (toolParamElement->Attribute("offsetX"))
|
||||
profile.toolParam.offsetX = toolParamElement->DoubleAttribute("offsetX");
|
||||
if (toolParamElement->Attribute("offsetY"))
|
||||
profile.toolParam.offsetY = toolParamElement->DoubleAttribute("offsetY");
|
||||
if (toolParamElement->Attribute("offsetZ"))
|
||||
profile.toolParam.offsetZ = toolParamElement->DoubleAttribute("offsetZ");
|
||||
}
|
||||
}
|
||||
|
||||
void SaveWorkpieceProfileParams(
|
||||
@ -116,6 +152,16 @@ void SaveWorkpieceProfileParams(
|
||||
growParamElement->SetAttribute("minLTypeTreeLen", profile.growParam.minLTypeTreeLen);
|
||||
growParamElement->SetAttribute("minVTypeTreeLen", profile.growParam.minVTypeTreeLen);
|
||||
profileElement->InsertEndChild(growParamElement);
|
||||
|
||||
XMLElement* toolParamElement = doc.NewElement("ToolParam");
|
||||
toolParamElement->SetAttribute("eulerOrder", profile.toolParam.eulerOrder);
|
||||
toolParamElement->SetAttribute("rotX", profile.toolParam.rotX);
|
||||
toolParamElement->SetAttribute("rotY", profile.toolParam.rotY);
|
||||
toolParamElement->SetAttribute("rotZ", profile.toolParam.rotZ);
|
||||
toolParamElement->SetAttribute("offsetX", profile.toolParam.offsetX);
|
||||
toolParamElement->SetAttribute("offsetY", profile.toolParam.offsetY);
|
||||
toolParamElement->SetAttribute("offsetZ", profile.toolParam.offsetZ);
|
||||
profileElement->InsertEndChild(toolParamElement);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -149,6 +195,14 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul
|
||||
return LOAD_CONFIG_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
const int productNumberingVersion =
|
||||
root->IntAttribute("productNumberingVersion", 1);
|
||||
const bool usesSoftwareProductNumbering =
|
||||
productNumberingVersion >= kSoftwareProductNumberingVersion;
|
||||
if (!usesSoftwareProductNumbering) {
|
||||
LOG_INFO("Migrating legacy workpiece profiles to software product numbering\n");
|
||||
}
|
||||
|
||||
// 解析摄像头列表
|
||||
ConfigXmlUtils::LoadCameraList(root, configResult.cameraList);
|
||||
|
||||
@ -181,7 +235,10 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul
|
||||
XMLElement* profileElement = algoParamsElement->FirstChildElement("WorkpieceParamGroup");
|
||||
if (profileElement) {
|
||||
while (profileElement) {
|
||||
const int workpieceNumber = profileElement->IntAttribute("workpieceNumber", 0);
|
||||
const int storedWorkpieceNumber =
|
||||
profileElement->IntAttribute("workpieceNumber", 0);
|
||||
const int workpieceNumber = ToSoftwareWorkpieceNumber(
|
||||
storedWorkpieceNumber, usesSoftwareProductNumbering);
|
||||
VrWorkpieceAlgorithmProfile* workpieceProfile =
|
||||
configResult.algorithmParams.FindWorkpieceProfile(workpieceNumber);
|
||||
if (workpieceProfile) {
|
||||
@ -265,6 +322,7 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
|
||||
|
||||
// 创建根元素
|
||||
XMLElement* root = doc.NewElement("WorkpieceHoleConfig");
|
||||
root->SetAttribute("productNumberingVersion", kSoftwareProductNumberingVersion);
|
||||
doc.InsertEndChild(root);
|
||||
|
||||
// 添加摄像头列表
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<WorkpieceHoleConfig>
|
||||
<WorkpieceHoleConfig productNumberingVersion="2">
|
||||
<Cameras>
|
||||
<Camera name="Camera1" ip="192.168.1.100"/>
|
||||
</Cameras>
|
||||
@ -10,32 +10,37 @@
|
||||
<WorkpieceParamGroup workpieceNumber="1">
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="47.0"/>
|
||||
<FilterParam continuityTh="20.0" outlierTh="5.0"/>
|
||||
<LineSegParam distScale="3.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<LineSegParam distScale="10.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="2.0" minVTypeTreeLen="2.0"/>
|
||||
<ToolParam eulerOrder="11" rotX="0.0" rotY="0.0" rotZ="0.0" offsetX="0.0" offsetY="0.0" offsetZ="0.0"/>
|
||||
</WorkpieceParamGroup>
|
||||
<WorkpieceParamGroup workpieceNumber="2">
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="47.0"/>
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="8.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="47.0"/>
|
||||
<FilterParam continuityTh="5.0" outlierTh="5.0"/>
|
||||
<LineSegParam distScale="3.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<LineSegParam distScale="10.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="2.0" minVTypeTreeLen="2.0"/>
|
||||
<ToolParam eulerOrder="11" rotX="0.0" rotY="0.0" rotZ="0.0" offsetX="0.0" offsetY="0.0" offsetZ="0.0"/>
|
||||
</WorkpieceParamGroup>
|
||||
<WorkpieceParamGroup workpieceNumber="3">
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="60.0"/>
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="28.0"/>
|
||||
<FilterParam continuityTh="5.0" outlierTh="5.0"/>
|
||||
<LineSegParam distScale="30.0" segGapTh_y="40.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="2.0" minVTypeTreeLen="2.0"/>
|
||||
<LineSegParam distScale="14.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="1.5" minVTypeTreeLen="1.5"/>
|
||||
<ToolParam eulerOrder="11" rotX="0.0" rotY="0.0" rotZ="0.0" offsetX="0.0" offsetY="0.0" offsetZ="0.0"/>
|
||||
</WorkpieceParamGroup>
|
||||
<WorkpieceParamGroup workpieceNumber="4">
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="60.0"/>
|
||||
<FilterParam continuityTh="5.0" outlierTh="5.0"/>
|
||||
<LineSegParam distScale="30.0" segGapTh_y="40.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="2.0" minVTypeTreeLen="2.0"/>
|
||||
<ToolParam eulerOrder="11" rotX="0.0" rotY="0.0" rotZ="0.0" offsetX="0.0" offsetY="0.0" offsetZ="0.0"/>
|
||||
</WorkpieceParamGroup>
|
||||
<WorkpieceParamGroup workpieceNumber="5">
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="10.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="28.0"/>
|
||||
<WorkpieceHoleParam workpieceType="0" holeDiameter="6.0" bigHoleDiameter="33.0" holeDist_L="40.0" holeDist_W="32.0" xLen="44.0" yLen="70.0" H="60.0"/>
|
||||
<FilterParam continuityTh="5.0" outlierTh="5.0"/>
|
||||
<LineSegParam distScale="3.0" segGapTh_y="15.0" segGapTh_z="0.0"/>
|
||||
<LineSegParam distScale="30.0" segGapTh_y="40.0" segGapTh_z="0.0"/>
|
||||
<GrowParam maxLineSkipNum="2" yDeviation_max="4.0" maxSkipDistance="0.0" zDeviation_max="10.0" minLTypeTreeLen="2.0" minVTypeTreeLen="2.0"/>
|
||||
<ToolParam eulerOrder="11" rotX="0.0" rotY="0.0" rotZ="0.0" offsetX="0.0" offsetY="0.0" offsetZ="0.0"/>
|
||||
</WorkpieceParamGroup>
|
||||
<PlaneCalibParams>
|
||||
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="false" planeHeight="-1.0"
|
||||
|
||||
@ -1,107 +1,107 @@
|
||||
# RK3588 螺栓测量主线路交付说明
|
||||
|
||||
## 交付定位
|
||||
|
||||
本包将原“四组图特化”实现升级为当前主线路。算法高度定义为:螺杆三维顶点沿拟合轴线,到轴线与局部支撑平面交点的距离。
|
||||
|
||||
推理后端只保留原有两条直接路线:
|
||||
|
||||
- Windows:OpenCV DNN 直接加载 ONNX。
|
||||
- RK3588:RKNN Runtime 直接加载 RKNN。
|
||||
|
||||
本包不包含、也不依赖 AI Studio Runtime 平台封装层。公共 C ABI、输出结构和函数签名保持不变。
|
||||
|
||||
## 主线算法
|
||||
|
||||
```text
|
||||
stereo rectify
|
||||
-> YOLO OBB left/right
|
||||
-> stereo ROI pairing
|
||||
-> 2D bolt centerline and top endpoint
|
||||
-> sparse stereo 3D centerline
|
||||
-> local SGBM support-plane fitting
|
||||
-> centerline/plane intersection
|
||||
-> axial bolt height and adjacent distances
|
||||
```
|
||||
|
||||
主线参数:
|
||||
|
||||
- `plane_use_sgbm: true`
|
||||
- `height_from_plane: true`
|
||||
- `plane_height_along_axis: true`
|
||||
- `plane_anchor_max_foot_gap_std_mm: 20.0`
|
||||
- `plane_sgbm_scale: 0.25`
|
||||
- `plane_max_samples: 40000`
|
||||
- YOLO input `1024x1024`, confidence `0.50`, IoU `0.45`
|
||||
|
||||
## 固定交付件
|
||||
|
||||
- 算法库:`lib/libstereo_bolt.so`
|
||||
- SHA256 `5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd`
|
||||
- AArch64 Build ID `79fbed2add268e6ceadad58e1e8f93731c844907`
|
||||
- RKNN 模型:`weights/model_20260713T084621+0000_c732327c.rknn`
|
||||
- SHA256 `b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169`
|
||||
- 配置:`config/config.rk3588.mainline.yaml`
|
||||
- SHA256 `d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de`
|
||||
- 标定:`calib/stereo_calib_20260621_202857_ascii.xml`
|
||||
- SHA256 `751e376ef074a4627f95256cac2ba8ea6b96dec2f672689fc5e3cf9b5cb205b4`
|
||||
- 公共头文件:`include/stereo_bolt/c_api.h`
|
||||
- SHA256 `61a56da934fe4ed1fb7e98a4d145aeaf1789c56a8494ccc9bec193cf9701c163`
|
||||
|
||||
## 接口兼容
|
||||
|
||||
导出的 9 个 C 符号与 `stereo_bolt_delivery_selected4_20260710` 完全一致:
|
||||
|
||||
```text
|
||||
sb_create
|
||||
sb_create_ex
|
||||
sb_default_params
|
||||
sb_destroy
|
||||
sb_last_error
|
||||
sb_free_bolt_module_result
|
||||
sb_process_bolt_module_files
|
||||
sb_process_bolt_module_buffers
|
||||
sb_range_bolt_binned
|
||||
```
|
||||
|
||||
生产集成继续使用原接口。`StereoBoltParamsC` 通过 `struct_size` 兼容较短的旧调用方结构;本次没有改变任何已发布函数签名或输出结构布局。
|
||||
|
||||
## 验证边界
|
||||
|
||||
Windows 对 `7.3` 全部 79 组图像做了真实执行:测距入口 79/79 找到结果,完整 center/top 53/79,严格精测成功 24/79。选定的 19/20/28/43 四帧均为 8 检测、8 配对、8 精测成功。详细结果见 `validation/WINDOWS_7_3_VALIDATION_SUMMARY.md`。
|
||||
|
||||
因此,本包对四帧验收集做了完整固定验收,但不能把 24/79 的全量结果表述为已经完成全场景泛化。其余失败主要为数量不符、配对失败和测量失败,仍需要后续数据/算法迭代。
|
||||
|
||||
RK3588 真机从本包独立解压执行的结果如下;两种入口均为 4/4 PASS:
|
||||
|
||||
| 帧 | 左/右检测 | 配对 | 精测 | 测距 | 显式入口代表距离/mm |
|
||||
|---:|---:|---:|---:|---:|---:|
|
||||
| 19 | 8/8 | 8 | 8 | 8 | 2306.606 |
|
||||
| 20 | 8/8 | 8 | 8 | 8 | 2286.189 |
|
||||
| 28 | 8/8 | 8 | 8 | 8 | 2554.382 |
|
||||
| 43 | 8/8 | 8 | 8 | 8 | 1890.147 |
|
||||
|
||||
真机原始输出保存在 `validation/mainline_package_reverify.log`,SHA256 为 `3faf413b9503400f3e89a8928108ec8bba1eb165d5e1160fd353edede74eb810`。
|
||||
|
||||
板端一键验收脚本同时覆盖:
|
||||
|
||||
1. `sb_create(YAML)` + 文件接口。
|
||||
2. `sb_create_ex(model bytes)` + buffer 精测接口 + 测距接口。
|
||||
3. 包内 MANIFEST 和动态依赖闭包。
|
||||
|
||||
本次脚本返回码为 0,末行已出现 `PACKAGE_REVERIFY_PASS`。
|
||||
|
||||
## 目录
|
||||
|
||||
```text
|
||||
lib/ 主算法库、RKNN 与 OpenCV 运行库
|
||||
include/stereo_bolt/c_api.h 公共 C ABI
|
||||
weights/ 主线 RKNN 模型
|
||||
calib/ 双目标定
|
||||
config/ RK3588 主线配置
|
||||
example/ 纯 C 接入示例
|
||||
validation/ 双入口验收器、日志和四组原图
|
||||
ABI_FINGERPRINT.txt ELF/ABI 指纹
|
||||
SELECTED4_PROVENANCE.txt 算法、模型、配置与构建来源
|
||||
INSTALL_3588.md 安装和复验命令
|
||||
```
|
||||
# RK3588 螺栓测量主线路交付说明
|
||||
|
||||
## 交付定位
|
||||
|
||||
本包将原“四组图特化”实现升级为当前主线路。算法高度定义为:螺杆三维顶点沿拟合轴线,到轴线与局部支撑平面交点的距离。
|
||||
|
||||
推理后端只保留原有两条直接路线:
|
||||
|
||||
- Windows:OpenCV DNN 直接加载 ONNX。
|
||||
- RK3588:RKNN Runtime 直接加载 RKNN。
|
||||
|
||||
本包不包含、也不依赖 AI Studio Runtime 平台封装层。公共 C ABI、输出结构和函数签名保持不变。
|
||||
|
||||
## 主线算法
|
||||
|
||||
```text
|
||||
stereo rectify
|
||||
-> YOLO OBB left/right
|
||||
-> stereo ROI pairing
|
||||
-> 2D bolt centerline and top endpoint
|
||||
-> sparse stereo 3D centerline
|
||||
-> local SGBM support-plane fitting
|
||||
-> centerline/plane intersection
|
||||
-> axial bolt height and adjacent distances
|
||||
```
|
||||
|
||||
主线参数:
|
||||
|
||||
- `plane_use_sgbm: true`
|
||||
- `height_from_plane: true`
|
||||
- `plane_height_along_axis: true`
|
||||
- `plane_anchor_max_foot_gap_std_mm: 20.0`
|
||||
- `plane_sgbm_scale: 0.25`
|
||||
- `plane_max_samples: 40000`
|
||||
- YOLO input `1024x1024`, confidence `0.50`, IoU `0.45`
|
||||
|
||||
## 固定交付件
|
||||
|
||||
- 算法库:`lib/libstereo_bolt.so`
|
||||
- SHA256 `5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd`
|
||||
- AArch64 Build ID `79fbed2add268e6ceadad58e1e8f93731c844907`
|
||||
- RKNN 模型:`weights/model_20260713T084621+0000_c732327c.rknn`
|
||||
- SHA256 `b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169`
|
||||
- 配置:`config/config.rk3588.mainline.yaml`
|
||||
- SHA256 `d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de`
|
||||
- 标定:`calib/stereo_calib_20260621_202857_ascii.xml`
|
||||
- SHA256 `751e376ef074a4627f95256cac2ba8ea6b96dec2f672689fc5e3cf9b5cb205b4`
|
||||
- 公共头文件:`include/stereo_bolt/c_api.h`
|
||||
- SHA256 `61a56da934fe4ed1fb7e98a4d145aeaf1789c56a8494ccc9bec193cf9701c163`
|
||||
|
||||
## 接口兼容
|
||||
|
||||
导出的 9 个 C 符号与 `stereo_bolt_delivery_selected4_20260710` 完全一致:
|
||||
|
||||
```text
|
||||
sb_create
|
||||
sb_create_ex
|
||||
sb_default_params
|
||||
sb_destroy
|
||||
sb_last_error
|
||||
sb_free_bolt_module_result
|
||||
sb_process_bolt_module_files
|
||||
sb_process_bolt_module_buffers
|
||||
sb_range_bolt_binned
|
||||
```
|
||||
|
||||
生产集成继续使用原接口。`StereoBoltParamsC` 通过 `struct_size` 兼容较短的旧调用方结构;本次没有改变任何已发布函数签名或输出结构布局。
|
||||
|
||||
## 验证边界
|
||||
|
||||
Windows 对 `7.3` 全部 79 组图像做了真实执行:测距入口 79/79 找到结果,完整 center/top 53/79,严格精测成功 24/79。选定的 19/20/28/43 四帧均为 8 检测、8 配对、8 精测成功。详细结果见 `validation/WINDOWS_7_3_VALIDATION_SUMMARY.md`。
|
||||
|
||||
因此,本包对四帧验收集做了完整固定验收,但不能把 24/79 的全量结果表述为已经完成全场景泛化。其余失败主要为数量不符、配对失败和测量失败,仍需要后续数据/算法迭代。
|
||||
|
||||
RK3588 真机从本包独立解压执行的结果如下;两种入口均为 4/4 PASS:
|
||||
|
||||
| 帧 | 左/右检测 | 配对 | 精测 | 测距 | 显式入口代表距离/mm |
|
||||
|---:|---:|---:|---:|---:|---:|
|
||||
| 19 | 8/8 | 8 | 8 | 8 | 2306.606 |
|
||||
| 20 | 8/8 | 8 | 8 | 8 | 2286.189 |
|
||||
| 28 | 8/8 | 8 | 8 | 8 | 2554.382 |
|
||||
| 43 | 8/8 | 8 | 8 | 8 | 1890.147 |
|
||||
|
||||
真机原始输出保存在 `validation/mainline_package_reverify.log`,SHA256 为 `3faf413b9503400f3e89a8928108ec8bba1eb165d5e1160fd353edede74eb810`。
|
||||
|
||||
板端一键验收脚本同时覆盖:
|
||||
|
||||
1. `sb_create(YAML)` + 文件接口。
|
||||
2. `sb_create_ex(model bytes)` + buffer 精测接口 + 测距接口。
|
||||
3. 包内 MANIFEST 和动态依赖闭包。
|
||||
|
||||
本次脚本返回码为 0,末行已出现 `PACKAGE_REVERIFY_PASS`。
|
||||
|
||||
## 目录
|
||||
|
||||
```text
|
||||
lib/ 主算法库、RKNN 与 OpenCV 运行库
|
||||
include/stereo_bolt/c_api.h 公共 C ABI
|
||||
weights/ 主线 RKNN 模型
|
||||
calib/ 双目标定
|
||||
config/ RK3588 主线配置
|
||||
example/ 纯 C 接入示例
|
||||
validation/ 双入口验收器、日志和四组原图
|
||||
ABI_FINGERPRINT.txt ELF/ABI 指纹
|
||||
SELECTED4_PROVENANCE.txt 算法、模型、配置与构建来源
|
||||
INSTALL_3588.md 安装和复验命令
|
||||
```
|
||||
|
||||
@ -1,66 +1,66 @@
|
||||
# RK3588 安装与验证
|
||||
|
||||
## 环境
|
||||
|
||||
- AArch64 RK3588
|
||||
- Ubuntu 22.04 / glibc >= 2.35
|
||||
- 可用的 RKNN NPU 驱动
|
||||
- 不需要安装系统 OpenCV;标准 SONAME 的 OpenCV 4.5.0 和 `librknnrt.so` 已包含在 `lib/`
|
||||
- 不需要 AI Studio Runtime
|
||||
|
||||
## 解包
|
||||
|
||||
```bash
|
||||
tar xzf stereo_bolt_delivery_mainline_20260714.tar.gz
|
||||
cd stereo_bolt_delivery_mainline_20260714
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib:${LD_LIBRARY_PATH:-}"
|
||||
chmod +x validation/c_example_centerline \
|
||||
validation/selected4_delivery_validate \
|
||||
validation/run_selected4.sh
|
||||
```
|
||||
|
||||
## 完整性与依赖
|
||||
|
||||
```bash
|
||||
sha256sum -c MANIFEST_SHA256.txt
|
||||
ldd lib/libstereo_bolt.so | grep "not found"
|
||||
# 第二条命令预期无输出
|
||||
```
|
||||
|
||||
关键文件预期 SHA256:
|
||||
|
||||
```text
|
||||
5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd lib/libstereo_bolt.so
|
||||
b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169 weights/model_20260713T084621+0000_c732327c.rknn
|
||||
d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de config/config.rk3588.mainline.yaml
|
||||
```
|
||||
|
||||
## 四帧板端复验
|
||||
|
||||
```bash
|
||||
bash validation/run_selected4.sh
|
||||
```
|
||||
|
||||
脚本会分别执行 YAML 文件接口和显式模型 buffer 接口,并检查 19、20、28、43 四帧。每帧应包含:
|
||||
|
||||
```text
|
||||
PRECISE ... success=1 yolo_left=8 yolo_right=8 pairs=8 bolts=8
|
||||
RANGE ... found=1 bolts=8 status=ok
|
||||
VERDICT=PASS
|
||||
```
|
||||
|
||||
最终必须出现:
|
||||
|
||||
```text
|
||||
=== PACKAGE_REVERIFY_PASS ===
|
||||
```
|
||||
|
||||
## 原接口调用
|
||||
|
||||
文件入口保持不变:
|
||||
|
||||
```c
|
||||
StereoBoltCtx* ctx = sb_create("config/config.rk3588.mainline.yaml");
|
||||
```
|
||||
|
||||
生产 buffer 入口仍使用 `sb_create_ex()`、`sb_process_bolt_module_buffers()` 和 `sb_range_bolt_binned()`;调用方只需继续包含 `include/stereo_bolt/c_api.h`。配置中的模型和标定路径均相对于解包根目录解析。
|
||||
# RK3588 安装与验证
|
||||
|
||||
## 环境
|
||||
|
||||
- AArch64 RK3588
|
||||
- Ubuntu 22.04 / glibc >= 2.35
|
||||
- 可用的 RKNN NPU 驱动
|
||||
- 不需要安装系统 OpenCV;标准 SONAME 的 OpenCV 4.5.0 和 `librknnrt.so` 已包含在 `lib/`
|
||||
- 不需要 AI Studio Runtime
|
||||
|
||||
## 解包
|
||||
|
||||
```bash
|
||||
tar xzf stereo_bolt_delivery_mainline_20260714.tar.gz
|
||||
cd stereo_bolt_delivery_mainline_20260714
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib:${LD_LIBRARY_PATH:-}"
|
||||
chmod +x validation/c_example_centerline \
|
||||
validation/selected4_delivery_validate \
|
||||
validation/run_selected4.sh
|
||||
```
|
||||
|
||||
## 完整性与依赖
|
||||
|
||||
```bash
|
||||
sha256sum -c MANIFEST_SHA256.txt
|
||||
ldd lib/libstereo_bolt.so | grep "not found"
|
||||
# 第二条命令预期无输出
|
||||
```
|
||||
|
||||
关键文件预期 SHA256:
|
||||
|
||||
```text
|
||||
5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd lib/libstereo_bolt.so
|
||||
b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169 weights/model_20260713T084621+0000_c732327c.rknn
|
||||
d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de config/config.rk3588.mainline.yaml
|
||||
```
|
||||
|
||||
## 四帧板端复验
|
||||
|
||||
```bash
|
||||
bash validation/run_selected4.sh
|
||||
```
|
||||
|
||||
脚本会分别执行 YAML 文件接口和显式模型 buffer 接口,并检查 19、20、28、43 四帧。每帧应包含:
|
||||
|
||||
```text
|
||||
PRECISE ... success=1 yolo_left=8 yolo_right=8 pairs=8 bolts=8
|
||||
RANGE ... found=1 bolts=8 status=ok
|
||||
VERDICT=PASS
|
||||
```
|
||||
|
||||
最终必须出现:
|
||||
|
||||
```text
|
||||
=== PACKAGE_REVERIFY_PASS ===
|
||||
```
|
||||
|
||||
## 原接口调用
|
||||
|
||||
文件入口保持不变:
|
||||
|
||||
```c
|
||||
StereoBoltCtx* ctx = sb_create("config/config.rk3588.mainline.yaml");
|
||||
```
|
||||
|
||||
生产 buffer 入口仍使用 `sb_create_ex()`、`sb_process_bolt_module_buffers()` 和 `sb_range_bolt_binned()`;调用方只需继续包含 `include/stereo_bolt/c_api.h`。配置中的模型和标定路径均相对于解包根目录解析。
|
||||
|
||||
@ -1,31 +1,31 @@
|
||||
# Windows 7.3 全量验证摘要
|
||||
|
||||
- 日期:2026-07-14
|
||||
- 数据:`7.3` 全部 79 组双目图像
|
||||
- 后端:OpenCV DNN 直接加载 `model_20260713T084621+0000_c732327c.onnx`
|
||||
- 阈值:confidence 0.50,期望螺栓数 8
|
||||
- 高度:三维顶点沿拟合螺杆轴线,到轴线/局部 SGBM 支撑平面交点的距离
|
||||
- 单元测试:29/29 PASS
|
||||
|
||||
## 汇总
|
||||
|
||||
| 指标 | 结果 |
|
||||
|---|---:|
|
||||
| 测距入口 found | 79/79 |
|
||||
| 完整 center/top | 53/79 |
|
||||
| 严格精测 success | 24/79 |
|
||||
| 检出数量正好为 8 | 26/79 |
|
||||
| 19/20/28/43 四帧精测 | 4/4,每帧 8 根 |
|
||||
|
||||
失败原因计数:`count_mismatch=26`、`pairing_failed=15`、`measurement_failed=14`。
|
||||
|
||||
严格成功帧:1, 2, 3, 4, 5, 6, 7, 15, 19, 20, 21, 22, 28, 29, 34, 36, 37, 43, 44, 49, 54, 60, 61, 66。
|
||||
|
||||
## 四帧轴向高度(mm)
|
||||
|
||||
- 19:188.573, 189.837, 185.532, 185.495, 179.350, 196.637, 194.677, 194.563
|
||||
- 20:193.177, 190.347, 184.390, 184.689, 190.018, 191.536, 192.582, 196.692
|
||||
- 28:189.841, 197.338, 195.061, 183.737, 175.843, 177.208, 171.662, 189.185
|
||||
- 43:209.407, 206.103, 204.961, 206.985, 196.889, 205.353, 207.920, 211.450
|
||||
|
||||
结果图和 CSV 位于 Windows 工程输出目录 `outputs/win_7_3_mainline_20260714/`。本摘要明确保留全量失败情况;四帧验收通过不等于 79 帧全量泛化已经完成。
|
||||
# Windows 7.3 全量验证摘要
|
||||
|
||||
- 日期:2026-07-14
|
||||
- 数据:`7.3` 全部 79 组双目图像
|
||||
- 后端:OpenCV DNN 直接加载 `model_20260713T084621+0000_c732327c.onnx`
|
||||
- 阈值:confidence 0.50,期望螺栓数 8
|
||||
- 高度:三维顶点沿拟合螺杆轴线,到轴线/局部 SGBM 支撑平面交点的距离
|
||||
- 单元测试:29/29 PASS
|
||||
|
||||
## 汇总
|
||||
|
||||
| 指标 | 结果 |
|
||||
|---|---:|
|
||||
| 测距入口 found | 79/79 |
|
||||
| 完整 center/top | 53/79 |
|
||||
| 严格精测 success | 24/79 |
|
||||
| 检出数量正好为 8 | 26/79 |
|
||||
| 19/20/28/43 四帧精测 | 4/4,每帧 8 根 |
|
||||
|
||||
失败原因计数:`count_mismatch=26`、`pairing_failed=15`、`measurement_failed=14`。
|
||||
|
||||
严格成功帧:1, 2, 3, 4, 5, 6, 7, 15, 19, 20, 21, 22, 28, 29, 34, 36, 37, 43, 44, 49, 54, 60, 61, 66。
|
||||
|
||||
## 四帧轴向高度(mm)
|
||||
|
||||
- 19:188.573, 189.837, 185.532, 185.495, 179.350, 196.637, 194.677, 194.563
|
||||
- 20:193.177, 190.347, 184.390, 184.689, 190.018, 191.536, 192.582, 196.692
|
||||
- 28:189.841, 197.338, 195.061, 183.737, 175.843, 177.208, 171.662, 189.185
|
||||
- 43:209.407, 206.103, 204.961, 206.985, 196.889, 205.353, 207.920, 211.450
|
||||
|
||||
结果图和 CSV 位于 Windows 工程输出目录 `outputs/win_7_3_mainline_20260714/`。本摘要明确保留全量失败情况;四帧验收通过不等于 79 帧全量泛化已经完成。
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -12,6 +12,7 @@
|
||||
#define SG_ERR_SCAN_DIR_NOT_SUPPORTED -1009
|
||||
#define SX_ERR_ZERO_OBJECTS -1010
|
||||
#define SX_ERR_NULL_GROUND_PLANE -1011
|
||||
#define SX_ERR_ZERO_2D_OBJECTS -1012
|
||||
|
||||
//BQ_workpiece
|
||||
#define SX_ERR_INVLD_VTREE_NUM -2001
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -189,7 +189,7 @@ void _outputWorkpieceInfo(char* fileName, std::vector< WD_workpieceInfo>& workpi
|
||||
int number = (int)workpiecePositioning.size();
|
||||
for (int i = 0; i < number; i++)
|
||||
{
|
||||
sprintf_s(dataStr, 250, "工件_%d", i + 1);
|
||||
sprintf_s(dataStr, 250, "工件_%d: type=%x", i + 1, workpiecePositioning[i].workpieceType);
|
||||
sw << dataStr << std::endl;
|
||||
int holeNumber = (int)workpiecePositioning[i].holes.size();
|
||||
for (int j = 0; j < holeNumber; j++)
|
||||
@ -947,11 +947,15 @@ void _XOYprojection(
|
||||
if (i == 0)
|
||||
{
|
||||
rgb = { 255, 0, 0 };
|
||||
if((holes[i].workpieceType&0xffff0000) > 0)//方向为反
|
||||
rgb = { 255, 0, 255 };
|
||||
size = int(3.0/scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
rgb = { 255, 255, 0 };
|
||||
if ((holes[i].workpieceType & 0xffff0000) > 0)//方向为反
|
||||
rgb = { 0, 255, 255 };
|
||||
size = int(3.0 / scale);
|
||||
}
|
||||
WD_workpieceInfo& a_hole = holes[i];
|
||||
@ -1569,7 +1573,7 @@ void TuoPuFa_holePosition_test3(void)
|
||||
};
|
||||
|
||||
SVzNLRange fileIdx[TPF3_TEST_GROUP] = {
|
||||
{1,10}, {1, 10}, {1,10}, {1,10}, {1, 10},
|
||||
{1,11}, {1, 10}, {1,10}, {1,10}, {1, 10},
|
||||
{1,10}, {1, 10},
|
||||
};
|
||||
|
||||
@ -1635,7 +1639,7 @@ void TuoPuFa_holePosition_test3(void)
|
||||
|
||||
for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++)
|
||||
{
|
||||
//fidx = 2;
|
||||
//fidx = 3;
|
||||
char _scan_file[256];
|
||||
sprintf_s(_scan_file, "%s%d_LaserData_Jh26B074.txt", dataPath[grp], fidx);
|
||||
std::vector<std::vector< SVzNL3DPosition>> scanLines;
|
||||
@ -2736,11 +2740,11 @@ int main()
|
||||
TuoPuFa_holePosition_test5();
|
||||
else if (keSG_拓普发孔定位_工件全测试 == testMode)
|
||||
{
|
||||
TuoPuFa_holePosition_test();
|
||||
TuoPuFa_holePosition_test2();
|
||||
//TuoPuFa_holePosition_test();
|
||||
//TuoPuFa_holePosition_test2();
|
||||
TuoPuFa_holePosition_test3();
|
||||
TuoPuFa_holePosition_test4();
|
||||
TuoPuFa_holePosition_test5();
|
||||
//TuoPuFa_holePosition_test5();
|
||||
}
|
||||
else if (keSG_华航孔定位 == testMode)
|
||||
HuaHang_holePosition_test();
|
||||
|
||||
@ -90,6 +90,9 @@ public:
|
||||
double& axialAngleMin, double& axialAngleMax) const;
|
||||
|
||||
int currentCameraIndex() const;
|
||||
bool hasAcceptableInput() const;
|
||||
// 单条目编辑场景可隐藏控件内置选择器,由外部选择器驱动 setData/getData。
|
||||
void setCameraSelectorVisible(bool visible);
|
||||
void setTargetOffsetVisible(bool visible);
|
||||
void setAxialAngleRangeVisible(bool visible);
|
||||
|
||||
@ -108,6 +111,7 @@ private:
|
||||
Ui::ToolExtrinsicWidget* ui = nullptr;
|
||||
|
||||
QGroupBox* m_groupExtrinsic;
|
||||
QLabel* m_labelCamera;
|
||||
QComboBox* m_comboCamera;
|
||||
QComboBox* m_comboEulerOrder;
|
||||
QLineEdit* m_editRotX;
|
||||
|
||||
@ -10,6 +10,7 @@ ToolExtrinsicWidget::ToolExtrinsicWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::ToolExtrinsicWidget)
|
||||
, m_groupExtrinsic(nullptr)
|
||||
, m_labelCamera(nullptr)
|
||||
, m_comboCamera(nullptr)
|
||||
, m_comboEulerOrder(nullptr)
|
||||
, m_editRotX(nullptr)
|
||||
@ -206,6 +207,29 @@ int ToolExtrinsicWidget::currentCameraIndex() const
|
||||
return m_comboCamera->currentData().toInt();
|
||||
}
|
||||
|
||||
bool ToolExtrinsicWidget::hasAcceptableInput() const
|
||||
{
|
||||
const auto isAcceptable = [](const QLineEdit* edit) {
|
||||
return !edit || edit->hasAcceptableInput();
|
||||
};
|
||||
|
||||
return m_comboEulerOrder && m_comboEulerOrder->currentIndex() >= 0
|
||||
&& isAcceptable(m_editRotX)
|
||||
&& isAcceptable(m_editRotY)
|
||||
&& isAcceptable(m_editRotZ)
|
||||
&& isAcceptable(m_editAxialAngleMin)
|
||||
&& isAcceptable(m_editAxialAngleMax)
|
||||
&& isAcceptable(m_editOffsetX)
|
||||
&& isAcceptable(m_editOffsetY)
|
||||
&& isAcceptable(m_editOffsetZ);
|
||||
}
|
||||
|
||||
void ToolExtrinsicWidget::setCameraSelectorVisible(bool visible)
|
||||
{
|
||||
if (m_labelCamera) m_labelCamera->setVisible(visible);
|
||||
if (m_comboCamera) m_comboCamera->setVisible(visible);
|
||||
}
|
||||
|
||||
void ToolExtrinsicWidget::setTargetOffsetVisible(bool visible)
|
||||
{
|
||||
if (m_labelTargetOffset) m_labelTargetOffset->setVisible(visible);
|
||||
@ -332,6 +356,7 @@ void ToolExtrinsicWidget::setupUI()
|
||||
}
|
||||
|
||||
m_groupExtrinsic = ui->groupExtrinsic;
|
||||
m_labelCamera = ui->labelCamera;
|
||||
m_comboCamera = ui->comboCamera;
|
||||
m_comboEulerOrder = ui->comboEulerOrder;
|
||||
m_editRotX = ui->editRotX;
|
||||
|
||||
2
Device
2
Device
@ -1 +1 @@
|
||||
Subproject commit 76d33caae885f1b47eec264f65f8e5f76cc131d0
|
||||
Subproject commit 1136a52250ae2595291f231492c770b29fe3354f
|
||||
@ -17,7 +17,7 @@
|
||||
| 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 |
|
||||
| 12 | 螺杆定位 | ScrewPosition | 1.2.4.1 |
|
||||
| 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 |
|
||||
| 14 | 工件孔定位 | WorkpieceHole | 1.2.1.1 |
|
||||
| 14 | 工件孔定位 | WorkpieceHole | 1.2.2.2 |
|
||||
| 15 | 轮胎孔定位 | TireHolePose | 1.0.2.1 |
|
||||
| 16 | 坑孔定位 | HolePitPosition | 无 |
|
||||
| 17 | 钢筋焊缝定位 | RodWeldSeam | 1.0.0.1 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user