QT-简单视觉框架代码

文章目录

简介

以下是一个满足需求的 Qt 程序示例,采用 C++ QT语言编写,通过合理的分层架构设计,实现了界面与业务逻辑的分离,具备对多台海康相机的高效控制以及丰富的交互功能:

1. 整体架构

程序分为三层:界面层(负责显示和用户交互)、业务逻辑层(处理相机相关操作)、数据模型层(存储相机数据及状态,本示例未详细展开,可按需扩展)。通过信号与槽机制在各层之间传递信息,保证模块的低耦合性。

2. 关键类功能概述

  • CameraInterface:定义相机操作的抽象接口,确保不同相机厂商实现的兼容性。
    HikCameraWorker:继承自CameraInterface,实现海康相机的具体业务逻辑,包括连接、取图、断线重连、手动触发等。
  • MainWindow:主窗口类,负责创建界面,集成菜单栏、工具栏、状态栏、图像显示区和日志区,响应用户操作并与业务逻辑层交互。
  • ImageViewerWidget:自定义图像显示部件,封装 QGraphicsView 和QGraphicsScene,负责接收并展示图像数据。

3. 详细代码实现

  • camerainterface.h(相机接口抽象类)
c 复制代码
#ifndef CAMERAINTERFACE_H
#define CAMERAINTERFACE_H

#include <QObject>

class CameraInterface : public QObject
{
    Q_OBJECT
public:
    virtual ~CameraInterface() {}

    // 初始化相机,返回是否成功
    virtual bool initialize() = 0;
    // 启动相机图像采集
    virtual void startCapture() = 0;
    // 停止相机图像采集
    virtual void stopCapture() = 0;
    // 手动触发一次图像采集
    virtual void manualTrigger() = 0;
    // 断开相机连接
    virtual void disconnectCamera() = 0;
    // 获取相机当前连接状态
    virtual bool isConnected() const = 0;

signals:
    // 图像采集成功信号,携带图像数据、宽度、高度
    void imageCaptured(unsigned char* imageData, int width, int height);
    // 相机出错信号
    void cameraError();
};

#endif // CAMERAINTERFACE_H

hikcameraworker.h 和 hikcameraworker.cpp(海康相机工作线程类)

c 复制代码
// hikcameraworker.h
#ifndef HIKCAMERAWORKER_H
#define HIKCAMERAWORKER_H

#include <QObject>
#include <QThread>
#include <MvCameraControl.h>
#include "camerainterface.h"

class HikCameraWorker : public CameraInterface
{
    Q_OBJECT
public:
    explicit HikCameraWorker(int cameraIndex, QObject *parent = nullptr);
    ~HikCameraWorker();

private:
    int m_cameraIndex;
    MV_CC_DEVICE_INFO_LIST m_deviceInfoList;
    MV_CC_DEVICE_INFO* m_pDeviceInfo;
    MV_CC_HANDLE m_cameraHandle;
    bool m_isCapturing;
    bool m_isConnected;

    bool connectCamera();
    void disconnectCamera();
    static void __stdcall imageCallback(MV_FRAME_OUT_INFO_EX* pFrameInfo, void* pUser);

    // CameraInterface 接口实现
    bool initialize() override;
    void startCapture() override;
    void stopCapture() override;
    void manualTrigger() override;
    void disconnectCamera() override;
    bool isConnected() const override;
};

// hikcameraworker.cpp
#include "hikcameraworker.h"

HikCameraWorker::HikCameraWorker(int cameraIndex, QObject *parent) :
    CameraInterface(parent),
    m_cameraIndex(cameraIndex),
    m_pDeviceInfo(null nullable),
    m_cameraHandle(null nullable),
    m_isCapturing(false),
    m_isConnected(false)
{
    // 枚举设备
    MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &m_deviceInfoList);
    if (m_deviceIdex >= 0 && m_deviceIdex < m_deviceInfoList.nDeviceNum) {
        m_pDeviceInfo = m_deviceInfoList.pDeviceInfo[m_deviceIndex];
    }
}

HikCameraWorker::~HikCameraWorker()
{
    stopCapture();
    disconnectCamera();
}

bool HikCameraWorker::initialize()
{
    return connectCamera();
}

void HikCameraWorker::startCapture()
{
    if (!m_isConnected &&!connectCamera()) {
        emit cameraError();
        return;
    }

    m_isCapturing = true;
    MV_CC_SetCallbackFunction(m_cameraHandle, imageCallback, this);
    MV_CC_StartGrabbing(m_cameraHandle);
}

void HikCameraWorker::stopCapture()
{
    m_isCapturing = false;
    if (m_cameraHandle) {
        MV_CC_StopGrabbing(m_cameraHandle);
        MV_CC_DestroyHandle(m_cameraHandle);
        m_cameraHandle = nullptr;
    }
}

void HikCameraWorker::manualTrigger()
{
    if (m_isConnected && m_cameraHandle) {
        MV_CC_SoftwareTriggerCommand(m_cameraHandle);
    }
}

bool HikCameraWorker::connectCamera()
{
    if (m_pDeviceInfo) {
        int nRet = MV_CC_CreateHandle(&m_cameraHandle, m_pDeviceInfo);
        if (nRet == MV_OK) {
            nRet = MV_CC_OpenDevice(m_cameraHandle);
            if (nRet == MV_OK) {
                m_isConnected = true;
                return true;
            } else {
                MV_CC_DestroyHandle(m_cameraHandle);
                char errorMsg[1024];
                MV_CC_GetLastErrorMsg(errorMsg, sizeof(errorMsg));
                qDebug() << "Open device error: " << errorMsg;
                m_cameraHandle = nullptr;
            }
        }
    }
    return false;
}

