PyQT: 开发一款ROI绘制小程序

news2025/1/19 3:08:21

在一些基于图像或者视频流的应用中,比如电子围栏/客流统计等,我们需要手动绘制一些感兴趣(Region of Interest,简称ROI)区域。

在这里,我们基于Python和PyQt5框架开发了一款桌面应用程序,允许用户加载图片或视频流,并在这些媒体上绘制感兴趣的区域。 

目录

Wath's New:

1. PyQT介绍

2. ROI绘制软件的开发

2.1. 需求

2.2. 涉及的知识点

2.3. 主要代码

2.4. 运行截图


Wath's New:

代码已开源,欢迎大家使用:

https://github.com/AICVHub/ROI-Drawing-APPicon-default.png?t=N7T8https://github.com/AICVHub/ROI-Drawing-APP同时,也提供了在linux和win两个平台的可执行程序包,大家可直接下载使用:

Releases · AICVHub/ROI-Drawing-APP · GitHubBased on PyQT5; Drawing a ROI on frame. 一款基于PyQT5开发的ROI绘制小程序。 - Releases · AICVHub/ROI-Drawing-APPicon-default.png?t=N7T8https://github.com/AICVHub/ROI-Drawing-APP/releases


1. PyQT介绍

        PyQt5是一个功能强大的Python库,用于创建图形用户界面(GUI)应用程序。它是基于Qt框架的Python绑定,允许开发人员使用Python语言轻松地创建丰富和交互式的应用程序。

官方文档:

https://www.riverbankcomputing.com/static/Docs/PyQt5/icon-default.png?t=N7T8https://www.riverbankcomputing.com/static/Docs/PyQt5/**%E3%80%82%E5%9C%A8%E8%BF%99%E4%B8%AA%E7%BD%91%E7%AB%99%E4%B8%8A%EF%BC%8C%E4%BD%A0%E5%8F%AF%E4%BB%A5%E6%89%BE%E5%88%B0%E5%85%B3%E4%BA%8EPyQt5%E7%9A%84%E8%AF%A6%E7%BB%86%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%8C%85%E6%8B%AC%E4%B8%8B%E8%BD%BD%E9%93%BE%E6%8E%A5%E3%80%81%E6%96%87%E6%A1%A3%E3%80%81%E6%95%99%E7%A8%8B%E4%BB%A5%E5%8F%8A%E7%A4%BE%E5%8C%BA%E6%94%AF%E6%8C%81%E7%AD%89%E3%80%82其主要特点有:

  1. 跨平台支持:PyQt5可以在多个操作系统上运行,包括Windows、macOS和Linux,使开发人员能够编写一次代码,在各种平台上都能稳定运行。
  2. 全功能性:PyQt5包含了Qt框架的所有功能,提供了丰富的工具和组件,用于开发桌面、移动和嵌入式应用程序。
  3. Python语言的力量:PyQt5充分利用了Python的简洁性和易读性,使得开发人员能够更快速地构建应用程序,提高开发效率。
  4. 丰富的文档和社区支持:PyQt5拥有广泛的文档和活跃的开发者社区,开发人员可以轻松地找到问题的解决方案和示例代码。
  5. 良好的性能:由于PyQt5是基于底层的Qt框架构建的,因此具有优秀的性能,能够处理大量数据和复杂操作。

主要的组件与模块:

  • QtCore:包含核心的非GUI功能,如时间、文件与文件夹、各种数据类型、流、URL、MIME类型、线程或进程等。
  • QtGui:包括窗口系统集成、事件处理、二维图形、基本成像、字体和文本等。
  • QtWidgets:包含基本控件,如按钮、文本框、标签、菜单栏、工具栏等,用于创建经典桌面风格的用户界面。
  • QtMultimedia:提供处理多媒体内容和API来访问相机和录音机的功能。
  • QtBluetooth:包含用于扫描设备和与之互动的类。
  • QtNetwork:包含网络编程的类,便于TCP/IP和UDP客户端和服务器的编码。

2. ROI绘制软件的开发

2.1. 需求

