Qt Demo之 deepseek 帮我写的关于双目标定的小界面
界面形式:
包含以下功能:
具体实现:
- 搜索描述:
你好,我想用C++, qt写个关于双目相机标定的界面,主要包括以下内容:
1.分别打开两张图像,然后用户可以手动在图像上通过鼠标点击进行选点,选点后自动出现图像坐
标,并且选点动作支持撤销当前点,撤销全部点
2. 包含标定按钮 ,图像界面支持缩放、拖拽及平移
3. 状态栏实时显示鼠标移动时对应的图像坐标
4. 4.给出完整可用代码,以.h .cpp给出,中文字符以QString::fromLocal8Bit形式给出
- 头文件 (stereocalibration.h):
#ifndef STEREOCALIBRATION_H
#define STEREOCALIBRATION_H
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QMouseEvent>
#include <QPointF>
#include <QList>
#include <QStatusBar>
#include <QLabel>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFileDialog>
#include <QMessageBox>
#include <QScrollBar>
#include <QWheelEvent>
#include <opencv2/opencv.hpp>
class ZoomGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit ZoomGraphicsView(QWidget *parent = nullptr);
void setImage(const QPixmap &pixmap);
void clearPoints();
void addPoint(const QPointF &point);
QList<QPointF> getPoints() const;
bool hasImage() const;
signals:
void mouseMoved(QPointF pos);
void mouseClicked(QPointF pos);
protected:
void wheelEvent(QWheelEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void drawForeground(QPainter *painter, const QRectF &rect) override;
private:
QGraphicsScene *scene;
QGraphicsPixmapItem *pixmapItem;
QList<QPointF> points;
double scaleFactor;
};
class StereoCalibration : public QMainWindow
{
Q_OBJECT
public:
StereoCalibration(QWidget *parent = nullptr);
~StereoCalibration();
private slots:
void openLeftImage();
void openRightImage();
void calibrate();
void undoPoint();
void resetPoints();
void onLeftViewMouseMoved(QPointF pos);
void onRightViewMouseMoved(QPointF pos);
void onLeftViewMouseClicked(QPointF pos);
void onRightViewMouseClicked(QPointF pos);
private:
void setupUI();
void updateStatusBar();
cv::Mat QImageToCvMat(const QImage &image);
ZoomGraphicsView *leftView;
ZoomGraphicsView *rightView;
QLabel *statusLabel;
QPushButton *openLeftBtn;
QPushButton *openRightBtn;
QPushButton *calibrateBtn;
QPushButton *undoBtn;
QPushButton *resetBtn;
cv::Mat leftImage;
cv::Mat rightImage;
QList<QPointF> leftPoints;
QList<QPointF> rightPoints;
bool isCalibrated;
cv::Mat cameraMatrix1, distCoeffs1;
cv::Mat cameraMatrix2, distCoeffs2;
cv::Mat R, T, E, F;
};
#endif // STEREOCALIBRATION_H
- 源文件 (stereocalibration.cpp):
#include "stereocalibration.h"
#include <cmath>
// ZoomGraphicsView 实现
ZoomGraphicsView::ZoomGraphicsView(QWidget *parent)
: QGraphicsView(parent), scene(new QGraphicsScene(this)),
pixmapItem(nullptr), scaleFactor(1.0)
{
setScene(scene);
setDragMode(QGraphicsView::ScrollHandDrag);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setRenderHint(QPainter::Antialiasing);
}
void ZoomGraphicsView::setImage(const QPixmap &pixmap)
{
scene->clear();
points.clear();
pixmapItem = scene->addPixmap(pixmap);
scene->setSceneRect(pixmap.rect());
scaleFactor = 1.0;
resetTransform();
}
void ZoomGraphicsView::clearPoints()
{
points.clear();
scene->update();
}
void ZoomGraphicsView::addPoint(const QPointF &point)
{
points.append(point);
scene->update();
}
QList<QPointF> ZoomGraphicsView::getPoints() const
{
return points;
}
bool ZoomGraphicsView::hasImage() const
{
return pixmapItem != nullptr;
}
void ZoomGraphicsView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
double factor = (event->angleDelta().y() > 0) ? 1.1 : 0.9;
scaleFactor *= factor;
scale(factor, factor);
event->accept();
} else {
QGraphicsView::wheelEvent(event);
}
}
void ZoomGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
QPointF scenePos = mapToScene(event->pos());
emit mouseMoved(scenePos);
QGraphicsView::mouseMoveEvent(event);
}
void ZoomGraphicsView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && pixmapItem) {
QPointF scenePos = mapToScene(event->pos());
// 检查点击位置是否在图像范围内
if (scenePos.x() >= 0 && scenePos.y() >= 0 &&
scenePos.x() < pixmapItem->pixmap().width() &&
scenePos.y() < pixmapItem->pixmap().height()) {
emit mouseClicked(scenePos);
}
}
QGraphicsView::mousePressEvent(event);
}
void ZoomGraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
{
Q_UNUSED(rect);
if (!hasImage()) return;
// 绘制所有点
painter->setPen(QPen(Qt::red, 3));
for (const QPointF &point : points) {
painter->drawEllipse(point, 5, 5);
}
// 绘制坐标文本
painter->setPen(QPen(Qt::green, 2));
QFont font = painter->font();
font.setPointSize(10);
painter->setFont(font);
for (const QPointF &point : points) {
QString text = QString("(%1, %2)").arg(point.x(), 0, 'f', 1).arg(point.y(), 0, 'f', 1);
painter->drawText(point + QPointF(10, -10), text);
}
}
// StereoCalibration 实现
StereoCalibration::StereoCalibration(QWidget *parent)
: QMainWindow(parent), isCalibrated(false)
{
setupUI();
resize(1200, 800);
setWindowTitle(QString::fromLocal8Bit("双目相机标定"));
}
StereoCalibration::~StereoCalibration()
{
}
cv::Mat StereoCalibration::QImageToCvMat(const QImage &image)
{
cv::Mat mat;
switch (image.format()) {
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
mat = cv::Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
cv::cvtColor(mat, mat, cv::COLOR_BGRA2BGR);
break;
case QImage::Format_RGB888:
mat = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR);
break;
case QImage::Format_Grayscale8:
mat = cv::Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
break;
default:
break;
}
return mat;
}
void StereoCalibration::setupUI()
{
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
// 创建工具栏
QHBoxLayout *toolLayout = new QHBoxLayout();
openLeftBtn = new QPushButton(QString::fromLocal8Bit("打开左图像"), this);
openRightBtn = new QPushButton(QString::fromLocal8Bit("打开右图像"), this);
calibrateBtn = new QPushButton(QString::fromLocal8Bit("标定"), this);
undoBtn = new QPushButton(QString::fromLocal8Bit("撤销点"), this);
resetBtn = new QPushButton(QString::fromLocal8Bit("重置所有点"), this);
toolLayout->addWidget(openLeftBtn);
toolLayout->addWidget(openRightBtn);
toolLayout->addWidget(calibrateBtn);
toolLayout->addWidget(undoBtn);
toolLayout->addWidget(resetBtn);
toolLayout->addStretch();
// 创建图像显示区域
QHBoxLayout *imageLayout = new QHBoxLayout();
leftView = new ZoomGraphicsView(this);
rightView = new ZoomGraphicsView(this);
imageLayout->addWidget(leftView);
imageLayout->addWidget(rightView);
// 创建主布局
QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
mainLayout->addLayout(toolLayout);
mainLayout->addLayout(imageLayout);
// 创建状态栏
statusLabel = new QLabel(QString::fromLocal8Bit("就绪"));
statusBar()->addWidget(statusLabel);
// 连接信号槽
connect(openLeftBtn, &QPushButton::clicked, this, &StereoCalibration::openLeftImage);
connect(openRightBtn, &QPushButton::clicked, this, &StereoCalibration::openRightImage);
connect(calibrateBtn, &QPushButton::clicked, this, &StereoCalibration::calibrate);
connect(undoBtn, &QPushButton::clicked, this, &StereoCalibration::undoPoint);
connect(resetBtn, &QPushButton::clicked, this, &StereoCalibration::resetPoints);
connect(leftView, &ZoomGraphicsView::mouseMoved, this, &StereoCalibration::onLeftViewMouseMoved);
connect(rightView, &ZoomGraphicsView::mouseMoved, this, &StereoCalibration::onRightViewMouseMoved);
connect(leftView, &ZoomGraphicsView::mouseClicked, this, &StereoCalibration::onLeftViewMouseClicked);
connect(rightView, &ZoomGraphicsView::mouseClicked, this, &StereoCalibration::onRightViewMouseClicked);
}
void StereoCalibration::openLeftImage()
{
QString fileName = QFileDialog::getOpenFileName(this,
QString::fromLocal8Bit("打开左图像"),
"",
QString::fromLocal8Bit("图像文件 (*.png *.jpg *.bmp *.tif)"));
if (!fileName.isEmpty()) {
QImage qimage(fileName);
if (!qimage.isNull()) {
QPixmap pixmap = QPixmap::fromImage(qimage);
leftView->setImage(pixmap);
leftImage = QImageToCvMat(qimage);
leftPoints.clear();
updateStatusBar();
statusLabel->setText(QString::fromLocal8Bit("左图像加载成功"));
} else {
QMessageBox::warning(this,
QString::fromLocal8Bit("错误"),
QString::fromLocal8Bit("无法加载左图像"));
}
}
}
void StereoCalibration::openRightImage()
{
QString fileName = QFileDialog::getOpenFileName(this,
QString::fromLocal8Bit("打开右图像"),
"",
QString::fromLocal8Bit("图像文件 (*.png *.jpg *.bmp *.tif)"));
if (!fileName.isEmpty()) {
QImage qimage(fileName);
if (!qimage.isNull()) {
QPixmap pixmap = QPixmap::fromImage(qimage);
rightView->setImage(pixmap);
rightImage = QImageToCvMat(qimage);
rightPoints.clear();
updateStatusBar();
statusLabel->setText(QString::fromLocal8Bit("右图像加载成功"));
} else {
QMessageBox::warning(this,
QString::fromLocal8Bit("错误"),
QString::fromLocal8Bit("无法加载右图像"));
}
}
}
void StereoCalibration::calibrate()
{
if (!leftView->hasImage() || !rightView->hasImage()) {
QMessageBox::warning(this,
QString::fromLocal8Bit("错误"),
QString::fromLocal8Bit("请先加载左右图像"));
return;
}
leftPoints = leftView->getPoints();
rightPoints = rightView->getPoints();
if (leftPoints.size() != rightPoints.size() || leftPoints.size() < 8) {
QMessageBox::warning(this,
QString::fromLocal8Bit("错误"),
QString::fromLocal8Bit("需要至少8对对应点才能进行标定"));
return;
}
// 准备标定数据
std::vector<std::vector<cv::Point2f>> imagePoints;
std::vector<cv::Point2f> leftPointsVec, rightPointsVec;
for (const QPointF &point : leftPoints) {
leftPointsVec.emplace_back(point.x(), point.y());
}
for (const QPointF &point : rightPoints) {
rightPointsVec.emplace_back(point.x(), point.y());
}
imagePoints.push_back(leftPointsVec);
imagePoints.push_back(rightPointsVec);
// 生成对象点 (假设是平面标定板)
std::vector<std::vector<cv::Point3f>> objectPoints;
std::vector<cv::Point3f> obj;
for (int i = 0; i < leftPoints.size(); i++) {
obj.emplace_back(i % 4, i / 4, 0.0f); // 简单的棋盘格假设
}
objectPoints.push_back(obj);
// 执行标定
cv::Size imageSize(leftImage.cols, leftImage.rows);
double rms = cv::stereoCalibrate(objectPoints,
std::vector<std::vector<cv::Point2f>>{leftPointsVec},
std::vector<std::vector<cv::Point2f>>{rightPointsVec},
cameraMatrix1, distCoeffs1,
cameraMatrix2, distCoeffs2,
imageSize, R, T, E, F,
cv::CALIB_FIX_INTRINSIC,
cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30, 1e-6));
isCalibrated = true;
QMessageBox::information(this,
QString::fromLocal8Bit("标定完成"),
QString::fromLocal8Bit("双目标定完成\n重投影误差: %1").arg(rms));
}
void StereoCalibration::undoPoint()
{
if (!leftPoints.isEmpty() && !rightPoints.isEmpty()) {
leftPoints.removeLast();
rightPoints.removeLast();
leftView->clearPoints();
rightView->clearPoints();
for (const QPointF &point : leftPoints) {
leftView->addPoint(point);
}
for (const QPointF &point : rightPoints) {
rightView->addPoint(point);
}
updateStatusBar();
}
}
void StereoCalibration::resetPoints()
{
leftPoints.clear();
rightPoints.clear();
leftView->clearPoints();
rightView->clearPoints();
updateStatusBar();
}
void StereoCalibration::onLeftViewMouseMoved(QPointF pos)
{
statusLabel->setText(QString::fromLocal8Bit("左图像坐标: (%1, %2)").arg(pos.x(), 0, 'f', 1).arg(pos.y(), 0, 'f', 1));
}
void StereoCalibration::onRightViewMouseMoved(QPointF pos)
{
statusLabel->setText(QString::fromLocal8Bit("右图像坐标: (%1, %2)").arg(pos.x(), 0, 'f', 1).arg(pos.y(), 0, 'f', 1));
}
void StereoCalibration::onLeftViewMouseClicked(QPointF pos)
{
leftPoints.append(pos);
leftView->addPoint(pos);
updateStatusBar();
}
void StereoCalibration::onRightViewMouseClicked(QPointF pos)
{
rightPoints.append(pos);
rightView->addPoint(pos);
updateStatusBar();
}
void StereoCalibration::updateStatusBar()
{
QString status = QString::fromLocal8Bit("左图像点数: %1, 右图像点数: %2").arg(leftPoints.size()).arg(rightPoints.size());
if (leftPoints.size() != rightPoints.size()) {
status += QString::fromLocal8Bit(" (点数不匹配)");
}
statusLabel->setText(status);
}
- 主程序文件 main.cpp
#include "stereocalibration.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
StereoCalibration w;
w.show();
return a.exec();
}
- 项目文件 (StereoCalibration.pro),若通过CMake编译有用:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
# OpenCV配置 - 请根据您的安装路径修改
unix:!macx {
INCLUDEPATH += /usr/local/include/opencv4
LIBS += -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_calib3d -lopencv_imgcodecs
}
win32 {
INCLUDEPATH += C:/opencv/build/include
LIBS += -LC:/opencv/build/x64/vc15/lib \
-lopencv_core451 \
-lopencv_imgproc451 \
-lopencv_calib3d451 \
-lopencv_imgcodecs451
}
macx {
INCLUDEPATH += /usr/local/include/opencv4
LIBS += -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_calib3d -lopencv_imgcodecs
}
SOURCES += \
main.cpp \
stereocalibration.cpp
HEADERS += \
stereocalibration.h
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target