void HikCameraWorker::disconnectCamera()
{
    if (m_cameraHandle) {
        MV_CC_CloseDevice(m_cameraHandle);
        MV_CC_DestroyHandle(m_cameraHandle);
        m_cameraHandle = nullptr;
    }
    m_isConnected = false;
}

void __stdcall HikCameraWorker::imageCallback(MV_FRAME_OUT_INFO_EX* pFrameInfo, void* pUser)
{
    HikCameraWorker* worker = static_cast<HikCameraWorker*>(pUser);
    if (worker && worker->m_isCapturing) {
        emit worker->imageCaptured(pFrameInfo->pBuf, pFrameInfo->nWidth, pFrameInfo->nHeight);
    }
}

bool HikCameraWorker::isConnected() const
{
    return m_isConnected;
}

imageviewerwidget.h 和 imageviewerwidget.cpp(图像查看部件类)

c 复制代码
// imageviewerwidget.h
#ifndef IMAGEVIEWERWIDGET_H
#define IMAGEVIEWERWIDGET_H

#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QGraphicsRectItem>
#include <QGraphicsItemGroup>
#include <QMenu>
#include <QAction>
#include <QDebug>
#include <QPainterPath>
#include <cmath>
#include <QToolTip>

class ImageViewerWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ImageViewerWidget(QWidget *parent = nullptr);
    ~ImageViewerWidget();

    // 动态添加工具项到右键菜单,例如找线卡尺、圆形卡尺等
    void addToolAction(QAction* action);
    // 动态移除工具项
    void removeToolAction(QAction* action);

public slots:
    void updateImage(unsigned char* imageData, int width, int height);

signals:
    // 当绘制矩形等操作完成后,发出信号通知外界(例如主窗口),携带矩形信息
    void shapeDrawn(QRectF rect);
    // 当使用找线卡尺完成测量后,发出信号携带线的相关信息(起点、终点坐标等)
    void lineMeasured(QPointF start, QPointF end);
    // 当使用圆形卡尺完成测量后,发出信号携带圆的相关信息(圆心、半径)
    void circleMeasured(QPointF center, double radius);
    // 当绘制旋转矩形完成后,发出信号携带旋转矩形相关信息(矩形、旋转角度)
    void rotatedRectDrawn(QRectF rect, double angle);

protected:
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void wheelEvent(QWheelEvent* event) override;

private:
    QGraphicsScene* m_scene;
    QGraphicsView* m_view;
    QImage m_image;
    QGraphicsItemGroup* m_currentItemGroup;  // 用于管理当前选中的图形组合(如矩形、卡尺等)
    QMenu* m_rightClickMenu;
    double m_scaleFactor;  // 缩放因子

    // 记录旋转矩形相关状态
    bool m_isDrawingRotatedRect;
    QPointF m_rotatedRectStartPoint;
    QPointF m_rotatedRectLastPoint;
    double m_rotatedRectRotation;

    // 正在操作的矩形角索引,用于四个角拖拽, -1 表示无操作
    int m_activeCornerIndex;
    // 记录矩形中心初始位置,用于中心平移
    QPointF m_rectCenterInitialPos;

    // 绘制矩形相关函数
    void drawRectangle(QPointF startPoint, QPointF endPoint);
    // 处理右键菜单事件
    void handleRightClickMenu(QMouseEvent* event);
    // 放大图片
    void zoomIn();
    // 缩小图片
    void zoomOut();
    // 平移图片
    void translateView(QPointF offset);

    // 找线卡尺工具相关函数
    void startLineMeasurement(QPointF startPoint);
    // 继续线测量
    void continueLineMeasurement(QPointF currentPoint);
    // 完成线测量
    void finishLineMeasurement(QPointF endPoint);

    // 圆形卡尺工具相关函数
    void startCircleMeasurement(QPointF centerPoint);
    // 继续圆测量
    void continueCircleMeasurement(QPointF currentPoint);
    // 完成圆测量
    void finishCircleMeasurement(QPointF currentPoint);

    // 旋转矩形工具相关函数
    void startRotatedRectMeasurement(QPointF startPoint);
    // 继续旋转矩形测量
    void continueRotatedRectMeasurement(QPointF currentPoint);
    // 完成旋转矩形测量
    void finishRotatedRectMeasurement(QPointF currentPoint);

    // 辅助函数,判断鼠标点击位置是否靠近矩形角
    bool isNearCorner(QPointF point, QRectF rect, double tolerance = 5.0);
    // 辅助函数,更新矩形旋转状态
    void updateRectRotation(QPointF currentPoint);
    // 辅助函数,根据鼠标移动更新矩形位置和大小
    void updateRectGeometry(QPointF currentPoint);
    // 辅助函数,显示矩形提示信息
    void showRectTooltip(QPointF point, QRectF rect);
};

#endif // IMAGEVIEWERWIDGET_H





// imageviewerwidget.cpp
#include "imageviewerWidget.h"

ImageViewerWidget::ImageViewerWidget(QWidget *parent) :
    QWidget(parent),
    m_scaleFactor(1.0),
    m_isDrawingRotatedRect(false),
    m_rotatedRectRotation(0),
    m_activeLineEndpointIndex(-1),
    m_lineCenterInitialPos(),
    m_activeCornerIndex(-1),
    m_rectCenterInitialPos()
{
    m_scene = new QGraphicsScene(this);
    m_view = new QGraphicsView(m_scene);
    m_view->setAlignment(Qt::AlignCenter);
    m_view->setDragMode(QGraphicsView::ScrollHandDrag);  // 开启平移模式

    m_rightClickMenu = new QMenu(this);
    m_currentItemGroup = nullptr;

    QVBoxLayout* layout = new QVBoxLayout(this);
    layout->addWidget(m_view);
    setLayout(layout);
}

