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_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.mzph.cn/news/890438.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网安瞭望台第16期

国内外要闻 Apache Struts 文件上传漏洞&#xff08;CVE - 2024 - 53677&#xff09; 近日&#xff0c;Apache Struts 被发现存在文件上传漏洞&#xff08;CVE - 2024 - 53677&#xff09;&#xff0c;安恒 CERT 评级为 2 级&#xff0c;CVSS3.1 评分为 8.1。 漏洞危害&#x…

基于python使用UDP协议对飞秋进行通讯—DDOS

基于飞秋的信息传输 声明&#xff1a;笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 老规矩&#xff0c;封面在文末&#xff01; 飞秋介绍 &#xff08;…

JAVA:组合模式(Composite Pattern)的技术指南

1、简述 组合模式(Composite Pattern)是一种结构型设计模式,旨在将对象组合成树形结构以表示“部分-整体”的层次结构。它使客户端对单个对象和组合对象的使用具有一致性。 设计模式样例:https://gitee.com/lhdxhl/design-pattern-example.git 2、什么是组合模式 组合模式…

LeetCode:222.完全二叉树节点的数量

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;222.完全二叉树节点的数量 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二…

MaxKB基于大语言模型和 RAG的开源知识库问答系统的快速部署教程

1 部署要求 1.1 服务器配置 部署服务器要求&#xff1a; 操作系统&#xff1a;Ubuntu 22.04 / CentOS 7.6 64 位系统CPU/内存&#xff1a;4C/8GB 以上磁盘空间&#xff1a;100GB 1.2 端口要求 在线部署MaxKB需要开通的访问端口说明如下&#xff1a; 端口作用说明22SSH安装…

基于指纹图像的数据隐藏和提取matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频&#xff09…

kubeadm一键部署K8S 集群架构

kubeadm一键部署K8S 集群架构(centos7) https://www.k8src.cn/ https://kubernetes.io/zh-cn/docs/home/ https://blog.csdn.net/m0_58709145/article/details/140128179 https://blog.csdn.net/jiaqijiaqi666/article/details/129745828 Kubeadm init报错[ERROR CRI]: contai…

直流电机驱动电路分享(HIP4082)

一、原理图分享 注意&#xff1a;M2_INA、M2_INB可直接接3.3V电平信号。 二、芯片介绍 1、HIP4082 HIP4082是一款高频驱动器&#xff0c;专为半桥和全桥应用而设计。它具有四个高/低侧驱动输出&#xff0c;可以提供高达100V的驱动电压。HIP4082还具有逻辑级输入和反馈输入&a…

企业版 YashanDB 23.2.4 分布式集群 数据库一主二备集群安装部署指南

一、概述 1.1 文档目标 本部分旨在为技术人员提供崖山数据库企业版 23.2 在 CentOS 7 x86_64 操作系统上进行安装部署操作的全面且清晰的指引。通过对系统架构、集群拓扑和部署需求的精确阐述&#xff0c;使读者能够在安装过程开始前形成系统的概念架构&#xff0c;为后续的详…

性能】JDK和Jmeter的安装与配置

一、JDK环境配置 1. 下载JDK 官网下载地址&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html 选择对应系统的安装包&#xff0c;下载后安装&#xff0c;安装中记录JDK安装的地址&#xff0c;之后一直点击下一…

Mysql之YUM安装时GPG 密钥报错问题处理

一、背景说明 使用YUM安装mysql5.7的时候报错&#xff0c;报错信息提示未安装公钥。博主查看/etc/yum.repos.d/mysql-community.repo配置文件中关于公钥的配置&#xff0c;确实启用了公钥验证&#xff0c;博主再排查过程中还是走了一些弯路&#xff0c;最终顺利解决了&#xff…

启动报错java.lang.NoClassDefFoundError: ch/qos/logback/core/status/WarnStatus

报错信息图片 日志&#xff1a; Exception in thread "Quartz Scheduler [scheduler]" java.lang.NoClassDefFoundError: ch/qos/logback/core/status/WarnStatus先说我自己遇到的问题&#xff0c;我们项目在web设置了自定义的log输出路径&#xff0c;多了一个 / 去…

Elasticsearch-分词器详解

什么是分词器 1、分词器介绍 对文本进行分析处理的一种手段&#xff0c;基本处理逻辑为按照预先制定的分词规则&#xff0c;把原始文档分割成若干更小粒度的词项&#xff0c;粒度大小取决于分词器规则。 常用的中文分词器有ik按照切词的粒度粗细又分为:ik_max_word和ik_smart&…

Docker 入门:如何使用 Docker 容器化 AI 项目(一)

引言 在人工智能&#xff08;AI&#xff09;项目的开发和部署过程中&#xff0c;环境配置和依赖管理往往是开发者遇到的挑战之一。开发者通常需要在不同的机器上运行同样的代码&#xff0c;确保每个人使用的环境一致&#xff0c;才能避免 “在我的机器上可以运行”的尴尬问题。…

ExcelVBA编程输出ColorIndex与对应颜色色谱

标题 ExcelVBA编程输出ColorIndex与对应颜色色谱 正文 解决问题编程输出ColorIndex与对应色谱共56&#xff0c;打算分4纵列输出&#xff0c;标题是ColorIndex,Color,Name 1. 解释VBA中的ColorIndex属性 在VBA&#xff08;Visual Basic for Applications&#xff09;中&#xff…

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题&#xff08;选择题&#xff09; 题目总数&#xff1a;5 总分数&#xff1a;50 选择题 第 1 题 单选题 Scratch运行以下程宇后&#xff0c;小兔子会&#xff08; &#xff09;。 A. 变小 B. 变大 C. 变色 D. …

springboot470基于协同过滤算法的东北特产销售系统的实现(论文+源码)_kaic

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

37. Three.js案例-绘制部分球体

37. Three.js案例-绘制部分球体 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中的一个渲染器类&#xff0c;用于将3D场景渲染到网页上。 构造器 WebGLRenderer( parameters : Object ) 参数类型描述parametersObject渲染器的配置参数&#xff0c;可选。 常用…

leetcode 面试经典 150 题:长度最小的子数组

链接长度最小的子数组题序号209题型数组解题方法滑动窗口难度中等 题目 给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件…

【游戏设计原理】22 - 石头剪刀布

一、游戏基础&#xff1a;拳头、掌心、分指 首先&#xff0c;石头剪刀布&#xff08;又名“Roshambo”&#xff09;看似简单&#xff0c;实际上可是个“深藏玄机”的零和博弈&#xff08;听起来很高深&#xff0c;其实就是输赢相抵消的意思&#xff09;。游戏中有三种手势&…