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

查看 23|回复 2
作者:Esnight000   
思路概括:车辆CAN报文数据,先转换为可视化曲线,根据曲线走势人工判定车辆运行工况,同时打标签做数据标注,生成可用于模型训练的数据文件。
流程概述:
步骤 1:CAN报文数据解析;
步骤 2:数据可视化;
步骤 3:人工判定运行工况;
步骤 4:工况标注工具;
步骤 5:输出标注数据;
其中,重点在于步骤4,标注工具的开发,使用了Matplotlib和Tkinter做一个交互工具,并做了功能的调试和简单完善,在此仅做探索开发。
[Python] 纯文本查看 复制代码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': [10 + i * 0.2 for i in range(100)],
    'throttle': [5 + i * 0.1 for i in range(100)],
    'torque': [50 + i * 0.3 for i in range(100)]
})
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[1:]):
            self.axs.plot(data['time'], data[col], 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]), 0,
                            ax.get_ylim()[1] - ax.get_ylim()[0],
                            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[label]
        rect = self.axs[0].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   

牛逼 可以值得学习
NastyAir   

很不错,学习下
您需要登录后才可以回帖 登录 | 立即注册