P2_C4-7:初始阶段

初始阶段:利益相关人就产品范围、愿景、使用场景达成一致

Inception is not requirements phase


阅读书上第4章

Inception Phase 初始阶段:预见项目的范围、设想和业务案例

  • 初始阶段需要考虑的事情:继续还是停止?购买还是自己构建?话费?涉众是否有统一看法?
  • 大多数analysis 是在elaboration中进行的
  • inception 阶段的activity和artifacts:vision、10~20%的用例、计划书等等
  • 该阶段时间要短,如果到一周甚至多周,进入了更深的研究就失去了意义

Evolutionary requirements


阅读书上第5章

Requirements

  • 需求是项目必须提供的能力和必须满足的条件
    • Goal:确定并文档出真实的需求
    • Challenge: 定义没有歧义的需求
  • 需求的类型可以按照“FURPS+”模型来进行分类和管理
    • Functional(功能性)
    • Usability (可用性)
    • Reliability (可靠性)
    • Performance (性能)
    • Supportability (可维护性)
  • UP会产生的需求制品:以下为可选的关键制品
    • 用例模型、补充性规格说明、词汇表、愿景书、业务规则

Use cases


阅读书上第6章

Actors(参与者):与系统进行交互的外部实体,如人、计算机、组织等。

  • Primary actors:
    • 特点:使用sud完成所具有的用户目标。
    • 作用:发现驱动用例的用户目标
  • Supporting actors:
    • 特点:为sud提供服务,通常是一个系统
    • 作用:为了明确外部接口和协议
  • Offstage actor:
    • 特点:在用例性为中有影响或获益,如政府机构
    • 作用:完备考虑利益关系

Use Case, 为满足驻澳参与者的目标而定义用例

  • 在FURPS中强调“F”, 功能性需求
  • scenario/use case instance:参与者和系统的一系列交互和活动
  • use case: 一组相关的成功和失败的场景集合
  • UML use case:提供了一个很好的系统的语境图
  • Use Case 的三种常见形式
  • Brief: 一段摘要,在早期的需求分析中很快得到的,主要用于成功场景
    • Casual:覆盖多个不通场景
    • Fully:(模板:书上P50)详细编写所有步骤和所有变化,包括前置条件和成功保证
      • use case name: 以动词开始
      • level:user-goal(实现)或subfunction(支持)
  • 用essential风格编写用例(摆脱UI)
    • essential style: 管理员标识自己的身份
    • concrete style: 管理员在对话框中填写了自己的ID和密码
  • black-box use cases:不对内部工作构件进行秒速,只通过职责来描述
  • take an actor and actor-goal perspective:关注涉众及其关注点
  • 如何发现用例
    • choose system boundary:
    • identity the primary actors and their goals
    • define use cases that satisfy user goals :基于参与者/基于事件
  • 什么测试有助于发现用例
    • boss test:
    • EBP test:一个人于某个时刻在一个地点所执行的任务
    • size test:规模不能太小,如输入定义、移动棋子

应用UML

  • 用例图
    • 准则:绘制简单的用例图,并与 参与者-目标 列表关联。重点是文本而不是图
  • 活动图
    • 描述某一用例中执行的步骤,使业务流程可视化
    • 可用于早期描述重要场景,用业务流程图识别用例
    • 重要的表示符号!!(ppt P66)

Other requirements


阅读书上第7章

除了用例以外的需求制品:

  • supplementary specifications:各种类型的补充说明,包括URPS中的一些元素(样例:书上P78)
  • glossary:数据字典
  • vision:项目的设想(样例:书上P82),文档中包含的特性最好少于10个
  • business rules(domain rules):长期政策法规









转载于:https://www.cnblogs.com/zengyh-1900/p/5523777.html

