我正在开展一个项目,需要使用 Qt 可视化 3D 表面。曲面的数据存储在 C++ 模型中的
cv::Mat
中,我正在尝试将此模型绑定到 QML 中的 ItemModelSurfaceDataProxy
。
这是我的 C++ 模型:
class MyTableModel: public QAbstractTableModel {
Q_OBJECT
public:
enum Roles {
PixelRole = Qt::UserRole + 1
};
QHash<int, QByteArray> roleNames() const override;
MyTableModel(const cv::Mat& data, QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
private:
cv::Mat m_dataMatrix;
};
Q_PROPERTY
在另一个Q_OBJECT
中定义如下:
Q_PROPERTY(QAbstractTableModel* myModel READ getMyModel NOTIFY dataChanged)
private:
MyTableModel* getMyModel() { return m_myModel; }
MyTableModel* m_myModel;
这是我的 QML 代码:
Surface3D
{
anchors.fill:parent
Surface3DSeries
{
ItemModelSurfaceDataProxy
{
itemModel: pageModel.myModel;
rowRole: "x"
columnRole: "y"
zPosRole: "PixelValue"
}
}
}
当我启动应用程序时,3D 表面是空的,没有数据显示。我怀疑从后端模型获取数据可能存在问题。我不确定我设定的角色是否正确。
我找到了有关如何使用
HeightMapSurfaceDataProxy
或直接在 QML 中内置的 ListModel
设置 Surface3D 的在线资源,但没有解释如何在 C++ 中构建模型并将其与 QML 绑定。
如果需要,我可以上传
MyTableModel
类的方法实现。
这里data方法的实现:
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || role != MyTableModel::Roles::PixelRole)
return QVariant();
float value = dataMatrix.at<float>(index.row(), index.column());
return QVariant(value);
}
QSurfaceDataProxy
向 Surface3DSeries
提供数据。源自 MatData
的 QSurfaceDataProxy
的构造函数包含使用随机数据创建 cv::Mat
并将其映射到 QSurfaceDataArray
。这里的关键是对 resetArray()
的调用,它将用提供的数据替换 QSurface3DSeries
的 QSurfaceDataProxy
。
我使用的是 Qt 6.5.4,因此
Surface3D
和 Surface3DSeries
的导入是 QtDataVisualization 而不是 QtGraphs。如果您使用 Qt 6.6 或更高版本,您可以调整导入。
构造函数中有一个小技巧,这是QTBUG-58884、QTBUG-65322和QTBUG-103965中报告的以下问题的解决方法。上一个错误报告对我正在使用的解决方法进行了评论。
您可以在here找到示例的完整源代码。
MatData::MatData(QObject *parent)
: QSurfaceDataProxy(parent)
{
// Create a cv::Mat to be used as surface
cv::Mat test(20, 20, CV_32F);
cv::Mat mean = cv::Mat::zeros(1, 1, CV_32F);
cv::Mat sigma = cv::Mat::ones(1, 1, CV_32F);
sigma.at<float>(0, 0, 0) = 10;
cv::RNG rng;
rng.fill(test, cv::RNG::NORMAL, mean, sigma);
cv::Mat mat;
blur(test, mat, cv::Size(5, 5));
m_dataArray = new QSurfaceDataArray;
m_dataArray->reserve(mat.rows);
for (int i = 0; i < mat.rows; i++) {
QSurfaceDataRow *newProxyRow = new QSurfaceDataRow(mat.cols);
m_dataArray->append(newProxyRow);
for (int j = 0; j < mat.cols; j++) {
QSurfaceDataItem &item = (*newProxyRow)[j];
item.setPosition(QVector3D(i, mat.at<float>(i, j), j));
// https://bugreports.qt.io/browse/QTBUG-103965
auto tmp = (*newProxyRow)[j];
tmp.setPosition(QVector3D(tmp.z(), tmp.y(), tmp.x()));
(*newProxyRow)[j] = tmp;
}
}
resetArray(m_dataArray);
}
import QtQuick
import QtDataVisualization
import CustomProxy
Window {
width: 800
height: 600
visible: true
title: qsTr("Hello World")
ColorGradient {
id: surfaceGradient
ColorGradientStop { position: 0.0; color: "black" }
ColorGradientStop { position: 0.2; color: "red" }
ColorGradientStop { position: 0.5; color: "blue" }
ColorGradientStop { position: 0.8; color: "yellow" }
ColorGradientStop { position: 1.0; color: "white" }
}
Surface3D {
id: surfaceGraph
anchors.fill: parent
Surface3DSeries {
id: surfaceSeries
flatShadingEnabled: false
drawMode: Surface3DSeries.DrawSurface | Surface3DSeries.DrawWireframe
baseGradient: surfaceGradient
colorStyle: Theme3D.ColorStyleRangeGradient
itemLabelFormat: "(@xLabel, @zLabel): @yLabel"
MatData {}
}
}
}