螺栓的更新算法
This commit is contained in:
parent
00de98b6be
commit
d3e6b4d0f3
@ -39,6 +39,7 @@ public:
|
||||
void SetStatusReceiver(IYDroneScrewCtrlStatus* receiver) { m_pReceiver = receiver; }
|
||||
|
||||
void ApplyConfig(const DroneScrewCtrlConfigResult& cfg);
|
||||
void UpdateDisplayOptions(const DroneScrewDisplayOption& options);
|
||||
|
||||
int InitApp();
|
||||
void DeinitApp();
|
||||
|
||||
@ -181,6 +181,11 @@ void DroneScrewCtrlPresenter::ApplyConfig(const DroneScrewCtrlConfigResult& cfg)
|
||||
}
|
||||
}
|
||||
|
||||
void DroneScrewCtrlPresenter::UpdateDisplayOptions(const DroneScrewDisplayOption& options)
|
||||
{
|
||||
m_cfg.display = options;
|
||||
}
|
||||
|
||||
int DroneScrewCtrlPresenter::InitApp()
|
||||
{
|
||||
if (m_bRunning.load()) return 0;
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
#define DRONESCREWCTRL_APP_NAME "无人机螺杆控制端"
|
||||
#define DRONESCREWCTRL_VERSION_STRING "1.0.0"
|
||||
#define DRONESCREWCTRL_BUILD_STRING "2"
|
||||
#define DRONESCREWCTRL_FULL_VERSION_STRING "V1.0.0_2"
|
||||
#define DRONESCREWCTRL_BUILD_STRING "3"
|
||||
#define DRONESCREWCTRL_FULL_VERSION_STRING "V1.0.0_3"
|
||||
|
||||
inline const char* GetDroneScrewCtrlFullVersion() { return DRONESCREWCTRL_FULL_VERSION_STRING; }
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
#include "dialogalgoarg.h"
|
||||
#include "ui_dialogalgoarg.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDoubleValidator>
|
||||
#include <QFormLayout>
|
||||
#include <QIntValidator>
|
||||
#include <QLabel>
|
||||
|
||||
DialogAlgoArg::DialogAlgoArg(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
@ -17,6 +20,7 @@ DialogAlgoArg::DialogAlgoArg(QWidget* parent)
|
||||
ui->edit_width->setValidator(new QIntValidator(0, 8192, this));
|
||||
ui->edit_height->setValidator(new QIntValidator(0, 8192, this));
|
||||
ui->edit_modelType->setValidator(new QIntValidator(0, 10, this));
|
||||
createDisplayRows();
|
||||
}
|
||||
|
||||
DialogAlgoArg::~DialogAlgoArg()
|
||||
@ -35,6 +39,17 @@ DroneScrewAlgoUiParams DialogAlgoArg::GetParams() const
|
||||
return m_params;
|
||||
}
|
||||
|
||||
void DialogAlgoArg::SetDisplayOptions(const DroneScrewDisplayOption& options)
|
||||
{
|
||||
m_displayOptions = options;
|
||||
loadDisplayToUi(options);
|
||||
}
|
||||
|
||||
DroneScrewDisplayOption DialogAlgoArg::GetDisplayOptions() const
|
||||
{
|
||||
return m_displayOptions;
|
||||
}
|
||||
|
||||
void DialogAlgoArg::loadToUi(const DroneScrewAlgoUiParams& p)
|
||||
{
|
||||
if (ui->edit_score) ui->edit_score->setText(QString::number(p.scoreThreshold, 'f', 2));
|
||||
@ -57,9 +72,39 @@ DroneScrewAlgoUiParams DialogAlgoArg::collectFromUi() const
|
||||
return p;
|
||||
}
|
||||
|
||||
void DialogAlgoArg::createDisplayRows()
|
||||
{
|
||||
if (!ui->fl_main)
|
||||
return;
|
||||
|
||||
m_checkDrawBoxes = new QCheckBox(QStringLiteral("显示"), this);
|
||||
m_checkDrawBoxes->setObjectName(QStringLiteral("check_drawBoxes"));
|
||||
m_checkDrawBoxes->setMinimumHeight(40);
|
||||
m_checkDrawBoxes->setStyleSheet(QStringLiteral(
|
||||
"QCheckBox { color: rgb(239, 241, 245); font-size: 18px; background: none; }"));
|
||||
|
||||
ui->fl_main->addRow(new QLabel(QStringLiteral("显示结果框"), this),
|
||||
m_checkDrawBoxes);
|
||||
}
|
||||
|
||||
void DialogAlgoArg::loadDisplayToUi(const DroneScrewDisplayOption& options)
|
||||
{
|
||||
if (m_checkDrawBoxes)
|
||||
m_checkDrawBoxes->setChecked(options.drawBoxes);
|
||||
}
|
||||
|
||||
DroneScrewDisplayOption DialogAlgoArg::collectDisplayFromUi() const
|
||||
{
|
||||
DroneScrewDisplayOption options = m_displayOptions;
|
||||
if (m_checkDrawBoxes)
|
||||
options.drawBoxes = m_checkDrawBoxes->isChecked();
|
||||
return options;
|
||||
}
|
||||
|
||||
void DialogAlgoArg::on_btn_ok_clicked()
|
||||
{
|
||||
m_params = collectFromUi();
|
||||
m_displayOptions = collectDisplayFromUi();
|
||||
accept();
|
||||
}
|
||||
|
||||
@ -71,10 +116,14 @@ void DialogAlgoArg::on_btn_cancel_clicked()
|
||||
void DialogAlgoArg::on_btn_apply_clicked()
|
||||
{
|
||||
m_params = collectFromUi();
|
||||
m_displayOptions = collectDisplayFromUi();
|
||||
}
|
||||
|
||||
void DialogAlgoArg::on_btn_reset_clicked()
|
||||
{
|
||||
DroneScrewAlgoUiParams p; // 默认值
|
||||
loadToUi(p);
|
||||
DroneScrewDisplayOption options = m_displayOptions;
|
||||
options.drawBoxes = true;
|
||||
loadDisplayToUi(options);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "IVrConfig.h"
|
||||
|
||||
namespace Ui { class DialogAlgoArg; }
|
||||
class QCheckBox;
|
||||
|
||||
class DialogAlgoArg : public QDialog
|
||||
{
|
||||
@ -16,6 +17,8 @@ public:
|
||||
|
||||
void SetParams(const DroneScrewAlgoUiParams& params);
|
||||
DroneScrewAlgoUiParams GetParams() const;
|
||||
void SetDisplayOptions(const DroneScrewDisplayOption& options);
|
||||
DroneScrewDisplayOption GetDisplayOptions() const;
|
||||
|
||||
private slots:
|
||||
void on_btn_ok_clicked();
|
||||
@ -26,10 +29,15 @@ private slots:
|
||||
private:
|
||||
void loadToUi(const DroneScrewAlgoUiParams& p);
|
||||
DroneScrewAlgoUiParams collectFromUi() const;
|
||||
void createDisplayRows();
|
||||
void loadDisplayToUi(const DroneScrewDisplayOption& options);
|
||||
DroneScrewDisplayOption collectDisplayFromUi() const;
|
||||
|
||||
private:
|
||||
Ui::DialogAlgoArg* ui{nullptr};
|
||||
DroneScrewAlgoUiParams m_params;
|
||||
DroneScrewDisplayOption m_displayOptions;
|
||||
QCheckBox* m_checkDrawBoxes{nullptr};
|
||||
};
|
||||
|
||||
#endif // DRONESCREWCTRL_DIALOG_ALGOARG_H
|
||||
|
||||
@ -671,12 +671,16 @@ void MainWindow::on_btn_algo_config_clicked()
|
||||
{
|
||||
dlg.SetParams(cfg.algo);
|
||||
}
|
||||
dlg.SetDisplayOptions(cfg.display);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted)
|
||||
{
|
||||
cfg.algo = dlg.GetParams();
|
||||
cfg.display = dlg.GetDisplayOptions();
|
||||
ConfigManager::Instance().SetConfig(cfg);
|
||||
ConfigManager::Instance().Save();
|
||||
m_maxResultItems = cfg.display.maxResultListItems;
|
||||
if (m_pPresenter) m_pPresenter->UpdateDisplayOptions(cfg.display);
|
||||
if (m_pPresenter) m_pPresenter->PushAlgoParams(cfg.algo);
|
||||
appendLog(QStringLiteral("已应用算法参数"));
|
||||
}
|
||||
|
||||
@ -9,9 +9,9 @@ namespace
|
||||
void setResultTextStyle(Ui::ResultItem* ui)
|
||||
{
|
||||
const QString titleStyle =
|
||||
QStringLiteral("color: rgb(239, 241, 245); background-color: rgb(37, 38, 42); font-size: 16px; font-weight: 600;");
|
||||
QStringLiteral("color: rgb(239, 241, 245); background-color: rgb(37, 38, 42); font-size: 20px; font-weight: 600;");
|
||||
const QString valueStyle =
|
||||
QStringLiteral("color: rgb(255, 255, 255); background-color: rgb(37, 38, 42); font-size: 20px; font-weight: 700;");
|
||||
QStringLiteral("color: rgb(255, 255, 255); background-color: rgb(37, 38, 42); font-size: 26px; font-weight: 700;");
|
||||
|
||||
if (ui->lb_id_t) ui->lb_id_t->setStyleSheet(titleStyle);
|
||||
if (ui->lb_time_t) ui->lb_time_t->setStyleSheet(titleStyle);
|
||||
@ -34,9 +34,7 @@ QString failedText(const CtrlDetectionFrame& frame)
|
||||
QString oneDistanceText(const CtrlDetectionDistance& d)
|
||||
{
|
||||
const double meters = d.distanceMm / 1000.0;
|
||||
if (d.fromId < 0)
|
||||
return QStringLiteral("整体距离: %1m").arg(meters, 0, 'f', 3);
|
||||
return QStringLiteral("目标%1距离: %2m").arg(d.fromId + 1).arg(meters, 0, 'f', 3);
|
||||
return QStringLiteral("%1m").arg(meters, 0, 'f', 3);
|
||||
}
|
||||
|
||||
QString distanceText(const CtrlDetectionFrame& frame, int targetIndex)
|
||||
@ -58,7 +56,7 @@ QString distanceIndexText(const CtrlDetectionFrame& frame, int targetIndex)
|
||||
if (targetIndex >= 0 && targetIndex < total)
|
||||
{
|
||||
const CtrlDetectionDistance& d = frame.distances[static_cast<size_t>(targetIndex)];
|
||||
return QString::number(d.toId);
|
||||
return QString::number(d.toId + 1);
|
||||
}
|
||||
return QStringLiteral("-");
|
||||
}
|
||||
@ -113,7 +111,14 @@ void ResultItem::setResultData(int targetIndex,
|
||||
{
|
||||
const bool precisionMode = (mode == DisplayMode::Precision);
|
||||
setResultTextStyle(ui);
|
||||
setMinimumHeight(precisionMode ? 92 : 82);
|
||||
setMinimumHeight(precisionMode ? 104 : 108);
|
||||
|
||||
if (ui->lb_id_t)
|
||||
ui->lb_id_t->setText(QStringLiteral("ID"));
|
||||
if (ui->lb_status_t)
|
||||
ui->lb_status_t->setText(mode == DisplayMode::Distance
|
||||
? QStringLiteral("距离")
|
||||
: QStringLiteral("状态"));
|
||||
|
||||
if (ui->label_id)
|
||||
{
|
||||
|
||||
@ -15,9 +15,13 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef DRONESCREW_STEREO_BOLT_DIRECT_LINK
|
||||
#include <opencv2/calib3d.hpp>
|
||||
#include <opencv2/core.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -28,6 +32,65 @@ constexpr int kDefaultYoloImgSize = 960;
|
||||
constexpr int kDefaultExpectedBoltCount = 8;
|
||||
constexpr int kMaxRangeBolts = 64;
|
||||
|
||||
cv::Mat mat64(int rows, int cols, const double* values)
|
||||
{
|
||||
cv::Mat m(rows, cols, CV_64F);
|
||||
std::memcpy(m.ptr<double>(0), values, sizeof(double) * rows * cols);
|
||||
return m;
|
||||
}
|
||||
|
||||
int roiSampleCount(double span)
|
||||
{
|
||||
if (span <= 1.0)
|
||||
return 2;
|
||||
return std::max(3, std::min(17, static_cast<int>(std::ceil(span / 64.0)) + 1));
|
||||
}
|
||||
|
||||
bool sampleMapBilinear(const std::vector<float>& mapX,
|
||||
const std::vector<float>& mapY,
|
||||
int mapWidth,
|
||||
int mapHeight,
|
||||
double rectX,
|
||||
double rectY,
|
||||
double& rawX,
|
||||
double& rawY)
|
||||
{
|
||||
if (mapWidth <= 0 || mapHeight <= 0 ||
|
||||
mapX.size() != static_cast<size_t>(mapWidth) * static_cast<size_t>(mapHeight) ||
|
||||
mapY.size() != mapX.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rectX = std::max(0.0, std::min(rectX, static_cast<double>(mapWidth - 1)));
|
||||
rectY = std::max(0.0, std::min(rectY, static_cast<double>(mapHeight - 1)));
|
||||
|
||||
const int x0 = static_cast<int>(std::floor(rectX));
|
||||
const int y0 = static_cast<int>(std::floor(rectY));
|
||||
const int x1 = std::min(x0 + 1, mapWidth - 1);
|
||||
const int y1 = std::min(y0 + 1, mapHeight - 1);
|
||||
const double tx = rectX - x0;
|
||||
const double ty = rectY - y0;
|
||||
|
||||
const size_t i00 = static_cast<size_t>(y0) * mapWidth + x0;
|
||||
const size_t i10 = static_cast<size_t>(y0) * mapWidth + x1;
|
||||
const size_t i01 = static_cast<size_t>(y1) * mapWidth + x0;
|
||||
const size_t i11 = static_cast<size_t>(y1) * mapWidth + x1;
|
||||
|
||||
const double xTop = static_cast<double>(mapX[i00]) * (1.0 - tx) +
|
||||
static_cast<double>(mapX[i10]) * tx;
|
||||
const double xBottom = static_cast<double>(mapX[i01]) * (1.0 - tx) +
|
||||
static_cast<double>(mapX[i11]) * tx;
|
||||
const double yTop = static_cast<double>(mapY[i00]) * (1.0 - tx) +
|
||||
static_cast<double>(mapY[i10]) * tx;
|
||||
const double yBottom = static_cast<double>(mapY[i01]) * (1.0 - tx) +
|
||||
static_cast<double>(mapY[i11]) * tx;
|
||||
|
||||
rawX = xTop * (1.0 - ty) + xBottom * ty;
|
||||
rawY = yTop * (1.0 - ty) + yBottom * ty;
|
||||
return std::isfinite(rawX) && std::isfinite(rawY);
|
||||
}
|
||||
|
||||
void appendUniqueDir(QStringList& dirs, const QString& dir)
|
||||
{
|
||||
if (dir.isEmpty())
|
||||
@ -325,18 +388,6 @@ bool validateInput(const DroneScrewInputImage& leftImage,
|
||||
return true;
|
||||
}
|
||||
|
||||
void appendRoiBox(const StereoBoltModuleRoiC& roi, DroneScrewResult& result)
|
||||
{
|
||||
DroneScrewBox box;
|
||||
box.classId = roi.class_id;
|
||||
box.score = static_cast<float>(roi.score);
|
||||
box.x = roi.x;
|
||||
box.y = roi.y;
|
||||
box.width = roi.width;
|
||||
box.height = roi.height;
|
||||
result.boxes.push_back(box);
|
||||
}
|
||||
|
||||
void appendDistance(const StereoBoltModuleDistanceC& distance, DroneScrewResult& result)
|
||||
{
|
||||
DroneScrewDistance d;
|
||||
@ -452,6 +503,203 @@ DroneScrewAlgoStub::~DroneScrewAlgoStub()
|
||||
UnInit();
|
||||
}
|
||||
|
||||
bool DroneScrewAlgoStub::buildRectifiedLeftToRawMap(const StereoBoltCalibC& calib,
|
||||
double rectAlpha,
|
||||
std::string& error)
|
||||
{
|
||||
m_rectMapWidth = 0;
|
||||
m_rectMapHeight = 0;
|
||||
m_leftRectToRawX.clear();
|
||||
m_leftRectToRawY.clear();
|
||||
|
||||
if (calib.image_width <= 0 || calib.image_height <= 0)
|
||||
{
|
||||
error = "invalid calibration image size for rectified-to-raw map";
|
||||
return false;
|
||||
}
|
||||
if (!std::isfinite(rectAlpha))
|
||||
{
|
||||
error = "invalid rectification alpha for rectified-to-raw map";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const cv::Size imageSize(calib.image_width, calib.image_height);
|
||||
const cv::Mat K1 = mat64(3, 3, calib.left_K);
|
||||
const cv::Mat D1 = mat64(1, 5, calib.left_D);
|
||||
const cv::Mat K2 = mat64(3, 3, calib.right_K);
|
||||
const cv::Mat D2 = mat64(1, 5, calib.right_D);
|
||||
const cv::Mat R = mat64(3, 3, calib.R);
|
||||
const cv::Mat T = mat64(3, 1, calib.T);
|
||||
|
||||
cv::Mat R1;
|
||||
cv::Mat R2;
|
||||
cv::Mat P1;
|
||||
cv::Mat P2;
|
||||
cv::Mat Q;
|
||||
cv::Rect validRoi1;
|
||||
cv::Rect validRoi2;
|
||||
cv::stereoRectify(K1, D1, K2, D2, imageSize, R, T,
|
||||
R1, R2, P1, P2, Q,
|
||||
cv::CALIB_ZERO_DISPARITY,
|
||||
rectAlpha,
|
||||
imageSize,
|
||||
&validRoi1,
|
||||
&validRoi2);
|
||||
|
||||
cv::Mat mapX;
|
||||
cv::Mat mapY;
|
||||
cv::initUndistortRectifyMap(K1, D1, R1, P1, imageSize,
|
||||
CV_32FC1, mapX, mapY);
|
||||
|
||||
if (mapX.empty() || mapY.empty() ||
|
||||
mapX.cols != imageSize.width || mapX.rows != imageSize.height ||
|
||||
mapY.cols != imageSize.width || mapY.rows != imageSize.height)
|
||||
{
|
||||
error = "OpenCV returned invalid rectified-to-raw map";
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t total = static_cast<size_t>(imageSize.width) *
|
||||
static_cast<size_t>(imageSize.height);
|
||||
m_leftRectToRawX.resize(total);
|
||||
m_leftRectToRawY.resize(total);
|
||||
for (int y = 0; y < imageSize.height; ++y)
|
||||
{
|
||||
const float* srcX = mapX.ptr<float>(y);
|
||||
const float* srcY = mapY.ptr<float>(y);
|
||||
const size_t offset = static_cast<size_t>(y) * imageSize.width;
|
||||
std::copy(srcX, srcX + imageSize.width, m_leftRectToRawX.begin() + offset);
|
||||
std::copy(srcY, srcY + imageSize.width, m_leftRectToRawY.begin() + offset);
|
||||
}
|
||||
|
||||
m_calib = calib;
|
||||
m_rectAlpha = rectAlpha;
|
||||
m_rectMapWidth = imageSize.width;
|
||||
m_rectMapHeight = imageSize.height;
|
||||
return true;
|
||||
}
|
||||
catch (const cv::Exception& e)
|
||||
{
|
||||
error = std::string("OpenCV rectified-to-raw map failed: ") + e.what();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
error = std::string("rectified-to-raw map failed: ") + e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "rectified-to-raw map failed: unknown exception";
|
||||
}
|
||||
|
||||
m_rectMapWidth = 0;
|
||||
m_rectMapHeight = 0;
|
||||
m_leftRectToRawX.clear();
|
||||
m_leftRectToRawY.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DroneScrewAlgoStub::mapRectifiedLeftRoi(const StereoBoltModuleRoiC& roi,
|
||||
int rawWidth,
|
||||
int rawHeight,
|
||||
DroneScrewBox& box) const
|
||||
{
|
||||
if (roi.width <= 0 || roi.height <= 0)
|
||||
return false;
|
||||
if (m_rectMapWidth <= 0 || m_rectMapHeight <= 0 ||
|
||||
m_leftRectToRawX.empty() || m_leftRectToRawY.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int dstWidth = rawWidth > 0 ? rawWidth : m_rectMapWidth;
|
||||
const int dstHeight = rawHeight > 0 ? rawHeight : m_rectMapHeight;
|
||||
if (dstWidth <= 0 || dstHeight <= 0)
|
||||
return false;
|
||||
|
||||
const double rectLeft = static_cast<double>(roi.x);
|
||||
const double rectTop = static_cast<double>(roi.y);
|
||||
const double rectRight = static_cast<double>(roi.x) + static_cast<double>(roi.width) - 1.0;
|
||||
const double rectBottom = static_cast<double>(roi.y) + static_cast<double>(roi.height) - 1.0;
|
||||
if (rectRight < 0.0 || rectBottom < 0.0 ||
|
||||
rectLeft > static_cast<double>(m_rectMapWidth - 1) ||
|
||||
rectTop > static_cast<double>(m_rectMapHeight - 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double minRawX = std::numeric_limits<double>::infinity();
|
||||
double minRawY = std::numeric_limits<double>::infinity();
|
||||
double maxRawX = -std::numeric_limits<double>::infinity();
|
||||
double maxRawY = -std::numeric_limits<double>::infinity();
|
||||
bool hasSample = false;
|
||||
|
||||
const int samplesX = roiSampleCount(rectRight - rectLeft);
|
||||
const int samplesY = roiSampleCount(rectBottom - rectTop);
|
||||
for (int sy = 0; sy < samplesY; ++sy)
|
||||
{
|
||||
const double ty = (samplesY <= 1) ? 0.0 : static_cast<double>(sy) / (samplesY - 1);
|
||||
const double rectY = rectTop + (rectBottom - rectTop) * ty;
|
||||
for (int sx = 0; sx < samplesX; ++sx)
|
||||
{
|
||||
const double tx = (samplesX <= 1) ? 0.0 : static_cast<double>(sx) / (samplesX - 1);
|
||||
const double rectX = rectLeft + (rectRight - rectLeft) * tx;
|
||||
|
||||
double rawX = 0.0;
|
||||
double rawY = 0.0;
|
||||
if (!sampleMapBilinear(m_leftRectToRawX, m_leftRectToRawY,
|
||||
m_rectMapWidth, m_rectMapHeight,
|
||||
rectX, rectY, rawX, rawY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
minRawX = std::min(minRawX, rawX);
|
||||
minRawY = std::min(minRawY, rawY);
|
||||
maxRawX = std::max(maxRawX, rawX);
|
||||
maxRawY = std::max(maxRawY, rawY);
|
||||
hasSample = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSample)
|
||||
return false;
|
||||
if (maxRawX < 0.0 || maxRawY < 0.0 ||
|
||||
minRawX > static_cast<double>(dstWidth - 1) ||
|
||||
minRawY > static_cast<double>(dstHeight - 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int left = std::max(0, std::min(dstWidth - 1,
|
||||
static_cast<int>(std::floor(minRawX))));
|
||||
const int top = std::max(0, std::min(dstHeight - 1,
|
||||
static_cast<int>(std::floor(minRawY))));
|
||||
const int right = std::max(0, std::min(dstWidth - 1,
|
||||
static_cast<int>(std::ceil(maxRawX))));
|
||||
const int bottom = std::max(0, std::min(dstHeight - 1,
|
||||
static_cast<int>(std::ceil(maxRawY))));
|
||||
if (right < left || bottom < top)
|
||||
return false;
|
||||
|
||||
box.classId = roi.class_id;
|
||||
box.score = static_cast<float>(roi.score);
|
||||
box.x = left;
|
||||
box.y = top;
|
||||
box.width = right - left + 1;
|
||||
box.height = bottom - top + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DroneScrewAlgoStub::appendRoiBox(const StereoBoltModuleRoiC& roi,
|
||||
DroneScrewResult& result) const
|
||||
{
|
||||
DroneScrewBox box;
|
||||
if (mapRectifiedLeftRoi(roi, result.imageWidth, result.imageHeight, box))
|
||||
result.boxes.push_back(box);
|
||||
}
|
||||
|
||||
int DroneScrewAlgoStub::Init(const DroneScrewAlgoParams& params)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_mutex);
|
||||
@ -464,6 +712,10 @@ int DroneScrewAlgoStub::Init(const DroneScrewAlgoParams& params)
|
||||
sb_destroy(m_ctx);
|
||||
m_ctx = nullptr;
|
||||
}
|
||||
m_rectMapWidth = 0;
|
||||
m_rectMapHeight = 0;
|
||||
m_leftRectToRawX.clear();
|
||||
m_leftRectToRawY.clear();
|
||||
|
||||
QStringList configBases;
|
||||
appendUniqueDir(configBases, QDir::currentPath());
|
||||
@ -536,10 +788,22 @@ int DroneScrewAlgoStub::Init(const DroneScrewAlgoParams& params)
|
||||
algoParams.rng_lrc_trusted_mm =
|
||||
yamlDouble(yaml, "ranging.lrc_trusted_mm", algoParams.rng_lrc_trusted_mm);
|
||||
|
||||
if (!buildRectifiedLeftToRawMap(calib, algoParams.rect_alpha, loadError))
|
||||
{
|
||||
m_initError = std::string("stereo_bolt build bbox rectified-to-raw map failed calib=") +
|
||||
calibPath.toStdString() + " err=" + loadError;
|
||||
LOG_ERROR("[ALGO] %s\n", m_initError.c_str());
|
||||
return ERR_CODE(DEV_OPEN_ERR);
|
||||
}
|
||||
|
||||
m_ctx = sb_create_ex(&calib, &model, &algoParams);
|
||||
|
||||
if (!m_ctx)
|
||||
{
|
||||
m_rectMapWidth = 0;
|
||||
m_rectMapHeight = 0;
|
||||
m_leftRectToRawX.clear();
|
||||
m_leftRectToRawY.clear();
|
||||
const char* err = sb_last_error(nullptr);
|
||||
m_initError = std::string("stereo_bolt create_ex failed config=") +
|
||||
configPath.toStdString() +
|
||||
@ -578,6 +842,10 @@ int DroneScrewAlgoStub::UnInit()
|
||||
sb_destroy(m_ctx);
|
||||
m_ctx = nullptr;
|
||||
}
|
||||
m_rectMapWidth = 0;
|
||||
m_rectMapHeight = 0;
|
||||
m_leftRectToRawX.clear();
|
||||
m_leftRectToRawY.clear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -37,12 +37,32 @@ public:
|
||||
std::string GetVersion() const override;
|
||||
|
||||
private:
|
||||
#ifdef DRONESCREW_STEREO_BOLT_DIRECT_LINK
|
||||
bool buildRectifiedLeftToRawMap(const StereoBoltCalibC& calib,
|
||||
double rectAlpha,
|
||||
std::string& error);
|
||||
bool mapRectifiedLeftRoi(const StereoBoltModuleRoiC& roi,
|
||||
int rawWidth,
|
||||
int rawHeight,
|
||||
DroneScrewBox& box) const;
|
||||
void appendRoiBox(const StereoBoltModuleRoiC& roi,
|
||||
DroneScrewResult& result) const;
|
||||
#endif
|
||||
|
||||
DroneScrewAlgoParams m_params;
|
||||
StereoBoltCtx* m_ctx{nullptr};
|
||||
std::string m_configPath;
|
||||
std::string m_calibPath;
|
||||
std::string m_modelPath;
|
||||
std::string m_initError;
|
||||
#ifdef DRONESCREW_STEREO_BOLT_DIRECT_LINK
|
||||
StereoBoltCalibC m_calib{};
|
||||
double m_rectAlpha{1.0};
|
||||
int m_rectMapWidth{0};
|
||||
int m_rectMapHeight{0};
|
||||
std::vector<float> m_leftRectToRawX;
|
||||
std::vector<float> m_leftRectToRawY;
|
||||
#endif
|
||||
mutable std::mutex m_mutex;
|
||||
std::atomic<bool> m_bInited{false};
|
||||
};
|
||||
|
||||
@ -103,7 +103,7 @@ win32:CONFIG(debug, debug|release) {
|
||||
OPENCV450_LIB_DIR = $$PWD/../../../SDK/OpenCV450/Arm/aarch64/lib
|
||||
INCLUDEPATH += $$PWD/../../../SDK/OpenCV450/Arm/aarch64/include/opencv4
|
||||
LIBS += -L$${OPENCV450_LIB_DIR}
|
||||
LIBS += -lopencv_imgcodecs -lopencv_imgproc -lopencv_core
|
||||
LIBS += -lopencv_imgcodecs -lopencv_imgproc -lopencv_calib3d -lopencv_core
|
||||
QMAKE_LFLAGS += -Wl,-rpath-link,$${OPENCV450_LIB_DIR}
|
||||
|
||||
# MVS SDK
|
||||
|
||||
@ -67,7 +67,9 @@ constexpr int64_t kDistanceDecimationVertical = 4;
|
||||
constexpr int kDetectPipelinePrecision = 0;
|
||||
constexpr int kDetectPipelineDistance = 1;
|
||||
constexpr const char* kImageSaveRootDir = "/home/cat";
|
||||
constexpr size_t kMaxImageSaveQueueDepth = 16;
|
||||
constexpr size_t kMaxImageSaveQueueDepth = 100;
|
||||
constexpr size_t kImageSaveWorkerCount = 4;
|
||||
constexpr int kDistanceImageSaveStride = 3;
|
||||
|
||||
const char* mvsSdkErrorName(int code)
|
||||
{
|
||||
@ -1812,9 +1814,9 @@ int DroneScrewServerPresenter::stopDetectionWork()
|
||||
|
||||
stopGpioTriggerLoop();
|
||||
m_bThreadExit = true;
|
||||
stopImageSaveThread();
|
||||
if (m_detectThread.joinable())
|
||||
m_detectThread.join();
|
||||
stopImageSaveThread();
|
||||
m_bIsDetecting = false;
|
||||
m_detectPipelineMode = kDetectPipelinePrecision;
|
||||
m_activeTriggerFps = static_cast<int>(kPrecisionFrameRate);
|
||||
@ -1933,9 +1935,9 @@ int DroneScrewServerPresenter::stopLiveStream()
|
||||
m_bRtspStarted = false;
|
||||
stopGpioTriggerLoop();
|
||||
m_bThreadExit = true;
|
||||
stopImageSaveThread();
|
||||
if (m_detectThread.joinable())
|
||||
m_detectThread.join();
|
||||
stopImageSaveThread();
|
||||
m_bIsDetecting = false;
|
||||
m_detectPipelineMode = kDetectPipelinePrecision;
|
||||
m_activeTriggerFps = static_cast<int>(kPrecisionFrameRate);
|
||||
@ -2356,28 +2358,51 @@ void DroneScrewServerPresenter::startImageSaveThread(const QString& modeName)
|
||||
m_imageSaveThreadExit = false;
|
||||
}
|
||||
|
||||
m_imageSaveThread = std::thread(&DroneScrewServerPresenter::imageSaveThreadFunc, this);
|
||||
LOG_INFO("[SAVE] image save thread started mode=%s dir=%s\n",
|
||||
modeName.toStdString().c_str(), sessionDir.toStdString().c_str());
|
||||
m_imageSaveThreads.reserve(kImageSaveWorkerCount);
|
||||
for (size_t i = 0; i < kImageSaveWorkerCount; ++i)
|
||||
{
|
||||
m_imageSaveThreads.emplace_back(&DroneScrewServerPresenter::imageSaveThreadFunc,
|
||||
this,
|
||||
static_cast<int>(i));
|
||||
}
|
||||
LOG_INFO("[SAVE] image save workers started count=%zu mode=%s dir=%s\n",
|
||||
m_imageSaveThreads.size(),
|
||||
modeName.toStdString().c_str(),
|
||||
sessionDir.toStdString().c_str());
|
||||
}
|
||||
|
||||
void DroneScrewServerPresenter::stopImageSaveThread()
|
||||
{
|
||||
const bool shouldJoin = m_imageSaveThread.joinable();
|
||||
const bool shouldJoin = !m_imageSaveThreads.empty();
|
||||
size_t droppedPending = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_imageSaveMutex);
|
||||
m_imageSaveQueue.clear();
|
||||
if (!shouldJoin)
|
||||
{
|
||||
m_imageSaveQueue.clear();
|
||||
m_imageSaveSessionDir.clear();
|
||||
m_imageSaveThreadExit = false;
|
||||
return;
|
||||
}
|
||||
droppedPending = m_imageSaveQueue.size();
|
||||
if (droppedPending > 0)
|
||||
m_imageSaveQueue.clear();
|
||||
m_imageSaveThreadExit = true;
|
||||
}
|
||||
|
||||
if (droppedPending > 0)
|
||||
{
|
||||
LOG_WARN("[SAVE] stop requested, drop pending save jobs=%zu\n",
|
||||
droppedPending);
|
||||
}
|
||||
|
||||
m_imageSaveCv.notify_all();
|
||||
m_imageSaveThread.join();
|
||||
for (std::thread& worker : m_imageSaveThreads)
|
||||
{
|
||||
if (worker.joinable())
|
||||
worker.join();
|
||||
}
|
||||
m_imageSaveThreads.clear();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_imageSaveMutex);
|
||||
@ -2457,9 +2482,9 @@ void DroneScrewServerPresenter::enqueueImageSave(const MvsImageData& leftImg,
|
||||
m_imageSaveCv.notify_one();
|
||||
}
|
||||
|
||||
void DroneScrewServerPresenter::imageSaveThreadFunc()
|
||||
void DroneScrewServerPresenter::imageSaveThreadFunc(int workerIndex)
|
||||
{
|
||||
LOG_DEBUG("[SAVE] worker entered\n");
|
||||
LOG_DEBUG("[SAVE] worker #%d entered\n", workerIndex);
|
||||
for (;;)
|
||||
{
|
||||
ImageSaveJob job;
|
||||
@ -2470,10 +2495,15 @@ void DroneScrewServerPresenter::imageSaveThreadFunc()
|
||||
});
|
||||
|
||||
if (m_imageSaveThreadExit.load())
|
||||
{
|
||||
m_imageSaveQueue.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_imageSaveQueue.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
job = std::move(m_imageSaveQueue.front());
|
||||
m_imageSaveQueue.pop_front();
|
||||
@ -2502,9 +2532,11 @@ void DroneScrewServerPresenter::imageSaveThreadFunc()
|
||||
};
|
||||
|
||||
saveMono8(job.left, QStringLiteral("left"));
|
||||
if (m_imageSaveThreadExit.load())
|
||||
break;
|
||||
saveMono8(job.right, QStringLiteral("right"));
|
||||
}
|
||||
LOG_DEBUG("[SAVE] worker stopped\n");
|
||||
LOG_DEBUG("[SAVE] worker #%d stopped\n", workerIndex);
|
||||
}
|
||||
|
||||
DroneScrewResult DroneScrewServerPresenter::runSingleDetection()
|
||||
@ -2800,6 +2832,7 @@ void DroneScrewServerPresenter::detectThreadFunc()
|
||||
}
|
||||
|
||||
int frameCnt = 0;
|
||||
unsigned long long savedFrameCnt = 0;
|
||||
bool haveProcessedFrame = false;
|
||||
unsigned long long lastProcessedLeftFrameId = 0;
|
||||
unsigned long long lastProcessedRightFrameId = 0;
|
||||
@ -2875,7 +2908,12 @@ void DroneScrewServerPresenter::detectThreadFunc()
|
||||
haveProcessedFrame = true;
|
||||
lastProcessedLeftFrameId = leftImg.frameID;
|
||||
lastProcessedRightFrameId = rightImg.frameID;
|
||||
enqueueImageSave(leftImg, rightImg, static_cast<unsigned long long>(frameCnt + 1));
|
||||
const bool distanceSaveMode =
|
||||
(m_detectPipelineMode.load() == kDetectPipelineDistance);
|
||||
const bool shouldSaveFrame =
|
||||
!distanceSaveMode || ((frameCnt % kDistanceImageSaveStride) == 0);
|
||||
if (shouldSaveFrame)
|
||||
enqueueImageSave(leftImg, rightImg, ++savedFrameCnt);
|
||||
|
||||
// Publish the display frame before detection work. detectMode only controls raw transport.
|
||||
if (m_bRawPubEnabled.load())
|
||||
|
||||
@ -236,7 +236,7 @@ private:
|
||||
unsigned int timeoutMs);
|
||||
void startImageSaveThread(const QString& modeName);
|
||||
void stopImageSaveThread();
|
||||
void imageSaveThreadFunc();
|
||||
void imageSaveThreadFunc(int workerIndex);
|
||||
void enqueueImageSave(const MvsImageData& leftImg,
|
||||
const MvsImageData& rightImg,
|
||||
unsigned long long index);
|
||||
@ -344,7 +344,7 @@ private:
|
||||
bool m_bRightImageReady{false};
|
||||
|
||||
// 异步图像保存
|
||||
std::thread m_imageSaveThread;
|
||||
std::vector<std::thread> m_imageSaveThreads;
|
||||
std::atomic<bool> m_imageSaveThreadExit{false};
|
||||
std::mutex m_imageSaveMutex;
|
||||
std::condition_variable m_imageSaveCv;
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
#define DRONESCREWSERVER_COMPANY_NAME "VisionTech"
|
||||
#define DRONESCREWSERVER_COPYRIGHT "Copyright (C) 2026"
|
||||
#define DRONESCREWSERVER_VERSION_STRING "1.0.0"
|
||||
#define DRONESCREWSERVER_VERSION_BUILD "2"
|
||||
#define DRONESCREWSERVER_VERSION_BUILD "3"
|
||||
|
||||
#endif // DRONESCREW_VERSION_H
|
||||
|
||||
@ -1,13 +1,38 @@
|
||||
# ABI fingerprint — self-contained delivery
|
||||
# OpenCV 4.5.0 (source build, standard SONAME — no Debian 'd' suffix)
|
||||
#
|
||||
# Target: aarch64, Ubuntu 22.04, glibc >= 2.32, RK3588 NPU driver
|
||||
# Bundled: OpenCV 4.5.0 + librknnrt 2.3.2, total 9 libs in lib/
|
||||
# SONAME: libopencv_core.so.4.5 (NOT .4.5d)
|
||||
# System: libc / libpthread / libm / libdl / libstdc++ / libgcc_s
|
||||
#
|
||||
# Install:
|
||||
# tar xzf stereo_bolt_delivery_selfcontained.tar.gz
|
||||
# cd stereo_bolt_delivery
|
||||
# export LD_LIBRARY_PATH=/e/02_Modules/StereoVisionBoltMeasurement_cpp/lib:
|
||||
# ldd lib/libstereo_bolt.so | grep "not found" # should be empty
|
||||
# ABI fingerprint of libstereo_bolt.so
|
||||
# Your board MUST match these or the .so will not load / not run.
|
||||
# generated: 2026-07-01 (rebuilt on RK3588; OBB-axis ranging stability fix)
|
||||
# libstereo_bolt.so SHA256:
|
||||
# 67d9406c1ab69c36b8f3b363fe6cf38567fdac97c25fa160e4c80a4d63b172ae
|
||||
|
||||
[arch]
|
||||
AArch64
|
||||
|
||||
[exported symbols] (9 total: lifecycle + precise + ranging)
|
||||
# --- lifecycle / construction ---
|
||||
sb_create # create from a config.yaml path (reads files from disk)
|
||||
sb_create_ex # NEW: create from in-memory structs (zero file IO / no path / no perms)
|
||||
sb_default_params # NEW: prefilled StereoBoltParamsC for sb_create_ex
|
||||
sb_destroy
|
||||
sb_last_error
|
||||
sb_free_bolt_module_result
|
||||
# --- precise measurement (12MP) ---
|
||||
sb_process_bolt_module_files # precise, file variant
|
||||
sb_process_bolt_module_buffers # precise, in-memory buffer variant
|
||||
# --- binned ranging (3MP) ---
|
||||
sb_range_bolt_binned # binned ranging, in-memory buffer
|
||||
|
||||
[min glibc required] (your board's glibc must be >= this)
|
||||
GLIBC_2.32
|
||||
|
||||
[NEEDED shared libraries] (provide matching sonames at runtime)
|
||||
librknnrt.so
|
||||
libopencv_calib3d.so.4.5
|
||||
libopencv_imgcodecs.so.4.5
|
||||
libopencv_dnn.so.4.5
|
||||
libopencv_imgproc.so.4.5
|
||||
libopencv_core.so.4.5
|
||||
libstdc++.so.6
|
||||
libm.so.6
|
||||
libgcc_s.so.1
|
||||
libc.so.6
|
||||
ld-linux-aarch64.so.1
|
||||
|
||||
67
AppAlgo/stereo_bolt_delivery/CHANGELOG.txt
Normal file
67
AppAlgo/stereo_bolt_delivery/CHANGELOG.txt
Normal file
@ -0,0 +1,67 @@
|
||||
CHANGELOG — libstereo_bolt delivery package
|
||||
============================================
|
||||
|
||||
## 2026-07-01 v3 — stable OBB-axis ranging summary
|
||||
|
||||
Current delivery contents:
|
||||
- Model: best_obb_1024.rknn (OBB, imgsz 1024, RKNN)
|
||||
- Precise measurement and ranging share the same OBB model.
|
||||
- Runtime libraries are bundled in lib/: libstereo_bolt.so, OpenCV 4.5.0, librknnrt.so.
|
||||
- No target-board apt install is required for runtime OpenCV.
|
||||
|
||||
Algorithm:
|
||||
- Ranging now uses left/right OBB-axis pairing before the NCC fallback.
|
||||
- Even-count OBB candidates no longer average two different pair hypotheses.
|
||||
The summary distance chooses the center candidate with the more stable sampled
|
||||
axis disparity, removing the RK3588 1498/1652 mm jump.
|
||||
|
||||
Performance / validation:
|
||||
- RK3588, distance_153304_403, 21 binned frames:
|
||||
found=21/21, distance mean=1652.360 mm, min=1652.091, max=1652.582,
|
||||
std=0.149 mm, elapsed mean=153.146 ms.
|
||||
- Windows ctest: 22/22 pass.
|
||||
|
||||
## 2026-06-30 v2 — OBB model + plane skip optimization
|
||||
|
||||
Changes vs previous delivery (2026-06-24, v1):
|
||||
|
||||
### Model
|
||||
- Previous package used an HBB detector at imgsz 960.
|
||||
- Current package uses best_obb_1024.rknn (OBB, imgsz 1024, YOLOv8 oriented bbox).
|
||||
- OBB model trained on mixed 5MP + 12MP data (2026-06-29)
|
||||
- Auto-detection: library detects OBB vs HBB from RKNN output shape
|
||||
|
||||
### Config
|
||||
- yolo.task: "auto" (auto-detect OBB/HBB from model output)
|
||||
- yolo.imgsz: 960 -> 1024
|
||||
- yolo.conf: 0.5 (unchanged)
|
||||
- measurement.height_from_plane: false (height = top-to-foot along axis)
|
||||
- working_distance.z_min_mm: 1200 -> 900
|
||||
- working_distance.z_max_mm: 2800 -> 4000
|
||||
- ranging.z_min_mm: 1300 -> 900
|
||||
- ranging.z_max_mm: 3600 -> 4000
|
||||
|
||||
### Algorithm
|
||||
- Plane computation skipped when height_from_plane=false
|
||||
(saves 300-550ms per frame on 8-bolt scenes)
|
||||
- OBB auto-detection in RKNN backend
|
||||
- OBB centerline rescue + peer height consensus clamp
|
||||
|
||||
### Calibration
|
||||
- Unchanged: stereo_calib_20260621_202857_ascii.xml (baseline 482mm)
|
||||
|
||||
### C API
|
||||
- ABI unchanged: c_api.h has zero changes
|
||||
- All function signatures identical
|
||||
- Struct layouts identical
|
||||
- Drop-in .so replacement, no recompile needed for downstream
|
||||
|
||||
### Performance (RK3588 board, 8-bolt scene, height_from_plane=false)
|
||||
- Precise: ~400ms/frame (was ~800ms with plane enabled)
|
||||
- Ranging: superseded by v3 benchmark above
|
||||
|
||||
### Verified on
|
||||
- RK3588 board (linaro): 0621 dataset 21 frames, 12/21 success
|
||||
- 0627 dataset 10 frames: 10/10 success
|
||||
- 0628 dataset 10 frames: 10/10 success
|
||||
- Windows ctest at the time: 20/20 pass
|
||||
@ -1,166 +1,226 @@
|
||||
# libstereo_bolt 交付说明(.so + 头文件)
|
||||
|
||||
你拿到的是**预编译的 aarch64 动态库 + C 头文件**,不含算法源码。
|
||||
OpenCV 4.5.4、RKNN runtime 及全部传递依赖(约 131 个 `.so`)**已随包内置于 `lib/`**,
|
||||
无需在目标板上安装任何软件包(NPU 内核驱动由固件提供,不在用户态包内)。
|
||||
你拿到的是**预编译的 aarch64 动态库 + C 头文件 + 模型 + 标定 + 示例**,不含算法源码。
|
||||
运行时依赖中 **`libstereo_bolt.so`、OpenCV 4.5.0 `.so`、`librknnrt.so` 均已随包 `lib/`**;无需 apt 安装 OpenCV。NPU 驱动由板子系统提供。
|
||||
|
||||
> **本包标定 = 12MP 生产相机(4096×3000)。** 喂进来的左右图必须是该相机的 **4096×3000** 帧
|
||||
> (`config` 里 `image.width=4096 / height=3000` 已配好,指向 `calib/stereo_calib_20260607_211135.xml`)。
|
||||
> **包内不含测试图** —— 请用你自己这套 12MP 相机拍的左右图。
|
||||
> ✅ **已在板上实测**:本 12MP 标定**加载 + rectify 通过**(4096×3000,stereo_rms=0.373、baseline=200.19、f_rect=3330)。
|
||||
> ⚠️ 但**检测 + 测量在该相机上还没用真实螺杆图验过**(包内无螺杆图)。
|
||||
> ⚠️ 本标定单相机 RMS=**0.373**(5MP 那套是 0.19),已把 `calibration.max_single_rms` **放宽到 0.40** 才放行;
|
||||
> 0.373 偏松会折损测量精度,建议后续**重标到 <0.20** 再收回此闸。首次上机务必用真实螺杆图核对结果。
|
||||
> **本库导出两个独立的产品接口**(同一个 `.so`、同一个 `StereoBoltCtx`):
|
||||
> 1. **精测(PRECISE)** `sb_process_bolt_module_buffers` / `_files` —— 12MP 高精度测量,
|
||||
> 每颗螺栓高度 / 相邻间距 / 地平面 / 3D 中心轴 / 顶底 3D 坐标。**触发式"测得准"路径(板上约 400ms/帧,依输入和 QC 耗时波动)。**
|
||||
> 2. **测距(RANGING)** `sb_range_bolt_binned` —— 2×2 binned(3MP) 实时测距,
|
||||
> 输出相机→螺栓代表直线距离。**无人机实时路径(统一用 OBB@1024,当前 distance_153304_403 复测约 153ms/帧)。**
|
||||
>
|
||||
> **创建上下文推荐用内存接口 `sb_create_ex`**(见下节):标定、模型、参数全部经接口/buffer 传入,
|
||||
> 库**零文件 IO、无路径假设、无安装目录读权限问题**。旧的 `sb_create(config.yaml)` 仍保留作向后兼容。
|
||||
|
||||
---
|
||||
|
||||
## ★ 创建上下文:`sb_create_ex`(推荐)vs `sb_create`(兼容)
|
||||
|
||||
两个产品接口共用同一个 `StereoBoltCtx`。创建它有两种方式:
|
||||
|
||||
### 推荐:`sb_create_ex` —— 全参数经接口传入,零文件 IO
|
||||
```c
|
||||
StereoBoltCtx* sb_create_ex(const StereoBoltCalibC* calib,
|
||||
const StereoBoltModelC* model,
|
||||
const StereoBoltParamsC* params);
|
||||
StereoBoltParamsC sb_default_params(void); /* 预填默认值,再覆盖你关心的 */
|
||||
```
|
||||
- `StereoBoltCalibC`:标定 —— 左右 `K[9]`/`D[5]` + `R[9]`/`T[3]` + `baseline_mm` + 图像尺寸。**替代标定 XML。**
|
||||
- `StereoBoltModelC`:模型 —— `.rknn` 字节流 `data`+`size` + `backend`(rk3588 用 `SB_YOLO_BACKEND_RKNN`) + `imgsz`/`conf`/`iou`。**替代 weights 文件路径。**
|
||||
- `StereoBoltParamsC`:算法数值参数 —— 视差带(`wd_z_min/max_mm` 物理带,**或** `sgbm_min/num_disparities` 固定带,二选一必填)、`rectification`、`measurement.foot_rim`、`ranging.*`。**替代 config.yaml 数值键。** 带 `struct_size` 做前向兼容。
|
||||
|
||||
**这条路径库不碰磁盘**:标定/模型从哪来由你决定(你的标定流程、资源文件、相机 SDK);本包提供的 `calib/*.xml` 与 `weights/*.rknn` 只是**给你读进来填结构体**的现成数据。
|
||||
|
||||
### 兼容:`sb_create(const char* config_yaml_path)`
|
||||
读 `config.rk3588.12mp.yaml`(其内部再按相对路径找标定 XML + 模型)。**有两文件依赖 + 工作目录/读权限要求**——正是 `sb_create_ex` 要规避的。仅在离线/快速测试时用。
|
||||
|
||||
> `sb_create` / `sb_create_ex` **只调一次**(载配置+标定+模型进 NPU,慢),之后每帧复用同一个 `ctx`。
|
||||
> `sb_destroy` / `sb_last_error` 两接口共用。线程安全:同一 `ctx` 别多线程并发。
|
||||
|
||||
---
|
||||
|
||||
## 交付边界(职责划分)
|
||||
|
||||
**本交付物是一个"算法模块",不是完整软件系统。** 软件总体集成由你(接入方)负责。
|
||||
|
||||
| 项 | 由谁负责 |
|
||||
|---|---|
|
||||
| 双目螺栓**测量/测距算法**(`.so` + `.h` + 模型 + 标定 + 示例 + 文档) | **算法方(本包提供)** |
|
||||
| 板子**运行时环境**:aarch64 + glibc、NPU 驱动;算法依赖的 OpenCV 与 `librknnrt.so` 已随包 `lib/` | **接入方** |
|
||||
| **取图**(相机驱动 / Mono8 buffer 喂入)、上层业务、UI、调度、与上位机/无人机通信 | **接入方** |
|
||||
| **标定数据来源**(填入 `StereoBoltCalibC`)、按 `c_api.h` 调用、对 height/distance/confidence 做业务决策 | **接入方** |
|
||||
|
||||
> 算法方只交付"喂进左右图 → 返回螺栓高度/距离"的纯算法能力(C ABI)。本库不碰相机、不碰网络、
|
||||
> **不碰磁盘(`sb_create_ex` + buffer 接口)**、不做悬停/对中等业务决策。逐颗 `confidence`(trusted/suspect) + `status` 由接入方取舍。
|
||||
|
||||
## 包内容
|
||||
| 路径 | 说明 |
|
||||
|---|---|
|
||||
| `lib/libstereo_bolt.so` | 预编译共享库(aarch64) |
|
||||
| `lib/librknnrt.so` 等(共 ~131 个) | OpenCV 4.5.4 + 全部传递依赖(**已随包,无需 apt**) |
|
||||
| `lib/libstereo_bolt.so` | 预编译共享库(aarch64,含 `sb_create_ex` 内存接口) |
|
||||
| `include/stereo_bolt/c_api.h` | C ABI 头文件(唯一对外接口) |
|
||||
| `weights/best_1280_20260621.rknn` | NPU 模型(精测 + 测距共用,imgsz 1280) |
|
||||
| `calib/stereo_calib_20260621_202857_ascii.xml` | 出厂标定(0621 装配,baseline 482 mm) |
|
||||
| `config/config.rk3588.12mp.yaml` | 配置文件(兼容 `sb_create` 接口用) |
|
||||
| `example/` | 最小 C 示例(精测 + 测距) + CMakeLists |
|
||||
| `INSTALL_3588.md` | 安装与运行说明(**从这里开始**) |
|
||||
| `DEPENDENCIES.md` | 依赖库详细清单及说明 |
|
||||
| `ABI_FINGERPRINT.txt` | 二进制 ABI 指纹 |
|
||||
| `weights/best_obb_1024.rknn` | NPU 模型 —— OBB 检测(精测+测距共用,imgsz 1024,FP16) |
|
||||
| `calib/stereo_calib_20260621_202857_ascii.xml` | 12MP 双目标定(**0621 装配 baseline 482mm**,Halcon 位姿已修正) |
|
||||
| `config/config.rk3588.12mp.yaml` | 仅 `sb_create`(兼容)用 + 作参数参考;**`sb_create_ex` 不需要它** |
|
||||
| `lib/*.so` | 算法库、OpenCV 4.5.0 运行库、RKNN 运行时(aarch64 2.3.2),运行时通过 `LD_LIBRARY_PATH=./lib` 加载 |
|
||||
| `INSTALL_3588.md` / `ABI_FINGERPRINT.txt` | 板上装环境说明 / .so 二进制指纹(**你的环境必须匹配**) |
|
||||
| `example/` | C 调用示例(`main.c` 精测,**sb_create + buffer 接口**)+ CMakeLists |
|
||||
|
||||
---
|
||||
|
||||
## 你什么时候需要什么(编译期 vs 运行期)
|
||||
## 编译期 vs 运行期
|
||||
|
||||
**重点:你不需要重编这个库**(它是预编译好的成品,你也没有源码)。你只编译*你自己*
|
||||
调用它的小程序,那一步几乎零依赖。OpenCV / librknnrt 是**运行那一刻**才需要的。
|
||||
**你不需要重编这个库**(预编译成品,无源码)。你只编译*你自己*调用它的程序。
|
||||
|
||||
| 东西 | 编译你自己的调用程序 | 在板上**运行** |
|
||||
| 东西 | 编译你的调用程序 | 板上运行 |
|
||||
|---|:---:|:---:|
|
||||
| `c_api.h` | ✅ | — |
|
||||
| `libstereo_bolt.so` | ✅(链接,带 `--allow-shlib-undefined`) | ✅ |
|
||||
| OpenCV 4.5.4 | ❌ 不需要(C ABI 不暴露 cv 类型) | ✅ **已随包 `lib/`,无需安装** |
|
||||
| `librknnrt.so`(RKNN 运行时) | ❌ | ✅ **已随包 `lib/`** |
|
||||
| OpenCV | 你的程序:❌ 不需要(C ABI 不暴露 cv 类型,相机直接给 Mono8 buffer)<br>本包 example:❌ 运行不需要系统 OpenCV | ✅ 已随包 `lib/`,无需 apt |
|
||||
| `librknnrt.so` | ❌ | ✅ 已随包 `lib/`,`LD_LIBRARY_PATH=./lib` |
|
||||
| NPU 驱动 + `/dev/rknpu` | ❌ | ✅ 必须(内核态) |
|
||||
| `.rknn` 模型 + 标定 XML + `config` | ❌ | ✅ 必须(**已在本包** weights/ config/ calib/) |
|
||||
| 模型 / 标定 | ❌ | ✅(已在本包;`sb_create_ex` 由你读进来传,`sb_create` 由库读) |
|
||||
|
||||
> **“YOLO 依赖”在板上很轻**:就是 `librknnrt.so` + NPU 驱动 + `.rknn` 文件。
|
||||
> **不需要** Python / PyTorch / ultralytics / ONNXRuntime / rknn-toolkit2 —— 那些是 PC 上把
|
||||
> `.onnx` 转成 `.rknn` 用的,转完板上就用不到。
|
||||
> example 用 OpenCV 只是为了独立跑(读本包的标定 XML + 测试图)。**你的产品里相机直接给 Mono8 buffer、
|
||||
> 标定来自你自己的来源,调用端无需 OpenCV,库也始终零文件 IO。**
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 必须匹配的 ABI 指纹(动态链接的硬约束)
|
||||
## ⚠️ 必须匹配的 ABI 指纹
|
||||
|
||||
这个 `.so` 在某个具体环境里编出来,二进制里**写死了**它依赖的 so 名字和 glibc 版本。
|
||||
**“自己准备 OpenCV” 不等于随便哪个 OpenCV 都行——必须是指纹里那个 major.minor。**
|
||||
`.so` 二进制里写死了依赖的 so 名字和 glibc 版本。打开 `ABI_FINGERPRINT.txt` 确保板子满足:
|
||||
- CPU = **aarch64**;**glibc ≥ 指纹版本**(GLIBC_2.32);
|
||||
- **运行时优先使用本包 `lib/`**(内含标准 SONAME `libopencv_*.so.4.5`,无需系统 OpenCV);
|
||||
- **librknnrt 版本** = 转 `.rknn` 的 toolkit 版本(**2.3.x**)。
|
||||
- 指纹应列出 9 个导出符号,含 **`sb_create_ex` / `sb_default_params`**。
|
||||
|
||||
打开 `ABI_FINGERPRINT.txt`,确保你的板子满足:
|
||||
|
||||
- CPU 架构 = **aarch64**
|
||||
- **glibc ≥ 指纹里的版本**(低于它,加载即 `GLIBC_x.xx not found`)
|
||||
- **OpenCV soname 与指纹一致**(本包指纹 = `libopencv_core.so.4.5d` ⇒ 装 **OpenCV 4.5.x**;其它版本 soname 不通用)
|
||||
- **librknnrt 版本** = 转换 `.rknn` 所用 toolkit 版本(**2.3.x**)
|
||||
|
||||
一条命令自检(应当**没有任何** `not found`):
|
||||
|
||||
```bash
|
||||
ldd lib/libstereo_bolt.so | grep "not found"
|
||||
```
|
||||
自检(应无任何 `not found`):`ldd lib/libstereo_bolt.so | grep "not found"`
|
||||
|
||||
---
|
||||
|
||||
## 板子上需要准备的
|
||||
## 公共错误码
|
||||
|
||||
| 依赖 | 状态 |
|
||||
|---|---|
|
||||
| OpenCV 4.5.4 + 传递依赖(131 个 .so) | ✅ **已随包 `lib/`**,无需 apt |
|
||||
| `librknnrt.so`(RKNN 运行时 2.3.2) | ✅ **已随包 `lib/`** |
|
||||
| `.rknn` 模型 + 标定 XML + config | ✅ **已随包** `weights/` `calib/` `config/` |
|
||||
| NPU 内核驱动 + `/dev/rknpu` | ⚠️ **板子固件提供**,出厂 RK3588 一般已有 |
|
||||
| glibc ≥ 2.32(Ubuntu 22.04 自带 2.35) | ✅ 系统自带,无需操作 |
|
||||
|
||||
> 完整依赖说明见 `DEPENDENCIES.md`。
|
||||
`SB_OK=0` / `SB_ERR_INVALID_CONFIG=-1` / `SB_ERR_IO=-2` / `SB_ERR_ALGORITHM_REJECTED=-3` / `SB_ERR_INTERNAL=-99`。
|
||||
> `SB_ERR_ALGORITHM_REJECTED` 不是崩溃,是算法判定该帧不满足 `expected` 颗(仅精测返回);**仍返回已测出的部分螺栓**(见下)。
|
||||
|
||||
---
|
||||
|
||||
## 怎么用(C ABI)
|
||||
## 接口 1 —— 精测(PRECISE)
|
||||
|
||||
完整示例见 `example/main.c`。有**两个**喂帧入口,生产用内存版:
|
||||
**用途**:12MP 逐颗高精度测量。触发式"测得准"路径,板上 ~2.5Hz@1024。
|
||||
**输入**:内存 Mono8 buffer(生产首选)或文件路径(离线)。buffer 尺寸**必须等于标定尺寸**,否则 `SB_ERR_IO`。
|
||||
|
||||
**① 生产路径 —— 内存版(不落盘,直接喂相机帧):**
|
||||
```c
|
||||
StereoBoltCtx* ctx = sb_create("config/config.rk3588.yaml"); // 一次:载配置/标定/RKNN 模型
|
||||
while (抓帧) {
|
||||
const uint8_t* L = camera_left_frame(); // 相机 SDK 给的 Mono8 缓冲(W*H 字节,在内存)
|
||||
const uint8_t* R = camera_right_frame();
|
||||
StereoBoltModuleResultC out;
|
||||
sb_process_bolt_module_buffers(ctx,
|
||||
L, W, H, 0, // stride=0 表示行紧凑排列(每行 W 字节)
|
||||
R, W, H, 0,
|
||||
1, // channels: 1=Mono8(灰度), 3=BGR 也接受(内部转灰度)
|
||||
8, &out); // 8 = 期望螺栓数
|
||||
/* out.success==1: out.data.bolts[i].height_mm / foot_xyz / top_xyz + adjacent_distances + ground_plane
|
||||
out.success==0: out.failure.reason + left_rois/right_rois/pairs(诊断用) */
|
||||
sb_free_bolt_module_result(&out);
|
||||
}
|
||||
sb_destroy(ctx); // 一次
|
||||
sb_status_t sb_process_bolt_module_buffers(
|
||||
StereoBoltCtx* ctx,
|
||||
const uint8_t* left_data, int left_width, int left_height, int left_stride_bytes,
|
||||
const uint8_t* right_data, int right_width, int right_height, int right_stride_bytes,
|
||||
int channels, int expected_bolt_count, StereoBoltModuleResultC* out);
|
||||
sb_status_t sb_process_bolt_module_files(StereoBoltCtx*, const char* left, const char* right,
|
||||
int expected_bolt_count, StereoBoltModuleResultC* out);
|
||||
void sb_free_bolt_module_result(StereoBoltModuleResultC* out); /* 用完必须释放 */
|
||||
```
|
||||
> `W`/`H` **必须等于标定分辨率**(`config` 的 `image.width`/`image.height`),否则返回 `SB_ERR_IO`。
|
||||
> 内存版**全程不碰磁盘**(已实测:删掉源文件后内存版照常出结果)。
|
||||
|
||||
**② 离线/测试路径 —— 文件版(读磁盘图,用你自己的 12MP 左右图):**
|
||||
**输出 `StereoBoltModuleResultC`** —— 每颗 `data.bolts[i]`:
|
||||
- **`height_mm`**(高度)
|
||||
- **`top_xyz` / `foot_xyz`**(顶 / 底 3D 坐标)
|
||||
- **`centerline`**(3D 中心轴:`point` + `direction`)
|
||||
- `left_roi` / `right_roi`(左右图 2D 框 x,y,w,h,score)
|
||||
- **`confidence`**(0=trusted / 1=suspect)+ `status`
|
||||
另含 `adjacent_distances[]`(相邻 `distance_mm`)、`ground_plane`(`model[4]`/`rms_mm`/`inlier_count`)。
|
||||
|
||||
**坐标系/单位**:高度、间距、3D 坐标 = **mm**,**校正后左相机坐标系**(x 右 / y 下 / z=深度,典型 z≈1600–2200mm)。
|
||||
|
||||
**★ 部分输出契约(去 all-or-nothing)**:即使 `success==0`(数量不符 `count_mismatch`,无论检到的多还是少于 `expected`),
|
||||
`data.bolts[]` 仍含**所有干净测出的螺栓**(各带 height/top/foot/confidence);`failure`(reason + 完整 ROI + 配对状态)始终填充,能看出缺哪颗、为何缺。**别只看返回码就丢结果——照样遍历 `data.bolts[]`,按 `confidence` 自行取舍。**
|
||||
|
||||
**最简用法(`sb_create_ex`)**:
|
||||
```c
|
||||
sb_process_bolt_module_files(ctx, "your_left.bmp", "your_right.bmp", 8, &out);
|
||||
StereoBoltCalibC calib = {...}; /* 填 K/D/R/T/size/baseline */
|
||||
StereoBoltModelC model = {.data=rknn_bytes, .size=n, .backend=SB_YOLO_BACKEND_RKNN, .imgsz=1024, .conf=0.5f};
|
||||
StereoBoltParamsC p = sb_default_params(); p.wd_z_min_mm=1200; p.wd_z_max_mm=2800;
|
||||
StereoBoltCtx* ctx = sb_create_ex(&calib, &model, &p); /* 一次 */
|
||||
StereoBoltModuleResultC out;
|
||||
sb_process_bolt_module_buffers(ctx, L,w,h,0, R,w,h,0, 1, /*expected=*/6, &out);
|
||||
/* 遍历 out.data.bolts[i].height_mm / top_xyz / foot_xyz ... */
|
||||
sb_free_bolt_module_result(&out); sb_destroy(ctx);
|
||||
```
|
||||
> 图必须是本套 12MP 相机的 4096×3000(灰度或彩色均可),格式 .bmp/.tiff/.png。本包不含测试图。
|
||||
|
||||
错误码:`SB_OK=0` / `SB_ERR_INVALID_CONFIG=-1` / `SB_ERR_IO=-2` /
|
||||
`SB_ERR_ALGORITHM_REJECTED=-3` / `SB_ERR_INTERNAL=-99`。
|
||||
> `SB_ERR_ALGORITHM_REJECTED` 不是崩溃,是算法判定该帧不可测,`out.failure` 里有原因 + ROI + 配对状态。
|
||||
完整示例见 `example/main.c`。**imgsz 首选 1024**(OBB 模型,自动检测旋转框);交付权重 FP16(INT8 检出全废,不可用)。
|
||||
|
||||
---
|
||||
|
||||
## 输入 / 输出(契约)
|
||||
## 高度测量模式(`measurement.height_from_plane`)
|
||||
|
||||
**输入**
|
||||
- 一次性(`sb_create`):`config/config.rk3588.yaml`(内部指向 `calib/*.xml` 与 `weights/best.rknn`)。
|
||||
- 每帧(生产用 `sb_process_bolt_module_buffers`):左/右 Mono8 内存缓冲 + 宽/高/stride、通道数、期望螺栓数(int)、`&out`。
|
||||
- 每帧(离线用 `sb_process_bolt_module_files`):左图路径、右图路径、期望螺栓数(int)、`&out`。
|
||||
精测接口支持两种高度计算方式,通过 config 中 `measurement.height_from_plane` 切换:
|
||||
|
||||
**输出**(`StereoBoltModuleResultC out` + 返回码)
|
||||
- 成功 `out.success==1` → `out.data`:
|
||||
- `bolts[]` 每颗:`bolt_id` / `left_roi`,`right_roi`(x,y,width,height,score) / **`height_mm`** /
|
||||
**`foot_xyz`,`top_xyz`**(底/顶 3D 点) / `centerline`(3D 轴 = point+direction)
|
||||
- `adjacent_distances[]`:`bolt_id_i`,`bolt_id_j`,**`distance_mm`**
|
||||
- `ground_plane`:`model[4]`(ax+by+cz+d=0)、`rms_mm`、`inlier_count`
|
||||
- 失败 `out.success==0` → `out.failure`:`reason` + `left_rois[]`/`right_rois[]` + `pairs[]`(配对诊断)
|
||||
| 模式 | config 值 | 高度定义 | 是否需要平面拟合 | 板上耗时(8颗场景) |
|
||||
|---|---|---|---|---|
|
||||
| **顶到底**(默认) | `height_from_plane: false` | 螺杆顶端到可见底端沿拟合轴的长度 | **不需要**(自动跳过) | ~400ms |
|
||||
| **顶到平面** | `height_from_plane: true` | 螺杆顶端到支撑平面的法向距离 | 需要(StereoBM 拟合) | ~800ms |
|
||||
|
||||
**单位 / 坐标系**:高度、间距 = **mm**;3D 坐标(foot/top/centerline) = **mm,校正后左相机坐标系**
|
||||
(x 右 / y 下 / z=深度,典型 z≈1600–2200mm)。`score`=YOLO 置信度。
|
||||
**默认关闭平面测量**(`height_from_plane: false`),此时平面计算完全跳过,节省 300-550ms。
|
||||
|
||||
输出格式示例(**仅示意字段结构**,数值来自早先 5MP 验证机,非本包 12MP 相机):
|
||||
|
||||
```
|
||||
success=1 bolts=8 distances=8
|
||||
bolt 0 height=151.2mm top=(-191.9,-122.9,1995.3) foot=(-204.3,2.9,2078.3)
|
||||
...
|
||||
dist 0-1=268.3mm dist 1-2=254.4mm ... dist 7-0=335.8mm
|
||||
**开启平面测量**:在 `config.rk3588.12mp.yaml` 中修改:
|
||||
```yaml
|
||||
measurement:
|
||||
height_from_plane: true # 开启:高度 = 螺杆顶端到支撑平面的法向距离
|
||||
```
|
||||
|
||||
> 一句话:**入 = 一对左右图 + 期望螺栓数(配置/标定/模型初始化时给一次);
|
||||
> 出 = 每颗螺栓 高度 + 顶/底 3D + 3D 中心轴 + 相邻间距 + 地平面,或失败原因 + 诊断。**
|
||||
`sb_create_ex` 用户:当前 `StereoBoltParamsC` 不含此字段,默认为 false(顶到底)。如需平面模式,使用 `sb_create(config.yaml)` 并在 config 中设置。
|
||||
|
||||
---
|
||||
|
||||
## 编译你自己的调用程序 + 运行(**都在包根目录执行**)
|
||||
## 接口 2 —— 测距(RANGING)`sb_range_bolt_binned`
|
||||
|
||||
**用途**:实时回答"左目光心→螺栓多远"。容忍部分可见:**绝不要求螺栓数**,≥1 颗即给距离。当前板上约 153ms/帧(~6.5Hz)@1024。
|
||||
**输入**:2×2 binned 内存 buffer(12MP→3MP=2048×1500),Mono8/BGR。binned 内参**内部推导**,无需单独 binned 标定。
|
||||
|
||||
```c
|
||||
typedef struct { double distance_mm; int left_x,left_y,right_x,right_y; double ncc_score,lrc_px; int confidence; } StereoBoltRangeBoltC;
|
||||
typedef struct { int found; double distance_mm; int bolt_count; char status[32]; } StereoBoltRangeSummaryC;
|
||||
sb_status_t sb_range_bolt_binned(
|
||||
StereoBoltCtx* ctx,
|
||||
const uint8_t* left_data, int left_width, int left_height, int left_stride_bytes,
|
||||
const uint8_t* right_data, int right_width, int right_height, int right_stride_bytes,
|
||||
int channels, StereoBoltRangeSummaryC* out, StereoBoltRangeBoltC* bolts, int max_bolts);
|
||||
```
|
||||
**输出**:主用 `out.distance_mm`(可见杆代表距离);`out.found` 每帧轮询;`bolts[]` 给出每颗候选的左右匹配点。OBB 轴路线下 `ncc_score` 为检测/匹配分数、`lrc_px` 为轴视差自洽度(字段名保留用于 ABI 兼容);NCC 回退路线下为 NCC/LRC 指标。精度目标 **±50mm**(稳定区 1.3–2.4m)。
|
||||
**可选 `ranging.*` 参数**(`StereoBoltParamsC` 里 `rng_*`,都有默认):`z_min/max_mm` 视差带、`lrc_max_mm`/`lrc_trusted_mm` 物理同根门 等。完整示例见 `example/range_example.cpp`。
|
||||
|
||||
> 注:`sb_create_ex` 即使**只做测距**也要求提供精测视差带(`wd_z_*` 或 `sgbm_*`),因为同一 `ctx` 两接口共用。
|
||||
|
||||
---
|
||||
|
||||
## 该用哪个 + 板上实测吞吐
|
||||
|
||||
| | 精测 `sb_process_bolt_module_*` | 测距 `sb_range_bolt_binned` |
|
||||
|---|---|---|
|
||||
| 问题 | 每颗螺栓高度/间距/地平面/3D 坐标 | 相机离螺栓多远(代表距离) |
|
||||
| 输入 | 12MP 原图(Mono8 buffer / 文件) | 2×2 binned 3MP buffer |
|
||||
| 螺栓数 | 要 `expected`(不符=部分输出+`count_mismatch`) | **不要**,≥1 颗即 `found` |
|
||||
| 板上吞吐@1024 | **约 400ms/帧** | **约 153ms/帧 ≈ 6.5Hz** |
|
||||
| 定位 | 触发式"测得准" | 无人机实时定位反馈 |
|
||||
|
||||
> 测距实测于 RK3588(best_obb_1024.rknn,distance_153304_403,21 帧):均值 153.146ms,距离均值 1652.360mm,跨度 0.491mm。
|
||||
> 本交付为单模型 OBB@1024(精测+测距共用)。
|
||||
|
||||
---
|
||||
|
||||
## 编译你的调用程序 + 运行
|
||||
|
||||
示例用 C/C++,运行时库从本包 `lib/` 加载:
|
||||
```bash
|
||||
# 在包根目录(与 lib/ config/ calib/ 同级)编译
|
||||
cmake -S example -B example/build -DSB_ROOT="$(pwd)"
|
||||
cmake --build example/build
|
||||
cd example
|
||||
cmake -S . -B build -DSB_ROOT="$(cd .. && pwd)"
|
||||
cmake --build build # -> bolt_demo(精测)+ range_demo(测距)
|
||||
|
||||
# 在包根目录运行(config 里 calib/、weights/ 是相对路径,按当前目录解析)
|
||||
# example 走【内存接口】: 读 raw Mono8 裸数据(W*H 字节)喂 sb_process_bolt_module_buffers,
|
||||
# 库全程不见文件路径。your_left/right.raw = 你 12MP 相机帧 dump 的 Mono8(4096*3000 字节)
|
||||
LD_LIBRARY_PATH=lib:/usr/lib:$LD_LIBRARY_PATH \
|
||||
./example/build/bolt_demo config/config.rk3588.yaml your_left.raw your_right.raw 4096 3000 8
|
||||
# ../lib 含 libstereo_bolt.so + OpenCV 4.5.0 + librknnrt.so
|
||||
# 精测:传标定 XML + 模型 + 左右图 + 期望螺栓数
|
||||
LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH \
|
||||
./build/bolt_demo ../calib/stereo_calib_20260621_202857_ascii.xml \
|
||||
../weights/best_obb_1024.rknn L.bmp R.bmp 6
|
||||
|
||||
# 测距:传标定 + 模型 + 左右图(demo 内部 2×2 binning;生产直接喂相机 binned buffer)
|
||||
LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH \
|
||||
./build/range_demo ../calib/stereo_calib_20260621_202857_ascii.xml \
|
||||
../weights/best_obb_1024.rknn L.bmp R.bmp
|
||||
```
|
||||
|
||||
@ -5,74 +5,25 @@
|
||||
|
||||
---
|
||||
|
||||
## 一、包含哪些库(lib/ 目录)
|
||||
## 一、包含哪些库(lib/ 目录,共 9 个)
|
||||
|
||||
### 核心算法库(本包提供)
|
||||
| 库 | 说明 |
|
||||
|---|---|
|
||||
| `libstereo_bolt.so` | 本算法库(主体) |
|
||||
| `librknnrt.so` | RKNN NPU 推理运行时(Rockchip 2.3.2) |
|
||||
| `libopencv_core.so.4.5` | OpenCV 4.5.0 — 基础矩阵/图像容器 |
|
||||
| `libopencv_imgproc.so.4.5` | OpenCV 4.5.0 — 图像处理 |
|
||||
| `libopencv_calib3d.so.4.5` | OpenCV 4.5.0 — 立体标定、rectify、三角测量 |
|
||||
| `libopencv_imgcodecs.so.4.5` | OpenCV 4.5.0 — 图像文件读写 |
|
||||
| `libopencv_dnn.so.4.5` | OpenCV 4.5.0 — DNN 模块 |
|
||||
| `libopencv_features2d.so.4.5` | OpenCV 4.5.0 — 特征点(calib3d 依赖) |
|
||||
| `libopencv_flann.so.4.5` | OpenCV 4.5.0 — FLANN 索引(features2d 依赖) |
|
||||
|
||||
### OpenCV 4.5.4(Ubuntu 22.04 release,aarch64)
|
||||
| 库 | 用途 |
|
||||
|---|---|
|
||||
| `libopencv_core.so.4.5d` | 基础矩阵/图像容器 |
|
||||
| `libopencv_imgproc.so.4.5d` | 图像处理(滤波、边缘、几何变换) |
|
||||
| `libopencv_calib3d.so.4.5d` | 立体标定、rectify、三角测量 |
|
||||
| `libopencv_imgcodecs.so.4.5d` | 图像文件读写(bmp/png/tiff) |
|
||||
| `libopencv_dnn.so.4.5d` | DNN 模块(YOLO ONNX 推理备用后端) |
|
||||
| `libopencv_features2d.so.4.5d` | 特征点(calib3d 依赖) |
|
||||
| `libopencv_flann.so.4.5d` | FLANN 索引(features2d 依赖) |
|
||||
|
||||
### 图像编解码层(OpenCV 依赖)
|
||||
| 库 | 用途 |
|
||||
|---|---|
|
||||
| `libjpeg.so.8` | JPEG |
|
||||
| `libpng16.so.16` | PNG |
|
||||
| `libtiff.so.5` | TIFF(含 libjbig/libdeflate/liblzma/libzstd/liblz4) |
|
||||
| `libwebp.so.7` | WebP |
|
||||
| `libopenjp2.so.7` | JPEG2000 |
|
||||
| `libIlmImf-2_5.so.25` 等 | OpenEXR(IlmImf/Imath/Iex/IlmThread/Half) |
|
||||
| `libheif.so.1` 等 | HEIF/HEVC(libaom/libde265/libx265/libdav1d) |
|
||||
| `libgif.so.7` | GIF |
|
||||
| `libcharls.so.2` | JPEG-LS |
|
||||
| `libz.so.1` | zlib |
|
||||
|
||||
### 并行计算(OpenCV 依赖)
|
||||
| 库 | 用途 |
|
||||
|---|---|
|
||||
| `libtbb.so.2` | Intel TBB 线程并行 |
|
||||
|
||||
### DNN / 模型序列化(OpenCV DNN 依赖)
|
||||
| 库 | 用途 |
|
||||
|---|---|
|
||||
| `libprotobuf.so.23` | Protobuf(ONNX 模型解析) |
|
||||
|
||||
### GDAL / 地理空间(OpenCV imgcodecs 依赖,随包但运行中不主动调用)
|
||||
libgdal、libgdcm\*、libgeotiff、libproj、libgeos\*、libogdi、libspatialite、
|
||||
libnetcdf、libhdf5\*、libpq(PostgreSQL)、libmysqlclient、libsqlite3、
|
||||
libcfitsio、libkml\*、libminizip、liburiparser、libxerces-c、libfreexl、
|
||||
libarmadillo、libblas/liblapack/libgfortran、libarpack、libsuperlu、
|
||||
libqhull\_r、libfyba/libfyut/libfygm、libblosc、libsnappy、libsz/libaec、
|
||||
libmfhdfalt/libdfalt、librttopo、libxml2、libgif
|
||||
|
||||
### 字体 / PDF(libgdal → libpoppler 链)
|
||||
libpoppler、libfreetype、libbrotli\*、libfontconfig、liblcms2
|
||||
|
||||
### 网络 / 安全(libgdal → libcurl 链,随包但不主动使用)
|
||||
libcurl、libcurl-gnutls、libssl/libcrypto(OpenSSL 3)、libgnutls、
|
||||
libnettle/libhogweed/libgmp、libp11-kit、libffi、libtasn1、
|
||||
libgssapi\_krb5/libkrb5/libk5crypto/libkrb5support(Kerberos)、
|
||||
libcom\_err、libkeyutils、libnghttp2、libidn2、libunistring、
|
||||
librtmp、libssh、libpsl、libldap/liblber、libsasl2
|
||||
|
||||
### 其他系统级(随包)
|
||||
libicuuc/libicudata(Unicode)、libnss\*/libnspr4/libplc4/libplds4/libsmime3(NSS)、
|
||||
libuuid、libjson-c、libexpat、libpcre2-8、libltdl、libodbc/libodbcinst、
|
||||
libnuma、libresolv、libtirpc
|
||||
|
||||
> 上述库来自 Ubuntu 22.04 apt(OpenCV 4.5.4 的官方传递依赖闭包),均为 **release/优化版本**,
|
||||
> 非 debug 版。库数量约 **131 个**,打包后约 **70 MB**。
|
||||
> OpenCV 从源码编译(非 apt 包),**SONAME 为标准的 `libopencv_*.so.4.5`**
|
||||
> (不带 Debian/Ubuntu 的 `d` 后缀),与自行源码编译 OpenCV 4.5.x 的环境兼容。
|
||||
>
|
||||
> 图像编解码(libjpeg/libpng/libtiff/libwebp/openjpeg)、protobuf、zlib
|
||||
> 均已**静态链入** OpenCV,不再作为独立 `.so` 出现。
|
||||
|
||||
---
|
||||
|
||||
@ -86,7 +37,6 @@ libnuma、libresolv、libtirpc
|
||||
| `libpthread.so.0` | 同上(已并入 glibc 2.34+) |
|
||||
| `libm.so.6` | 同上 |
|
||||
| `libdl.so.2` | 同上 |
|
||||
| `librt.so.1` | 同上 |
|
||||
| `libstdc++.so.6` | GCC 11+(Ubuntu 22.04 自带) |
|
||||
| `libgcc_s.so.1` | 同上 |
|
||||
| `ld-linux-aarch64.so.1` | 动态链接器,系统自带 |
|
||||
@ -95,47 +45,48 @@ libnuma、libresolv、libtirpc
|
||||
|
||||
## 三、为什么这么设计
|
||||
|
||||
**问题:** OpenCV 在 Ubuntu 22.04 拖着约 131 个传递依赖(包括 GDAL、curl、Kerberos、
|
||||
OpenSSL 等)。如果只靠目标板 apt 安装,新板子需要联网、可能遇到镜像源不可达、
|
||||
版本不一致等问题,交付不可控。
|
||||
**问题:** 如果目标板通过 apt 安装 OpenCV,Debian/Ubuntu 会给 SONAME 加私有后缀
|
||||
(如 `libopencv_core.so.4.5d` 而非标准的 `…4.5`),导致与自行编译的 OpenCV 不兼容;
|
||||
另外 apt 安装需要联网,新板子部署不可控。
|
||||
|
||||
**解法:** 把整个依赖闭包随包发布。运行时通过 `LD_LIBRARY_PATH` 优先搜索 `lib/`,
|
||||
所有依赖在包内自洽。这些库只对本进程生效,**不替换系统库**,不影响系统
|
||||
ssl/网络栈/其他程序。
|
||||
**解法:** 从 OpenCV 4.5.0 官方源码编译,只构建算法实际需要的 7 个模块
|
||||
(core/imgproc/calib3d/imgcodecs/dnn/features2d/flann),图像编解码等第三方库
|
||||
全部静态链入。最终 `lib/` 只有 9 个 `.so`(含本库 + librknnrt),约 **22 MB**,
|
||||
依赖关系极简,无需联网、无需 apt。
|
||||
|
||||
---
|
||||
|
||||
## 四、运行时加载方式
|
||||
|
||||
```bash
|
||||
# 解包后,设一次环境变量,之后所有调用本库的程序都能找到这些依赖
|
||||
# 解包后,设一次环境变量
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
# 验证:应当没有任何 "not found" 输出
|
||||
ldd lib/libstereo_bolt.so | grep "not found"
|
||||
```
|
||||
|
||||
`LD_LIBRARY_PATH` 对所有传递依赖生效——当 `libopencv_core` 加载 `libjpeg` 时,
|
||||
链接器同样优先搜索 `lib/`,无需对每个 `.so` 单独处理。
|
||||
---
|
||||
|
||||
## 五、SONAME 兼容性说明
|
||||
|
||||
| 编译来源 | SONAME | 与本包兼容? |
|
||||
|---|---|---|
|
||||
| **本包(源码编译 4.5.0)** | `libopencv_core.so.4.5` | ✅ |
|
||||
| **自行源码编译 4.5.x** | `libopencv_core.so.4.5` | ✅ |
|
||||
| **Ubuntu apt `libopencv-dev`** | `libopencv_core.so.4.5d` | ❌ SONAME 不匹配 |
|
||||
|
||||
本包的 `libstereo_bolt.so` 链接的 NEEDED 是标准 `libopencv_*.so.4.5`(无 `d`),
|
||||
与任何从源码编译的 OpenCV 4.5.x 兼容。如果目标板上碰巧装了 apt 的 OpenCV(带 `d`),
|
||||
只要 `LD_LIBRARY_PATH` 优先指向 `lib/`,就会用本包内的库,不会冲突。
|
||||
|
||||
---
|
||||
|
||||
## 五、编译调用程序时注意
|
||||
## 六、编译调用程序时注意
|
||||
|
||||
运行不需要 OpenCV,但**编译** `example/` 示例代码时需要 OpenCV **头文件**
|
||||
(`.h`),而 `lib/` 里只有 `.so`(运行库),没有头文件。
|
||||
**生产路径**:你自己的代码只用 `include/stereo_bolt/c_api.h`,不引用任何 OpenCV 头,
|
||||
相机 buffer 直接传给 `sb_process_bolt_module_buffers`,**完全不需要 OpenCV 头文件**。
|
||||
|
||||
两种方案:
|
||||
|
||||
**A. 目标板上 apt 装头文件(推荐,只需一次)**
|
||||
```bash
|
||||
# 只装 -dev 包获取头文件;运行库用包内 lib/ 的,不用 apt 装的 .so
|
||||
sudo apt install libopencv-dev
|
||||
```
|
||||
|
||||
**B. 仅使用 C ABI,无需 OpenCV 头(生产路径)**
|
||||
|
||||
你自己的产品代码只用 `include/stereo_bolt/c_api.h`,不引用任何 OpenCV 头,
|
||||
相机 buffer 直接传给 `sb_process_bolt_module_buffers`。这是生产路径,
|
||||
**完全不需要 OpenCV 头文件**,也不需要 apt。
|
||||
`example/` 仅作集成验证用,生产端不需要编译它。
|
||||
**编译 `example/` 示例**(可选,仅用于集成验证):当前示例是纯 C,
|
||||
只读 headerless Mono8 raw 文件并调用 C ABI,不引用 OpenCV 头文件。
|
||||
运行时仍用本包 `lib/` 里的库。
|
||||
|
||||
@ -32,9 +32,9 @@ cd stereo_bolt_delivery
|
||||
|
||||
```
|
||||
stereo_bolt_delivery/
|
||||
├── lib/ ← 算法库 + OpenCV 4.5.4 + 全部传递依赖(131 个 .so)
|
||||
├── lib/ ← 算法库 + OpenCV 4.5.0 + librknnrt(共 9 个 .so)
|
||||
├── include/stereo_bolt/ ← c_api.h(唯一对外头文件)
|
||||
├── weights/ ← best_1280_20260621.rknn(NPU 模型)
|
||||
├── weights/ ← best_obb_1024.rknn(NPU OBB 模型)
|
||||
├── calib/ ← 出厂标定 XML(0621 装配,baseline 482mm)
|
||||
├── config/ ← config.rk3588.12mp.yaml(兼容接口用)
|
||||
├── example/ ← 最小 C 示例 + CMakeLists
|
||||
@ -82,20 +82,31 @@ StereoBoltModelC model = {
|
||||
.data = rknn_bytes, // weights/*.rknn 的原始字节
|
||||
.size = rknn_size,
|
||||
.backend = SB_YOLO_BACKEND_RKNN,
|
||||
.imgsz = 1280,
|
||||
.imgsz = 1024,
|
||||
.conf = 0.5f
|
||||
};
|
||||
StereoBoltParamsC p = sb_default_params();
|
||||
p.wd_z_min_mm = 1200; // 工作距离下限(mm)
|
||||
p.wd_z_max_mm = 2800; // 工作距离上限(mm)
|
||||
p.wd_z_min_mm = 900; // 工作距离下限(mm)
|
||||
p.wd_z_max_mm = 4000; // 工作距离上限(mm)
|
||||
|
||||
StereoBoltCtx* ctx = sb_create_ex(&calib, &model, &p); // 只调一次(慢,载模型进 NPU)
|
||||
```
|
||||
|
||||
> 兼容方式:`sb_create("config/config.rk3588.12mp.yaml")`(读文件,需工作目录正确)。
|
||||
> 模型:`weights/best_1280_20260621.rknn`(精测 + 测距共用,imgsz 1280)。
|
||||
> 模型:`weights/best_obb_1024.rknn`(精测 + 测距共用,OBB imgsz 1024)。
|
||||
> 标定:`calib/stereo_calib_20260621_202857_ascii.xml`(0621 装配,baseline 482 mm)。
|
||||
|
||||
### 高度测量模式
|
||||
|
||||
默认 `height_from_plane: false`:高度 = 螺杆顶端到可见底端(**不跑平面拟合**,快 ~400ms/帧)。
|
||||
|
||||
如需平面模式(高度 = 顶端到支撑平面法向距离),在 config 中改为:
|
||||
```yaml
|
||||
measurement:
|
||||
height_from_plane: true
|
||||
```
|
||||
开启后每帧增加 300-550ms 平面拟合耗时。
|
||||
|
||||
---
|
||||
|
||||
## 6. 每帧调用
|
||||
@ -118,7 +129,7 @@ sb_free_bolt_module_result(&m); // 必须 free
|
||||
StereoBoltRangeSummaryC s;
|
||||
StereoBoltRangeBoltC bolts[64];
|
||||
sb_range_bolt_binned(ctx, lb, bw, bh, 0, rb, bw, bh, 0, 1, &s, bolts, 64);
|
||||
// s.distance_mm:最近螺栓到相机的距离(mm)
|
||||
// s.distance_mm:相机到可见螺栓的代表距离(mm)
|
||||
|
||||
// ── 退出 ─────────────────────────────────────────────────────────────────
|
||||
sb_destroy(ctx); // 只调一次
|
||||
@ -131,19 +142,15 @@ sb_destroy(ctx); // 只调一次
|
||||
|
||||
## 7. 编译示例程序(可选,用于集成验证)
|
||||
|
||||
示例代码读图用了 OpenCV,编译时需要 **OpenCV 头文件**(`lib/` 里只有 `.so`,没有 `.h`)。
|
||||
示例代码 `main.c` 是纯 C,**不依赖 OpenCV**(读 raw Mono8 文件 + C ABI 调用)。
|
||||
|
||||
```bash
|
||||
# 安装 OpenCV 头文件(只需 -dev 包;运行库用 lib/ 里的,不用 apt 的 .so)
|
||||
sudo apt install libopencv-dev
|
||||
|
||||
# 编译
|
||||
cd example
|
||||
cmake -S . -B build -DSB_ROOT="$(cd .. && pwd)"
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
> **生产代码不需要此步骤**:你自己的调用端只用 `c_api.h`,不引用任何 OpenCV 头,
|
||||
> **生产代码同理**:你自己的调用端只用 `c_api.h`,
|
||||
> 相机 buffer 直接喂给 `sb_process_bolt_module_buffers`,全程无 OpenCV 依赖。
|
||||
|
||||
---
|
||||
@ -154,20 +161,13 @@ cmake --build build
|
||||
# 在 stereo_bolt_delivery/ 根目录执行(使 lib/ 路径正确)
|
||||
export LD_LIBRARY_PATH="$(pwd)/lib:$LD_LIBRARY_PATH"
|
||||
|
||||
# 精测 demo(需要左右 12MP 图,4096×3000,与标定分辨率一致)
|
||||
# 精测 demo(需要左右 12MP raw Mono8,4096×3000)
|
||||
./example/build/bolt_demo \
|
||||
calib/stereo_calib_20260621_202857_ascii.xml \
|
||||
weights/best_1280_20260621.rknn \
|
||||
/path/to/left.bmp /path/to/right.bmp 6
|
||||
|
||||
# 测距 demo
|
||||
./example/build/range_demo \
|
||||
calib/stereo_calib_20260621_202857_ascii.xml \
|
||||
weights/best_1280_20260621.rknn \
|
||||
/path/to/left.bmp /path/to/right.bmp
|
||||
config/config.rk3588.12mp.yaml \
|
||||
/path/to/left.raw /path/to/right.raw 4096 3000 8
|
||||
```
|
||||
|
||||
板上实测吞吐:**精测 ~582 ms/帧 ≈ 1.7 Hz**、**测距 ~238 ms/帧 ≈ 4.2 Hz**(0621 装配,帧 1)。
|
||||
板上实测吞吐:**精测 ~400 ms/帧 ≈ 2.5 Hz**(height_from_plane=false)、**测距 ~238 ms/帧 ≈ 4.2 Hz**(0621 装配)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
# ============================================================
|
||||
# (C++ port)
|
||||
#
|
||||
# Python e:/02_Modules/StereoVisionBoltMeasurement/config.yaml
|
||||
#
|
||||
# Python paths
|
||||
# RK3588 delivery config — OBB model, baseline 482mm
|
||||
# Only used by sb_create(); sb_create_ex() ignores this file.
|
||||
# ============================================================
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- working distance ----------
|
||||
working_distance:
|
||||
z_min_mm: 900
|
||||
z_max_mm: 4000
|
||||
|
||||
# ---------- image ----------
|
||||
image:
|
||||
width: 4096
|
||||
height: 3000
|
||||
@ -14,35 +16,32 @@ image:
|
||||
expected_dtype: "uint8"
|
||||
allowed_extensions: [".tiff", ".tif", ".bmp", ".png"]
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- image validation ----------
|
||||
image_validation:
|
||||
min_max_value: 10
|
||||
max_saturation_ratio: 0.20
|
||||
|
||||
# ---------- ChArUco ----------
|
||||
# ---------- ChArUco ----------
|
||||
charuco:
|
||||
rows: 9
|
||||
cols: 9
|
||||
square_length_mm: 50.0
|
||||
marker_length_mm: 37.0
|
||||
dict_id: 11 # DICT_6X6_1000
|
||||
dict_id: 11
|
||||
min_corners_per_image: 30
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- calibration ----------
|
||||
calibration:
|
||||
source: "xml"
|
||||
xml_path: "calib/stereo_calib.xml" # 0621 新装配 baseline 482mm (ChArUco)
|
||||
xml_path: "calib/stereo_calib.xml"
|
||||
min_image_pairs: 15
|
||||
max_skip_ratio: 0.30
|
||||
max_single_rms: 0.40 # : 12MP RMS=0.373(5MP 0.19)
|
||||
# 0.40 ;0.373 0.1px ,,
|
||||
# <0.20
|
||||
max_single_rms: 0.40
|
||||
max_stereo_rms: 0.80
|
||||
expected_baseline_range: [80, 600]
|
||||
use_fix_intrinsic: true
|
||||
|
||||
# ---------- ----------
|
||||
# alpha=1.0 CLAUDE.md #1
|
||||
# ---------- rectification ----------
|
||||
rectification:
|
||||
alpha: 1.0
|
||||
interpolation: "cubic"
|
||||
@ -53,9 +52,7 @@ rectification:
|
||||
max_epipolar_error_pixel: 5.0
|
||||
min_valid_roi_ratio: 0.25
|
||||
|
||||
# ---------- SGBM ----------
|
||||
# min_disparity / num_disparities f_rectB
|
||||
# f_rect3190, B235mmalpha=1.0
|
||||
# ---------- SGBM ----------
|
||||
sgbm:
|
||||
min_disparity: 288
|
||||
num_disparities: 736
|
||||
@ -69,24 +66,18 @@ sgbm:
|
||||
pre_filter_cap: 63
|
||||
mode: "SGBM_3WAY"
|
||||
|
||||
# ---------- WLS ----------
|
||||
# ---------- WLS ----------
|
||||
wls:
|
||||
lambda_value: 8000.0
|
||||
sigma_color: 1.5
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- disparity validation ----------
|
||||
disparity_validation:
|
||||
min_valid_pixel_ratio: 0.10
|
||||
min_disparity_range: 50
|
||||
max_nan_ratio: 0.90
|
||||
|
||||
# ---------- ----------
|
||||
# z_min_mm / z_max_mm / expected_wd_range are FALLBACK only when run_pipeline /
|
||||
# c_api are wired (the standard path) SceneRuntime derives the real reachable
|
||||
# Z range from (f_rect, baseline, sgbm.min_disparity, sgbm.num_disparities) and
|
||||
# overrides these values per frame. See docs/L_100_SCENE_HANDOFF.md 6.2 and
|
||||
# include/stereo_bolt/scene_runtime.hpp. Keep the fallback values conservative
|
||||
# enough to cover every supported workpiece distance.
|
||||
# ---------- pointcloud ----------
|
||||
pointcloud:
|
||||
z_min_mm: 600
|
||||
z_max_mm: 3000
|
||||
@ -108,13 +99,7 @@ pointcloud:
|
||||
dilate_px: 100
|
||||
min_area_ratio: 0.02
|
||||
|
||||
# ---------- 2D ----------
|
||||
# mask_method: foam_mask( mask)
|
||||
# "brightness" + (;
|
||||
# rectify ROI 0, mask ROI )
|
||||
# "ransac" dense xyz_map RANSAC ,
|
||||
# " < ransac.distance_thresh_mm" mask
|
||||
# "brightness" parity_tests , "ransac"
|
||||
# ---------- 2D detection ----------
|
||||
bolt_detect_2d:
|
||||
mask_method: "ransac"
|
||||
foam_brightness_threshold: 70
|
||||
@ -125,99 +110,75 @@ bolt_detect_2d:
|
||||
bolt_min_area_px: 800
|
||||
min_bolts: 1
|
||||
max_bolts: 0
|
||||
bolt_exclude_dilate_px: 0 # 0 CLAUDE.md #5
|
||||
bolt_exclude_dilate_px: 0
|
||||
ransac:
|
||||
distance_thresh_mm: 8.0 # < inlier(3
|
||||
# frame 100 residual_std2.6 mm)
|
||||
distance_thresh_mm: 8.0
|
||||
max_iterations: 200
|
||||
min_inlier_ratio: 0.15 # inlier
|
||||
sample_stride: 4 # xyz_map ( 44 1 RANSAC )
|
||||
morph_close_kernel_px: 9 # mask +
|
||||
min_inlier_ratio: 0.15
|
||||
sample_stride: 4
|
||||
morph_close_kernel_px: 9
|
||||
|
||||
# ---------- (SVD on foam mask) ----------
|
||||
# ---------- plane ----------
|
||||
plane:
|
||||
max_residual_std_mm: 15.0
|
||||
max_normal_to_axis_deg: 60
|
||||
outlier_rejection_iterations: 5
|
||||
outlier_mad_k: 2.5
|
||||
|
||||
# ---------- ----------
|
||||
# plane fit bolt extract bolt 3D
|
||||
# inlier OBB, OBB margin_mm WARN
|
||||
# margin_mm bolt.expected_diameter_mm,
|
||||
# /
|
||||
# ---------- support gate ----------
|
||||
support_gate:
|
||||
enabled: true
|
||||
margin_mm: 10.0
|
||||
|
||||
# ---------- ----------
|
||||
# (run_pipeline / c_api) bolt bbox_projection h_est,
|
||||
# extract height max(h_est 1.3, h_est + 20 mm),
|
||||
# validate_measurements |measured - h_est| < bolt.height_tolerance_mm
|
||||
# max_height_mm / extract_height_filter_mm fallback
|
||||
# bbox_projection h_est
|
||||
# (expected_diameter_mm),
|
||||
# ---------- bolt ----------
|
||||
bolt:
|
||||
min_height_mm: 5.0
|
||||
max_height_mm: 250.0 # fallback ceiling (used only when h_est is unavailable)
|
||||
extract_height_filter_mm: 200.0 # fallback filter (same condition)
|
||||
height_tolerance_mm: 60.0 # |measured - h_est| ; validate_measurements
|
||||
max_height_mm: 250.0
|
||||
extract_height_filter_mm: 200.0
|
||||
height_tolerance_mm: 60.0
|
||||
expected_diameter_mm: 16.0
|
||||
min_points_per_bolt: 50
|
||||
max_axis_plane_angle_deg: 30
|
||||
extract_fallback: "bbox_projection" # 'none' | 'bbox_projection'; YOLO bbox + , SGBM /
|
||||
extract_fallback: "bbox_projection"
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- measurement ----------
|
||||
measurement:
|
||||
top_extraction_method: "percentile_on_axis"
|
||||
top_percentile: 0.99
|
||||
foot_rim_correction: false # 0621 决定: 关 rim(圆顶无领圈杆上 rim 修正会过冲)
|
||||
height_from_plane: false
|
||||
plane_use_sgbm: true
|
||||
plane_sgbm_scale: 0.5
|
||||
plane_max_samples: 40000
|
||||
|
||||
# ---------- Diagnostics ----------
|
||||
# ---------- diagnostics ----------
|
||||
diagnostics:
|
||||
enabled: true
|
||||
blind_strip_warn_ratio: 0.25
|
||||
min_roi_pointcloud_coverage_ratio: 0.001
|
||||
|
||||
# ---------- YOLO ----------
|
||||
# C++ ONNX Python best_v4_finetune.pt .onnx
|
||||
# dilate (50/30),
|
||||
# scene_runtime.cpp pixels_per_mm(= f / Z_repr),
|
||||
# /
|
||||
# ---------- YOLO ----------
|
||||
yolo:
|
||||
enabled: true
|
||||
# 统一模型: 精测(sb_process_bolt_module_*)与测距(sb_range_bolt_binned)共用 weights/best.rknn。
|
||||
# 当前交付模型为 960 输入;文件名保持稳定,模型更新时只替换 weights/best.rknn。
|
||||
model_path: "weights/best.rknn"
|
||||
backend: "rknn"
|
||||
imgsz: 960
|
||||
task: "auto"
|
||||
imgsz: 1024
|
||||
conf: 0.5
|
||||
dead_zone_min_valid_pts: 50
|
||||
dead_zone_bbox_dilate_mm: 24.0 # = expected_diameter_mm 1.5; valid_xyz bbox ,/
|
||||
foam_bbox_dilate_mm: 16.0 # = expected_diameter_mm 1.0; foam_mask YOLO bbox
|
||||
|
||||
# Experimental: keep sparse-global YOLO bolts only when right YOLO + local LR check pass.
|
||||
dead_zone_bbox_dilate_mm: 24.0
|
||||
foam_bbox_dilate_mm: 16.0
|
||||
local_stereo_fallback:
|
||||
enabled: true
|
||||
min_valid_points: 50
|
||||
|
||||
# ---------- working distance (physical, rig-level) ----------
|
||||
# 0621 新装配 B=482mm: 精测视差带从 WD + 标定自推导 (disp = f*B/Z), 换基线无需重调像素。
|
||||
# 旧固定 sgbm 像素带(288/736)是 200mm 装配的, 对 482mm 会 NEAR-clip 近处螺柱。
|
||||
# 注意: 需 .so 含视差带解耦(commit dc696d8 起); 旧 .so 不读此块仍用固定带。
|
||||
working_distance:
|
||||
z_min_mm: 1200
|
||||
z_max_mm: 2800
|
||||
|
||||
# ---------- ranging (binned) ----------
|
||||
# 测距视差带也按 0621 rig 的 WD; 物理 LRC 门(mm)随距离自紧。
|
||||
ranging:
|
||||
z_min_mm: 1300
|
||||
z_max_mm: 3600
|
||||
z_min_mm: 900
|
||||
z_max_mm: 4000
|
||||
lrc_max_mm: 2.5
|
||||
lrc_trusted_mm: 1.2
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- evaluation ----------
|
||||
evaluation:
|
||||
height_max_mm: 2.0
|
||||
height_p95_mm: 1.5
|
||||
@ -226,24 +187,23 @@ evaluation:
|
||||
repeatability_std_max_mm: 1.0
|
||||
matching_max_distance_mm: 50.0
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- pipeline ----------
|
||||
pipeline:
|
||||
fail_fast: true
|
||||
resume_from_checkpoint: true
|
||||
parallel_frames: false
|
||||
save_intermediate: false
|
||||
|
||||
# ---------- ----------
|
||||
# ---------- logging ----------
|
||||
logging:
|
||||
level: "INFO"
|
||||
log_to_file: true
|
||||
log_file: "outputs/logs/pipeline.log"
|
||||
|
||||
# ---------- ----------
|
||||
# dataset/ , Python
|
||||
# ---------- paths ----------
|
||||
paths:
|
||||
calibration_dir: "calib"
|
||||
test_dir: "dataset/test"
|
||||
groundtruth_csv: "dataset/groundtruth.csv"
|
||||
output_dir: "outputs_macro257"
|
||||
stereo_params_yaml: "outputs_macro257/stereo_params.yaml"
|
||||
output_dir: "outputs"
|
||||
stereo_params_yaml: "outputs/stereo_params.yaml"
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
# Minimal downstream build: link the prebuilt libstereo_bolt.so via c_api.h only.
|
||||
# No OpenCV / RKNN headers needed here — the C ABI hides all C++/OpenCV types.
|
||||
# Minimal downstream build — links the prebuilt libstereo_bolt.so.
|
||||
# bolt_demo (main.c) -> sb_create + sb_process_bolt_module_buffers
|
||||
#
|
||||
# OpenCV here is NOT required by the demo — it uses only C ABI + raw Mono8
|
||||
# buffers. OpenCV is a transitive runtime dep of libstereo_bolt.so only.
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(bolt_demo LANGUAGES C)
|
||||
|
||||
# Point at the unpacked delivery dir (contains include/ and lib/).
|
||||
set(SB_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." CACHE PATH "unpacked libstereo_bolt delivery root")
|
||||
|
||||
add_executable(bolt_demo main.c)
|
||||
target_include_directories(bolt_demo PRIVATE "${SB_ROOT}/include")
|
||||
target_link_directories(bolt_demo PRIVATE "${SB_ROOT}/lib")
|
||||
target_link_libraries(bolt_demo PRIVATE stereo_bolt)
|
||||
target_link_options(bolt_demo PRIVATE "LINKER:--allow-shlib-undefined")
|
||||
|
||||
# libstereo_bolt.so pulls in OpenCV + librknnrt transitively. You do NOT need
|
||||
# those at LINK time — defer their resolution to runtime so you can link with
|
||||
# just -lstereo_bolt. (Without this, ld follows the NEEDED chain and fails with
|
||||
# "undefined reference to rknn_*/cv::*".)
|
||||
target_link_options(bolt_demo PRIVATE "LINKER:--allow-shlib-undefined")
|
||||
|
||||
# Runtime: LD_LIBRARY_PATH must include the dirs holding
|
||||
# libstereo_bolt.so + libopencv_*.so (matching ABI_FINGERPRINT.txt) + librknnrt.so
|
||||
# Runtime: LD_LIBRARY_PATH must include ../lib (holds libstereo_bolt.so +
|
||||
# OpenCV 4.5.0 + librknnrt.so). Example:
|
||||
# LD_LIBRARY_PATH=../lib ./build/bolt_demo \
|
||||
# ../config/config.rk3588.12mp.yaml L.raw R.raw 4096 3000 8
|
||||
|
||||
@ -184,7 +184,7 @@ typedef struct {
|
||||
double wd_z_max_mm;
|
||||
double sgbm_min_disparity; /* fixed pixel band (used when wd_* unset) */
|
||||
double sgbm_num_disparities;
|
||||
int foot_rim_correction; /* -1 = built-in default, 0 = off, 1 = on */
|
||||
int foot_rim_correction; /* -1 = built-in default (off), 0 = off, 1 = on */
|
||||
|
||||
/* --- binned ranging path (sb_range_bolt_binned) --- */
|
||||
double rng_z_min_mm;
|
||||
@ -259,16 +259,16 @@ typedef struct {
|
||||
double distance_mm; /* straight-line distance, left optical centre -> bolt */
|
||||
int left_x; /* bbox centre in the rectified-left (binned) image */
|
||||
int left_y;
|
||||
int right_x; /* NCC correspondence in the rectified-right (binned) image */
|
||||
int right_y; /* -> the "same bolt" evidence; mark to verify */
|
||||
double ncc_score; /* match score */
|
||||
double lrc_px; /* left-right consistency residual (px); small == same point */
|
||||
int right_x; /* matched point in the rectified-right (binned) image */
|
||||
int right_y; /* OBB-axis route: right OBB axis point; NCC fallback: correspondence */
|
||||
double ncc_score; /* detector/match score; ABI name kept for compatibility */
|
||||
double lrc_px; /* OBB disparity stddev or NCC LRC residual; smaller is better */
|
||||
int confidence; /* 0 = trusted (strong + self-consistent), 1 = suspect */
|
||||
} StereoBoltRangeBoltC;
|
||||
|
||||
typedef struct {
|
||||
int found; /* 1 if >= 1 bolt was detected and NCC/LRC-verified */
|
||||
double distance_mm; /* MEDIAN straight-line distance over visible bolts */
|
||||
int found; /* 1 if >= 1 bolt was detected and paired/matched */
|
||||
double distance_mm; /* representative straight-line distance over visible bolts */
|
||||
int bolt_count; /* total visible bolts (may exceed the number written to bolts[]) */
|
||||
char status[32]; /* "ok" | "no_detection" | "no_valid_match" (why found==0) */
|
||||
} StereoBoltRangeSummaryC;
|
||||
@ -281,7 +281,7 @@ typedef struct {
|
||||
* - The binned size may be ANY clean downscale of the calibrated size
|
||||
* (config image.width/height); the binned intrinsics are derived internally
|
||||
* (no separate binned calibration needed). 2x2 binning is the intended case.
|
||||
* - out : summary (found / median distance / visible count). Required.
|
||||
* - out : summary (found / representative distance / visible count). Required.
|
||||
* - bolts : optional caller-allocated array (capacity max_bolts); filled with
|
||||
* per-bolt detail (distance + left/right match points), near -> far.
|
||||
* Pass NULL / max_bolts=0 to skip. The caller owns it; nothing to free.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -4,17 +4,53 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
# SDK(需要先编译)
|
||||
SUBDIRS += ../SDK/Device/EpicEye/EpicEyeSDK.pro
|
||||
isEmpty(TARGET_APP) {
|
||||
SUBDIRS += ../SDK/Device/EpicEye/EpicEyeSDK.pro
|
||||
|
||||
SUBDIRS += \
|
||||
VrEyeDevice/VrEyeDevice.pro \
|
||||
EpicEyeDevice/EpicEyeDevice.pro \
|
||||
GalaxyDevice/GalaxyDevice.pro \
|
||||
HikDevice/HikDevice.pro \
|
||||
MvsDevice/MvsDevice.pro \
|
||||
GlLineLaserDevice/GlLineLaserDevice.pro
|
||||
SUBDIRS += \
|
||||
VrEyeDevice/VrEyeDevice.pro \
|
||||
EpicEyeDevice/EpicEyeDevice.pro \
|
||||
GalaxyDevice/GalaxyDevice.pro \
|
||||
HikDevice/HikDevice.pro \
|
||||
MvsDevice/MvsDevice.pro \
|
||||
GlLineLaserDevice/GlLineLaserDevice.pro
|
||||
} else {
|
||||
!equals(TARGET_APP, "BeltTearing") {
|
||||
SUBDIRS += VrEyeDevice/VrEyeDevice.pro
|
||||
}
|
||||
|
||||
contains(TARGET_APP, "^(WorkpiecePosition|WorkpieceProject)$") {
|
||||
SUBDIRS += ../SDK/Device/EpicEye/EpicEyeSDK.pro
|
||||
SUBDIRS += EpicEyeDevice/EpicEyeDevice.pro
|
||||
SUBDIRS += VrEyeDevice/VrEyeDevice.pro
|
||||
}
|
||||
|
||||
equals(TARGET_APP, "BinocularMark") {
|
||||
SUBDIRS += GalaxyDevice/GalaxyDevice.pro
|
||||
}
|
||||
|
||||
contains(TARGET_APP, "^(DroneScrewServer|DroneScrewbolt)$") {
|
||||
SUBDIRS += MvsDevice/MvsDevice.pro
|
||||
}
|
||||
|
||||
equals(TARGET_APP, "BagThreadPosition") {
|
||||
SUBDIRS += VrEyeDevice/VrEyeDevice.pro
|
||||
SUBDIRS += GlLineLaserDevice/GlLineLaserDevice.pro
|
||||
}
|
||||
|
||||
equals(TARGET_APP, "TunnelChannel") {
|
||||
SUBDIRS += VrEyeDevice/VrEyeDevice.pro
|
||||
SUBDIRS += HikDevice/HikDevice.pro
|
||||
}
|
||||
|
||||
contains(TARGET_APP, "^(BeltTearing|WorkpieceSplice|GrabBag|LapWeld|Workpiece|ParticleSize|ScrewPosition|WorkpieceHole|DiscHolePose|TireHolePose|StatorPosition|RodAndBarPosition|RodWeldSeam|HoleDetection|HolePitPosition|WheelMeasure)$") {
|
||||
SUBDIRS += VrEyeDevice/VrEyeDevice.pro
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
win32-msvc {
|
||||
SUBDIRS += RsLidarDevice/RsLidarDevice.pro
|
||||
}
|
||||
|
||||
SUBDIRS = $$unique(SUBDIRS)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user