ImageViewerWidget::~ImageViewerWidget()
{
    delete m_scene;
    delete m_view;
    delete m_rightClickMenu;
}

void ImageViewerWidget::updateImage(unsigned char* imageData, int width, int height)
{
    m_image = QImage(imageData, width, height, QImage::Format_RGB888);
    m_scene->clear();
    m_scene->addPixmap(QPixmap::fromImage(m_image));
    m_view->resetTransform();  // 重置视图变换,避免缩放等影响新图像显示
    m_scaleFactor = 1.0;
    m_isDrawingRotatedRect = false;
}

void ImageViewerWidget::addToolAction(QAction* action)
{
    m_rightClickMenu->addAction(action);
}

void ImageViewerWidget::removeToolAction(QAction* action)
{
    m_rightClickMenu->removeAction(action);
}

void ImageViewerWidget::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::RightButton) {
        handleRightClickMenu(event);
    } else if (event->button() == Qt::LeftButton) {
        if (!m_currentItemGroup) {
            m_currentItemGroup = new QGraphicsItemGroup;
            m_scene->addItem(m_currentItemGroup);
        }
        if (m_rightClickMenu->actions().contains(findChild<QAction*>("lineMeasurementAction"))) {
            startLineMeasurement(mapToScene(event->pos()));
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("circleMeasurementAction"))) {
            startCircleMeasurement(mapToScene(event->pos()));
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("rotatedRectAction"))) {
            startRotatedRectMeasurement(mapToScene(event->pos()));
        } else {
            drawRectangle(mapToScene(event->pos()), mapToScene(event->pos()));
        }

        // 检查是否点击在线端点、旋转矩形角上
        if (m_currentItemGroup) {
            if (m_rightClickMenu->actions().contains(findChild<QAction*>("lineMeasurementAction"))) {
                QPainterPath linePath = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front())->path();
                m_activeLineEndpointIndex = getLineEndpointIndex(mapToScene(event->pos()), linePath);
                if (m_activeLineEndpointIndex!= -1) {
                    m_lineCenterInitialPos = linePath.boundingRect().center();
                }
            }
            if (m_rightClickMenu->actions().contains(findChild<QAction*>("rotatedRectAction"))) {
                QRectF rect = m_currentItemGroup->boundingRect();
                m_activeCornerIndex = getCornerIndex(mapToScene(event->pos()), rect);
                if (m_activeCornerIndex!= -1) {
                    m_rectCenterInitialPos = rect.center();
                }
            }
        }
    }
    QWidget::mousePressEvent(event);
}

void ImageViewerWidget::mouseMoveEvent(QMouseEvent* event)
{
    if (m_currentItemGroup && event->buttons() == Qt::LeftButton) {
        QPointF newPos = mapToScene(event->pos());
        QPointF offset = newPos - m_currentItemGroup->pos();
        m_currentItemGroup->moveBy(offset.x(), offset.y());
        if (m_rightClickMenu->actions().contains(findChild<QAction*>("lineMeasurementAction"))) {
            continueLineMeasurement(newPos);
            if (m_activeLineEndpointIndex!= -1) {
                updateLineGeometry(newPos);
            } else {
                updateLineRotation(newPos);
            }
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("circleMeasurementAction"))) {
            continueCircleMeasurement(newPos);
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("rotatedRectAction"))) {
            continueRotatedRectMeasurement(newPos);
            if (m_activeCornerIndex!= -1) {
                updateRectGeometry(newPos);
            } else {
                updateRectRotation(newPos);
            }
        }
        // 显示线、矩形或圆的提示信息
        if (m_rightClickMenu->actions().contains(findChild<QAction*>("lineMeasurementAction"))) {
            QPainterPath linePath = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front())->path();
            showLineTooltip(mapToScene(event->pos()), linePath);
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("rotatedRectAction"))) {
            QRectF rect = m_currentItemGroup->boundingRect();
            showRectTooltip(mapToScene(event->pos()), rect);
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("circleMeasurementAction"))) {
            QGraphicsPathItem* circleItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
            QPainterPath circlePath = circleItem->path();
            showCircleTooltip(mapToScene(event->pos()), circlePath);
        }
    }
    QWidget::mouseMoveEvent(event);
}

void ImageViewerWidget::mouseReleaseEvent(QMouseEvent* event)
{
    if (m_currentItemGroup && event->button() == Qt::LeftButton) {
        if (m_rightClickMenu->actions().contains(findChild<QAction*>("lineMeasurementAction"))) {
            finishLineMeasurement(mapToScene(event->pos()));
            m_activeLineEndpointIndex = -1;
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("circleMeasurementAction"))) {
            finishCircleMeasurement(mapToScene(event->pos()));
        } else if (m_rightClickMenu->actions().contains(findChild<QAction*>("rotatedRectAction"))) {
            finishRotatedRectMeasurement(mapToScene(event->pos()));
            m_activeCornerIndex = -1;
        } else {
            QRectF rect = m_currentItemGroup->boundingRect();
            emit shapeDrawn(rect);
        }
        m_currentItemGroup = nullptr;
    }
    QWidget::mouseReleaseEvent(event);
}

void ImageViewerWidget::wheelEvent(QWheelEvent* event)
{
    if (event->angleDelta().y() > 0) {
        zoomIn();
    } else {
        zoomOut();
    }
    QWidget::wheelEvent(event);
}

void ImageViewerWidget::drawRectangle(QPointF startPoint, QPointF endPoint)
{
    QGraphicsRectItem* rectItem = new QGraphicsRectItem(QRectF(startPoint, endPoint));
    m_currentItemGroup->addToGroup(rectItem);
}