这款软件,我们主要设定了以下需求:

  1. 媒体加载: 用户可以加载本地或网络的图片和视频流作为绘制ROI的基础媒体。

  2. 实时预览: 支持实时显示媒体内容,用户可以在观看的同时进行操作。

  3. ROI绘制: 用户可以通过鼠标操作在媒体上绘制自定义形状的ROI,适用于图像标注、对象检测等任务。

  4. 配置管理: 用户可以通过配置文件管理多个媒体源,方便切换和使用不同的图片或视频。

  5. 多线程处理: 通过创建子线程来加载媒体,确保用户界面的流畅性,避免因媒体加载导致的界面卡顿。

  6. 交互式操作: 提供直观的图形用户界面,使用户能够通过简单的鼠标点击和工具栏按钮来控制ROI的绘制过程。

  7. 复制功能: 允许用户将ROI数据复制到剪贴板,提高了数据传输的便捷性。

  8. 用户帮助与反馈: 提供帮助文档和关于软件的信息,方便用户了解如何使用软件以及获取开发者信息。

  9. 界面自适应: 应用程序界面能够适应不同的窗口大小,确保用户在不同分辨率和屏幕尺寸下都能获得良好的体验。

2.2. 涉及的知识点

涉及的关于PyQt5的知识点主要包括:

  1. QApplication: 应用程序的主控制和管理类,负责处理事件循环和应用程序的生命周期。

  2. QMainWindow: 应用程序的主窗口类,提供了一个有菜单栏、工具栏、状态栏和中心部件的窗口。

  3. QMenu and menuBar: 用于创建下拉菜单和菜单栏的类,可以添加菜单项和组织菜单项。

  4. QAction: 表示菜单项或工具栏按钮的类,可以触发特定的槽函数。

  5. QToolBar: 工具栏类,用于在窗口上创建和组织工具栏。

  6. QLabel: 用于显示文本或图片的控件。

  7. QComboBox: 下拉列表控件,允许用户从列表中选择一个选项。

  8. QFileDialog: 文件对话框类,用于打开和保存文件。

  9. QMessageBox: 消息提示框类,用于向用户显示消息和获取简单的输入。

  10. QInputDialog: 输入对话框类,用于获取用户输入。

  11. QPixmap and QImage: 用于处理和显示图像的类,支持图像的基本操作和转换。

  12. QThread and threading: 多线程支持,允许应用程序执行后台任务而不影响用户界面的响应性。

  13. Signals and Slots: PyQt5的信号和槽机制,用于对象间的通信和事件处理。

  14. QWidget: 所有控件的基类,提供了通用的容器功能。

  15. QApplication.clipboard(): 访问系统剪贴板的函数,用于复制和粘贴数据。

  16. StyleSheet: 用于定义控件的样式和外观的CSS样式表。

  17. Layout Management: 如使用QHBoxLayoutQVBoxLayout等布局管理器,用于自动调整控件大小和位置。

  18. Custom Widgets: 创建自定义控件,如CustomLabel,用于实现特定的功能需求。

  19. Event Handling: 如重写resizeEventcloseEvent等方法,响应窗口大小变化和关闭事件。

  20. Qt's Painting System: 用于自定义绘制的系统,可以在自定义控件上进行绘制。

  21. Qt's Geometry Classes: 如QPointQSizeQRect等,用于处理坐标和尺寸。

2.3. 主要代码

下面是主要的代码,其中涉及到的SourcePuller和CustomLabel,可以参考我的github中的代码。

import sys
import cv2
import json
import time
import threading

import numpy as np
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QLabel, QStyle, \
    QFileDialog, QAction, QComboBox, QToolBar, QInputDialog

from utils.source_pull import SourcePuller
from utils.custom_qlabel import CustomLabel