# 导入必要的库和模块 from smartcar import ticker, encoder from seekfree import KEY_HANDLER from machine import * import gc import time from display import * from seekfree import TSL1401, MOTOR_CONTROLLER, WIRELESS_UART, IMU963RX import math import ustruct # 初始化硬件设备 imu = IMU963RX() # IMU传感器 ccd = TSL1401(10) # CCD传感器,采集周期10次 key = KEY_HANDLER(5) # 按键处理 motor_l = MOTOR_CONTROLLER(MOTOR_CONTROLLER.PWM_C25_DIR_C27, 13000, duty=0, invert=True) # 左电机 motor_r = MOTOR_CONTROLLER(MOTOR_CONTROLLER.PWM_C24_DIR_C26, 13000, duty=0, invert=True) # 右电机 encoder_l = encoder("D0", "D1", True) # 左编码器 encoder_r = encoder("D2", "D3") # 右编码器 wireless = WIRELESS_UART(115200) # 无线通信 uart2 = UART(2) # 串口2 uart2.init(115200) # 初始化显示设备 cs = Pin('C5', Pin.OUT, pull=Pin.PULL_UP_47K, value=1) # 片选引脚 # 拉高拉低一次 CS 片选确保屏幕通信时序正常 cs.high() cs.low() rst = Pin('B9', Pin.OUT, pull=Pin.PULL_UP_47K, value=1) # 复位引脚 dc = Pin('B8', Pin.OUT, pull=Pin.PULL_UP_47K, value=1) # 数据/命令选择 blk = Pin('C4', Pin.OUT, pull=Pin.PULL_UP_47K, value=1) # 背光控制 drv = LCD_Drv(SPI_INDEX=1, BAUDRATE=60000000, DC_PIN=dc, RST_PIN=rst, LCD_TYPE=LCD_Drv.LCD200_TYPE) # LCD驱动 lcd = LCD(drv) # LCD实例 lcd.color(0x0000, 0xFFFF) # 设置颜色 lcd.mode(2) # 设置显示模式 lcd.clear(0xDEF7) # 清屏使用淡灰色背景 # 初始化引脚 led = Pin('C4', Pin.OUT, pull=Pin.PULL_UP_47K, value=True) # LED指示灯 beep = Pin('C9', Pin.OUT, pull=Pin.PULL_UP_47K, value=False) # 蜂鸣器 switch1 = Pin('D8', Pin.IN, pull=Pin.PULL_UP_47K, value=True) # 拨码开关1 switch2 = Pin('D9', Pin.IN, pull=Pin.PULL_UP_47K, value=True) # 拨码开关2 end_switch = Pin('C19', Pin.IN, pull=Pin.PULL_UP_47K, value=True) # 终点开关 # 全局变量定义 # PID控制相关变量 err_1 = err_sum_1 = err_x_1 = err_last_1 = 0 # PID1误差项 err_2 = err_sum_2 = err_x_2 = err_last_2 = 0 # PID2误差项 err_3 = err_sum_3 = err_x_3 = err_last_3 = 0 # PID3误差项 errt = errt_sum = errt_x = errt_last = 0 # 转向PID误差项 # 系统状态标志 ticker_flag = ticker_flag2 = False # 定时器标志 ticker_count = ticker_count0 = ticker_count3 = 0 # 定时器计数器 motor_dir = 1 # 电机方向 motor_duty = 0 # 电机占空比 motor_duty_max = 1000 # 最大占空比 turn = 0 # 转向量 state1 = switch1.value() # 开关1状态 state2 = switch2.value() # 开关2状态 end_state = end_switch.value() # 终点开关状态 # CCD相关变量 ccd1_zhongzhi = ccd2_zhongzhi = 64 # CCD中心值 left1 = right1 = left2 = right2 = 0 # CCD边界 flag_stute1 = flag_stute2 = 0 # 状态标志 flag_shizi1 = flag_shizi2 = 0 # 十字标志 shreshlod1 = shreshlod2 = 0 # 阈值 # 路径跟踪相关变量 lline = rline = lline2 = rline2 = 0 # 左右边界线 zThreshold = zThreshold2 = 15 # 阈值 zhong = zhong2 = 64 # 中心位置 banma_flag = stop = ysbzw = 0 # 标志位 track_stop = huang_tag = 0 # 跟踪停止标志 huang_l_flag = huang_r_flag = 0 # 黄线标志 huang_l_zt = huang_r_zt = 0 # 黄线状态 bizhan_l = bizhan_r = txzb = 0 # 避障相关 bizhan_flag = cha2 = 0 # 避障标志和差值 zuocha = zuocha2 = 0 # 左差值 zebra = road2 = jifen = 0 # 斑马线和道路标志 art1 = art2 = art3 = puodao_flag = 0 # 其他标志 road = fix = circle = zebra2 = banma_slow = stop_flag = 0 # 控制参数 delta_T = 0.001 # 采样周期为5ms I_ex, I_ey, I_ez = 0.0, 0.0, 0.0 # 积分误差 """""""""""""""主要调节以下参数""""""""""""""" """根据屏幕显示Pitch确定平衡角度""" med_roll_angle = 34 # 平衡角度 前倾+ 后仰- med_speed1 = -185#-1.9 # 速度(速度为零即静止) med_speed = med_speed1 #0 当前速度 # 角速度PID参数##高频震动:增大kd或减小kp angle_kp = -650#2280.0 调节响应速度 angle_ki = -2.25#-2.25 控制车辆在平衡点附近移动 angle_kd = -320 #-122 # 角度PID参数+- roll_angle_Kp = 0.055 #0.06585 控制车辆平衡角度 roll_angle_Ki = 0.00001 #0.00001 roll_angle_Kd = 0.38 #0.2 低频震动 # 速度PID参数 speed_Kp = -0.070#0.0792 加速/减速过慢,调节绝对值 speed_Ki = 0.00000001 #0.00000001 speed_Kd = 0.014 #0.01055 速度波动过大则增大kd或减小kp """""""""""""""主要调节以上参数""""""""""""""" CCD_dir = [64,64] # 转向PID参数 a = 0.35#0.152 Kpc = 0.45#0.2 turn_ki = 20#25 #中线位置调节 turn_kd = 2.0 #防止左右摆动幅度过大 turn_kd2 = -18 #转向速度 # 按键控制变量 param_index = 0 # 当前选择的参数索引 param_editing = True # 是否处于参数编辑模式 # 参数菜单定义 PARAM_MENU = [ "1.AngVel Kp", "2.AngVel Ki", "3.AngVel Kd", "4.Angle Kp", "5.Angle Ki", "6.Angle Kd", "7.Speed Kp", "8.Speed Ki","9.Speed Kd", "10.med_roll_angle", "11.Launch!" ] pid_params = [ # 可调参数列表 angle_kp, angle_ki, angle_kd, roll_angle_Kp, roll_angle_Ki, roll_angle_Kd, speed_Kp, speed_Ki,speed_Kd,med_roll_angle ] PARAM_STEP = [ # 各参数调节步长 10.0, 0.01, 1.0, # 角速度环 0.001, 0.00001, 0.001,# 角度环 0.0000001, 0.00001, 0.00001, # 速度环 0.5#角度 ] # 编码器卡尔曼滤波参数 KAL_P = KAL_P2 = 0.02 # 估算协方差 KAL_G = KAL_G2 = 0.0 # 卡尔曼增益 KAL_Q = KAL_Q2 = 0.70 # 过程噪声协方差 KAL_R = KAL_R2 = 200 # 测量噪声协方差 KAL_Output = KAL_Output2 = 0.0 # 卡尔曼滤波器输出 # 中间变量 angle_1 = speed_1 = counts = motor1 = motor2 = 0 angle_2 = speed_2 =0 distance = id_number = 0 # 距离和ID号 Filter_data = [0, 0, 0] # 滤波数据 PI = 3.14159265358 # 圆周率 last_yaw = 0 # 上次偏航角 max_gyro_x = 0 # 最大角速度X # 无线数据传输缓冲区 data_flag = wireless.data_analysis() data_wave = [0]*8 # 类定义 class anjian: """按键处理类""" def __init__(self, KEY_1, KEY_2, KEY_3, KEY_4): self.One = KEY_1 self.Two = KEY_2 self.Three = KEY_3 self.Four = KEY_4 class bianmaqi: """编码器类""" def __init__(self, KAL_templ_pluse, KAL_tempr_pluse): self.KAL_templ_pluse = KAL_templ_pluse self.KAL_tempr_pluse = KAL_tempr_pluse class Imu_element: """IMU数据类""" def __init__(self, acc_x, acc_y, acc_z, gyro_x, gyro_y, gyro_z, Pitch, Roll, Yaw, X, Y, Z, Total_Yaw): self.acc_x = acc_x self.acc_y = acc_y self.acc_z = acc_z self.gyro_x = gyro_x self.gyro_y = gyro_y self.gyro_z = gyro_z self.Pitch = Pitch self.Roll = Roll self.Yaw = Yaw self.X = X self.Y = Y self.Z = Z self.Total_Yaw = Total_Yaw class param: """PID参数类""" def __init__(self, param_Kp, param_Ki): self.param_Kp = param_Kp self.param_Ki = param_Ki class QInfo: """四元数信息类""" def __init__(self): self.q0 = 1.0 self.q1 = 0.0 self.q2 = 0.0 self.q3 = 0.0 # 实例化类 KEY = anjian(0, 0, 0, 0) Encoders = bianmaqi(0, 0) Imu = Imu_element(0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00) Param = param(15.5, 0.006) Q_info = QInfo() # 函数定义 def limit(value, min_value, max_value): """限制数值范围""" return min(max(value, min_value), max_value) def calculate_pid(err, err_sum, err_last, med, value, kp, ki, kd): """计算PID控制值""" err = med - value err_sum += err err_x = err - err_last pwm = kp * err + ki * err_sum + kd * err_x err_last = err return pwm def pid_position_1(med, value, kp, ki, kd): """PID控制器1""" global err_1, err_sum_1, err_last_1 pwm_1 = calculate_pid(err_1, err_sum_1, err_last_1, med, value, kp, ki, kd) err_last_1 = err_1 return pwm_1 def pid_position_2(med, value, kp, ki, kd): """PID控制器2""" global err_2, err_sum_2, err_last_2 pwm_2 = calculate_pid(err_2, err_sum_2, err_last_2, med, value, kp, ki, kd) err_last_2 = err_2 return pwm_2 def pid_position_3(med, value, kp, ki, kd): """PID控制器3""" global err_3, err_sum_3, err_last_3 pwm_3 = calculate_pid(err_3, err_sum_3, err_last_3, med, value, kp, ki, kd) err_last_3 = err_3 return pwm_3 def pid_turn(med, value, kp, ki, kd): """转向PID控制器""" global errt, errt_sum, errt_last pwmt = calculate_pid(errt, errt_sum, errt_last, med, value, kp, ki, kd) errt_last = errt return pwmt def Key(): global param_index, param_editing, pid_params, angle_kp, angle_ki, angle_kd global roll_angle_Kp, roll_angle_Ki, roll_angle_Kd, speed_Kp, speed_Ki key_data = key.get() beep_triggered = False # 标记是否已经触发蜂鸣器 if param_editing: if key_data[0]: # 参数增加 pid_params[param_index] += PARAM_STEP[param_index] print("Key1") beep_triggered = True if key_data[1]: # 参数减少 pid_params[param_index] -= PARAM_STEP[param_index] print("Key2") beep_triggered = True if key_data[2]: # 切换参数 param_index = (param_index + 1) % len(PARAM_MENU) print("Key3") beep_triggered = True if param_index == 10: # 到达"启动!"选项 param_index = 0 if key_data[3]: # 确认参数并控制电机启动的停止 print("Key4:",param_editing) # 更新实际PID参数 angle_kp, angle_ki, angle_kd = pid_params[0], pid_params[1], pid_params[2] roll_angle_Kp = pid_params[3] roll_angle_Ki = pid_params[4] roll_angle_Kd = pid_params[5] speed_Kp, speed_Ki,speed_Kd= pid_params[6], pid_params[7],pid_params[8] med_roll_angle = pid_params[9] param_editing = not param_editing beep_triggered = True # 如果有按键触发,则蜂鸣器提示 if beep_triggered: beep.on() # 蜂鸣器提示 time.sleep_ms(200) beep.off() def invSqrt(x): """快速平方根倒数""" return 1.0 # 简化实现,实际使用时需要优化 def Imu963(): """IMU数据处理函数""" global imu_data, max_gyro_x, last_yaw alpha = 0.3 # 滤波系数 # 数据有效性检查 for i in range(3, 6): if abs(imu_data[i]) < 30 or abs(imu_data[i]) > 30000: imu_data[i] = 0 # 原始数据处理 Imu.X = int(imu_data[3] / 16.4) Imu.Y = int(imu_data[4] / 16.4) Imu.Z = int(imu_data[5] / 16.4) # 角速度处理 Imu.gyro_x = round((float(imu_data[3]) - Filter_data[0]), 3) * PI / 180 / 16.4 Imu.gyro_y = round((float(imu_data[4]) - Filter_data[1]), 3) * PI / 180 / 16.4 Imu.gyro_z = round((float(imu_data[5]) - Filter_data[2]), 3) * PI / 180 / 14.4 # 加速度滤波处理 Imu.acc_x = round(((float(imu_data[0]) * alpha) / 4096 + Imu.acc_x * (1 - alpha)), 3) Imu.acc_y = round(((float(imu_data[1]) * alpha) / 4096 + Imu.acc_y * (1 - alpha)), 3) Imu.acc_z = round(((float(imu_data[2]) * alpha) / 4096 + Imu.acc_z * (1 - alpha)), 3) # 更新AHRS姿态 IMU_AHRSupdate(Imu.gyro_x, Imu.gyro_y, Imu.gyro_z, Imu.acc_x, Imu.acc_y, Imu.acc_z) # 记录最大角速度 if abs(max_gyro_x) < abs(Imu.Pitch): max_gyro_x = Imu.Pitch def Imu963ra_Init(): """IMU初始化校准""" global Filter_data, imu_data Filter_data = [0, 0, 0] # 采集1000次数据进行平均校准 for i in range(1000): imu_data = imu.get() Filter_data[0] += imu_data[3] Filter_data[1] += imu_data[4] Filter_data[2] += imu_data[5] time.sleep_ms(1) # 计算平均值 Filter_data[0] = float(Filter_data[0] / 1000) Filter_data[1] = float(Filter_data[1] / 1000) Filter_data[2] = float(Filter_data[2] / 1000) def IMU_AHRSupdate(gx, gy, gz, ax, ay, az): """AHRS姿态更新算法""" global I_ex, I_ey, I_ez, last_yaw halfT = 0.5 * delta_T # 计算当前重力单位向量 q0q0 = Q_info.q0 * Q_info.q0 q0q1 = Q_info.q0 * Q_info.q1 q0q2 = Q_info.q0 * Q_info.q2 q1q1 = Q_info.q1 * Q_info.q1 q1q3 = Q_info.q1 * Q_info.q3 q2q2 = Q_info.q2 * Q_info.q2 q2q3 = Q_info.q2 * Q_info.q3 q3q3 = Q_info.q3 * Q_info.q3 # 加速度归一化 norm = invSqrt(ax*ax + ay*ay + az*az) ax *= norm ay *= norm az *= norm # 计算当前重力单位向量 vx = 2 * (q1q3 - q0q2) vy = 2 * (q0q1 + q2q3) vz = q0q0 - q1q1 - q2q2 + q3q3 # 计算误差 ex = ay * vz - az * vy ey = az * vx - ax * vz ez = ax * vy - ay * vx # PI修正 I_ex += delta_T * ex I_ey += delta_T * ey I_ez += delta_T * ez gx += Param.param_Kp * ex + Param.param_Ki * I_ex gy += Param.param_Kp * ey + Param.param_Ki * I_ey gz += Param.param_Kp * ez + Param.param_Ki * I_ez # 四元数微分方程 q0 = Q_info.q0 q1 = Q_info.q1 q2 = Q_info.q2 q3 = Q_info.q3 Q_info.q0 = q0 + (-q1*gx - q2*gy - q3*gz) * halfT Q_info.q1 = q1 + (q0*gx + q2*gz - q3*gy) * halfT Q_info.q2 = q2 + (q0*gy - q1*gz + q3*gx) * halfT Q_info.q3 = q3 + (q0*gz + q1*gy - q2*gx) * halfT # 四元数归一化 norm = invSqrt(Q_info.q0**2 + Q_info.q1**2 + Q_info.q2**2 + Q_info.q3**2) Q_info.q0 *= norm Q_info.q1 *= norm Q_info.q2 *= norm Q_info.q3 *= norm # 计算欧拉角 value1 = limit(-2 * Q_info.q1 * Q_info.q3 + 2 * Q_info.q0 * Q_info.q2, -1, 1) Imu.Roll = round(math.asin(value1) * 180 / math.pi, 3) # 横滚角 Imu.Pitch = round(math.atan2(2 * Q_info.q2 * Q_info.q3 + 2 * Q_info.q0 * Q_info.q1, -2 * Q_info.q1**2 - 2 * Q_info.q2**2 + 1) * 180 / math.pi, 3) # 俯仰角 Imu.Yaw = round(math.atan2(2 * Q_info.q1 * Q_info.q2 + 2 * Q_info.q0 * Q_info.q3, -2 * Q_info.q2**2 - 2 * Q_info.q3**2 + 1) * 180 / math.pi, 3) # 偏航角 # 计算总偏航角 error_yaw = Imu.Yaw - last_yaw if error_yaw < -360: error_yaw += 360 if error_yaw > 360: error_yaw -= 360 Imu.Total_Yaw += error_yaw last_yaw = Imu.Yaw # 保证总偏航角在0-360度范围内 if Imu.Total_Yaw > 360: Imu.Total_Yaw -= 360 if Imu.Total_Yaw < 0: Imu.Total_Yaw += 360 def Wireless(): """无线数据传输处理""" global data_flag, data_wave data_flag = wireless.data_analysis() # 数据解析 # 更新虚拟示波器数据 data_wave[0] = angle_1 data_wave[1] = speed_1 data_wave[2] = Imu.acc_z data_wave[3] = Imu.gyro_x data_wave[4] = Imu.gyro_y data_wave[5] = Imu.Roll data_wave[6] = Imu.Pitch # 发送数据到上位机 wireless.send_oscilloscope(*data_wave) def KalmanFilter(input): """卡尔曼滤波器""" global KAL_P, KAL_G, KAL_Output KAL_P = KAL_P + KAL_Q # 估算协方差更新 KAL_G = KAL_P / (KAL_P + KAL_R) # 计算卡尔曼增益 KAL_Output = KAL_Output + KAL_G * (input - KAL_Output) # 更新最优估计值 KAL_P = (1 - KAL_G) * KAL_P # 更新协方差 return KAL_Output def KalmanFilter2(input): """第二个卡尔曼滤波器""" global KAL_P2, KAL_G2, KAL_Output2 KAL_P2 = KAL_P2 + KAL_Q2 KAL_G2 = KAL_P2 / (KAL_P2 + KAL_R2) KAL_Output2 = KAL_Output2 + KAL_G2 * (input - KAL_Output2) KAL_P2 = (1 - KAL_G2) * KAL_P2 return KAL_Output2 def draw_rect(x, y, width, height, color, thick=1, fill=False): """用line方法实现矩形绘制""" if fill: # 填充矩形 - 用垂直线条填充 for i in range(height): lcd.line(x, y+i, x+width-1, y+i, color=color, thick=thick) else: # 只画边框 lcd.line(x, y, x+width-1, y, color=color, thick=thick) # 上边 lcd.line(x, y+height-1, x+width-1, y+height-1, color=color, thick=thick) # 下边 lcd.line(x, y, x, y+height-1, color=color, thick=thick) # 左边 lcd.line(x+width-1, y, x+width-1, y+height-1, color=color, thick=thick) # 右边 last_title = None last_param_value = None last_param_index = None def ips200_display(): """美化后的LCD显示函数""" global last_title,last_motors global last_param_value, last_param_index #1. ---- 顶部状态栏 ---- if last_title is None: draw_rect(0, 0, 320, 20, 0x0000, fill=True) # 黑色背景 title = " RT1021 Dashboard " colors = [0xF800, 0xFC00, 0xFFE0, 0x07E0, 0x07FF, 0x001F, 0x801F, 0xF81F] # 彩虹色 # 阴影效果(偏移1像素) for i, char in enumerate(title): x_pos = 40 + i * 8 lcd.str16(x_pos + 1, 2 + 1, char, 0x3186) # 灰色阴影 for i, char in enumerate(title): x_pos = 40 + i * 8 color = colors[i % len(colors)] lcd.str16(x_pos, 2, char, color) last_title = True # 2. IMU数据区域 (带边框) draw_rect(0, 20, 105, 65, 0x001F, thick=1) # 蓝色边框 lcd.str16(15, 20, "Attitude", 0x001F) # 蓝色标题 lcd.str16(5, 40, f"Pitch:{Imu.Pitch:6.2f}", 0x0000) # 黑色数据 lcd.str16(5, 60, f"Yaw:{Imu.Yaw:6.2f}", 0x0000) # 3. 运动控制区域 (带边框) draw_rect(105, 20, 135, 65, 0xF800, thick=1) # 红色边框 lcd.str16(127, 20, "Motion Ctrl", 0xF800) lcd.str16(117, 40, f"Speed: {med_speed:4.1f}", 0x0000) lcd.str16(117, 60, f"Turn: {turn:6.3f}", 0x0000) # 4. 电机信息区域 (带边框) draw_rect(0, 85, 310, 75, 0x0000, thick=1) # 黑色边框 lcd.str16(5, 110, "Motor", 0x0000) lcd.str16(5, 126, "Output", 0x0000) # 5. 陀螺仪数据区域 (带边框) draw_rect(0, 160, 250, 50, 0xFC80, thick=1) # 橙色边框 lcd.str16(5, 165, "Gyroscope", 0xFC80) lcd.str16(5, 185, f"X:{Imu.gyro_x:7.3f} Y:{Imu.gyro_y:7.3f} Z:{Imu.gyro_z:7.3f}", 0x0000) # 6. 编码器数据区域 (带边框) draw_rect(0, 210, 250, 40, 0x79FF, thick=1) # 天蓝色边框 lcd.str16(5, 220, f"Encoder L:{Encoders.KAL_templ_pluse:6.1f} R:{Encoders.KAL_tempr_pluse:6.1f}", 0x0000) # 7. CCD数据区域 (带边框) draw_rect(0, 250, 250, 70, 0xAA55, thick=1) # 紫色边框 lcd.str16(5, 255, "CCD Line Position", 0xAA55) lcd.str16(5, 275, f"L1:{lline:3d} R1:{rline:3d} Mid:{zhong:3d}", 0x0000) lcd.str16(5, 295, f"L2:{lline2:3d} R2:{rline2:3d} Mid2:{zhong2:3d}", 0x0000) # 8. 道路识别信息 lcd.str16(5, 325, f"Road Type: {road:.2f}", 0x0000) # 9. 参数编辑模式特殊显示 lcd.str16(60, 90, PARAM_MENU[param_index], 0xFFFF) # 白色标题 # 操作提示可以只绘制一次 if last_param_index is None: draw_rect(60, 135, 190, 20, 0xFFFF, thick=1, fill=True) lcd.str16(65, 137, "1:+ 2:- 3:Next 4:OK", 0x0000) # 参数值显示 (带背景色) if pid_params[param_index] != last_param_value or param_index != last_param_index: color = 0x07E0 if pid_params[param_index] >=0 else 0xF800 #绿/红区分正负 draw_rect(60, 110, 190, 20, color, thick=1, fill=True) lcd.str16(65, 112, f"Value: {pid_params[param_index]:.4f}", 0x0000) last_param_value = pid_params[param_index] last_param_index = param_index class ALL_CCD: def __init__(self): self.num=0 self.dir = 0 self.dir_0 = 0 self.left = 38 self.right = 82 self.left_0 = 43 self.right_0 = 78 self.lode_wid = 44 self.lode_wid_0 = 35 self.mid = 60 self.mid_line = 60 self.mid_line_0 = 60 self.last_angle = 62 self.last_angle_1 = 62 def CCD1(self,ccd_data): global av_dir0,a8 #计算左右平均像素值 value_av = (max(ccd_data)+min(ccd_data))//2 #二次处理 ccd_data = [250 if num > value_av else 0 for num in ccd_data] #计算左右平均像素值 av_l = int(sum(ccd_data[15: 64])/49) av_r = int(sum(ccd_data[64:113])/49) if av_l>200 and av_r>200: a8=8 else: a8=0 print("ccd1",av_l,av_r) for i in range(127): if ccd_data[i]==0 and ccd_data[i] != ccd_data[i-1] and ccd_data[i] != ccd_data[i+1]: ccd_data[i] = 250 elif ccd_data[i]==250 and ccd_data[i] != ccd_data[i-1] and ccd_data[i] != ccd_data[i+1]: ccd_data[i] =0 ccd_dataA = ccd_data #差比和 new_data = [int(abs(ccd_dataA[i]-ccd_dataA[i+1]))for i in range(len(ccd_dataA)-1)]#127 result_l = [self.mid_line - i for i,x in enumerate(new_data[self.mid_line:15+a8:-1]) if x > 0] result_r = [i + self.mid_line for i,x in enumerate(new_data[self.mid_line:len(new_data)-15-a8]) if x >0] #print("1bianjie",result_l,result_r) #判断边界 if len(result_l)>0 and len(result_r)>0: self.dir = 0 av_dir0 = 0 self.left = result_l[0] self.right = result_r[0] if abs(self.left - self.right) > 92: self.mid_line = 62 self.dir = 1 av_dir0 = 404 #边界二次处理 if abs(self.left - self.right) - self.lode_wid > 15: if abs(self.left - self.mid) > abs(self.right- self.mid): self.left = self.right - self.lode_wid elif abs(self.left - self.mid) < abs(self.right- self.mid): self.right = self.left + self.lode_wid elif len(result_l)== 0 and len(result_r)>0 and result_r[0] < 108: if av_r < av_l and av_l>=240 and av_r<180: av_dir0 = 11 else: av_dir0 = 1 if av_r < av_l: self.right = result_r[0] self.left = self.right - self.lode_wid else: self.left = result_r[0] self.right = self.left + self.lode_wid elif len(result_l)>0 and len(result_r)==0 and result_l[0] > 20: if av_r > av_l and av_r>=240 and av_l<180: av_dir0 = 22 else: av_dir0 = 2 if av_r < av_l: self.right = result_l[0] self.left = self.right - self.lode_wid else: self.left = result_l[0] self.right = self.left + self.lode_wid else: self.mid_line = 62 self.dir = 1 av_dir0 = 404 track_info = (self.left + self.right)//2# 计算赛道中心线位置 if abs(track_info - self.mid_line) > 30: track_info = self.mid_line self.dir = 1 self.last_angle = track_info self.mid_line = track_info if self.dir == 1: if abs(track_info-self.last_angle)<4: self.dir =0 else: return self.last_angle else: return track_info def CCD2(self,ccd_data): global av_dir1,a8,speed_l,speed_r,mortor_dir self.num+=1 count=0 value_av = (max(ccd_data)+min(ccd_data))//2 #二次处理 ccd_data = [250 if num > value_av else 0 for num in ccd_data] zebra_crossing = [i for i in range(127) if (ccd_data[i] == 0 and ccd_data[i + 1] == 250) or (ccd_data[i] == 250 and ccd_data[i + 1] == 0)] for i in range(len(zebra_crossing) - 1): if abs(zebra_crossing[i] - zebra_crossing[i + 1]) < 5: count += 1 if count >= 10 and self.num>250 and -25<int(imu_z)<25: speed_l=0 speed_r=0 mortor_dir=2 self.num=0 #计算左右平均像素值 av_l = int(sum(ccd_data[15:64])/49) av_r = int(sum(ccd_data[64:113])/49) if av_l>200 and av_r>200: a8=8 else: a8=0 #print("ccd2",av_l,av_r) for i in range(127): if ccd_data[i]==0 and ccd_data[i] != ccd_data[i-1] and ccd_data[i] != ccd_data[i+1]: ccd_data[i] = 250 elif ccd_data[i]==250 and ccd_data[i] != ccd_data[i-1] and ccd_data[i] != ccd_data[i+1]: ccd_data[i] =0 ccd_dataA = ccd_data #差比和 new_data = [int(abs(ccd_dataA[i]-ccd_dataA[i+1]))for i in range(len(ccd_dataA)-1)]#127 result_l = [self.mid_line_0 - i for i,x in enumerate(new_data[self.mid_line_0:15+a8:-1]) if x > 0] result_r = [i + self.mid_line_0 for i,x in enumerate(new_data[self.mid_line_0:len(new_data)-15-a8]) if x >0] #print("2bianjie",result_l,result_r) #判断边界 if len(result_l)>0 and len(result_r)>0: av_dir1 = 0 self.dir_0 = 0 self.left_0 = result_l[0] self.right_0 = result_r[0] if abs(self.left_0 - self.right_0) > 92: self.mid_line_0 = 62 av_dir1 = 404 self.dir_0 = 1 #边界二次处理 if abs(self.left_0 - self.right_0) - self.lode_wid_0 > 15: if abs(self.left_0 - self.mid) > abs(self.right_0- self.mid): self.left_0 = self.right_0 - self.lode_wid_0 elif abs(self.left_0 - self.mid) < abs(self.right_0- self.mid): self.right_0 = self.left_0 + self.lode_wid_0 elif len(result_l)== 0 and len(result_r)>0 and result_r[0] < 108: if av_r < av_l and av_l>=240 and av_r<180: av_dir1 = 11 else: av_dir1 = 1 if av_r < av_l: self.right_0 = result_r[0] self.left_0 = self.right_0 - self.lode_wid_0 else: self.left_0 = result_r[0] self.right_0 = self.left_0 + self.lode_wid_0 elif len(result_l)>0 and len(result_r)==0 and result_l[0] > 20: if av_r > av_l and av_r>=240 and av_l<180: av_dir1 = 22 else: av_dir1 = 2 if av_r < av_l: self.right_0 = result_l[0] self.left_0 = self.right_0 - self.lode_wid_0 else: self.left_0 = result_l[0] self.right_0 = self.left_0 + self.lode_wid_0 else: self.mid_line_0 = 62 av_dir1 = 404 self.dir_0 = 1 track_info = (self.left_0 + self.right_0)//2# 计算赛道中心线位置 if abs(track_info - self.mid_line_0) > 30 : track_info = self.mid_line_0 self.dir_0 = 1 self.last_angle_1 = track_info self.mid_line_0 = track_info if self.dir_0 == 1: if abs(track_info-self.last_angle_1)<4: self.dir_0 =0 else: return self.last_angle_1 else: return track_info def control_turn(zhong1_2): """转向控制函数""" if zhong1_2 is None: # 如果输入无效,使用默认值(居中) zhong1_2 = 64 errt = (zhong1_2 - 63) turn_kp = a * abs(errt) + Kpc turn = pid_turn(63, zhong1_2, turn_kp, turn_ki, turn_kd) + Imu.gyro_z * turn_kd2 return turn def angle_speed1(med_gyro, cur_gyro): """角速度控制函数""" motor = pid_position_1(med_gyro, cur_gyro, angle_kp, angle_ki, angle_kd) return limit(motor, -4000, 4000) def angle(med_roll_angle, cur_roll_angle): """角度控制函数""" global angle_1 angle_1 = pid_position_2(med_roll_angle, cur_roll_angle, roll_angle_Kp, roll_angle_Ki, roll_angle_Kd) return angle_1 def speed(med_speed, cur_speed): """速度控制函数""" global speed_1 speed_1 = pid_position_3(med_speed, cur_speed, speed_Kp, speed_Ki, speed_Kd) return speed_1 def parse_data(): """解析串口数据""" if uart2.any(): line = uart2.readline() # 读取一行数据 if line is None: return None, None try: line_str = line.decode('utf-8').strip() # 转换为字符串 parts = line_str.split(',') # 按逗号分割 if len(parts) >= 2: var1 = float(parts[0]) # 第一个参数为浮点数 var2 = int(parts[1]) # 第二个参数为整数 return var1, var2 except (UnicodeDecodeError, ValueError): pass return None, None # 定时器中断处理函数 def time_pit_handler(time): """定时器1中断处理""" global ticker_flag, ticker_count, speed_1, angle_1, motor1, motor2 ticker_flag = True ticker_count = (ticker_count + 1) if (ticker_count < 10) else 1 # 1ms周期任务 if ticker_count % 1 == 0: Imu963() # 更新IMU数据 # 电机控制 motor1 = angle_speed1(angle_1, Imu.gyro_x) motor2 = angle_speed1(angle_1, Imu.gyro_x) motor1 = limit(motor1 + control_turn(CCD_dir[0]), -4000, 4000) motor2 = limit(motor2 - control_turn(CCD_dir[0]), -4000, 4000) if not param_editing: motor_l.duty(-motor1) motor_r.duty(-motor2) else: motor_l.duty(0) motor_r.duty(0) # 5ms周期任务 if ticker_count % 5 == 0: angle_1 = angle(med_roll_angle - speed_1, Imu.Pitch) # 10ms周期任务 if ticker_count % 10 == 0: speed_1 = speed(med_speed, Encoders.KAL_templ_pluse) speed_2 = speed(med_speed, Encoders.KAL_tempr_pluse) #print("左误差 ={:>6f}, 右误差 ={:>6f}\r\n".format(speed_1,speed_2)) def time_pit2_handler(time): """定时器2中断处理""" global ticker_flag2 ticker_flag2 = True Encoders.KAL_templ_pluse = KalmanFilter(encoder_l.get()) # 左编码器滤波 Encoders.KAL_tempr_pluse = KalmanFilter2(encoder_r.get()) # 右编码器滤波 CCD = ALL_CCD()#实例化 # 初始化定时器 pit1 = ticker(1) pit2 = ticker(2) pit3 = ticker(3) # 关联数据采集 pit1.capture_list(imu) pit2.capture_list(ccd) pit3.capture_list(encoder_l, encoder_r,key) # 设置定时器回调函数 pit1.callback(time_pit_handler) pit2.callback(time_pit2_handler) # 初始化IMU Imu963ra_Init() # 启动定时器 pit1.start(1) # 1ms周期 pit2.start(6) # 6ms周期 pit3.start(10) # 10ms周期 # 主循环 while True: Key() # 处理按键输入 if ticker_flag: ccd_data1 = ccd.get(0) ccd_data2 = ccd.get(1) CCD_dir[0] =CCD.CCD1(ccd_data1) CCD_dir[1] =CCD.CCD2(ccd_data2) # print(ccd_data1) # print(ccd_data2) if CCD_dir[0] is None: zhong2 = 404 else : zhong2=CCD_dir[0] # print(ccd_data1) # print("CCD1:",CCD_dir[0]) # ticker_flag = False #电机启动屏幕关闭刷新 if not param_editing and last_title: lcd.str16(165, 90, "RUN>>>", 0x0000) last_title = None #与上相反 if param_editing: ips200_display() # 检查拨码开关状态 if switch2.value() != state2: print("Test program stop.") break # 垃圾回收 gc.collect() 小车运行代码,不根据轨道走,蛇形走,我应该如何调参
最新发布
06-11
import os import datetime import argparse import torch import torch.nn as nn import torch.nn.functional as F import torchvision from torchvision.models.feature_extraction import create_feature_extractor import copy from collections import OrderedDict import transforms from network_files import FasterRCNN, AnchorsGenerator from my_dataset import VOCDataSet from train_utils import GroupedBatchSampler, create_aspect_ratio_groups from train_utils import train_eval_utils as utils # ---------------------------- ECA注意力模块 ---------------------------- class ECAAttention(nn.Module): def __init__(self, channels, kernel_size=3): super(ECAAttention, self).__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.conv = nn.Conv1d(1, 1, kernel_size=kernel_size, padding=(kernel_size - 1) // 2, bias=False) self.sigmoid = nn.Sigmoid() def forward(self, x): # x: [B, C, H, W] b, c, h, w = x.size() # 全局平均池化 y = self.avg_pool(x).view(b, c, 1) # 一维卷积实现跨通道交互 y = self.conv(y.transpose(1, 2)).transpose(1, 2).view(b, c, 1, 1) # 生成注意力权重 y = self.sigmoid(y) return x * y.expand_as(x) # ---------------------------- 特征融合模块 ---------------------------- class FeatureFusionModule(nn.Module): def __init__(self, student_channels, teacher_channels): super().__init__() # 1x1卷积用于通道对齐 self.teacher_proj = nn.Conv2d(teacher_channels, student_channels, kernel_size=1) # 注意力机制 self.attention = nn.Sequential( nn.Conv2d(student_channels * 2, student_channels // 8, kernel_size=1), nn.ReLU(), nn.Conv2d(student_channels // 8, 2, kernel_size=1), nn.Softmax(dim=1) ) # ECA注意力模块 self.eca = ECAAttention(student_channels, kernel_size=3) def forward(self, student_feat, teacher_feat): # 调整教师特征的空间尺寸以匹配学生特征 if student_feat.shape[2:] != teacher_feat.shape[2:]: teacher_feat = F.interpolate(teacher_feat, size=student_feat.shape[2:], mode='bilinear', align_corners=False) # 通道投影 teacher_feat_proj = self.teacher_proj(teacher_feat) # 特征拼接 concat_feat = torch.cat([student_feat, teacher_feat_proj], dim=1) # 计算注意力权重 attn_weights = self.attention(concat_feat) # 加权融合 fused_feat = attn_weights[:, 0:1, :, :] * student_feat + attn_weights[:, 1:2, :, :] * teacher_feat_proj # 应用ECA注意力 fused_feat = self.eca(fused_feat) return fused_feat # ---------------------------- 简化版FPN实现 ---------------------------- class SimpleFPN(nn.Module): def __init__(self, in_channels_list, out_channels): super().__init__() self.inner_blocks = nn.ModuleList() self.layer_blocks = nn.ModuleList() # FPN输出层的ECA模块 self.eca_blocks = nn.ModuleList() # 为每个输入特征创建内部卷积和输出卷积 for in_channels in in_channels_list: inner_block = nn.Conv2d(in_channels, out_channels, 1) layer_block = nn.Conv2d(out_channels, out_channels, 3, padding=1) self.inner_blocks.append(inner_block) self.layer_blocks.append(layer_block) # 为每个FPN输出添加ECA模块 self.eca_blocks.append(ECAAttention(out_channels, kernel_size=3)) # 初始化权重 for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_uniform_(m.weight, a=1) if m.bias is not None: nn.init.constant_(m.bias, 0) def forward(self, x): # 假设x是一个有序的字典,包含'c2', 'c3', 'c4', 'c5'特征 c2, c3, c4, c5 = x['c2'], x['c3'], x['c4'], x['c5'] # 处理最顶层特征 last_inner = self.inner_blocks[3](c5) results = [] # 处理P5 p5 = self.layer_blocks[3](last_inner) p5 = self.eca_blocks[3](p5) # 应用ECA注意力 results.append(p5) # 自顶向下路径 for i in range(2, -1, -1): inner_lateral = self.inner_blocks[i](x[f'c{i + 2}']) feat_shape = inner_lateral.shape[-2:] last_inner = F.interpolate(last_inner, size=feat_shape, mode="nearest") last_inner = last_inner + inner_lateral # 应用卷积和ECA注意力 layer_out = self.layer_blocks[i](last_inner) layer_out = self.eca_blocks[i](layer_out) # 应用ECA注意力 results.insert(0, layer_out) # 返回有序的特征字典 return { 'p2': results[0], 'p3': results[1], 'p4': results[2], 'p5': results[3] } # ---------------------------- 通道剪枝器类 ---------------------------- class ChannelPruner: def __init__(self, model, input_size, device='cpu'): self.model = model self.input_size = input_size # (B, C, H, W) self.device = device self.model.to(device) self.prunable_layers = self._identify_prunable_layers() # 识别可剪枝层 self.channel_importance = {} # 存储各层通道重要性 self.mask = {} # 剪枝掩码 self.reset() def reset(self): """重置剪枝状态""" self.pruned_model = copy.deepcopy(self.model) self.pruned_model.to(self.device) for name, module in self.prunable_layers.items(): self.mask[name] = torch.ones(module.out_channels, dtype=torch.bool, device=self.device) def _identify_prunable_layers(self): """识别可剪枝的卷积层(优先中间层,如layer2、layer3)""" prunable_layers = OrderedDict() for name, module in self.model.named_modules(): # 主干网络层(layer1-layer4) if "backbone.backbone." in name: # layer1(底层,少剪或不剪) if "layer1" in name: if isinstance(module, nn.Conv2d) and module.out_channels > 1: prunable_layers[name] = module # layer2和layer3(中间层,优先剪枝) elif "layer2" in name or "layer3" in name: if isinstance(module, nn.Conv2d) and module.out_channels > 1: prunable_layers[name] = module # layer4(高层,少剪) elif "layer4" in name: if isinstance(module, nn.Conv2d) and module.out_channels > 1: prunable_layers[name] = module # FPN层(inner_blocks和layer_blocks) elif "fpn.inner_blocks" in name or "fpn.layer_blocks" in name: if isinstance(module, nn.Conv2d) and module.out_channels > 1: prunable_layers[name] = module # FeatureFusionModule的teacher_proj层 elif "feature_fusion." in name and "teacher_proj" in name: if isinstance(module, nn.Conv2d) and module.out_channels > 1: prunable_layers[name] = module return prunable_layers def compute_channel_importance(self, dataloader=None, num_batches=10): """基于激活值计算通道重要性""" self.pruned_model.eval() for name in self.prunable_layers.keys(): self.channel_importance[name] = torch.zeros( self.prunable_layers[name].out_channels, device=self.device) with torch.no_grad(): if dataloader is None: # 使用随机数据计算 for _ in range(num_batches): inputs = torch.randn(self.input_size, device=self.device) self._forward_once([inputs]) # 包装为列表以匹配数据加载器格式 else: # 使用验证集计算 for inputs, _ in dataloader: if num_batches <= 0: break # 将图像列表移至设备 inputs = [img.to(self.device) for img in inputs] self._forward_once(inputs) num_batches -= 1 def _forward_once(self, inputs): """前向传播并记录各层激活值""" def hook(module, input, output, name): # 计算通道重要性(绝对值均值) channel_impact = torch.mean(torch.abs(output), dim=(0, 2, 3)) self.channel_importance[name] += channel_impact hooks = [] for name, module in self.pruned_model.named_modules(): if name in self.prunable_layers: hooks.append(module.register_forward_hook(lambda m, i, o, n=name: hook(m, i, o, n))) # 修改这里以正确处理目标检测模型的输入格式 self.pruned_model(inputs) for hook in hooks: hook.remove() def prune_channels(self, layer_prune_ratios): """按层剪枝(支持不同层不同比例)""" for name, ratio in layer_prune_ratios.items(): if name not in self.prunable_layers: continue module = self.prunable_layers[name] num_channels = module.out_channels num_prune = int(num_channels * ratio) if num_prune <= 0: continue # 获取最不重要的通道索引(按重要性从小到大排序) importance = self.channel_importance[name] _, indices = torch.sort(importance) prune_indices = indices[:num_prune] # 更新掩码 self.mask[name][prune_indices] = False def apply_pruning(self): """应用剪枝掩码到模型""" pruned_model = copy.deepcopy(self.model) pruned_model.to(self.device) # 存储每层的输入通道掩码(用于处理非连续层的依赖关系) output_masks = {} # 第一遍:处理所有可剪枝层,记录输出通道掩码 for name, module in pruned_model.named_modules(): if name in self.mask: curr_mask = self.mask[name] # 处理卷积层 if isinstance(module, nn.Conv2d): # 剪枝输出通道 module.weight.data = module.weight.data[curr_mask] if module.bias is not None: module.bias.data = module.bias.data[curr_mask] module.out_channels = curr_mask.sum().item() # 记录该层的输出通道掩码 output_masks[name] = curr_mask print(f"处理层: {name}, 原始输出通道: {len(curr_mask)}, 剪枝后: {curr_mask.sum().item()}") # 处理ECA注意力模块 elif isinstance(module, ECAAttention): # ECA模块不需要修改,因为它不改变通道数 pass # 处理FeatureFusionModule的teacher_proj elif "teacher_proj" in name: # 输入通道来自教师特征,输出通道由curr_mask决定 module.weight.data = module.weight.data[curr_mask] module.out_channels = curr_mask.sum().item() # 记录该层的输出通道掩码 output_masks[name] = curr_mask # 第二遍:处理非剪枝层的输入通道 for name, module in pruned_model.named_modules(): if name in output_masks: # 已在第一遍处理过,跳过 continue # 对于卷积层,查找其输入来源的层的掩码 if isinstance(module, nn.Conv2d): # 尝试查找前一层的输出掩码 prev_mask = None # 简化的查找逻辑,实际情况可能需要更复杂的实现 # 这里假设命名约定能帮助我们找到前一层 for possible_prev_name in reversed(list(output_masks.keys())): if possible_prev_name in name or ( "backbone" in name and "backbone" in possible_prev_name): prev_mask = output_masks[possible_prev_name] break # 应用输入通道掩码 if prev_mask is not None and prev_mask.shape[0] == module.weight.shape[1]: print( f"应用输入掩码到层: {name}, 原始输入通道: {module.weight.shape[1]}, 剪枝后: {prev_mask.sum().item()}") module.weight.data = module.weight.data[:, prev_mask] module.in_channels = prev_mask.sum().item() else: print(f"警告: 无法为层 {name} 找到匹配的输入掩码,保持原始通道数") return pruned_model def evaluate_model(self, dataloader, model=None): """评估模型性能(mAP)""" if model is None: model = self.pruned_model model.eval() # 调用评估函数 coco_info = utils.evaluate(model, dataloader, device=self.device) return coco_info[1] # 返回mAP值 def get_model_size(self, model=None): """获取模型大小(MB)""" if model is None: model = self.pruned_model torch.save(model.state_dict(), "temp.pth") size = os.path.getsize("temp.pth") / (1024 * 1024) os.remove("temp.pth") return size def create_model(num_classes): # ---------------------------- 学生模型定义 ---------------------------- try: # 尝试使用新版本API backbone = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1) except AttributeError: # 旧版本API backbone = torchvision.models.resnet18(pretrained=True) # 提取多个层作为特征融合点 return_nodes = { "layer1": "c2", # 对应FPN的P2 (1/4) "layer2": "c3", # 对应FPN的P3 (1/8) "layer3": "c4", # 对应FPN的P4 (1/16) "layer4": "c5", # 对应FPN的P5 (1/32) } backbone = create_feature_extractor(backbone, return_nodes=return_nodes) # 添加简化版FPN fpn = SimpleFPN([64, 128, 256, 512], 256) # 创建一个包装模块,将backbone和FPN组合在一起 class BackboneWithFPN(nn.Module): def __init__(self, backbone, fpn): super().__init__() self.backbone = backbone self.fpn = fpn self.out_channels = 256 # FPN输出通道数 def forward(self, x): x = self.backbone(x) x = self.fpn(x) return x # 替换原始backbone为带FPN的backbone backbone_with_fpn = BackboneWithFPN(backbone, fpn) # 增加更多anchor尺度和宽高比 anchor_sizes = ((16, 32, 48), (32, 64, 96), (64, 128, 192), (128, 256, 384), (256, 512, 768)) aspect_ratios = ((0.33, 0.5, 1.0, 2.0, 3.0),) * len(anchor_sizes) anchor_generator = AnchorsGenerator( sizes=anchor_sizes, aspect_ratios=aspect_ratios ) roi_pooler = torchvision.ops.MultiScaleRoIAlign( featmap_names=['p2', 'p3', 'p4', 'p5'], output_size=[7, 7], sampling_ratio=2 ) model = FasterRCNN( backbone=backbone_with_fpn, num_classes=num_classes, rpn_anchor_generator=anchor_generator, box_roi_pool=roi_pooler ) # 添加多尺度特征融合模块 model.feature_fusion = nn.ModuleDict({ 'p2': FeatureFusionModule(256, 256), 'p3': FeatureFusionModule(256, 256), 'p4': FeatureFusionModule(256, 256), 'p5': FeatureFusionModule(256, 256), }) return model def main(args): # 确保输出目录存在 if args.output_dir: os.makedirs(args.output_dir, exist_ok=True) # ---------------------------- 模型剪枝流程 ---------------------------- print("开始模型剪枝...") device = torch.device(args.device if torch.cuda.is_available() else "cpu") # 加载已训练的模型 model = create_model(num_classes=args.num_classes + 1) checkpoint = torch.load(args.resume, map_location=device) model.load_state_dict(checkpoint["model"]) model.to(device) # 输入尺寸(需与训练时一致) input_size = (1, 3, 800, 600) # (B, C, H, W) # 加载验证集 val_dataset = VOCDataSet(args.data_path, "2012", transforms.Compose([transforms.ToTensor()]), "val.txt") val_data_loader = torch.utils.data.DataLoader( val_dataset, batch_size=1, shuffle=False, pin_memory=True, num_workers=4, collate_fn=val_dataset.collate_fn ) # 初始化剪枝器 pruner = ChannelPruner(model, input_size, device=device) # 计算通道重要性 print("计算通道重要性...") pruner.compute_channel_importance(dataloader=val_data_loader, num_batches=10) # 定义分层剪枝比例(中间层多剪,底层和高层少剪) layer_prune_ratios = { # 主干网络 "backbone.backbone.layer1": args.layer1_ratio, # 底层:剪10% "backbone.backbone.layer2": args.layer2_ratio, # 中间层:剪30% "backbone.backbone.layer3": args.layer3_ratio, # 中间层:剪30% "backbone.backbone.layer4": args.layer4_ratio, # 高层:剪10% # FPN层 "fpn.inner_blocks": args.fpn_inner_ratio, # FPN内部卷积:剪20% "fpn.layer_blocks": args.fpn_layer_ratio, # FPN输出卷积:剪20% # FeatureFusionModule的teacher_proj "feature_fusion.p2.teacher_proj": args.ff_p2_ratio, "feature_fusion.p3.teacher_proj": args.ff_p3_ratio, "feature_fusion.p4.teacher_proj": args.ff_p4_ratio, "feature_fusion.p5.teacher_proj": args.ff_p5_ratio, } # 执行剪枝 print("执行通道剪枝...") pruner.prune_channels(layer_prune_ratios) # 应用剪枝并获取新模型 pruned_model = pruner.apply_pruning() # 评估剪枝前后的性能和模型大小 original_size = pruner.get_model_size(model) pruned_size = pruner.get_model_size(pruned_model) original_map = pruner.evaluate_model(val_data_loader, model) pruned_map = pruner.evaluate_model(val_data_loader, pruned_model) print(f"原始模型大小: {original_size:.2f} MB") print(f"剪枝后模型大小: {pruned_size:.2f} MB") print(f"模型压缩率: {100 * (1 - pruned_size / original_size):.2f}%") print(f"原始mAP: {original_map:.4f}, 剪枝后mAP: {pruned_map:.4f}") # 保存剪枝后的模型 pruned_model_path = os.path.join(args.output_dir, f"pruned_resNetFpn_{args.num_classes}classes.pth") torch.save(pruned_model.state_dict(), pruned_model_path) print(f"剪枝后的模型已保存至: {pruned_model_path}") # 对剪枝后的模型进行微调(默认启用) print("准备对剪枝后的模型进行微调...") # 加载训练集 train_dataset = VOCDataSet(args.data_path, "2012", transforms.Compose([ transforms.ToTensor(), transforms.RandomHorizontalFlip(0.5) ]), "train.txt") # 创建训练数据加载器 train_sampler = torch.utils.data.RandomSampler(train_dataset) train_data_loader = torch.utils.data.DataLoader( train_dataset, batch_size=args.batch_size, sampler=train_sampler, num_workers=4, collate_fn=train_dataset.collate_fn ) # 定义优化器 params = [p for p in pruned_model.parameters() if p.requires_grad] optimizer = torch.optim.SGD( params, lr=args.lr, momentum=0.9, weight_decay=0.0005 ) # 定义学习率调度器 lr_scheduler = torch.optim.lr_scheduler.StepLR( optimizer, step_size=args.lr_step_size, gamma=args.lr_gamma ) # 微调训练 print(f"开始微调: 批次大小={args.batch_size}, 学习率={args.lr}, 轮数={args.epochs}") best_map = 0.0 for epoch in range(args.epochs): # 训练一个epoch utils.train_one_epoch(pruned_model, optimizer, train_data_loader, device, epoch, print_freq=50) # 更新学习率 lr_scheduler.step() # 评估模型 coco_info = utils.evaluate(pruned_model, val_data_loader, device=device) # 保存当前最佳模型 map_50 = coco_info[1] # COCO评估指标中的IoU=0.50时的mAP if map_50 > best_map: best_map = map_50 torch.save({ 'model': pruned_model.state_dict(), 'optimizer': optimizer.state_dict(), 'lr_scheduler': lr_scheduler.state_dict(), 'epoch': epoch, 'args': args }, os.path.join(args.output_dir, f"finetuned_pruned_best.pth")) print(f"Epoch {epoch + 1}/{args.epochs}, mAP@0.5: {map_50:.4f}") print(f"微调完成,最佳mAP@0.5: {best_map:.4f}") if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) # ---------------------------- 剪枝参数 ---------------------------- parser.add_argument('--device', default='cuda:0', help='device') parser.add_argument('--data-path', default='./', help='dataset') parser.add_argument('--num-classes', default=6, type=int, help='num_classes') parser.add_argument('--output-dir', default='./save_weights', help='path where to save') parser.add_argument('--resume', default='./save_weights/resNetFpn-zuizhong.pth', type=str, help='resume from checkpoint') # 分层剪枝比例参数 parser.add_argument('--layer1-ratio', default=0.1, type=float, help='layer1 pruning ratio') parser.add_argument('--layer2-ratio', default=0.5, type=float, help='layer2 pruning ratio') parser.add_argument('--layer3-ratio', default=0.5, type=float, help='layer3 pruning ratio') parser.add_argument('--layer4-ratio', default=0.1, type=float, help='layer4 pruning ratio') parser.add_argument('--fpn-inner-ratio', default=0.2, type=float, help='FPN inner blocks pruning ratio') parser.add_argument('--fpn-layer-ratio', default=0.2, type=float, help='FPN layer blocks pruning ratio') parser.add_argument('--ff-p2-ratio', default=0.2, type=float, help='Feature fusion P2 pruning ratio') parser.add_argument('--ff-p3-ratio', default=0.2, type=float, help='Feature fusion P3 pruning ratio') parser.add_argument('--ff-p4-ratio', default=0.2, type=float, help='Feature fusion P4 pruning ratio') parser.add_argument('--ff-p5-ratio', default=0.2, type=float, help='Feature fusion P5 pruning ratio') # ---------------------------- 微调参数 ---------------------------- parser.add_argument('--epochs', default=10, type=int, help='number of total epochs to run') parser.add_argument('--batch-size', default=8, type=int, help='batch size') parser.add_argument('--lr', default=0.05, type=float, help='initial learning rate') parser.add_argument('--lr-step-size', default=3, type=int, help='decrease lr every step-size epochs') parser.add_argument('--lr-gamma', default=0.1, type=float, help='decrease lr by a factor of lr-gamma') args = parser.parse_args() main(args)以上代码没有实现成功的剪枝,请你仔细检查并修改,帮助我实现成功的剪枝。
06-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值