void ImageViewerWidget::handleRightClickMenu(QMouseEvent* event)
{
    m_rightClickMenu->exec(mapToGlobal(event->pos()));
}

void ImageViewerWidget::zoomIn()
{
    m_scaleFactor *= 1.2;
    m_view->scale(m_scaleFactor, m_scaleFactor);
}

void ImageViewerWidget::zoomOut()
{
    m_scaleFactor /= 1.2;
    m_view->scale(m_scaleFactor, m_scaleFactor);
}

void ImageViewerWidget::translateView(QPointF offset)
{
    m_view->translate(offset.x(), offset.y());
}

void ImageViewerWidget::startLineMeasurement(QPointF startPoint)
{
    QGraphicsPathItem* lineItem = new QGraphicsPathItem;
    QPainterPath path;
    path.moveTo(startPoint);
    lineItem->setPath(path);
    m_currentItemGroup->addToGroup(lineItem);
}

void ImageViewerWidget::continueLineMeasurement(QPointF currentPoint)
{
    QGraphicsPathItem* lineItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
    QPainterPath path = lineItem->path();
    path.lineTo(currentPoint);
    lineItem->setPath(path);
}

void ImageViewerWidget::finishLineMeasurement(QPointF endPoint)
{
    QGraphicsPathItem* lineItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
    QPainterPath path = lineItem->path();
    path.lineTo(endPoint);
    lineItem->setPath(path);
    emit lineMeasured(path.elementAt(0).x, path.elementAt(0).y, path.elementAt(path.elementCount() - 1).x, path.elementAt(path.elementCount() - 1).y);
}

void ImageViewerWidget::updateLineGeometry(QPointF currentPoint)
{
    if (m_currentItemGroup) {
        QPainterPath linePath = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front())->path();
        QPointF center = linePath.boundingRect().center();
        QPointF offset = currentPoint - m_lineCenterInitialPos;
        switch (m_activeLineEndpointIndex) {
        case 0: // 起点
            linePath.setElementPositionAt(0, linePath.elementAt(0).x + offset.x(), linePath.elementAt(0).y + offset.y());
            break;
        case 1: // 终点
            linePath.setElementPositionAt(linePath.elementCount() - 1, linePath.elementAt(linePath.elementCount() - 1).x + offset.x(), linePath.elementAt(linePath.elementCount() - 1).y + offset.y());
            break;
        }
        QGraphicsPathItem* lineItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
        lineItem->setPath(linePath);
    }
}

bool ImageViewerWidget::isNearLineEndpoint(QPointF point, QPainterPath linePath, double tolerance = 5.0)
{
    const QPointF endpoints[2] = { QPointF(linePath.elementAt(0).x, linePath.elementAt(0).y), QPointF(linePath.elementAt(linePath.elementCount() - 1).x, linePath.elementAt(linePath.elementCount() - 1).y) };
    for (int i = 0; i < 2; i++) {
        if (QLineF(point, endpoints[i]).length() < tolerance) {
            return true;
        }
    }
    return false;
}

int ImageViewerWidget::getLineEndpointIndex(QPointF point, QPainterPath linePath)
{
    const QPointF endpoints[2] = { QPointF(linePath.elementAt(0).x, linePath.elementAt(0).y), QPointF(linePath.elementAt(linePath.elementCount() - 1).x, linePath.elementAt(linePath.elementCount() - 1).y) };
    for (int i = 0; i < 2; i++) {
        if (QLineF(point, endpoints[i]).length() < 5.0) {
            return i;
        }
    }
    return -1;
}

void ImageViewerWidget::updateLineRotation(QPointF currentPoint)
{
    if (m_currentItemGroup) {
        QPainterPath linePath = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front())->path();
        double angle = std::atan2(currentPoint.y() - linePath.boundingRect().center().y(), currentPoint.x() - linePath.boundingRect().center().x());
        QGraphicsPathItem* lineItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
        lineItem->setRotation(angle * 180 / M_PI);
    }
}

void ImageViewerWidget::startCircleMeasurement(QPointF centerPoint)
{
    QGraphicsPathItem* circleItem = new QGraphicsPathItem;
    QPainterPath path;
    path.addEllipse(centerPoint, 0, 0);
    circleItem->setPath(path);
    m_currentItemGroup->addToGroup(circleItem);
}

void ImageViewerWidget::continueCircleMeasurement(QPointF currentPoint)
{
    QGraphicsPathItem* circleItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
    QPainterPath path = circleItem->path();
    double radius = std::sqrt(std::pow(currentPoint.x() - path.elementAt(0).x, 2) + std::pow(currentPoint.y() - path.elementAt(0).y, 2));
    path = QPainterPath();
    path.addEllipse(path.elementAt(0).x, path.elementAt(0).y, radius, radius);
    circleItem->setPath(path);
}

void ImageViewerWidget::finishCircleMeasurement(QPointF currentPoint)
{
    QGraphicsPathItem* circleItem = static_cast<QGraphicsPathItem*>(m_currentItemGroup->childItems().front());
    QPainterPath path = circleItem->path();
    double radius = std::sqrt(std::pow(currentPoint.x() - path.elementAt(0).x, 2) + std::pow(currentPoint.y() - path.elementAt(0).y, 2));
    emit circleMeasured(path.elementAt(0).x, path.elementAt(0).y, radius);
}

void ImageViewerWidget::startRotatedRectMeasurement(QPointF startPoint)
{
    m_isDrawingRotatedRect = true;
    m_rotatedRectStartPoint = startPoint;
    m_rotatedRectLastPoint = startPoint;
}