class DrawROI(QMainWindow):
    def __init__(self, fps=10):
        super().__init__()
        self.scaled_pixmap = None
        self.config = None
        self.points_image = []  # 存储鼠标点击的坐标点(image坐标系)
        self.close_drawing_flag = False
        self.source_puller = None
        self.clipboard = QApplication.clipboard()  # 剪贴板对象作为类属性

        self.fps = fps
        self.setWindowTitle("Draw ROI")
        self.setGeometry(300, 100, 1280, 720 + 30)
        self.setWindowIcon(self.style().standardIcon(QStyle.SP_FileDialogStart))

        # 创建菜单栏
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('文件')
        aboutMenu = menubar.addMenu('关于')

        # 添加"打开/更改配置"菜单项
        openConfigAction = QAction('打开/更改配置', self)
        openConfigAction.triggered.connect(self.open_config)
        fileMenu.addAction(openConfigAction)
        # 添加“手动输入源”菜单项
        manualInputAction = QAction('手动输入源', self)
        manualInputAction.triggered.connect(self.manual_input_source)
        fileMenu.addAction(manualInputAction)
        # 添加退出菜单项
        exitAction = QAction('退出', self)
        exitAction.triggered.connect(self.simple_close)
        fileMenu.addAction(exitAction)
        # 添加"关于"的菜单项
        aboutAction = QAction('关于', self)
        aboutAction.triggered.connect(self.about_info)
        aboutMenu.addAction(aboutAction)
        # 添加"帮助"
        helpAction = QAction('帮助', self)
        helpAction.triggered.connect(self.help_info)
        aboutMenu.addAction(helpAction)
        # 创建下拉框
        self.comboBox = QComboBox(self)
        self.comboBox.setGeometry(self.width() - 400, 0, 400, self.menuBar().height() - 2)  # xywh
        self.prompt_text = "请选择一个源(视频或者图片)"
        self.comboBox.addItem(self.prompt_text)
        self.comboBox.setEditText(self.prompt_text)  # 设置为当前显示文本
        self.comboBox.activated.connect(self.on_combobox_activated)
        # 创建用于展示图片的 QLabel
        self.imageLabel = QLabel(self)
        self.imageLabel.setGeometry(0, self.menuBar().height(), self.width(), self.height() - self.menuBar().height())
        self.imageLabel.setStyleSheet("background-color: #aaaaaa;")
        self.imageLabel.setAlignment(Qt.AlignCenter)  # 居中显示图片
        # 创建一个自定义的透明 QLabel,用于绘制点
        self.transparentLabel = CustomLabel(self)
        self.transparentLabel.setGeometry(0, self.menuBar().height(), self.width(),
                                          self.height() - self.menuBar().height())
        self.transparentLabel.setStyleSheet("background-color: rgba(255, 179, 169, 0);")  # 透明背景
        self.transparentLabel.mousePressEvent = self.mouse_pressed  # 绑定鼠标点击事件

        # 创建一个线程来加载和展示图片
        threading.Thread(target=self.load_and_show_image, daemon=True).start()

        # 创建工具栏
        self.create_tool_bar()

    def init_source_puller(self, source_path):
        self.source_puller = SourcePuller(source_path)
        self.points_image = []  # 存储鼠标点击的坐标点(image坐标系)
        self.transparentLabel.points = []  # 清空点

    def open_config(self):
        options = QFileDialog.Options()
        config_path, _ = QFileDialog.getOpenFileName(
            self, "Open Config", ".a", "Config Files (*.json)", options=options
        )
        if config_path:
            with open(config_path, 'r') as f:
                self.config = json.load(f)
                print(self.config)
                # 清空下拉框
                self.comboBox.clear()
                self.comboBox.addItem(self.prompt_text)
                # 添加配置项到下拉框
                self.add_config_items_to_combobox(self.config)

    def manual_input_source(self):
        # 弹出输入对话框让用户输入图片或视频的地址
        # 同时让用户选择是输入图片还是视频
        item, ok = QInputDialog.getItem(self, '手动输入源', '请选择源类型:',
                                        ['图片', '视频'], 0, False)
        if ok and item:
            source_type = "图片" if item == "图片" else "视频"
            text, text_ok = QInputDialog.getText(self, f'输入{source_type}地址', f'请输入{source_type}文件的地址:')
            self.comboBox.clear()
            if text_ok and text:
                # 根据用户选择的类型处理输入的地址
                if source_type == "图片":
                    self.comboBox.addItem(f"images: {text}")
                elif source_type == "视频":
                    self.comboBox.addItem(f"videos: {text}")
                self.start_source_puller(self.comboBox.currentText())

    def simple_close(self):
        # 直接关闭窗口,不显示任何消息框
        self.close()  # 该方法会触发closeEvent事件,与直接点击X效果一致

    def add_config_items_to_combobox(self, config):
        # 根据config字典中的videos和images键添加项到下拉框
        if 'videos' in config:
            for video_path in config['videos']:
                self.comboBox.addItem(f"videos: {video_path}")
        if 'images' in config:
            for image_path in config['images']:
                self.comboBox.addItem(f"images: {image_path}")

    def on_combobox_activated(self):
        # 当下拉框选项被激活时执行的槽函数
        if self.comboBox.currentText() == self.prompt_text:
            # 如果用户选择了提示语,我们不进行任何操作,或者可以提示用户选择一个有效项
            pass  # 这里不执行任何操作
        else:
            # 用户选择了一个有效项,从下拉框中移除提示语
            self.comboBox.removeItem(self.comboBox.findText(self.prompt_text))
            # 处理选中的项
            item_text = self.comboBox.currentText()
            print(f"选中的项是: {item_text}")
            self.start_source_puller(item_text)
            self.clear_roi()

    def start_source_puller(self, source_path):
        # 根据选中的项创建一个SourcePuller对象: 将创建过程放到一个子线程,以避免阻塞
        thread_source = threading.Thread(target=self.init_source_puller, args=(source_path,), daemon=True)
        thread_source.start()

    def create_tool_bar(self):
        # 创建一个工具栏
        toolbar = QToolBar("底部工具栏")
        self.addToolBar(Qt.BottomToolBarArea, toolbar)  # 添加到顶部

        # 创建一个只显示文本的 QAction 作为标题
        title_action = QAction("轮廓绘制工具栏", self)
        title_action.setEnabled(False)  # 设置不可点击
        title_action.setCheckable(False)  # 设置不可选中
        toolbar.addAction(title_action)

        # 将 QAction 转换为 QWidget 并添加到工具栏
        title_widget = toolbar.widgetForAction(title_action)
        title_widget.setStyleSheet("color: gray; ")  # 设置标题颜色
        # 设置工具栏样式
        toolbar.setStyleSheet("QToolBar { border: 10px; }")  # 设置边框
        toolbar.setStyleSheet("""
        QToolBar {
            border: 1px solid #ffffff; /* 白色边框 */
            border-radius: 4px;
            background-color: rgba(255, 255, 255, 0.5); /* 半透明白色背景 */
        }
        """)  # 设置背景
        toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)  # 图标和文本并排显示

        # 创建一些按钮并添加到工具栏中
        end_action = QAction("闭合", self)
        end_action.triggered.connect(self.close_drawing)
        toolbar.addAction(end_action)
        clear_action = QAction("清空", self)
        clear_action.triggered.connect(self.clear_roi)
        toolbar.addAction(clear_action)
        save_action = QAction("保存", self)
        save_action.triggered.connect(self.save_roi)
        toolbar.addAction(save_action)

    def close_drawing(self):
        # 结束绘制的逻辑
        if len(self.transparentLabel.points) > 1 and not self.close_drawing_flag:
            if self.transparentLabel.points[0] != self.transparentLabel.points[-1]:
                self.transparentLabel.points.append(self.transparentLabel.points[0])  # 闭合区域
                self.transparentLabel.color = Qt.green  # 更改颜色
                self.transparentLabel.update()  # 重新绘制 CustomLabel
                self.close_drawing_flag = True

    def clear_roi(self):
        # 清空绘制的逻辑
        if len(self.transparentLabel.points) > 0:
            self.points_image = []  # 清空图像坐标系中的点
            self.transparentLabel.init_attributes()  # 重置属性
            self.transparentLabel.update()  # 重新绘制 CustomLabel
            self.close_drawing_flag = False  # 重置结束标识

    def save_roi(self):
        # 保存ROI:将点的坐标保存为json格式的字符串
        if len(self.points_image) > 0:
            self.close_drawing()  # 闭合轮廓并结束绘制
            roi_data = json.dumps(self.points_image, ensure_ascii=False)
            # 创建一个可交互的QMessageBox
            message_box = QMessageBox()
            message_box.setWindowTitle("ROI数据已生成")
            message_box.setText("点击【复制】按钮可保存到您的剪贴板!")
            message_box.setInformativeText(roi_data)
            message_box.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard)
            message_box.setStandardButtons(QMessageBox.NoButton)  # 移除所有标准按钮

            # 添加自定义的“复制”按钮
            copy_button = message_box.addButton("复制", QMessageBox.AcceptRole)
            message_box.buttonClicked.connect(lambda _: self.copy_to_clipboard(message_box))  # 使用lambda表达式传递message_box

            # 显示消息框
            message_box.exec_()

            # # 如果需要复制到剪贴板,可以使用以下代码:
            # clipboard = QApplication.clipboard()
            # clipboard.setText(roi_data)

            self.clear_roi()  # 清空绘制的轮廓点

    def copy_to_clipboard(self, message_box):
        # 复制文本到剪贴板并关闭消息框
        self.clipboard.setText(message_box.informativeText())
        message_box.accept()  # 关闭消息框

    def load_and_show_image(self):
        # 循环读取图片并展示
        while True:
            time.sleep(1 / self.fps)
            try:
                if self.source_puller is None:
                    continue
                ret, image = self.source_puller.pull_frame()
                if not ret:
                    continue
                # 将 cv2 图片转换为 RGB 格式
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

                # 将图片转换为 QImage 对象
                h, w, ch = image.shape
                bytes_per_line = ch * w
                qt_image = QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888)

                # 使用 QPixmap 包装 QImage 对象
                pixmap = QPixmap.fromImage(qt_image)
                if pixmap.isNull():
                    continue

                # 调整 QPixmap 大小以适应 QLabel
                self.scaled_pixmap = pixmap.scaled(self.imageLabel.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
                # print(scaled_pixmap.size(), self.imageLabel.size())
                # 获取实际展示的图片在 self.imageLabel.size() 中的位置,self.imageLabel是居中展示的

                # 使用 QLabel 的 setPixmap 方法展示图片
                self.imageLabel.setPixmap(self.scaled_pixmap)
            except Exception as e:
                time.sleep(1)

    def mouse_pressed(self, event):
        if event.button() == Qt.LeftButton:
            if self.close_drawing_flag:
                return
            # 获取鼠标点击的坐标(self.imageLabel坐标系)
            QLabel_x = event.pos().x()
            QLabel_y = event.pos().y()

            print(f"QLabel坐标: x={QLabel_x}, y={QLabel_y}")
            if self.scaled_pixmap is not None:
                # 计算图像在 QLabel 中的居中偏移量
                xOffset = (self.transparentLabel.width() - self.scaled_pixmap.width()) / 2
                yOffset = (self.transparentLabel.height() - self.scaled_pixmap.height()) / 2
                # 将 QLabel 坐标系下的坐标转换为图像坐标系
                image_x = np.clip(QLabel_x - xOffset, 0, self.scaled_pixmap.width())
                image_y = np.clip(QLabel_y - yOffset, 0, self.scaled_pixmap.height())
                draw_x = np.clip(QLabel_x, xOffset, self.transparentLabel.width() - xOffset)
                draw_y = np.clip(QLabel_y, yOffset, self.transparentLabel.height() - yOffset)

                print(f"image坐标: x={image_x}, y={image_y}")
                self.transparentLabel.points.append(QPoint(int(draw_x), int(draw_y)))
                self.transparentLabel.update()  # 更新 CustomLabel 以显示新点
                self.points_image.append((
                    round(image_x / self.scaled_pixmap.width(), 4),
                    round(image_y / self.scaled_pixmap.height(), 4)
                ))  # 添加点到列表,并进行归一化
        elif event.button() == Qt.RightButton:
            # 如果鼠标右键被按下,则结束绘制
            self.close_drawing()
        elif event.button() == Qt.MiddleButton:
            self.clear_roi()

    def about_info(self):
        QMessageBox.information(self, 'About',
                                'ROI绘制小程序\n作者:@AICVHub\n主页:https://liwensong.blog.csdn.net\n版本:V1.0.0')

    def help_info(self):
        message = """
        <html><head><meta charset='utf-8'></head><body>
        <h3>使用帮助</h3>
        <p>欢迎使用本应用程序,以下是基本的使用步骤:</p>
        <ul>
            <li><strong>加载配置项:</strong>通过菜单栏选择“文件”->“打开/更改配置”。</li>
            <li><strong>选择源:</strong>在配置中选择所需的图片或视频流。</li>
            <li><strong>绘制ROI:</strong>使用鼠标进行操作:
                <ul style='list-style-type: disc;'>
                    <li>继续单击左键绘制。</li>
                    <li>单击鼠标右键闭合轮廓。</li>
                    <li>单击鼠标中键清空当前轮廓。</li>
                </ul>
            </li>
            <li><strong>控制绘制行为:</strong>利用工具栏上的按钮来开始、结束或清空绘制。</li>
        </ul>
        <p>如果需要更多帮助,请参阅用户手册或联系技术支持。</p>
        </body></html>
        """
        QMessageBox.information(self, '帮助', message)

    def resizeEvent(self, event):
        # 每次窗口大小变化时,更新相关组件的大小/位置
        # self.label.setGeometry(0, 0, self.width(), self.height())
        self.comboBox.setGeometry(self.width() - 400, 0, 400, self.menuBar().height() - 2)  # xywh
        self.imageLabel.setGeometry(0, self.menuBar().height(), self.width(), self.height() - self.menuBar().height())
        self.transparentLabel.setGeometry(0, self.menuBar().height(), self.width(),
                                          self.height() - self.menuBar().height())

        super().resizeEvent(event)  # 调用父类的resizeEvent

    def closeEvent(self, event):
        # 当用户尝试关闭窗口时,显示退出确认消息框
        reply = QMessageBox.question(self, '警告', '您确定要退出吗?',
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()  # 用户点击"Yes",接受关闭事件
        else:
            event.ignore()  # 用户点击"No",忽略关闭事件,窗口不关闭


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = DrawROI()
    window.show()
    sys.exit(app.exec_())

2.4. 运行截图


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1894745.html

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

相关文章

谷粒商城学习笔记-05-项目微服务划分图

文章目录 一&#xff0c;商城业务服务-前端服务二&#xff0c;商城业务服务-后端服务三&#xff0c;存储服务四&#xff0c;第三方服务五&#xff0c;服务治理六&#xff0c;日志七&#xff0c;监控预警系统1&#xff0c;Prometheus2&#xff0c;Grafana3&#xff0c;Prometheu…

TSINGSEE智能分析网关V4人员区域徘徊AI检测:算法原理介绍及技术应用场景

一、引言 在现代社会&#xff0c;随着科技的不断发展&#xff0c;视频监控系统已广泛应用于各个领域&#xff0c;如公共安全、商业管理、交通监控等。其中&#xff0c;区域徘徊检测算法作为一种重要的视频分析技术&#xff0c;能够有效地识别出特定区域内人员的徘徊行为&#…

LLMs之gpt_academic:gpt_academic的简介、安装和使用方法、案例应用之详细攻略

LLMs之gpt_academic&#xff1a;gpt_academic的简介、安装和使用方法、案例应用之详细攻略 目录 gpt_academic的简介 1、版本更新历史 版本: 1、新增功能及其描述 新界面&#xff08;修改config.py中的LAYOUT选项即可实现“左右布局”和“上下布局”的切换&#xff09; 所…

SpringBoot开发细节

1.✨前后端时间不一致 解决方法1&#xff1a;在实体类属性上添加 JsonFormat(pattern "yyyy-MM-dd HH:mm:ss", timezone "GMT8")解决方法2&#xff1a;springboot配置文件中 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT82.定义返回…

电商数据仓库

1.数据仓库的数据来源为业务数据库&#xff08;mysql&#xff09; 2.通过sqoop将mysql中的业务数据导入到大数据平台&#xff08;hive&#xff09; 3.通过hive进行数据计算和数据分析 形成数据报表 4.再通过sqoop将数据报表导出到mysql 5.使用FineReport制作数据报表 1.数据…

开始尝试从0写一个项目--后端(一)

创建文件的目录结构 利用这个界面创建 序号 名称 说明 1 SEMS maven父工程&#xff0c;统一管理依赖版本&#xff0c;聚合其他子模块 2 sems-common 子模块&#xff0c;存放公共类&#xff0c;例如&#xff1a;工具类、常量类、异常类等 3 sems-pojo 子模块&#x…

前端Debugger时复制的JS对象字符转JSON对象

前端debugger时&#xff0c;复制的对象在控制台输出时是如下格式&#xff0c;需要转换为对象格式来进行验证操作 bridgeId : 4118 createBy : null createTime : "2023-03-24 10:35:26" createUserId : 1 具体实现代码&#xff1a; // 转换transform (text) {l…

数据库表导出到excel:前置知识3 项目封装的Quartz实现动态定时任务

参考网址 目标&#xff1a;定时任务持久化到数据库&#xff0c;动态调整数据库里保存的cron表达式使定时任务可以跟随变化。 从SYS_QUARTZ_JOB表(通过反射创建任务)和SYS_QUARTZ_LOG表(主要就是记录日志)构建两个对应的实体类&#xff1a;QuartzJob和QuartzLog 1.看表结构 …

云计算【第一阶段(26)】Linux网络设置

一、查看网络配置 1.查看网络接口信息ifconfig 查看所有活动的网络接口信息 2.ifconfig命令 查看指定网络接口信息 ifconfig 网络接口 &#xff08;1&#xff09;第一行&#xff1a;以太网卡的名字 ens33其中en代表以太网卡&#xff0c; centos6的是eth0&#xff0c; e…

什么是数字体验成熟度,以及数字成熟度的模型计算和实现方法

“开发成功的全渠道数字身份&#xff0c;并通过无缝的数字体验吸引广泛的受众。无论您身在何处&#xff0c;都可以加速数字化转型并促进业务增长。通过直观、全面的工具&#xff0c;并了解您个人的数字体验成熟度水平&#xff0c;超越不断增长的客户期望并超越竞争对手。今天就…

第二十条:与抽象类相比,优先选择接口

要定义多种实现的类型&#xff1a;JAVA有两种机制&#xff1a;接口和抽象类。这两种机制都支持为某些实例方法提供实现&#xff0c;但二者有个重要的区别&#xff1a;要实现由抽象类定义的类型&#xff0c;这个类必须是抽象类的子类。因为Java只允许单继承&#xff0c;对抽象类…

2025年中国国际新能源汽车技术零部件及服务展览会

中国国际新能源汽车技术零部件及服务展览会&#xff0c;从设计到制造、从使用到服务&#xff0c;精准“链”接新能源汽车全产业链的技术供应商和汽车制造商&#xff0c;专业面向新能源造车供应链的行业盛会。2024展会回顾&#xff1a;在展会的3天里&#xff0c;有62家车企核心供…

如何自动筛选螺丝不良品?

四角螺丝是一种特殊设计的螺丝&#xff0c;其螺纹头部呈四个平行的角状结构&#xff0c;与传统的六角螺丝相比具有独特的外观和功能。这种设计使得四角螺丝在安装和拆卸时更容易使用&#xff0c;并提供了更好的扭矩传递效率。四角螺丝头部呈现四个平行的角&#xff0c;与常见的…

Web前端开发——HTML快速入门

HTML&#xff1a;控制网页的结构CSS&#xff1a;控制网页的表现 一、什么是HTML、CSS &#xff08;1&#xff09;HTML &#xff08;HyperText Markup Languaqe&#xff1a;超文本标记语言&#xff09; 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更强大。除了…

上海计算机考研炸了,这所学校慎报!上海大学计算机考研考情分析!

上海大学&#xff08;Shanghai University&#xff09;&#xff0c;简称“上大”&#xff0c;是上海市属、国家“211工程”重点建设的综合性大学&#xff0c;教育部与上海市人民政府共建高校&#xff0c;国防科技工业局与上海市人民政府共建高校&#xff0c;国家“双一流”世界…

羊大师:羊奶养生,解锁健康之道的新密码

在探寻健康与养生的旅途中&#xff0c;我们总渴望找到那把开启健康之门的钥匙。而今&#xff0c;羊奶以其独特的营养价值和健康益处&#xff0c;正悄然成为那把解锁健康之道的新密码。 羊奶&#xff0c;自古以来便是自然赋予的珍贵礼物。它富含优质蛋白、多种维生素及矿物质&am…

【机器学习】Google开源大模型Gemma2:原理、微调训练及推理部署实战

目录 一、引言 二、模型简介 2.1 Gemma2概述 2.2 Gemma2 模型架构 三、训练与推理 3.1 Gemma2 模型训练 3.1.1 下载基座模型 3.1.2 导入依赖库 3.1.3 量化配置 3.1.4 分词器和模型实例化 3.1.5 引入PEFT进行LORA配置 3.1.6 样本数据清洗与加载 3.1.7 模型训练与保…

ERROR: No matching distribution found for numpy

1.原因&#xff1a; 出现这两行英文是因为原先输入pip install numpy的方式不安全&#xff0c;不被信任所以无法下载。 2.解决方法&#xff1a; 改成以下命令执行&#xff1a; pip install numpy -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun…

部署LVS-DR 群集

1 LVS-DR 集群 LVS-DR &#xff08;Linux Virtual Server Director Server ) 工作模式&#xff0c; 是生产环境中最常用的一种工作模式 1.1&#xff1a;LVS-DR工作原理 LVS-DR 模式&#xff0c; Director Server 作为群集的访问入口&#xff0c; 不作为网关使用&#xff0c;…

Spring Boot 的机场投诉管理平台-计算机毕业设计源码22030

摘要 随着航空运输业的迅速发展&#xff0c;机场的客流量不断增加&#xff0c;旅客对机场服务的质量和效率也提出了更高的要求。为了提高机场的服务质量&#xff0c;及时处理旅客的投诉&#xff0c;建立一个高效、便捷的机场投诉管理平台显得尤为重要。 本项目旨在设计与实现一…