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

在一些基于图像或者视频流的应用中,比如电子围栏/客流统计等,我们需要手动绘制一些感兴趣(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 threadingimport 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, QInputDialogfrom utils.source_pull import SourcePuller
from utils.custom_qlabel import CustomLabelclass DrawROI(QMainWindow):def __init__(self, fps=10):super().__init__()self.scaled_pixmap = Noneself.config = Noneself.points_image = []  # 存储鼠标点击的坐标点(image坐标系)self.close_drawing_flag = Falseself.source_puller = Noneself.clipboard = QApplication.clipboard()  # 剪贴板对象作为类属性self.fps = fpsself.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)  # xywhself.prompt_text = "请选择一个源(视频或者图片)"self.comboBox.addItem(self.prompt_text)self.comboBox.setEditText(self.prompt_text)  # 设置为当前显示文本self.comboBox.activated.connect(self.on_combobox_activated)# 创建用于展示图片的 QLabelself.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()  # 重新绘制 CustomLabelself.close_drawing_flag = Truedef clear_roi(self):# 清空绘制的逻辑if len(self.transparentLabel.points) > 0:self.points_image = []  # 清空图像坐标系中的点self.transparentLabel.init_attributes()  # 重置属性self.transparentLabel.update()  # 重新绘制 CustomLabelself.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)# 创建一个可交互的QMessageBoxmessage_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:continueret, image = self.source_puller.pull_frame()if not ret:continue# 将 cv2 图片转换为 RGB 格式image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 将图片转换为 QImage 对象h, w, ch = image.shapebytes_per_line = ch * wqt_image = QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888)# 使用 QPixmap 包装 QImage 对象pixmap = QPixmap.fromImage(qt_image)if pixmap.isNull():continue# 调整 QPixmap 大小以适应 QLabelself.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()) / 2yOffset = (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)  # xywhself.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)  # 调用父类的resizeEventdef 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.mzph.cn/news/868246.shtml

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

相关文章

c#类型转换和常见集合类型

目录 1. 整数转换&#xff0c;整数和字符串&#xff0c;字符串和整数之间的转换怎么实现&#xff1f; 2. 日期转换&#xff0c;获取当前日期&#xff0c;字符串转日期&#xff0c;日期转字符串怎么实现&#xff1f; 3. 举例一维、二维、三维数组 4. 需求&#xff1a;有个88…

事务(数据库)

是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;事物会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 create table account(id int auto_increment primary key comment 主键ID,name va…

VPN 的入门介绍

VPN&#xff08;虚拟专用网络&#xff09; 简介 虚拟专用网络&#xff0c;简称虚拟专网&#xff08;VPN&#xff09;&#xff0c;其主要功能是在公用网络上建立专用网络&#xff0c;进行加密通讯。在企业网络中有广泛应用。VPN网关通过对数据包的加密和数据包目标地址的转换实…

14-48 剑和诗人22 - RAG 的主要痛点和解决方案

​​​​​ 检索增强生成 (RAG) 模型已成为一种有前途的方法&#xff0c;它利用存储在文档中的外部知识来提高生成文本的准确性和相关性。通过检索和调节相关的上下文文档&#xff0c;与传统语言模型相比&#xff0c;RAG 模型可以产生更真实、更深入和更具体的响应。 然而&…

诸葛亮的空城计 - 代理模式

定场诗 “无形之中蕴含至理&#xff0c;虚实相生方见大道。” 在三国演义中&#xff0c;诸葛亮的空城计可谓神来之笔。这看似冒险的策略&#xff0c;实则蕴含深意。今天&#xff0c;我们将透过空城计&#xff0c;一窥软件设计中代理模式的奥秘。 西城无人旦夕危&#xff0c;…

君方智能设计平台-事务管理技术方案

1.背景介绍 事务处理是指对数据进行一组操作&#xff0c;这些操作要么全部成功&#xff0c;要么全部失败&#xff0c;以确保数据的一致性和完整性。软件的事务管理主要实现方案主要涉及以下几个方面&#xff1a; &#xff08;1&#xff09;数据一致性&#xff1a;在CAD软件中…

STM32实现看门狗(HAL库)

文章目录 一. 看门狗1. 独立看门狗&#xff08;IWDG&#xff09;1.1 原理1.2 相关配置1.3 相关函数 2. 窗口看门狗&#xff08;WWDG&#xff09;2.1 原理2.2 相关配置2.3 相关函数 一. 看门狗 单片机在日常工作中常常会因为用户配置代码出现BUG&#xff0c;而导致芯片无法正常工…

Flask项目搭建及部署(完整版!全网最全)

flask搭建及部署 pip 19.2.3 python 3.7.5 Flask 1.1.1 Flask-SQLAlchemy 2.4.1 Pika 1.1.0 Redis 3.3.11 flask-wtf 0.14.2 1、创建flask项目&#xff1a; 创建完成后整个项目结构树&#xff1a; app.py: 项⽬管理⽂件&#xff0c;通过它管理项⽬。 static: 存放静态文…

map和set的原理、优劣势、应用场景和示例代码,统统告诉你。

map和set的原理都是基于哈希表实现的&#xff0c;通过哈希值来快速查找和插入元素&#xff0c;从而实现高效的数据存储和管理&#xff0c;那么他们之间有什么不同呢&#xff0c;该如何选择&#xff0c;本文带你了解。 一、map和set的原理 map和set都是数据结构&#xff0c;用…

【分布式系统三】监控平台Zabbix对接grafana(截图详细版)

目录 一.安装grafana并启动 二.浏览器访问 三.导入zabbix数据&#xff0c;对接grafana 四.如何导入模版 以前两篇博客为基础 【分布式系统】监控平台Zabbix介绍与部署&#xff08;命令截图版&#xff09;-CSDN博客 【分布式系统】监控平台Zabbix自定义模版配置-CSDN博客 …

java ReadWriteLock接口

在 Java 中&#xff0c;ReadWriteLock 接口的实现类ReentrantReadWriteLock 类提供了一种允许多个线程同时读取某一资源但只允许一个线程写的锁定机制。这种机制可以提高并发性能&#xff0c;特别是在读操作远多于写操作的场景下。 特性&#xff1a; 可重入&#xff1b;不存…

前端使用Threejs加载机械臂并控制机械臂跳舞

1. 前言 在我的第一篇博客中,大致讲解了如何使用threejs导入机械臂模型,以及如何让机械臂模型动起来的案例,可以看一下之前的博客前端使用Threejs控制机械臂模型运动 本篇博客主要讲解的是在原来的基础上添加GSAP动画库的应用,可以通过动画,来让机械臂进行指定轨迹位姿的运动…

基于SpringBoot的休闲娱乐代理售票系统

本系统主要包括管理员和用户两个角色组成&#xff1b;主要包括&#xff1a;首页、个人中心、用户管理、折扣票管理、分类管理、订单信息管理、退票信息管理、出票信息管理、系统管理等功能的管理系统。 &#x1f495;&#x1f495;作者&#xff1a;Weirdo &#x1f495;&#x…

Python酷库之旅-第三方库Pandas(009)

目录 一、用法精讲 19、pandas.read_xml函数 19-1、语法 19-2、参数 19-3、功能 19-4、返回值 19-5、说明 19-6、用法 19-6-1、数据准备 19-6-2、代码示例 19-6-3、结果输出 20、pandas.DataFrame.to_xml函数 20-1、语法 20-2、参数 20-3、功能 20-4、返回值 …

GTP/GTX 手动对齐,谈谈思路。

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

【server】springboot 整合 redis

1、redis 使用模式 1.1 单机模式 1.1.1 编译安装方式 1.1.1.1 下载 Redis的安装非常简单&#xff0c;到Redis的官网&#xff08;Downloads - Redis&#xff09;&#xff0c;下载对应的版本&#xff0c;简单几个命令安装即可。 1.1.1.2 编译安装 tar xzf redis-stable.tar.…

GPU发展史(二):改变游戏规则的3Dfx Voodoo

小伙伴们&#xff0c;大家好呀&#xff0c;我是老猫。 在上一篇GPU发展史&#xff08;一&#xff09;文章中&#xff0c;我们介绍了1976-1995期间早期显卡的发展故事&#xff0c;今天我们将介绍在1995-1999年这段时间显卡的故事&#xff0c;而这段故事的主角就是——3Dfx 提起…

探索多模态预训练:MAnTiS、ActionCLIP、CPT与CoOp的Prompt技巧

上一篇博文整理了 预训练新范式&#xff08;Prompt-tuning&#xff0c;Prefix-tuning&#xff0c;P-tuning&#xff09; &#xff0c;主要是围绕NLP上的成果&#xff0c;具体的概念本文也不做过多赘述。本篇文章将主要整理几篇有代表性的Prompt方法在多模态领域中的应用。 Mult…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置介绍】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进…

RabbitMQ(集群相关部署)

RabbitMQ 集群部署 环境准备&#xff1a;阿里云centos8 服务器&#xff0c;3台服务器&#xff0c;分别进行安装&#xff1b; 下载Erlang Erlang和RabbitMQ版本对照&#xff1a;https://www.rabbitmq.com/which-erlang.html 创建yum库配置文件 vim /etc/yum.repos.d/rabbi…