void ImageViewerWidget::continueRotatedRectMeasurement(QPointF currentPoint)
{
    if (m_isDrawingRotatedRect) {
        QGraphicsItemGroup* group = m_currentItemGroup;

        // 先移除之前可能存在的矩形,用于实时更新显示
        QList<QGraphicsItem*> items = group->childItems();
        for (QGraphicsItem* item : items) {
            if (QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item)) {
                group->removeFromGroup(rectItem);
                delete rectItem;
            }
        }

        QRectF rect = QRectF(m_rotatedRectStartPoint, currentPoint).normalized();
        double angle = std::atan2(currentPoint.y() - m_rotatedRectStartPoint.y(), currentPoint.x() - m_rotatedRectStartPoint.x());

        QGraphicsRectItem* rectItem = new QGraphicsRectItem(rect);
        rectItem->setTransformOriginPoint(rect.center());
        rectItem->setRotation(angle * 180 / M_PI);
        group->addToGroup(rectItem);

        m_rotatedRectLastPoint = currentPoint;
        m_rotatedRectRotation = angle;
    }
}

void ImageViewerWidget::finishRotatedRectMeasurement(QPointF currentPoint)
{
    if (m_isDrawingRotatedRect) {
        continueRotatedRectMeasurement(currentPoint);
        QRectF rect = QRectF(m_rotatedRectStartPoint, currentPoint).normalized();
        emit rotatedRectDrawn(rect, m_rotatedRectRotation * 180 / M_PI);
        m_isDrawingRotatedRect = false;
    }
}

bool ImageViewerWidget::isNearCorner(QPointF point, QRectF rect, double tolerance = 5.0)
{
    const QPointF corners[4] = { rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft() };
    for (int i = 0; i < 4; i++) {
        if (QLineF(point, corners[i]).length() < tolerance) {
            return true;
        }
    }
    return false;
}
int ImageViewerWidget::getCornerIndex(QPointF point, QRectF rect)
{
    const QPointF corners[4] = { rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft() };
    for (int i = 0; i < 4; i++) {
        if (QLineF(point, corners[i]).length() < 5.0) {
            return i;
        }
    }
    return -1;
}

void ImageViewerWidget::updateRectGeometry(QPointF currentPoint)
{
    if (m_currentItemGroup) {
        QRectF rect = m_currentItemGroup->boundingRect();
        QPointF center = rect.center();
        QPointF offset = currentPoint - m_rectCenterInitialPos;
        switch (m_activeCornerIndex) {
        case 0: // 左上角
            rect.setTopLeft(rect.topLeft() + offset);
            break;
        case 1: // 右上角
            rect.setTopRight(rect.topRight() + offset);
            break;
        case 2: // 右下角
            rect.setBottomRight(rect.bottomRight() + offset);
            break;
        case 3: // 左下角
            rect.setBottomLeft(rect.bottomLeft() + offset);
            break;
        }
        QGraphicsRectItem* rectItem = static_cast<QGraphicsRectItem*>(m_currentItemGroup->childItems().first());
        rectItem->setRect(rect);
    }
}

void ImageViewerWidget::updateRectRotation(QPointF currentPoint)
{
    if (m_currentItemGroup) {
        QRectF rect = m_currentItemGroup->boundingRect();
        double angle = std::atan2(currentPoint.y() - rect.center().y(), currentPoint.x() - rect.center().x());
        QGraphicsRectItem* rectItem = static_cast<QGraphicsRectItem*>(m_currentItemGroup->childItems().first());
        rectItem->setRotation(angle * 180 / M_PI);
        m_rotatedRectRotation = angle;
    }
}

void ImageViewerWidget::showLineTooltip(QPointF point, QPainterPath linePath)
{
    QPointF start = QPointF(linePath.elementAt(0).x, linePath.elementAt(0).y);
    QPointF end = QPointF(linePath.elementAt(linePath.elementCount() - 1).x, linePath.elementAt(linePath.elementCount() - 1).y);
    QString tooltipText = QString("线信息:\n起点坐标: (%1, %2)\n终点坐标: (%3, %4)")
                         .arg(start.x())
                         .arg(start.y())
                         .arg(end.x())
                         .arg(end.y());
    QToolTip::showText(mapToGlobal(mapFromScene(point)), tooltipText, this);
}

void ImageViewerWidget::showRectTooltip(QPointF point, QRectF rect)
{
    QString tooltipText = QString("矩形信息:\n左上角坐标: (%1, %2)\n右下角坐标: (%3, %4)\n旋转角度: %5°")
                         .arg(rect.topLeft().x())
                         .arg(rect.topLeft().y())
                         .arg(rect.bottomRight().x())
                         .arg(rect.bottomRight().y())
                         .arg(m_rotatedRectRotation * 180 / M_PI);
    QToolTip::showText(mapToGlobal(mapFromScene(point)), tooltipText, this);
}

void ImageViewerWidget::showCircleTooltip(QPointF point, QPainterPath circlePath)
{
    QPointF center = QPointF(circlePath.elementAt(0).x, circlePath.elementAt(0).y);
    double radius = std::sqrt(std::pow(point.x() - center.x, 2) + std::pow(point.y() - center.y, 2));
    QString tooltipText = QString("圆信息:\n圆心坐标: (%1, %2)\n半径: %3")
                         .arg(center.x())
                         .arg(center.y())
                         .arg(radius);
    QToolTip::showText(mapToGlobal(mapFromScene(point)), tooltipText, this);
}

以下是对 ImageViewerWidget.cpp 中各主要函数功能的简单介绍:

构造函数 ImageViewerWidget

  • 功能 :初始化 ImageViewerWidget 类相关的成员变量、创建图形场景 m_scene、图形视图 m_view,设置视图平移模式等,还初始化右键菜单和用于管理图形组合的指针,搭建好界面布局,为后续操作做准备。

