QT-简单视觉框架代码
文章目录
- 简介
- 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_scene
、m_view
、m_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 槽函数中停止当前相机取图