QT-简单视觉框架代码

server/2024/12/27 22:00:54/

文章目录

  • 简介
  • 1. 整体架构
  • 2. 关键类功能概述
  • 3. 详细代码实现
  • hikcameraworker.h 和 hikcameraworker.cpp(海康相机工作线程类)
  • imageviewerwidget.h 和 imageviewerwidget.cpp(图像查看部件类)
      • 构造函数 `ImageViewerWidget`
      • 析构函数 `~ImageViewerWidget`
      • `updateImage`
      • `addToolAction`
      • `removeToolAction`
      • `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(相机管理类,负责相机业务逻辑)
  • mainwindow.h 和 mainwindow.cpp(主窗口类)
  • main.cpp(程序入口)
  • 4. 功能说明

简介

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

1. 整体架构

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

2. 关键类功能概述

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

3. 详细代码实现

  • camerainterface.h(相机接口抽象类)
#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(海康相机工作线程类)

// 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(图像查看部件类)

// 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(相机管理类,负责相机业务逻辑)

// 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(主窗口类)

// 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(程序入口)

#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 槽函数中停止当前相机取图

http://www.ppmy.cn/server/153735.html

相关文章

Linux学习

Linux Linux目录结构 Linux只有一个顶级目录&#xff0c;称之为&#xff1a;根目录 /在Linux系统中表示 出现在开头的/表示&#xff1a;根目录 出现在后面的/表示&#xff1a;层次关系 Linux命令基础 什么是命令、命令行 命令&#xff1a;即Linux操作指令&#xff0c;是系…

WiFi、蓝牙共存,物联网无线通信技术,设备无线连接数据传输应用

WiFi、蓝牙共存 一、简介 什么是共存 共存是指允许多个2.4GHZ**&#xff08;频段范围2400-2483.5MHZ&#xff09;**技术&#xff08;包括WiFi、Zigbee、Thread和蓝牙&#xff09;同时存在而不会发生来自一个无线电的信号干扰相邻无线信号的现象 为什么要用WiFi、蓝牙共存 …

各种网站(学习资源、常用工具及其他,持续更新中~)

欢迎围观笔者的个人博客~ 也欢迎通过RSS网址https://kangaroogao.github.io/atom.xml进行订阅~ 大学指南 上海交通大学生存手册中国科学技术大学人工智能与数据科学学院本科进阶指南USTC不完全入学指南大学生活质量指北科研论 信息搜集 AI信息搜集USTC飞跃网站计算机保研 技…

YoloV8改进策略:Head改进|DynamicHead,利用注意力机制统一目标检测头部|即插即用

摘要 论文介绍 本文介绍了一种名为DynamicHead的模块,该模块旨在通过注意力机制统一目标检测头部,以提升目标检测的性能。论文详细阐述了DynamicHead的工作原理,并通过实验证明了其在COCO基准测试上的有效性和效率。 创新点 DynamicHead模块的创新之处在于它首次尝试在一…

React 组件中 State 的定义、使用及正确更新方式

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来React篇专栏内容React 组件中 State 的定义、使用及正确更新方式 前言 在 React 应用开发中&#xff0c;state …

【Linux】linux系统修改磁盘 inode个数

步骤&#xff08;过程是清空数据&#xff0c;所以重要的数据先保存&#xff09; 卸载文件系统 umount /DATACENTER3 建立文件系统&#xff0c;修改inode节点数 4TB mkfs.ext4 /dev/sdb -N 244195328 修改fatab文件 vi /etc/fstab/dev/sdb /DATACENTER3 ext4 defaults 1 2…

OpenCVSharp 霍夫变换

文章目录 一、概念二、霍夫直线变换原理示例代码代码解释三、霍夫圆变换原理示例代码代码解释四、扩展总结性能优化与其他算法结合局限性与改进方向一、概念 霍夫变换是一种在图像中检测特定几何形状(如直线、圆等)的强大算法。它基于投票原理,将图像空间中的像素点依据给定…

VSCode/Visual Studio Code实现点击方法名跳转到具体方法的

在 Visual Studio Code 中&#xff0c;通过设置和使用一些快捷键&#xff0c;您可以轻松地点击方法名跳转到方法实现。这通常依赖于所安装的语言扩展&#xff0c;并使用 IntelliSense 功能。以下是步骤和相关设置&#xff1a; 1. 安装必要的扩展 确保您已经安装适合编程语言的…