析构函数 ~ImageViewerWidget

  • 功能 :释放之前构造函数中创建的 m_scenem_viewm_rightClickMenu 等资源,避免内存泄漏,进行清理工作。

updateImage

  • 功能:依据传入的图像数据更新显示的图像,包括清除原有场景内容、添加新图像、重置视图变换以及相关操作状态变量,确保后续操作基于新图像开展。

addToolAction

  • 功能 :往右键菜单里添加一个工具操作选项(QAction),可动态扩充右键菜单功能。

removeToolAction

  • 功能 :从右键菜单中移除指定的工具操作选项(QAction),实现动态调整菜单内容。

mousePressEvent

  • 功能:处理鼠标按下事件,右键按下弹出右键菜单,左键按下依据右键菜单中的工具选项启动相应图形绘制或测量操作,同时判断是否点击在图形关键位置(如线端点、矩形角)并记录相关信息。

mouseMoveEvent

  • 功能:响应鼠标移动且左键按下的情况,实现图形的平移,根据不同激活工具继续对应图形的绘制或修改操作(如更新线、圆、矩形的相关参数),还实时显示鼠标悬停处图形的提示信息。

mouseReleaseEvent

  • 功能:处理鼠标释放事件,针对不同激活工具完成相应操作、重置操作状态,并在普通矩形绘制时发送绘制完成信号,结束当前操作。

wheelEvent

  • 功能:依据鼠标滚轮滚动方向,调用相应函数实现图片放大或缩小功能。

drawRectangle

  • 功能:创建矩形图形项并添加到当前图形管理组,用于在场景中绘制矩形。

handleRightClickMenu

  • 功能:在鼠标右键点击位置弹出右键菜单,方便选择操作。

zoomIn

  • 功能:增大图片的缩放因子,实现图片放大显示效果。

zoomOut

  • 功能:减小图片的缩放因子,让图片缩小显示。

translateView

  • 功能:按照传入的偏移量对视图进行平移,便于浏览图像不同位置。

startLineMeasurement

  • 功能:开启找线卡尺测量,创建初始线图形项并添加到管理组,准备绘制线。

continueLineMeasurement

  • 功能:在找线卡尺测量中,随着鼠标移动持续更新线的终点位置,动态绘制线。

finishLineMeasurement

  • 功能:结束找线卡尺测量,确定线的最终形态,并发送测量完成信号携带线的起止坐标信息。

updateLineGeometry

  • 功能:根据鼠标操作情况,更新找线卡尺绘制线的端点位置,实现线的拖拽变形。

isNearLineEndpoint

  • 功能:判断鼠标位置是否靠近线的端点,辅助后续操作判断。

getLineEndpointIndex

  • 功能:获取鼠标位置靠近的线端点索引,确定操作的具体端点。

updateLineRotation

  • 功能:依据鼠标位置改变找线卡尺绘制线的旋转角度,实现线的旋转操作。

startCircleMeasurement

  • 功能:启动圆形卡尺测量,创建初始圆形图形项添加到管理组,准备后续绘制圆。

continueCircleMeasurement

  • 功能:在圆形卡尺测量中,随着鼠标移动更新圆的半径,动态绘制圆。

finishCircleMeasurement

  • 功能:结束圆形卡尺测量,确定圆的最终形态,并发送测量完成信号携带圆心和半径信息。

startRotatedRectMeasurement

  • 功能:开启旋转矩形绘制操作,记录起始点等初始信息。

continueRotatedRectMeasurement

  • 功能:在旋转矩形绘制中,根据鼠标位置实时更新矩形形状和旋转角度,动态展示旋转矩形。

finishRotatedRectMeasurement

  • 功能:结束旋转矩形绘制,确定最终矩形形态,并发送绘制完成信号携带矩形及旋转角度信息。

isNearCorner

  • 功能:判断鼠标位置是否靠近矩形的角,辅助后续矩形角操作判断。

getCornerIndex

  • 功能:获取鼠标位置靠近的矩形角索引,确定操作的具体角。

updateRectGeometry

  • 功能:依据鼠标对矩形角的操作,更新旋转矩形的大小和位置,实现角的拖拽改变矩形形态。

updateRectRotation

  • 功能:根据鼠标位置更新旋转矩形的旋转角度,实现旋转操作。

showLineTooltip

  • 功能:在鼠标悬停在线上时,显示包含线相关参数(起止坐标)的提示信息。

showRectTooltip

  • 功能:当鼠标悬停在矩形上时,展示包含矩形关键参数(坐标、旋转角度)的提示信息。

showCircleTooltip

  • 功能:鼠标悬停在圆上时,显示包含圆的圆心和半径等参数的提示信息。

cameramanager.h 和 cameramanager.cpp(相机管理类,负责相机业务逻辑)

c 复制代码
// cameramanager.h
#ifndef CAMERAMANAGER_H
#define CAMERAMANAGER_H

#include <QObject>
#include <QTimer>
#include <vector>
#include "camerainterface.h"

class CameraManager : public QObject
{
    Q_OBJECT
public:
    CameraManager(QObject *parent = nullptr);
    ~CameraManager();

    // 初始化相机列表,根据实际连接的相机数量
    void initializeCameras();
    // 启动所有相机的图像采集
    void startCapture();
    // 停止所有相机的图像采集
    void stopCapture();
    // 手动触发所有相机采集一次图像
    void manualTrigger();
    // 关闭所有相机连接
    void closeAllCameras();

signals:
    // 图像采集成功信号,携带图像数据、宽度、高度以及相机索引
    void imageCaptured(unsigned char* imageData, int width, int height, int cameraIndex);
    // 相机出错信号,携带相机索引
    void cameraError(int cameraIndex);

private:
    std::vector<CameraInterface*> m_cameraWorkers;
    QTimer* m_reconnectionTimer;
    void setupCameraWorkers(int numCameras);
    void handleCameraError(int cameraIndex);
    void checkForReconnection();
};

