基于PyQt5的UI界面开发——图像与视频的加载与显示

介绍

这里我们的主要目标是实现一个基于PyQt5和OpenCV的图像浏览和视频播放应用。用户可以选择本地的图像或视频文件夹,进行图像自动播放和图像切换以及视频播放和调用摄像头等操作,并且支持图像保存功能。项目的核心设计包括文件路径选择、图像或视频的显示、自动播放、图像保存等功能。

UI的初步设计

整体的设计如图所示,主体的窗口名我命名为ImageLoadWindow,其他控件的标记我已经写在了上方图片中,大家可以和下面的代码进行对比。

视频的加载

文件路径的读取

当我们在开发视频处理或视频播放应用时,经常需要让用户选择视频文件以及指定保存路径。

SetPFilePath 方法用于选择视频文件。它使用 QFileDialog.getOpenFileName 弹出一个文件选择对话框,允许用户从文件系统中选择一个视频文件。如果用户选择了文件,该方法将更新界面上的文本和内部变量。

    def SetPFilePath(self):
        filename, _ = QFileDialog.getOpenFileName(self, "选择视频文件", '.', "Video Files (*.mp4 *.avi *.mkv)")
        if filename:
            self.PFilePathLiEd.setText(filename)
            self.PFilePath = filename

SetSFilePath 方法用于设置保存路径。它使用 QFileDialog.getExistingDirectory 弹出一个目录选择对话框,允许用户选择一个目录作为保存路径。如果用户选择了目录,该方法将更新界面上的文本和内部变量。

    def SetSFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "选择保存目录", '.')
        if dirname:
            self.SFilePathLiEd.setText(dirname)
            self.SFilePath = dirname + '/'

这是一个很好用的框架,以后的ui设计可以直接对着我这里命名,这样在创建新的时候,可以实现代码的移植。

视频文件的读取和显示

这里,我们想让用户选择本地的视频文件或者通过摄像头实时获取视频流,并将视频帧显示在 PyQt5 的窗口中。

SetPFilePath,用于选择视频文件,并将文件路径显示在输入框中。此处的SetSFilePath,用于选择保存目录路径,但实际在下面的代码中并没有添加相关的逻辑。

当我们选择了视频文件,并点击运行按钮后,在左侧的OutputLab(QLable控件)显示了视频的播放。当不没有选择视频文件,就会根据LoadWayCBox(Combo Box控件)的索引去选择我们的摄像头,请注意此处是下拉框选项,索引是从0开始,符合摄像头的读取顺序,如果要用在其他项目时,要注意是否正确。

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QTimer
import qimage2ndarray
import cv2
from load_image import Ui_ImageLoadWindow

class ImageLoadWindow(QMainWindow, Ui_ImageLoadWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.PrepParameters()
        self.CallBackFunctions()

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        self.capture = None  # 用于存储视频捕获对象
        self.frame = None  # 用于存储当前帧

    def PrepParameters(self):
        # 选择文件路径
        self.PFilePath = ""
        # 保存文件路径
        self.SFilePath = ""
        self.PFilePathLiEd.setText(self.PFilePath)
        self.SFilePathLiEd.setText(self.SFilePath)

    def CallBackFunctions(self):
        self.PFilePathBt.clicked.connect(self.SetPFilePath)
        self.SFilePathBt.clicked.connect(self.SetSFilePath)
        self.RunBt.clicked.connect(self.start_video_playback)
        self.ExitBt.clicked.connect(self.close_application)

    def SetPFilePath(self):
        filename, _ = QFileDialog.getOpenFileName(self, "选择视频文件", '.', "Video Files (*.mp4 *.avi *.mkv)")
        if filename:
            self.PFilePathLiEd.setText(filename)
            self.PFilePath = filename

    def SetSFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "选择保存目录", '.')
        if dirname:
            self.SFilePathLiEd.setText(dirname)
            self.SFilePath = dirname + '/'

    def start_video_playback(self):
        # 选择视频文件后,启动视频播放
        if self.PFilePath:
            self.capture = cv2.VideoCapture(self.PFilePath)
        else:
            load_way = self.LoadWayCBox.currentIndex()
            self.capture = cv2.VideoCapture(load_way)

        if self.capture.isOpened():
            self.timer.start(30)  # 每30毫秒更新一次帧
        else:
            print("无法打开视频文件或摄像头!")

    def update_frame(self):
        ret, self.frame = self.capture.read()
        if ret:
            rgb_image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
            # 使用 qimage2ndarray 将 NumPy 数组转换为 QImage
            qimg = qimage2ndarray.array2qimage(rgb_image)
            pixmap = QPixmap.fromImage(qimg)
            self.OutputLab.setPixmap(pixmap)
        else:
            self.timer.stop()  # 停止定时器,当视频播放完时

    def close_application(self):
        """关闭应用程序"""
        reply = QMessageBox.question(self, '退出', '您确定要退出程序吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.capture:
                self.capture.release()
            QApplication.quit()


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

视频文件的处理 

如下所示,即为选择了视频文件后运行的情况。如果不选就会调用本地的摄像头。

 关于对视频处理的一块,你可以在下面注释的地方完成逻辑的实现。

    def update_frame(self):
        ret, self.frame = self.capture.read()
        if ret:
            # self.frame = ... 此处实现
            rgb_image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
            qimg = qimage2ndarray.array2qimage(rgb_image)
            pixmap = QPixmap.fromImage(qimg)
            self.OutputLab.setPixmap(pixmap)
        else:
            self.timer.stop()

图像的加载

文件路径的读取

SetPFilePath方法允许用户选择一个目录,目录中的所有文件都会被读取并存储在 self.pimage_list 中。为了仅获取图片文件,我过滤了所有非图片文件,确保 self.pimage_list 只包含有,比如.jpg、.png、.jpeg、.bmp 和 .gif 等图片格式。

    def SetPFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.PFilePathLiEd.setText(dirname)
            self.PFilePath=dirname+'/'
            self.pimage_list = [f for f in os.listdir(self.PFilePath) if
                                f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]

SetSFilePath方法选择一个保存目录。选择完成后,保存路径会被显示在 SFilePathLiEd 文本框中,并将 SFilePath 变量更新为所选目录的路径。

    def SetSFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.SFilePathLiEd.setText(dirname)
            self.SFilePath=dirname+'/'

图像的切换

为了进一步丰富我们ui的功能,这里我们提供了两个方式去显示图像,第一种是通过控件显示图片,另外一种可以实现自动播放的功能。

下面为更新设计后的UI:

这里我们先要理清楚我们接下来要做什么,首先选择处理文件的路径,以及保存的路径,并点击运行按钮,显示第一张图像,如果没有选择保存路径,则只显示图像而不进行保存。

通过Last和Next键去控制图像的显示,并且第一张图像Last键被禁用,最后一张图像Next键被禁用。图像的显示和保存都通过opencv实现。

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
import qimage2ndarray
import cv2
import os
from load_image import Ui_ImageLoadWindow

class ImageLoadWindow2(QMainWindow, Ui_ImageLoadWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.PrepParameters()
        self.CallBackFunctions()

    def PrepParameters(self):
        self.PFilePath = r''  # 初始化路径为空
        self.SFilePath = r''  # 初始化保存路径为空
        self.PFilePathLiEd.setText(self.PFilePath)
        self.SFilePathLiEd.setText(self.SFilePath)

        self.pimage_list = []  # 存储图像文件列表
        self.current_image_idx = -1  # 用于追踪当前显示的图像索引

        self.current_image = None  # 用于存储当前显示的图像数据

    def CallBackFunctions(self):
        self.PFilePathBt.clicked.connect(self.SetPFilePath)
        self.SFilePathBt.clicked.connect(self.SetSFilePath)
        self.RunBt.clicked.connect(self.start_image_show)  # 运行按钮,开始显示图像
        self.LastBt.clicked.connect(self.show_last_image)  # 上一张按钮
        self.NextBt.clicked.connect(self.show_next_image)  # 下一张按钮
        self.ExitBt.clicked.connect(self.close_application)  # 退出按钮

    def SetPFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.PFilePathLiEd.setText(dirname)
            self.PFilePath = dirname + '/'
            self.pimage_list = [f for f in os.listdir(self.PFilePath)
                                if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
            self.current_image_idx = -1  # 重置图像索引

    def SetSFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.SFilePathLiEd.setText(dirname)
            self.SFilePath = dirname + '/'

    def start_image_show(self):
        if not self.pimage_list:
            QMessageBox.warning(self, '警告', '没有找到图像文件!', QMessageBox.Ok)
            return

        # 如果保存路径为空,弹出警告窗口询问是否继续
        if not self.SFilePath:
            reply = QMessageBox.question(
                self, '保存路径未选择', '保存路径未选择,是否继续显示图像?',
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )
            if reply == QMessageBox.No:
                return  # 如果用户选择 No,返回不继续

        self.current_image_idx = 0  # 从第一张图像开始显示
        self.show_image(self.current_image_idx)
        # 启用和禁用按钮
        self.update_navigation_buttons()

    def show_image(self, idx):
        if 0 <= idx < len(self.pimage_list):
            # 获取图像文件路径
            img_path = os.path.join(self.PFilePath, self.pimage_list[idx])
            image = cv2.imread(img_path)

            if image is not None:
                # 转换为RGB模式
                rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                self.current_image = image

                qimg = qimage2ndarray.array2qimage(rgb_image)
                pixmap = QPixmap.fromImage(qimg)
                self.OutputLab.setPixmap(pixmap)

                # 如果有保存路径,才进行自动保存
                if self.SFilePath:
                    self.auto_save_image(self.pimage_list[idx])

            else:
                print(f"无法读取图像: {img_path}")

    def auto_save_image(self, image_filename):
        if self.current_image is None:
            return
        save_path = os.path.join(self.SFilePath, image_filename)
        os.makedirs(self.SFilePath, exist_ok=True)
        cv2.imwrite(save_path, self.current_image)
        print(f"图像已自动保存到: {save_path}")

    def show_last_image(self):
        if self.current_image_idx > 0:
            self.current_image_idx -= 1
            self.show_image(self.current_image_idx)
        self.update_navigation_buttons()

    def show_next_image(self):
        if self.current_image_idx < len(self.pimage_list) - 1:
            self.current_image_idx += 1
            self.show_image(self.current_image_idx)
        self.update_navigation_buttons()

    def update_navigation_buttons(self):
        if self.current_image_idx == 0:
            self.LastBt.setEnabled(False)  # 禁用上一张按钮
        else:
            self.LastBt.setEnabled(True)  # 启用上一张按钮

        if self.current_image_idx == len(self.pimage_list) - 1:
            self.NextBt.setEnabled(False)  # 禁用下一张按钮
        else:
            self.NextBt.setEnabled(True)  # 启用下一张按钮

    def close_application(self):
        """关闭应用程序"""
        reply = QMessageBox.question(self, '退出', '您确定要退出程序吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            QApplication.quit()

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

逻辑流程概述:

  1. 用户选择图像文件夹和保存路径(可选)。
  2. 点击运行按钮开始显示第一张图像。
  3. 如果保存路径未选择,程序会弹出提示,询问用户是否继续显示图像。
  4. 用户可以通过上一张(LastBt)和下一张(NextBt)按钮切换图像。
  5. 当图像显示时,若保存路径已选择,程序会自动保存当前显示的图像。
  6. 如果用户关闭程序,会弹出确认退出的对话框。

实现自动播放功能

下面就是完整的设计了,由于考虑到自动播放,所以我在这里添加了暂停恢复键,用于播放过程中的暂停。

好的,这里我们还是需要理清楚我们需要做什么。首先在选择好图像文件夹路径和保存路径后,如果勾选了自动播放就会禁用掉Last和Next键,点击运行会弹出窗口是否进行循环播放,如果选择进行循环播放,则图像会显示在OutputLab上,如果选择了否也会进行自动播放,只是在播放完成之后,可以切换到Last和Next键进行图片的切换。

在自动播放过程中,如果点击了暂停键,文字变为恢复,再点击文字变为暂停,在点击了暂停后,再点击运行可以重新进行选择。

from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QTimer
import qimage2ndarray
import cv2
import os
from load_image import Ui_ImageLoadWindow

class ImageLoadWindow2(QMainWindow, Ui_ImageLoadWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.PrepParameters()
        self.CallBackFunctions()

    def PrepParameters(self):
        self.PFilePath = r''  # 初始化路径为空
        self.SFilePath = r''  # 初始化保存路径为空
        self.PFilePathLiEd.setText(self.PFilePath)
        self.SFilePathLiEd.setText(self.SFilePath)

        self.pimage_list = []  # 存储图像文件列表
        self.current_image_idx = -1  # 用于追踪当前显示的图像索引
        self.current_image = None  # 用于存储当前显示的图像数据
        self.is_autoplay = False  # 是否启用自动播放标志
        self.is_loop = False  # 是否循环播放标志
        self.is_paused = False  # 是否暂停自动播放标志

    def CallBackFunctions(self):
        self.PFilePathBt.clicked.connect(self.SetPFilePath)
        self.SFilePathBt.clicked.connect(self.SetSFilePath)
        self.RunBt.clicked.connect(self.start_image_show)  # 运行按钮,开始显示图像
        self.LastBt.clicked.connect(self.show_last_image)  # 上一张按钮
        self.NextBt.clicked.connect(self.show_next_image)  # 下一张按钮
        self.ExitBt.clicked.connect(self.close_application)  # 退出按钮
        self.AutoplaycheckBox.stateChanged.connect(self.toggle_autoplay)  # 连接自动播放勾选框
        self.StopRecoverBt.clicked.connect(self.pause_or_resume_autoplay)  # 连接暂停/恢复按钮

    def SetPFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.PFilePathLiEd.setText(dirname)
            self.PFilePath = dirname + '/'
            self.pimage_list = [f for f in os.listdir(self.PFilePath)
                                if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
            self.current_image_idx = -1  # 重置图像索引

    def SetSFilePath(self):
        dirname = QFileDialog.getExistingDirectory(self, "浏览", '.')
        if dirname:
            self.SFilePathLiEd.setText(dirname)
            self.SFilePath = dirname + '/'

    def toggle_autoplay(self, state):
        """根据勾选状态设置是否启用自动播放"""
        self.is_autoplay = state == 2  # 2表示勾选状态
        self.StopRecoverBt.setEnabled(self.is_autoplay)  # 只有启用自动播放时才启用暂停/恢复按钮

    def start_image_show(self):
        if not self.pimage_list:
            QMessageBox.warning(self, '警告', '没有找到图像文件!', QMessageBox.Ok)
            return

        # 如果保存路径为空,弹出警告窗口询问是否继续
        if not self.SFilePath:
            reply = QMessageBox.question(
                self, '保存路径未选择', '保存路径未选择,是否继续显示图像?',
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )
            if reply == QMessageBox.No:
                return  # 如果用户选择 No,返回不继续

        self.current_image_idx = 0  # 从第一张图像开始显示
        self.show_image(self.current_image_idx)

        if self.is_autoplay:
            # 如果是自动播放模式,弹出对话框确认是否循环播放
            reply = QMessageBox.question(
                self, '循环播放', '是否循环播放图像?',
                QMessageBox.Yes | QMessageBox.No, QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                self.is_loop = True  # 启用循环播放
            else:
                self.is_loop = False  # 不循环播放

            # 启动自动播放
            self.start_autoplay()

        else:
            # 启用和禁用按钮
            self.update_navigation_buttons()

        # 如果是自动播放并且已经暂停了,点击运行时,确保按钮文本恢复为'暂停'
        if self.is_paused:
            self.StopRecoverBt.setText('暂停')  # 恢复为暂停按钮文本

    def start_autoplay(self):
        """启动自动播放模式"""
        if self.is_paused:
            # 如果当前是暂停状态,直接恢复定时器
            self.is_paused = False
            self.StopRecoverBt.setText('暂停')  # 修改按钮文本为 '暂停'

        self.LastBt.setEnabled(False)  # 禁用上一张按钮
        self.NextBt.setEnabled(False)  # 禁用下一张按钮

        # 使用QTimer定时器进行自动播放
        if not hasattr(self, 'autoplay_timer'):  # 如果定时器不存在,则创建
            self.autoplay_timer = QTimer(self)
            self.autoplay_timer.timeout.connect(self.next_image_in_autoplay)

        self.autoplay_timer.start(1000)  # 每1秒切换一张图像

    def next_image_in_autoplay(self):
        """自动播放下一张图像"""
        if self.is_paused:
            return  # 如果已暂停,不进行任何操作

        if self.current_image_idx < len(self.pimage_list) - 1:
            self.current_image_idx += 1
            self.show_image(self.current_image_idx)
        else:
            if self.is_loop:
                self.current_image_idx = 0  # 如果是循环播放,回到第一张
                self.show_image(self.current_image_idx)
            else:
                self.stop_autoplay()  # 自动播放完成后停止并恢复按钮

    def stop_autoplay(self):
        """停止自动播放"""
        if hasattr(self, 'autoplay_timer'):
            self.autoplay_timer.stop()
        self.update_navigation_buttons()  # 恢复按钮状态

    def pause_or_resume_autoplay(self):
        """暂停或恢复自动播放"""
        if self.is_paused:
            self.is_paused = False
            self.StopRecoverBt.setText('暂停')  # 修改按钮文本为 '暂停'
            self.start_autoplay()  # 恢复播放
        else:
            self.is_paused = True
            self.StopRecoverBt.setText('恢复')  # 修改按钮文本为 '恢复'
            self.autoplay_timer.stop()  # 暂停定时器

    def show_image(self, idx):
        if 0 <= idx < len(self.pimage_list):
            # 获取图像文件路径
            img_path = os.path.join(self.PFilePath, self.pimage_list[idx])
            image = cv2.imread(img_path)

            if image is not None:
                # 转换为RGB模式
                rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                self.current_image = image

                qimg = qimage2ndarray.array2qimage(rgb_image)
                pixmap = QPixmap.fromImage(qimg)
                self.OutputLab.setPixmap(pixmap)

                # 如果有保存路径,才进行自动保存
                if self.SFilePath:
                    self.auto_save_image(self.pimage_list[idx])

            else:
                print(f"无法读取图像: {img_path}")

    def auto_save_image(self, image_filename):
        if self.current_image is None:
            return
        save_path = os.path.join(self.SFilePath, image_filename)
        os.makedirs(self.SFilePath, exist_ok=True)
        cv2.imwrite(save_path, self.current_image)
        print(f"图像已自动保存到: {save_path}")

    def show_last_image(self):
        if self.current_image_idx > 0:
            self.current_image_idx -= 1
            self.show_image(self.current_image_idx)
        self.update_navigation_buttons()

    def show_next_image(self):
        if self.current_image_idx < len(self.pimage_list) - 1:
            self.current_image_idx += 1
            self.show_image(self.current_image_idx)
        self.update_navigation_buttons()

    def update_navigation_buttons(self):
        """更新上一张和下一张按钮的状态"""
        if self.current_image_idx == 0:
            self.LastBt.setEnabled(False)  # 禁用上一张按钮
        else:
            self.LastBt.setEnabled(True)  # 启用上一张按钮

        if self.current_image_idx == len(self.pimage_list) - 1:
            self.NextBt.setEnabled(False)  # 禁用下一张按钮
        else:
            self.NextBt.setEnabled(True)  # 启用下一张按钮

    def close_application(self):
        """关闭应用程序"""
        reply = QMessageBox.question(self, '退出', '您确定要退出程序吗?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            QApplication.quit()

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

图像文件的处理

可以在注释处进行图像处理。

    def show_image(self, idx):
        if 0 <= idx < len(self.pimage_list):
            # 获取图像文件路径
            img_path = os.path.join(self.PFilePath, self.pimage_list[idx])
            image = cv2.imread(img_path)
            # image = ... 图像处理
            if image is not None:
                # 转换为RGB模式
                rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                self.current_image = image

                qimg = qimage2ndarray.array2qimage(rgb_image)
                pixmap = QPixmap.fromImage(qimg)
                self.OutputLab.setPixmap(pixmap)

                # 如果有保存路径,才进行自动保存
                if self.SFilePath:
                    self.auto_save_image(self.pimage_list[idx])

            else:
                print(f"无法读取图像: {img_path}")

其他

这里记录一下使用plainTextEdit显示信息。由于内容有限,所以不另外再写一篇博客了。

名字就叫plainTextEdit,这里我就不修改了。

这里只需要做两个修改就好了。

在初始化的时候PrepParameters,添加一个

self.plainTextEdit.clear()  # 清除文本框内容

你也可以和按钮结合在一起。

还有一个是将内容显示在plainTextEdit上面:

 运行效果如下所示:

总结

本篇展示了如何使用PyQt5和OpenCV实现一个图像浏览与视频播放应用,涵盖了文件路径选择、图像/视频显示、自动播放或切换、暂停与恢复、图像保存等多种功能。本篇的代码具有较高的灵活性,你只需要按照相同的命名方式就能够实现代码的移植。希望通过本篇,为其他大家提供有用的参考和实现思路。

另外,需要注意的一点是,在循环播放的时候图像会重复的保存,关于这一部分的相关逻辑,我不想再修改,也不怎么碍事。最后,我这里不提供原来设计ui文件,因为,我写的已经很清楚了,大家能够自己简单的设计,没必要再找我要。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法之动态规划: LeetCode 62. 不同路径 (Ts版)

不同路径 https://leetcode.cn/problems/unique-paths/description/ 描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “…

java自定义注解对枚举类型参数的校验

目录 1.前提准备条件 1.1 pom.xml文件依赖: 1.2 枚举类&#xff1a; 1.3 controller接口&#xff1a; 1.4 实体参数&#xff1a; 1.5 knife4j的配置 2.实现要求 3.实现步骤 3.1 自定义注解类&#xff1a; 3.2 使用注解&#xff1a; 3.3 添加注解校验类&#xff1a; …

Type c系列接口驱动电路·内置供电驱动电路使用USB2.0驱动电路!!!

目录 前言 Type c常见封装类型 Type c引脚功能详解 Type c常见驱动电路详解 Type c数据手册 ​​​​​​​ ​​​​​​​ 编写不易&#xff0c;仅供学习&#xff0c;请勿搬运&#xff0c;感谢理解 常见元器件驱动电路文章专栏连接 LM7805系列降压芯片驱动电路…

Mybatis 01

JDBC回顾 select 语句 "select *from student" 演示&#xff1a; 驱动包 JDBC 的操作流程&#xff1a; 1. 创建数据库连接池 DataSource 2. 通过 DataSource 获取数据库连接 Connection 3. 编写要执⾏带 ? 占位符的 SQL 语句 4. 通过 Connection 及 SQL 创建…

基础数据结构--二叉树

一、二叉树的定义 二叉树是 n( n > 0 ) 个结点组成的有限集合&#xff0c;这个集合要么是空集&#xff08;当 n 等于 0 时&#xff09;&#xff0c;要么是由一个根结点和两棵互不相交的二叉树组成。其中这两棵互不相交的二叉树被称为根结点的左子树和右子树。 如图所示&am…

协议幻变者:DeviceNet转ModbusTCP网关开启机器手臂智能新纪元

技术背景DeviceNet是一种广泛应用于工业自动化领域的现场总线标准&#xff0c;它能够实现控制器与现场设备之间的高效通信&#xff0c;常用于连接各种传感器、执行器以及其他工业设备&#xff0c;如机器人、电机驱动器等&#xff0c;具有实时性强、可靠性高的特点。而ModbusTCP…

Spring Security 3.0.2.3版本

“前言” 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往复以至无…

MiFlash 线刷工具下载合集

MiFlash 线刷工具下载合集 MiFlash 线刷工具下载合集 – MIUI历史版本相较于小米助手的刷机功能&#xff0c;线刷还是偏好使用 MiFlash。特点是界面简单纯粹&#xff0c;有自定义高级选项&#xff0c;可以选择刷机不上 BL 锁&#xff0c;自定义刷机脚本&#xff0c;EDL 刷机模…

Oracle 多租户架构简介

目录 零. 简介一. CDB&#xff08;Container Database&#xff0c;容器数据库&#xff09;二. PDB&#xff08;Pluggable Database&#xff0c;可插拔数据库&#xff09;三. CDB 与 PDB 的比较四. 用户的种类五. XE 与 XEPDB1 零. 简介 ⏹Oracle 多租户架构&#xff08;Multit…

掌握大数据处理利器:Flink 知识点全面总结【上】

1.Flink的特点 Apache Flink 是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行状态计算。 Flink主要特点如下&#xff1a; 高吞吐和低延迟。每秒处理数百万个事件&#xff0c;毫秒级延迟。结果的准确性。Flink提供了事件时间(event--time)和处理时间(proces…

[论文阅读] (34)ESWA2024 基于SGDC的轻量级入侵检测系统

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

《向量数据库指南》——Milvus Cloud 2.5:Sparse-BM25引领全文检索新时代

Milvus Cloud BM25:重塑全文检索的未来 在最新的Milvus Cloud 2.5版本中,我们自豪地引入了“全新”的全文检索能力,这一创新不仅巩固了Milvus Cloud在向量数据库领域的领先地位,更为用户提供了前所未有的灵活性和效率。作为大禹智库的向量数据库高级研究员,以及《向量数据…

常用的数据库类型都有哪些

在Java开发和信息系统架构中&#xff0c;数据库扮演着存储和管理数据的关键角色。数据库种类繁多&#xff0c;各有特色&#xff0c;适用于不同的应用场景。 1. 关系型数据库&#xff08;RDBMS&#xff09;&#xff1a; • 关系型数据库是最为人熟知的数据库类型&#xff0c;数据…

计算机网络—————考研复试

第一章、计算机网络体系结构 1. OSI参考模型和TCP/IP模型&#xff1a; OSI与TCP/IP的记忆方法&#xff1a;只需把OSI的七层记住&#xff0c;将应用层、表示层、会话层一起记&#xff0c;到TCP/IP变成应用层。物理层和数据链路层换成网络接口层。把网络层换个字变成网际层。 而…

从2024看2025前端发展趋势

前言 又至年关&#xff0c;回顾整个2024年&#xff0c;前端行业仍旧百废待兴&#xff0c;IT业界同样也未见有所起色&#xff0c;AI风潮也从狂热兴奋逐步走向了冷静稳定阶段&#xff0c;造成此形势感观并非单一行业或者某一企业之特例&#xff0c;实为政经等综合影响之结果。因…

国内机器视觉产业链全解析

欢迎关注《光场视觉》 简单的&#xff0c;我们可以把机器视觉产业链可以分为底层开发商&#xff08;核心零部件和软件提供商&#xff09;、集成和软件服务商&#xff08;二次开发&#xff09;&#xff0c;核心零部件及软件又可以再细分为光源、镜头、工业相机、图像采集卡、图…

node.js之---事件循环机制

事件循环机制 Node.js 事件循环机制&#xff08;Event Loop&#xff09;是其核心特性之一&#xff0c;它使得 Node.js 能够高效地处理大量并发的 I/O 操作。Node.js 基于 非阻塞 I/O&#xff0c;使用事件驱动的模型来实现异步编程。事件循环是 Node.js 实现异步编程的基础&…

如何在没有 iCloud 的情况下将数据从 iPhone 传输到 iPhone

概括 您可能会遇到将数据从 iPhone 转移到 iPhone 的情况&#xff0c;尤其是当您获得新的 iPhone 15/14 时&#xff0c;您会很兴奋并希望将数据转移到它。 使用iCloud最终可以做到这一点&#xff0c;但它的缺点也不容忽视&#xff0c;阻碍了你选择它。例如&#xff0c;您需要…

HTML——26.像素单位

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>像素</title></head><body><!--像素&#xff1a;1.指设备屏幕上的一个点&#xff0c;单位px&#xff0c;如led屏上的小灯朱2.当屏幕分辨率固定时&…

智能商业分析 Quick BI

Quick BI 是阿里云提供的一款智能商业分析&#xff08;BI&#xff09;工具&#xff0c;旨在帮助企业快速获取业务洞察、优化决策过程、提升数据分析效率。通过强大的数据可视化和分析功能&#xff0c;Quick BI 能够帮助用户轻松连接多种数据源、创建多维度的报表和仪表盘&#…