Python tkinter: 开发一个目标检测GUI小程序

程序提供了一个用户友好的界面,允许用户选择图片或文件夹,使用行人检测模型进行处理,并在GUI中显示检测结果。用户可以通过点击画布上的检测结果来获取更多信息,并使用键盘快捷键来浏览不同的图片。

一. 基本功能介绍

  1. 界面布局:程序使用tkinter库创建一个窗口界面,包括标题栏、可调整大小的画布以及底部的操作按钮。

  2. 图片加载:用户可以通过点击“选择文件夹”或“选择图片”按钮来加载需要检测的图片。支持多种图片格式,如JPG、JPEG、PNG等。

  3. 目标检测:程序内置了一个检测器(例如YOLOv8),用于识别图片中的行人,并在图片上绘制矩形框和置信度标签。

  4. 进度显示:在处理多张图片时,程序会显示一个进度条,告知用户当前的检测进度。

  5. 结果展示:检测完成后,程序会在GUI中展示第一张图片的检测结果,并允许用户通过键盘操作(A键上一张,D键下一张)浏览所有图片。

  6. 交互反馈:用户点击画布上的检测框时,程序会弹出一个消息框显示选中目标的类别和置信度。

  7. 图片缩放:程序能够根据窗口大小调整图片的显示尺寸,确保图片在不同分辨率的屏幕上都能清晰显示。

  8. 日志记录:程序使用logging模块记录操作日志,便于问题追踪和调试。

  9. 多线程处理:为了不阻塞GUI操作,图片的检测处理在后台线程中进行。

  10. 配置灵活:程序允许用户通过参数配置检测器的行为,例如模型路径、图片尺寸、置信度阈值等。

二. 主要方法介绍

  1. __init__: 类的构造函数,用于初始化GUI窗口、设置窗口属性、创建组件和绑定事件。

  2. load_dir: 允许用户通过文件对话框选择一个文件夹,程序会加载该文件夹下的所有支持格式的图片。

  3. load_imgs: 使用文件对话框让用户选择一个或多个图片文件,并将这些文件的路径添加到图片列表中。

  4. show_progress_window: 显示进度条窗口,用于在处理图片时提供用户反馈。

  5. process_files: 在后台线程中处理所有选中的图片,对每张图片运行检测算法,并更新进度条。

  6. run_detect: 对单张图片运行检测器,返回检测结果。

  7. draw_result: 在原始图片上绘制检测结果,如边界框和置信度标签。

  8. draw_detections: 在GUI的画布上绘制检测结果,包括边界框和文本标签。

  9. remove_progress_bar: 在所有图片处理完毕后,移除进度条和相关组件。

  10. display_image: 在GUI中显示当前选中的图片及其检测结果。

  11. on_canvas_click: 绑定到画布的点击事件,用于检测用户点击的位置是否在检测框内,并显示选中目标的详细信息。

  12. win_change: 响应窗口大小变化事件,调整图片的显示大小以适应窗口。

  13. on_win_change: 处理窗口大小变化事件,调用win_change方法。

  14. on_key_press: 绑定到窗口的键盘按键事件,允许用户通过按键浏览图片。

  15. cv2pil: 将使用OpenCV加载的BGR格式图片转换为PIL图像,以便在Tkinter中显示。

三. 代码

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import cv2
import math
import json
import time
import os
import threading
from PIL import Image, ImageTk
import logging
logging.basicConfig(level=logging.INFO)

from person_detection import api


class MyGUI(tk.Tk):
    def __init__(self, det_kwargs):
        super().__init__()
        self.title('行人检测小程序')
        self.geometry('600x400')
        self.resizable(width=True, height=True)
        self.label = tk.Label(self)
        self.bind('<Configure>', self.on_win_change)
        self.bind('<Key>', self.on_key_press)

        # 创建检测器
        self.detector = api.YOLOv8(**det_kwargs) if det_kwargs else None
        self.classes = {0: 'person'}

        # 用于记录当前的图片数据
        self.raw_img_file_path = None
        self.visual_image = None
        self.photo_image = None

        # 创建按钮框架
        self.button_frame = tk.Frame(self)
        self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, expand=False)
        # 创建按钮
        self.load_dir_button = tk.Button(self.button_frame, text="选择文件夹", command=self.load_dir)
        self.load_dir_button.pack(side=tk.LEFT, padx=10, pady=10, expand=False)
        self.load_imgs_button = tk.Button(self.button_frame, text="选择图片", command=self.load_imgs)
        self.load_imgs_button.pack(side=tk.LEFT, padx=10, pady=10, expand=False)
        # 创建用于显示图片的Canvas
        self.image_canvas = tk.Canvas(self, bg='white')
        self.image_canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        self.image_id = self.image_canvas.create_image(
            0, 0, image=self.photo_image, anchor='nw'
        )
        # 创建进度条窗口和进度条
        self.bar_frame = None
        self.progress_window = None
        self.progress_bar = None
        self.processing_label = None

        # 为Canvas绑定鼠标点击事件
        self.image_canvas.bind("<Button-1>", self.on_canvas_click)
        # 为窗口绑定大小和位置变化事件的监听
        self.bind("<Configure>", self.on_win_change)

        # 记录每次加载的图片路径列表,检测结果,以及当前的图片索引
        self.img_list = []
        self.result_list = []
        self.index = 0

        # 初始化缩放比例属性
        self.scale_width = 1.0
        self.scale_height = 1.0

    def load_dir(self):
        """
        选择文件夹,从中加载图片
        """
        # 选择图片
        file_dir = filedialog.askdirectory(title='选择文件夹')
        logging.info("选择的文件夹: {}".format(file_dir))
        if file_dir != ():
            for i, file in enumerate(os.listdir(file_dir)):
                if file.endswith(('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')):
                    img_path = os.path.join(file_dir, file)
                    self.img_list.append(img_path)
            # 显示进度条窗口
            self.show_progress_window(self.img_list)

    def load_imgs(self):
        """
        选择单个或多个文件以加载图片
        """
        file_paths = filedialog.askopenfilenames(
            title='选择图片文件', filetypes=[('图像文件', '*.jpg *.jpeg *.png *.bmp *.tiff *.webp'), ('所有文件', '*.*')])
        logging.info("选择的文件: {}".format(file_paths))
        if file_paths != ():
            for img_path in file_paths:
                self.img_list.append(img_path)
            # 显示进度条窗口
            self.show_progress_window(self.img_list)

    def show_progress_window(self, img_list):
        # 创建进度条框架
        self.bar_frame = tk.Frame(self)
        self.bar_frame.pack(side=tk.TOP, fill=tk.Y, pady=20, expand=False)
        # 创建正在处理的文本标签和进度条
        self.processing_label = tk.Label(self.bar_frame, text="正在处理...")
        self.processing_label.pack(side=tk.BOTTOM, fill=tk.Y, pady=0, expand=False)
        self.progress_bar = ttk.Progressbar(self.bar_frame, orient='horizontal', length=300, mode='determinate')
        self.progress_bar.pack(side=tk.BOTTOM, pady=0, expand=False)

        # 设置最大值为文件数量
        total_files = len(img_list)
        self.progress_bar['maximum'] = total_files

        # 更新初始进度
        self.progress_bar['value'] = 0

        # 在新线程中处理文件以避免阻塞GUI
        threading.Thread(target=self.process_files, args=(img_list,)).start()
        # 使用阻塞的方式处理文件
        # self.process_files(file_dir)

    def process_files(self, img_list):
        for i, img_path in enumerate(img_list):
            # 模拟处理文件的耗时操作
            logging.info("处理文件: {}".format(img_path))
            img = cv2.imread(img_path)
            self.result_list.append(self.run_detect(img))

            # 更新进度条
            self.progress_bar['value'] = i + 1  # 更新进度条

        # 处理完成后,显示消息框并移除进度条
        # self.after(100, self.remove_progress_bar)  # 稍后执行
        self.remove_progress_bar()  # 立即执行

        # 显示图片到Canvas
        self.display_image()

        # 调整图片至窗口大小
        self.win_change()

    def run_detect(self, img):
        return self.detector.detect([img])

    def draw_result(self, img, result):
        img = img.copy()
        dets = result['data'][0]['dets']
        for det in dets:
            id, score, bbox = det['id'], det['score'], det['bbox']
            x1, y1, x2, y2 = map(int, bbox)
            cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(img, '%.2f' % score, (x1, y1 - 4),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), thickness=1, lineType=cv2.LINE_AA)
        return img

    def draw_detections(self, result):
        dets = result['data'][0]['dets']
        for det in dets:
            id, score, bbox = det['id'], det['score'], det['bbox']
            x1, y1, x2, y2 = map(int, bbox)
            x1 = int(x1 / self.scale_width)
            y1 = int(y1 / self.scale_height)
            x2 = int(x2 / self.scale_width)
            y2 = int(y2 / self.scale_height)
            self.image_canvas.create_rectangle(x1, y1, x2, y2, outline='red', width=2)
            self.image_canvas.create_text(x1, y1 - 10, text=f'{score:.2f}', fill='red', font=('Arial', 8))

    def remove_progress_bar(self):
        """
        移除进度条,进行一些销毁与重置的操作
        """
        # 显示消息框
        messagebox.showinfo("完成", "文件处理完成!")
        # 从主窗体中移除进度条
        if self.progress_bar is not None:
            self.progress_bar.pack_forget()  # 隐藏进度条
            self.progress_bar.destroy()  # 销毁进度条对象

        # 从主窗体中移除进度条标签
        if hasattr(self, 'processing_label') and self.processing_label is not None:
            self.processing_label.pack_forget()  # 隐藏进度条
            self.processing_label.destroy()  # 销毁进度条对象

        # 从主窗体中移除进度条框架
        if self.bar_frame is not None:
            self.bar_frame.destroy()  # 销毁进度条框架

    def display_image(self):
        if len(self.img_list) == 0:
            return
        image = cv2.imread(self.img_list[self.index])
        image = self.draw_result(image, self.result_list[self.index])
        # 将OpenCV图像转换为PIL图像,然后转换为PhotoImage
        pil_image = self.cv2pil(image)
        self.photo_image = ImageTk.PhotoImage(pil_image)

        # 显示图片
        self.image_canvas.delete("all")  # 删除旧的图片
        self.image_id = self.image_canvas.create_image(
            0, 0, image=self.photo_image, anchor='nw'
        )
        self.image_canvas.image = self.photo_image  # 保持对图像的引用

    def on_canvas_click(self, event):
        # 使用缩放比例将画布坐标转换为图像坐标
        canvas_x = event.x
        canvas_y = event.y
        img_x = int(canvas_x / self.scale_width)
        img_y = int(canvas_y / self.scale_height)

        # 初始化最近目标的距离和索引
        min_distance = float('inf')  # 正无穷大,用于比较
        closest_index = -1
        closest_id = None
        closest_score = None

        # 检查点击坐标是否在检测结果的边界框内
        if len(self.result_list) > 0:
            dets = self.result_list[self.index]['data'][0]['dets']
            for i, det in enumerate(dets):
                id, score, bbox = det['id'], det['score'], det['bbox']
                x1, y1, x2, y2 = map(int, bbox)  # 边界框的坐标

                # 计算边界框的中心点坐标
                center_x = (x1 + x2) / 2
                center_y = (y1 + y2) / 2

                if x1 <= img_x <= x2 and y1 <= img_y <= y2:
                    # 计算点击位置到边界框中心的欧氏距离
                    distance = math.sqrt((img_x - center_x) ** 2 + (img_y - center_y) ** 2)

                    # 更新最近目标的距离和索引
                    if distance < min_distance:
                        min_distance = distance
                        closest_index = i
                        closest_id = id
                        closest_score = score

            # 如果找到最近的目标,则显示信息
            if closest_index != -1:
                messagebox.showinfo("检测到的目标", f"标签: {self.classes[closest_id]}\n置信度: {closest_score:.2f}")
            # else:
            #     messagebox.showinfo("点击区域", "未检测到目标")

    def win_change(self):
        if len(self.result_list) > 0:  # 确保图像不为空
            image = cv2.imread(self.img_list[self.index])
            visual_image = self.draw_result(image, self.result_list[self.index])
            # 保存原始图像尺寸和画布尺寸
            self.orig_width = visual_image.shape[1]
            self.orig_height = visual_image.shape[0]
            canvas_width = self.image_canvas.winfo_width()
            canvas_height = self.image_canvas.winfo_height()

            # 计算缩放比例
            self.scale_width = canvas_width / self.orig_width
            self.scale_height = canvas_height / self.orig_height

            # 根据缩放比例调整图像大小
            new_width = int(self.orig_width * self.scale_width)
            new_height = int(self.orig_height * self.scale_height)
            resized_image = cv2.resize(visual_image, (new_width, new_height))

            # 显示调整大小后的图像
            pil_image = self.cv2pil(resized_image)
            self.photo_image = ImageTk.PhotoImage(pil_image)
            self.image_canvas.itemconfig(self.image_id, image=self.photo_image)

    def on_win_change(self, event):
        """
        监控窗口大小和位置的变化
        :param event:
        :return:
        """
        self.win_change()

    def on_key_press(self, event):
        """
        监控键盘按键的按下事件,根据按键进行index增减,以进行图片浏览切换
        设定规则:
            A:上一张
            D:下一张
        """
        if len(self.result_list) == 0:
            return
        if event.keysym in ['A', 'a']:
            if self.index == 0:
                messagebox.showinfo("提示", "已经是第一张图片了")
            self.index = max(0, self.index - 1)
        elif event.keysym in ['D', 'd']:
            if self.index == len(self.img_list) - 1:
                messagebox.showinfo("提示", "已经是最后一张图片了")
            self.index = min(len(self.img_list)-1, self.index + 1)
        else:
            messagebox.showinfo("提示", "请按 A 或 D 键进行图片上翻/下翻")
        self.display_image()
        self.win_change()

    def cv2pil(self, cv2_img):
        # 转换图片至RGB颜色空间
        image = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB)

        # 转换图片至PIL格式
        return Image.fromarray(image)

    def get_filename(self, file_path):
        return os.path.splitext(os.path.split(file_path)[-1])[0]


if __name__ == '__main__':
    det_kwargs = dict(
        model_path='/home/leon/Nextcloud/ChengDu_Research/computer_vision/algorithms/human/person_detection/1.0.1/person_detection_1.0.1.onnx',
        img_size=(640, 384),
        mode=api.ie.MODE_ORT,
        conf_thresh=0.5,
        providers=['CUDAExecutionProvider'],  # if no GPU, use 'CPUExecutionProvider'
        # half=True,
    )
    app = MyGUI(det_kwargs)
    app.mainloop()

请注意:在代码中,我用到了一个目标检测器,你需要替换为你自己的检测器,从而实现不同目标的检测!

代码中和检测相关方法/变量如下:

方法:

  1. run_detect(self, img): 使用检测器对提供的图像进行检测。
  2. draw_result(self, img, result): 在图像上绘制检测结果,如边界框和分数。
  3. process_files(self, img_list): 处理一个图片列表,对每张图片执行检测。
  4. display_image(self): 在GUI上显示当前选中的图片和其检测结果。
  5. draw_detections(self, result): 在Canvas上绘制检测结果。
  6. show_progress_window(self, img_list): 显示进度条窗口,准备开始处理图片列表。
  7. remove_progress_bar(self): 完成图片处理后,移除进度条。

变量:

  1. self.detector: 用于存储检测器实例,例如api.YOLOv8
  2. self.classes: 一个字典,用于将检测到的类别ID映射到类别名称。
  3. self.img_list: 存储加载的图片路径列表。
  4. self.result_list: 存储每张图片的检测结果。
  5. self.index: 当前显示图片的索引。
  6. self.raw_img_file_path: 记录当前处理的原始图片文件路径。
  7. self.visual_image: 用于存储绘制了检测结果的图像。
  8. self.photo_image: 用于存储Tkinter能够显示的图像对象。
  9. self.image_id: 存储Canvas上图像的ID,用于更新显示的图像。
  10. self.scale_width 和 self.scale_height: 存储图像的缩放比例。

对于我的检测器,这里贴出来一个输出示例:

{
  "code": "0",
  "message": "",
  "data": [
    {
      "dets": [
        {
          "id": 0,
          "score": 0.7589585781097412,
          "bbox": [
            873.7188720703125,
            236.35150146484375,
            910.048095703125,
            335.6061706542969
          ]
        },
        {
          "id": 0,
          "score": 0.716355562210083,
          "bbox": [
            447.7972717285156,
            278.9081726074219,
            521.7301025390625,
            421.3373718261719
          ]
        }
      ]
    }
  ]
}

参考:

tkinter — Python interface to Tcl/Tk — Python 3.12.4 documentation

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

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

相关文章

C++封装

1. 封装 1.1. struct 当单一变量无法完成描述需求的时候&#xff0c;结构体类型解决了这一问题。可以将多个类型打包成一体&#xff0c;形成新的类型&#xff0c;这是c语言中的封装 但是&#xff0c;新类型并不包含&#xff0c;对数据类的操作。所有操作都是通过函数的方式进…

CrimsonEDR:一款恶意软件模式识别与EDR策略评估工具

关于CrimsonEDR CrimsonEDR是一个功能强大的开源项目&#xff0c;该项目旨在帮助广大研究人员识别特定的恶意软件模式&#xff0c;以此来优化终端检测与响应&#xff08;EDR&#xff09;的策略方案。通过使用各种不同的检测方案&#xff0c;可以加深开发人员与研究人员加深对安…

在Ubuntu 14.04上安装和配置Mumble服务器(Murmur)的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 介绍 Mumble是一款免费开源的语音通信应用程序&#xff0c;主要设计用于游戏玩家使用。Mumble类似于TeamSpeak和Ventrilo。Mumble采用客…

考研生活day1--王道课后习题2.2.1、2.2.2、2.2.3

2.2.1 题目描述&#xff1a; 解题思路&#xff1a; 这是最基础的操作&#xff0c;思路大家应该都有&#xff0c;缺少的应该是如何下笔&#xff0c;很多同学都是有思路但是不知道如何下笔&#xff0c;这时候看思路的意义不大&#xff0c;可以直接看答案怎么写&#xff0c;最好…

cube-studio 开源一站式云原生机器学习/深度学习/大模型训练推理平台介绍

全栈工程师开发手册 &#xff08;作者&#xff1a;栾鹏&#xff09; 一站式云原生机器学习平台 前言 开源地址&#xff1a;https://github.com/tencentmusic/cube-studio cube studio 腾讯开源的国内最热门的一站式机器学习mlops/大模型训练平台&#xff0c;支持多租户&…

python sklearn机械学习模型-分类

&#x1f308;所属专栏&#xff1a;【机械学习】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您…

什么是应用安全态势管理 (ASPM):综合指南

软件开发在不断发展&#xff0c;应用程序安全也必须随之发展。 传统的应用程序安全解决方案无法跟上当今开发人员的工作方式或攻击者的工作方式。 我们需要一种新的应用程序安全方法&#xff0c;而ASPM在该方法中发挥着关键作用。 什么是 ASPM&#xff1f; 应用程序安全…

神经网络训练(一):基于残差连接的图片分类网络(ResNet18)

目录 一、简介:二、图片分类网络1.记载训练数据(torch自带的cifa10数据集)2.数据增强3.模型构建4.模型训练三、完整源码及文档一、简介: 基于残差连接的图片分类网络,本网络使用ResNet18作为基础模块,根据cifa10的特点进行改进网络,使用交叉熵损失函数和SGD优化器。本网…

源代码层面分析Appium-inspector工作原理

Appium-inspector功能 Appium Inspector 基于 Appium 框架&#xff0c;Appium 是一个开源工具&#xff0c;用于自动化移动应用&#xff08;iOS 和 Android&#xff09;和桌面应用&#xff08;Windows 和 Mac&#xff09;。Appium 采用了客户端-服务器架构&#xff0c;允许用户通…

实践Go的命令模式

简介 现在的软件系统往往是分层设计。在业务层执行一次请求时&#xff0c;我们很清楚请求的上下文&#xff0c;包括&#xff0c;请求是做什么的、参数有哪些、请求的接收者是谁、返回值是怎样的。相反&#xff0c;基础设施层并不需要完全清楚业务上下文&#xff0c;它只需知道…

Typora导出为Word

文章目录 一、场景二、安装1、网址2、解压并验证 三、配置四、重启Typora 一、场景 在使用Typora软件编辑文档时&#xff0c;我们可能需要将其导出为Word格式文件 当然我们可以直接在菜单里进行导出操作 文件-> 导出-> Word(.docx) 如果是第一次导出word文件&#xff0…

Python实现接糖果小游戏

介绍: 基于Pygame的糖果从屏幕顶部下落的游戏代码。这个游戏包括了一个可以左右移动的篮子来接住下落的糖果&#xff0c;接住糖果会增加得分。 代码: import pygame import random import os# 初始化pygame和设置屏幕大小 pygame.init() screen_width, screen_height 800, 6…

数据资产的创新应用与未来展望:探讨数据资产在人工智能、物联网等新兴领域的应用前景,提出前瞻性的数据资产解决方案,为企业探索新的增长点,推动行业创新发展

目录 一、引言 二、数据资产在人工智能领域的应用 1、机器学习与深度学习 2、自然语言处理 3、计算机视觉 三、数据资产在物联网领域的应用 1、智能家居 2、工业物联网 3、智慧城市 四、前瞻性的数据资产解决方案 1、构建统一的数据管理平台 2、加强数据安全和隐私…

OkHttp的源码解读1

介绍 OkHttp 是 Square 公司开源的一款高效的 HTTP 客户端&#xff0c;用于与服务器进行 HTTP 请求和响应。它具有高效的连接池、透明的 GZIP 压缩和响应缓存等功能&#xff0c;是 Android 开发中广泛使用的网络库。 本文将详细解读 OkHttp 的源码&#xff0c;包括其主要组件…

认识100种电路之耦合电路

在电子电路的世界中&#xff0c;耦合电路宛如一座精巧的桥梁&#xff0c;连接着各个功能模块&#xff0c;发挥着至关重要的作用。 【为什么电路需要耦合】 在复杂的电子系统中&#xff0c;不同的电路模块往往需要协同工作&#xff0c;以实现特定的功能。然而&#xff0c;这些模…

推荐算法学习笔记2.1:基于深度学习的推荐算法-基于共线矩阵的深度推荐算法-NeuralCF模型

NeuralCF模型 NeuralCF模型将矩阵分解和逻辑回归思想进行结合&#xff0c;利用神经网络分别学习用户和物品的隐向量表示&#xff08;Embedding&#xff09;&#xff0c;然后将矩阵分解中的内积互操作替换成神经网络计算&#xff0c;从而更好地从特征中学习到有用的信息。 原论…

【划分型动态规划 马拉车 中心扩展】2472. 不重叠回文子字符串的最大数目

如果有不明白的&#xff0c;请加文末QQ群。 本文涉及知识点 划分型动态规划 马拉车 中心扩展 LeetCode2472. 不重叠回文子字符串的最大数目 给你一个字符串 s 和一个 正 整数 k 。 从字符串 s 中选出一组满足下述条件且 不重叠 的子字符串&#xff1a; 每个子字符串的长度 …

SCI一区 | Matlab实现DBO-TCN-LSTM-Attention多变量时间序列预测

SCI一区 | Matlab实现DBO-TCN-LSTM-Attention多变量时间序列预测 目录 SCI一区 | Matlab实现DBO-TCN-LSTM-Attention多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.【SCI一区级】Matlab实现DBO-TCN-LSTM-Attention多变量时间序列预测&#xff08;程…

Golang | Leetcode Golang题解之第210题课程表II

题目&#xff1a; 题解&#xff1a; func findOrder(numCourses int, prerequisites [][]int) []int {var (edges make([][]int, numCourses)indeg make([]int, numCourses)result []int)for _, info : range prerequisites {edges[info[1]] append(edges[info[1]], info[0…

Tech Talk:智能电视eMMC存储的五问五答

智能电视作为搭载操作系统的综合影音载体&#xff0c;以稳步扩大的市场规模走入越来越多的家庭&#xff0c;成为人们生活娱乐的重要组成部分。存储部件是智能电视不可或缺的组成部分&#xff0c;用于保存操作系统、应用程序、多媒体文件和用户数据等信息。智能电视使用eMMC作为…