// cameramanager.cpp
#include "cameramanager.h"

CameraManager::CameraManager(QObject *parent) :
    QObject(parent),
    m_reconnectionTimer(new QTimer(this))
{
    connect(m_reconnectionTimer, &QTimer::timeout, this, &CameraManager::checkForReconnection);
}

CameraManager::~CameraManager()
{
    closeAllCameras();
    delete m_reconnectionTimer;
    for (CameraInterface* worker : m_cameraWorkers) {
        delete worker;
    }
}

void CameraManager::initializeCameras()
{
    MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &m_deviceInfoList);
    int numCameras = m_deviceInfoList.nDeviceNum;
    setupCameraWorkers(numCameras);
}

void CameraManager::startCapture()
{
    for (CameraInterface* worker : m_cameraWorkers) {
        QThread* thread = new QThread;
        worker->moveToThread(thread);
        connect(thread, &QThread::started, worker, &CameraInterface::startCapture);
        connect(worker, &CameraInterface::destroyed, thread, &QThread::quit);
        connect(thread, &QThread::finished, thread, &QThread::deleteLater);
        thread->start();
    }
}

void CameraManager::stopCapture()
{
    for (CameraInterface* worker : m_cameraWorkers) {
        worker->stopCapture();
    }
}

void CameraManager::manualTrigger()
{
    for (CameraInterface* worker : m_cameraWorkers) {
        worker->manualTrigger();
    }
}

void CameraManager::closeAllCameras()
{
    for (CameraInterface* worker : m_cameraWorkers) {
        worker->disconnectCamera();
    }
}

void CameraManager::setupCameraWorkers(int numCameras)
{
    m_cameraWorkers.resize(numCameras);
    for (int i = 0; i < numCameras; ++i) {
        m_cameraWorkers[i] = new HikCameraWorker(i);
        connect(m_cameraWorkers[i], &CameraInterface::imageCaptured, this, [this, i](unsigned char* imageData, int width, int height) {
            emit imageCaptured(imageData, width, height, i);
        });
        connect(m_cameraWorkers[i], &CameraInterface::cameraError, this, &CameraManager::handleCameraError);
    }
}

void CameraManager::handleCameraError(int cameraIndex)
{
    m_cameraWorkers[cameraIndex]->stopCapture();
    m_cameraWorkers[cameraIndex]->disconnectCamera();
    m_reconnectionTimer->start(5000);
    emit cameraError(cameraIndex);
}

void CameraManager::checkForReconnection()
{
    m_reconnectionTimer->stop();
    for (int i = 0; i < m_cameraWorkers.size(); ++i) {
        if (!m_cameraWorkers[i]->isConnected()) {
            QThread* thread = new QThread;
            m_cameraWorkers[i]->moveToThread(thread);
            connect(thread, &QThread::started, m_cameraWorkers[i], &CameraInterface::startCapture);
            connect(m_cameraWorkers[i], &CameraInterface::destroyed, thread, &QThread::quit);
            connect(thread, &QThread::finished, thread, &QThread::deleteLater);
            thread->start();
        }
    }
}

mainwindow.h 和 mainwindow.cpp(主窗口类)

c 复制代码
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
#include <QStatusBar>
#include <QAction>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QTextEdit>
#include <vector>
#include "imageviewerwidget.h"

class CameraManager;

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void onStartCaptureClicked();
    void onStopCaptureClicked();
    void updateImage(unsigned char* imageData, int width, int height, int cameraIndex);
    void handleCameraError(int cameraIndex);
    void onManualTriggerClicked();
    void onOpenCameraClicked();
    void onCloseCameraClicked();
    void onToggleCameraClicked();

private:
    CameraManager* m_cameraManager;
    std::vector<ImageViewerWidget*> m_imageViewers;
    QMenuBar* m_menuBar;
    QToolBar* m_toolBar;
    QStatusBar* m_statusBar;
    QAction* m_startCaptureAction;
    QAction* m_stopCaptureAction;
    QAction* m_manualTriggerAction;
    QAction* m_openCameraAction;
    QAction* m_closeCameraAction;
    QAction* m_toggleCameraAction;
    QWidget* m_centralWidget;
    QVBoxLayout* m_layout;
    QHBoxLayout* m_imageLayout;
    QTextEdit* m_logTextEdit;

    void setupMenuBar();
    void setupToolBar();
    void setupStatusBar();
    void setupCameraUI();
    void addImageViewerWidget();
    void removeImageViewerWidget();
};

#endif // MAINWINDOW_H
#endif // MAINWINDOW_H


// mainwindow.cpp
#include "mainwindow.h"
#include "cameramanager.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    m_cameraManager = new CameraManager();
    m_cameraManager->initializeCameras();

    setupCameraUI();

    connect(m_cameraManager, &CameraManager::imageCaptured, this, &MainWindow::updateImage);
    connect(m_cameraManager, &CameraManager::cameraError, this, &MainWindow::handleCameraError);
}

MainWindow::~MainWindow()
{
    delete m_cameraManager;
    for (ImageViewerWidget* viewer : m_imageViewers) {
        delete viewer;
    }
}

