Esnight000 发表于 2024-11-14 16:41

用于深度学习的车辆数据特征提取及标注工具开发探索

本帖最后由 Esnight000 于 2024-11-14 17:44 编辑

思路概括:车辆CAN报文数据,先转换为可视化曲线,根据曲线走势人工判定车辆运行工况,同时打标签做数据标注,生成可用于模型训练的数据文件。
流程概述:
步骤 1:CAN报文数据解析;
步骤 2:数据可视化;
步骤 3:人工判定运行工况;
步骤 4:工况标注工具;
步骤 5:输出标注数据;

其中,重点在于步骤4,标注工具的开发,使用了Matplotlib和Tkinter做一个交互工具,并做了功能的调试和简单完善,在此仅做探索开发。



import tkinter as tk
from tkinter import ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pandas as pd
from matplotlib.patches import Rectangle

# 示例数据
data = pd.DataFrame({
    'time': range(100),
    'speed': ,
    'throttle': ,
    'torque':
})

LABEL_COLORS = {
    "重载上坡": "red",
    "重载下坡": "blue",
    "空载平路": "green",
    "空载下坡": "orange"
}


class AnnotationTool:
    def __init__(self, root, data):
      self.root = root
      self.data = data
      self.annotations = []
      self.rects = []
      self.start_time = None
      self.end_time = None
      self.current_rect = None
      self.zoom_axis = 'x'# 默认缩放x轴
      self.fullscreen = False# 全屏状态记录

      # 初始化多子图结构
      self.fig, self.axs = plt.subplots(nrows=len(data.columns) - 1, sharex=True, figsize=(8, 6))

      # 逐列绘制数据
      for i, col in enumerate(data.columns):
            self.axs.plot(data['time'], data, label=col)
            self.axs.legend()
            self.axs.set_ylabel(col)

      self.axs[-1].set_xlabel('Time')

      # 嵌入Matplotlib到Tkinter中
      self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
      self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
      self.canvas.mpl_connect("button_press_event", self.on_click)
      self.canvas.mpl_connect("scroll_event", self.on_scroll)
      self.canvas.mpl_connect("motion_notify_event", self.on_motion)

      # 标签选择和布局
      self.label_var = tk.StringVar(value="Select Condition")
      self.label_menu = ttk.Combobox(root, textvariable=self.label_var, values=list(LABEL_COLORS.keys()))
      self.label_menu.set("Select Condition")

      # 创建按钮和布局
      self.button_frame = tk.Frame(root)
      self.save_button = tk.Button(self.button_frame, text="Save Annotations", command=self.save_annotations)
      self.delete_button = tk.Button(self.button_frame, text="Delete Last Annotation",
                                       command=self.delete_last_annotation)
      self.zoom_in_button = tk.Button(self.button_frame, text="Zoom In", command=self.zoom_in)
      self.zoom_out_button = tk.Button(self.button_frame, text="Zoom Out", command=self.zoom_out)
      self.fullscreen_button = tk.Button(self.button_frame, text="Toggle Fullscreen", command=self.toggle_fullscreen)

      # 将按钮横向排列
      self.label_menu.pack(side=tk.LEFT, padx=5)
      self.save_button.pack(side=tk.LEFT, padx=5)
      self.delete_button.pack(side=tk.LEFT, padx=5)
      self.zoom_in_button.pack(side=tk.LEFT, padx=5)
      self.zoom_out_button.pack(side=tk.LEFT, padx=5)
      self.fullscreen_button.pack(side=tk.LEFT, padx=5)
      self.button_frame.pack(fill=tk.X, pady=5)

    def on_click(self, event):
      # 检查点击位置是否在子图内
      if event.inaxes in self.axs:
            ax = event.inaxes

            # 如果是右键点击
            if event.button == 3:
                # 切换当前子图的纵轴缩放功能
                if self.zoom_axis == 'y' and self.zoom_enabled_ax == ax:
                  self.zoom_axis = None
                  self.zoom_enabled_ax = None
                  print("Right-clicked again: Y-axis zoom disabled for this subplot")
                else:
                  self.zoom_axis = 'y'
                  self.zoom_enabled_ax = ax
                  print("Right-clicked: Y-axis zoom enabled for this subplot")

            # 如果是左键点击用于标注
            elif event.button == 1:
                label = self.label_var.get()
                if label == "Select Condition":
                  messagebox.showwarning("Warning", "Please select a condition before annotating.")
                  return

                if event.xdata is None:
                  print("Invalid click: event.xdata is None")
                  return

                # 初始化或更新标注框的起始位置
                if self.start_time is None:
                  self.start_time = event.xdata
                  self.current_rects = []
                  for ax in self.axs:
                        rect = Rectangle(
                            (self.start_time, ax.get_ylim()), 0,
                            ax.get_ylim() - ax.get_ylim(),
                            color='gray', alpha=0.3
                        )
                        ax.add_patch(rect)
                        self.current_rects.append(rect)
                else:
                  self.end_time = event.xdata
                  self.add_annotation(self.start_time, self.end_time, label)
                  self.reset_selection()

    def on_motion(self, event):
      # 检查是否处于标注模式
      if self.start_time is not None and event.xdata is not None:
            # 更新每个子图的矩形宽度
            width = event.xdata - self.start_time
            for rect in self.current_rects:
                rect.set_width(width)
            self.canvas.draw()
      else:
            print("Invalid motion event: either start_time or event.xdata is None")

    def on_scroll(self, event):
      # 滚轮缩放
      ax = self.axs[-1] if self.zoom_axis == 'x' else event.inaxes
      if ax:
            scale_factor = 1.1 if event.button == 'up' else 0.9
            x_min, x_max = ax.get_xlim() if self.zoom_axis == 'x' else ax.get_ylim()
            center = (x_max + x_min) / 2
            delta = (x_max - x_min) * scale_factor / 2
            if self.zoom_axis == 'x':
                ax.set_xlim(center - delta, center + delta)
            else:
                ax.set_ylim(center - delta, center + delta)
            self.canvas.draw()

    def add_annotation(self, start, end, label):
      color = LABEL_COLORS
      rect = self.axs.axvspan(start, end, color=color, alpha=0.3)
      self.annotations.append((start, end, label))
      self.rects.append(rect)
      self.canvas.draw()

    def reset_selection(self):
      self.start_time = None
      self.end_time = None
      self.current_rect = None

    def save_annotations(self):
      df = pd.DataFrame(self.annotations, columns=['start_time', 'end_time', 'label'])
      df.to_csv("annotations.csv", index=False)
      messagebox.showinfo("Info", "Annotations saved successfully.")

    def delete_last_annotation(self):
      if self.annotations:
            self.annotations.pop()
            rect = self.rects.pop()
            rect.remove()
            self.canvas.draw()
      else:
            messagebox.showwarning("Warning", "No annotations to delete.")

    def zoom_in(self):
      for ax in self.axs:
            x_min, x_max = ax.get_xlim()
            ax.set_xlim(x_min + (x_max - x_min) * 0.1, x_max - (x_max - x_min) * 0.1)
      self.canvas.draw()

    def zoom_out(self):
      for ax in self.axs:
            x_min, x_max = ax.get_xlim()
            ax.set_xlim(x_min - (x_max - x_min) * 0.1, x_max + (x_max - x_min) * 0.1)
      self.canvas.draw()

    def toggle_fullscreen(self):
      self.fullscreen = not self.fullscreen
      self.root.attributes("-fullscreen", self.fullscreen)


# 主程序入口
root = tk.Tk()
root.title("CAN Data Annotation Tool")
root.geometry("800x600")

app = AnnotationTool(root, data)
root.mainloop()



JokerShow 发表于 2024-11-17 18:18

牛逼 可以值得学习

NastyAir 发表于 2024-11-17 18:44

很不错,学习下
页: [1]
查看完整版本: 用于深度学习的车辆数据特征提取及标注工具开发探索