void MainWindow::setupMenuBar()
{
    m_menuBar = new QMenuBar(this);

    QMenu* cameraMenu = m_menuBar->addMenu("相机");
    m_startCaptureAction = cameraMenu->addAction("开始采集");
    connect(m_startCaptureAction, &QAction::triggered, this, &MainWindow::onStartCaptureClicked);
    m_stopCaptureAction = cameraMenu->addAction("停止采集");
    connect(m_stopCaptureAction, &QAction::triggered, this, &MainWindow::onStopCaptureClicked);
    m_manualTriggerAction = cameraMenu->addAction("手动触发");
    connect(m_manualTriggerAction, &QAction::triggered, this, &MainWindow::onManualTriggerClicked);
    m_openCameraAction = cameraMenu->addAction("打开相机");
    connect(m_openCameraAction, &QAction::triggered, this, &MainWindow::onOpenCameraClicked);
    m_closeCameraAction = cameraMenu->addAction("关闭相机");
    connect(m_closeCameraAction, &QAction::triggered, this, &MainWindow::onCloseCameraClicked);
    m_toggleCameraAction = cameraMenu->addAction("动态开关相机");
    connect(m_toggleCameraAction, &QAction::triggered, this, &MainWindow::onToggleCameraClicked);

    setMenuBar(m_menuBar);
}

void MainWindow::setupToolBar()
{
    m_toolBar = new QToolBar(this);
    m_toolBar->addAction(m_startCaptureAction);
    m_toolBar->addAction(m_stopCaptureAction);
    m_toolBar->addAction(m_manualTriggerAction);
    m_toolBar->addAction(m_openCameraAction);
    m_toolBar->addAction(m_closeCameraAction);
    m_toolBar->addAction(m_toggleCameraAction);
    addToolBar(m_toolBar);
}

void MainWindow::setupStatusBar()
{
    m_statusBar = new QStatusBar(this);
    setStatusBar(m_statusBar);
}

void MainWindow::setupCameraUI()
{
    int numCameras = m_cameraManager->getCameraCount();

    m_imageViewers.resize(numCameras);

    m_centralWidget = new QWidget(this);
    setCentralWidget(m_centralWidget);

    m_layout = new QVBoxLayout(m_centralWidget);

    m_imageLayout = new QHBoxLayout();
    m_layout->addLayout(m_imageLayout);

    m_logTextEdit = new QTextEdit(this);
    m_layout->addWidget(m_logTextEdit);

    for (int i = 0; i < numCameras; ++i) {
        m_imageViewers[i] = new ImageViewerWidget(this);
        m_imageLayout->addWidget(m_imageViewers[i]);
    }

    setupMenuBar();
    setupToolBar();
    setupStatusBar();
}

void MainWindow::onStartCaptureClicked()
{
    m_cameraManager->startCapture();
}

void MainWindow::onStopCaptureClicked()
{
    m_cameraManager->stopCapture();
}

void MainWindow::updateImage(unsigned char* imageData, int width, int height, int cameraIndex)
{
    if ( cameraIndex >= 0 && cameraIndex < m_imageViewers.size()) {
        m_imageViewers[cameraIndex]->updateImage(imageData, width, height);
    }
}

void MainWindow::handleCameraError(int cameraIndex)
{
    m_logTextEdit->append("相机 " + QString::number(cameraIndex) + " 出错");
}

void MainWindow::onManualTriggerClicked()
{
    m_cameraManager->manualTrigger();
}

void MainWindow::onOpenCameraClicked()
{
    m_logTextEdit->append("打开相机操作被触发");
}

void MainWindow::onCloseCameraClicked()
{
    m_cameraManager->closeAllCameras();
    m_logTextEdit->append("关闭相机操作被触发");
}

void MainWindow::onToggleCameraClicked()
{
    if (m_imageViewers.size() < m_cameraManager->getCameraCount()) {
        addImageViewerWidget();
    } else if (m_imageViewers.size() > 0) {
        removeImageViewerWidget();
    }
}

void MainWindow::addImageViewerWidget()
{
    int newIndex = m_imageViewers.size();
    ImageViewerWidget* newWidget = new ImageViewerWidget(this);
    m_imageViewers.push_back(newWidget);
    m_imageLayout->addWidget(newWidget);
}

void MainWindow::removeImageViewerWidget()
{
    if (!m_imageViewers.empty()) {
        int lastIndex = m_imageViewers.size() - 1;
        ImageViewerWidget* widgetToRemove = m_imageViewers.back();
        m_imageViewers.pop_back();
        m_imageLayout->removeWidget(widgetToRemove);
        delete widgetToRemove;
    }
}

在上述代码中:

  • 新增了一个 QAction 用于表示动态开关相机按钮,以及对应的槽函数 onToggleCameraClicked。
  • 在 onToggleCameraClicked 函数中,根据当前显示图片控件数量与相机总数的比较,决定是添加还是移除一个 ImageViewerWidget。
    addImageViewerWidget 函数用于创建并添加新的图片显示控件到布局中,removeImageViewerWidget 函数用于移除最后一个图片显示控件并释放相关资源。
  • 通过这些修改,实现了动态调整显示图片控件的功能,以适配相机的开启与关闭操作。
    请注意,上述代码基于之前提供的代码框架基础上修改,运行时需要确保 CameraManager 类及其他相关依赖正确实现并链接。

main.cpp(程序入口)

c 复制代码
#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MainWindow window;
    window.show();

    return app.exec();
}

4. 功能说明

  • 多线程取图:每个 HikCameraWorker 运行在独立线程中,通过 startCapture 启动相机采集,在 imageCallback 回调中发送采集到的图像信号,避免阻塞主线程,保证界面响应流畅。
  • 断线重连:当相机出现错误(如连接断开),触发 cameraError 信号,在 handleCameraError 槽函数中停止当前相机取图
相关推荐
Quz3 小时前
QML Hello World 入门示例
qt
xcyxiner3 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner4 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner4 天前
DicomViewer (添加模型类)3
qt
xcyxiner5 天前
DicomViewer (目录调整) 2
qt
xcyxiner5 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR0067 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术7 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园7 天前
C++20 Modules 模块详解
java·开发语言·spring
swordbob7 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio