资讯动态

OPENMV云台颜色追踪实战:从HSV动态阈值到PID视觉伺服闭环

发布时间:2026/10/20 18:28:55 来源:尧图企业网站定制
简介本资源是一套基于OPENMV视觉模块与STM32微控制器协同实现颜色追踪的完整嵌入式开发实践方案面向嵌入式初学者、智能硬件爱好者及高校电赛/课程设计学生解决目标识别、云台闭环控制与多模块联调等典型工程问题。压缩包共205个文件含36个C源码如stm32f10x_usart.c、stm32f10x_tim.c等底层驱动、36个头文件h、36个编译中间文件o及35个依赖描述crf辅以Keil工程配置uvprojx/uvoptx、Python主控脚本py和调试脚本bat整体6.71MB结构清晰便于理解从图像采集→串口通信→PID角度解算→PWM电机驱动的全链路实现。目前已有405人学习下载资源提供可直接烧录运行的STM32固件框架、OPENMV端颜色阈值标定逻辑、双轴云台运动学映射关系及关键PID参数调优说明显著降低视觉追踪类项目的入门门槛与调试成本。1. OPENMV驱动云台实现颜色追踪不是调个PID就能稳住的“视觉伺服”闭环你手头有一块OPENMV Cam比如H7或M7一个两自由度舵机云台还有一张红球照片——但把球放桌上一动画面里目标就飘出视野舵机咔咔乱转甚至原地抖动。这不是硬件坏了而是你还没真正理解「颜色追踪」在这套系统里到底意味着什么它不是单帧识别开环转动而是一个带延迟补偿的实时视觉伺服闭环。OPENMV本身算力有限、图像处理有固有延时、舵机响应非线性、HSV阈值受光照剧烈扰动——这三重黑匣子叠在一起让90%的初学者卡在“能识别但追不稳”这个玄学阶段。本文面向已烧录固件、能连串口、会改Python脚本的嵌入式视觉入门者不讲OpenCV桌面端仿真不堆理论公式只拆解从“识别到坐标”到“云台平滑跟上”的完整链路HSV动态校准怎么写、PID参数怎么试出来不震荡、为什么必须加死区和限幅、舵机抖动时该盯哪几行日志。所有代码可直接粘贴进OPENMV IDE运行所有参数有物理意义解释所有翻车现场都来自某实验室连续三天的实测记录。2. 硬件连接与OPENMV基础配置先让舵机听懂“角度”指令要让云台动起来第一步不是写追踪逻辑而是确保OPENMV能可靠、低延迟地控制舵机。很多翻车其实发生在底层通信环节比如用软件串口模拟PWM导致占空比漂移或电源共模干扰引发舵机误触发。我们采用最稳妥的硬件方案——利用OPENMV H7的TIM2_CH1PA0和TIM2_CH2PA1两路硬件定时器输出标准50Hz PWM信号直接驱动MG90S或SG90这类微型舵机。2.1 舵机接线与供电隔离注意舵机峰值电流可达500mA以上绝不可与OPENMV共用USB供电必须使用独立的5V/2A开关电源GND与OPENMV共地但VCC严格分离。若共用USB供电舵机启动瞬间电压跌落会导致OPENMV复位表现为串口日志突然中断。接线方式如下以OPENMV H7 Dev Board为例OPENMV引脚连接对象说明PA0 (TIM2_CH1)云台水平舵机信号线黄线控制Yaw轴左右旋转PA1 (TIM2_CH2)云台俯仰舵机信号线黄线控制Pitch轴上下俯仰GND任意电源负极 舵机负极共地是关键但正极必须隔离5V独立电源两个舵机VCC红线禁止接OPENMV的5V引脚2.2 初始化硬件PWM用TimerPWM外设而非bit-bangingOPENMV MicroPython对硬件定时器封装较深需绕过pyb.Servo()这种高阶API直接操作底层寄存器以获得确定性时序。以下代码在boot.py中初始化两路PWM频率固定为50Hz周期20ms占空比映射为0°~180°# boot.py —— 系统启动即加载避免运行时初始化延迟 import pyb from pyb import Timer, Pin # 配置PA0为TIM2_CH1PA1为TIM2_CH2 pin_yaw Pin(P0, Pin.OUT_PP) # PA0 → P0 pin_pitch Pin(P1, Pin.OUT_PP) # PA1 → P1 # 初始化TIM2频率50Hz → 周期20ms20000us tim Timer(2, freq50) # 创建两路PWM通道CH1控制YawCH2控制Pitch ch_yaw tim.channel(1, Timer.PWM, pinpin_yaw) ch_pitch tim.channel(2, Timer.PWM, pinpin_pitch) # 设置初始占空比90°对应1.5ms高电平 → 占空比1.5/200.075 ch_yaw.pulse_width_percent(7.5) # 0°2.5%, 90°7.5%, 180°12.5% ch_pitch.pulse_width_percent(7.5) print(PWM initialized: Yaw90°, Pitch90°)参数说明pulse_width_percent(x)中x是占空比百分比非角度值。标准舵机协议0.5ms→0°2.5%1.5ms→90°7.5%2.5ms→180°12.5%。务必实测你的舵机范围部分廉价舵机实际有效范围仅30°~150°。Timer(2, freq50)强制使用TIM2因H7上只有TIM2/TIM5支持CH1CH2双通道同步输出避免两路PWM相位偏移导致云台扭动。此初始化放在boot.py而非主脚本是因为MicroPython启动后首次调用Timer.channel()存在约120ms延迟放boot.py可压缩到10ms。2.3 验证舵机基础运动写一个手动校准函数在主脚本如main.py中加入手动校准函数用于后续调试时快速定位机械零点# main.py 片段 def set_servo_angle(yaw_deg, pitch_deg): 安全设置舵机角度限制在30°~150°防止打齿带软限幅 yaw_deg: 水平角-90~900为正前方 pitch_deg: 俯仰角-45~450为水平 # 映射到舵机物理范围实测MG90S30°~150°对应0°~180°逻辑角 yaw_mapped max(30, min(150, 90 yaw_deg)) # 逻辑0°物理90°yaw_deg向右转 pitch_mapped max(30, min(150, 90 - pitch_deg)) # 逻辑0°物理90°-pitch_deg向下俯 # 转换为占空比30°→2.5%, 150°→12.5% → 线性插值 yaw_duty 2.5 (yaw_mapped - 30) * (10.0 / 120.0) # 10%跨度对应120°物理行程 pitch_duty 2.5 (pitch_mapped - 30) * (10.0 / 120.0) ch_yaw.pulse_width_percent(yaw_duty) ch_pitch.pulse_width_percent(pitch_duty) pyb.delay(50) # 给舵机响应时间避免连续写入丢帧 # 测试让云台缓慢扫过水平面 for a in range(-60, 61, 5): set_servo_angle(a, 0) pyb.delay(200)关键逻辑说明为什么用90 yaw_deg因为OPENMV默认朝向为0°向右转yaw为正但舵机0°物理位置通常对应云台左极限需做中心偏移校准。max/min限幅是硬性保护超出30°~150°范围会导致舵机堵转发热长期运行损坏齿轮。pyb.delay(50)不可省略MG90S典型响应时间400ms但小角度步进时200ms内到位此处取保守值。3. 颜色识别核心HSV动态阈值与ROI裁剪的协同设计OPENMV的颜色追踪本质是在HSV色彩空间中做区域生长质心计算但直接套用IDE里的“Threshold Editor”生成的固定HSV值在真实场景中必然失败——窗外光线变化、LED灯频闪、目标反光都会让H值漂移±15S值压缩至30%以下。我们必须把阈值变成可在线调节的变量并用ROIRegion of Interest强制聚焦目标区域形成“先粗筛再精算”的两级过滤。3.1 HSV空间原理与OPENMV的特殊性OPENMV的image.get_histogram()返回的是归一化后的直方图其H通道范围是0~100非OpenCV的0~179S/V通道是0~255。这是关键差异很多教程照搬OpenCV阈值导致完全识别不到。实测某红色球在OPENMV中的典型HSV分布H: 60~85正红偏橙非纯红S: 80~220光照强时S高阴影下S骤降V: 100~255V太低过暗直接丢弃因此固定阈值必须覆盖这个浮动区间但又不能太宽否则把木桌纹理也当红色。3.2 动态阈值生成用“当前帧直方图”替代“静态阈值”我们放弃IDE里生成的thresholds [(h_min, h_max, s_min, s_max, v_min, v_max)]改为每帧实时计算当前ROI内的HSV统计值再按比例收缩作为新阈值# 在主循环中伪代码结构 def get_dynamic_threshold(img, roiNone): 基于当前图像ROI生成动态HSV阈值 roi: (x, y, w, h) 裁剪区域None则用全图 返回: (h_min, h_max, s_min, s_max, v_min, v_max) if roi: img_roi img.copy(roiroi) # 复制ROI区域避免修改原图 else: img_roi img # 获取HSV直方图OPENMV专用API hist img_roi.get_histogram() # 提取各通道统计值H:0~100, S/V:0~255 h_stats hist.get_statistics(h_bins100) # H通道100级直方图 s_stats hist.get_statistics(s_bins255) v_stats hist.get_statistics(v_bins255) # 取均值±1.5σ作为阈值边界比固定值鲁棒 h_mean, h_std h_stats.mean(), h_stats.stdev() s_mean, s_std s_stats.mean(), s_stats.stdev() v_mean, v_std v_stats.mean(), v_stats.stdev() # 收缩阈值H放宽S/V收紧因S/V更易受光照影响 h_min max(0, int(h_mean - 1.5 * h_std)) h_max min(100, int(h_mean 1.5 * h_std)) s_min max(30, int(s_mean - 0.8 * s_std)) # S下限抬高滤除灰暗区域 s_max min(255, int(s_mean 0.8 * s_std)) v_min max(80, int(v_mean - 0.5 * v_std)) # V下限强制80排除阴影 v_max 255 return (h_min, h_max, s_min, s_max, v_min, v_max) # 主循环中调用 while(True): img sensor.snapshot() # Step 1: 先用粗略ROI如画面中心1/4找目标大致位置 center_roi (img.width()//4, img.height()//4, img.width()//2, img.height()//2) dyn_thresh get_dynamic_threshold(img, roicenter_roi) # Step 2: 用动态阈值二值化 binary img.binary([dyn_thresh], invertFalse, zeroTrue) # Step 3: 形态学去噪OPENMV内置 binary.erode(1, threshold1) # 去除孤立噪点 binary.dilate(1, threshold1) # 填充目标空洞参数说明与物理意义h_bins100OPENMV H通道只有100级量化必须匹配否则直方图失真。s_min max(30, ...)S值低于30的区域基本是灰/黑不可能是鲜艳目标强制过滤。v_min max(80, ...)实测环境光V值常在60~70目标V需显著高于背景80是经验值下限。erode/dilate各1次OPENMV的形态学运算开销极小但能消除传感器热噪声产生的白点比单纯binary鲁棒得多。3.3 ROI自适应裁剪用“上一帧目标位置”引导当前搜索窗固定ROI如永远搜中心在目标快速移动时会丢失。我们采用运动预测搜索窗收缩策略以目标上一帧质心为中心建立一个随速度扩大的搜索框既保证不漏检又避免全图扫描拖慢帧率。# 全局变量在main.py顶部声明 last_blob None # 存储上一帧识别到的blob对象 search_window_scale 1.0 # 初始搜索窗为1倍目标尺寸 while(True): img sensor.snapshot() if last_blob is not None: # 根据上一帧blob大小和位置生成动态ROI x, y, w, h last_blob.rect() # 搜索窗 上一帧bbox扩大scale倍但不超过图像边界 search_w int(w * search_window_scale) search_h int(h * search_window_scale) search_x max(0, min(img.width()-search_w, x w//2 - search_w//2)) search_y max(0, min(img.height()-search_h, y h//2 - search_h//2)) search_roi (search_x, search_y, search_w, search_h) # 更新搜索窗尺度目标移动越快窗越大但上限2.5倍 dx abs(x - last_blob.cx()) dy abs(y - last_blob.cy()) speed (dx dy) / 2.0 search_window_scale min(2.5, 1.0 speed * 0.02) # 速度每增1px窗扩0.02倍 # 用动态ROI生成阈值并二值化 dyn_thresh get_dynamic_threshold(img, roisearch_roi) binary img.binary([dyn_thresh], invertFalse, zeroTrue) binary.erode(1).dilate(1) # 在binary图上找blob只搜ROI区域 blobs binary.find_blobs([(0, 100)], roisearch_roi, pixels_threshold50, area_threshold50) else: # 首帧或丢失目标时全图搜索 binary img.binary([(60, 85, 80, 220, 100, 255)], invertFalse, zeroTrue) binary.erode(1).dilate(1) blobs binary.find_blobs([(0, 100)], pixels_threshold50, area_threshold50) # 更新last_blob取最大blob防多目标干扰 if blobs: largest_blob max(blobs, keylambda b: b.pixels()) last_blob largest_blob # 绘制跟踪框调试用 img.draw_rectangle(largest_blob.rect(), color(255,0,0)) img.draw_cross(largest_blob.cx(), largest_blob.cy(), color(0,255,0)) else: last_blob None # 目标丢失下一帧恢复全图搜索关键设计点search_window_scale动态更新目标静止时窗最小1.0高速移动时自动扩大避免“目标刚移出ROI就丢失”的经典问题。pixels_threshold50和area_threshold50过滤掉小于50像素的噪点OPENMV H7在QVGA320×240下50像素约直径8px足够滤除大部分传感器噪声。find_blobs的roi参数直接作用于二值图比先copy(roi...)再find_blobs更省内存——OPENMV RAM仅几百KB必须精打细算。4. 视觉伺服闭环PID控制器设计与舵机运动学补偿识别出目标坐标只是开始真正的难点在于如何把(cx, cy)误差转化为舵机角度增量且运动平滑不震荡。这里涉及三个层面坐标系映射图像像素→云台角度、控制算法选型为什么不用纯比例控制、以及运动学补偿为什么目标在画面边缘时舵机要转得更快。4.1 坐标系映射从图像像素到舵机角度的线性模型OPENMV摄像头FOV视场角为70.8°H7广角镜头图像宽320px则每像素对应水平角度 70.8° / 320 ≈ 0.221°/px。同理高240px对应垂直FOV 53.5°即0.223°/px。但实际安装时镜头可能倾斜需实测校准# 实测校准函数用已知角度的标定板测量像素/角度比 def calibrate_fov(): 手动校准将云台Yaw转到0°记下目标在图像中x坐标x0 再转到90°记下新x坐标x90则kx 90.0 / (x90 - x0) print(Step1: Set Yaw to 0°, press any key...) pyb.udelay(1000000) img0 sensor.snapshot() blob0 img0.find_blobs([(60,85,80,220,100,255)], pixels_threshold50) if blob0: x0 max(blob0, keylambda b: b.pixels()).cx() print(x0 , x0) print(Step2: Set Yaw to 90°, press any key...) pyb.udelay(1000000) img90 sensor.snapshot() blob90 img90.find_blobs([(60,85,80,220,100,255)], pixels_threshold50) if blob90: x90 max(blob90, keylambda b: b.pixels()).cx() print(x90 , x90) kx 90.0 / abs(x90 - x0) print(Calibrated kx , kx, deg/px) return kx return 0.221 # fallback # 使用校准后的kx进行坐标转换 kx calibrate_fov() # 运行一次后注释掉用实测值 ky 0.223 # 垂直方向暂用理论值同理可校准映射公式水平误差角yaw_error (img.width()//2 - cx) * kxcx width/2 时为正需向右转俯仰误差角pitch_error (cy - img.height()//2) * kycy height/2 时为正需向下俯4.2 PID控制器实现为什么必须加积分和微分项纯P控制output Kp * error会导致稳态误差目标停在画面边缘误差不为零舵机持续施加力矩最终因摩擦力平衡而停在非中心位置。I项消除稳态误差D项抑制超调。但OPENMV算力有限我们采用位置式PID简化版去掉积分抗饱和因舵机物理限幅已起作用class PIDController: def __init__(self, kp, ki, kd, dt0.04): # dt25fps≈0.04s self.kp kp self.ki ki self.kd kd self.dt dt self.last_error 0.0 self.integral 0.0 def update(self, error): # 比例项 p self.kp * error # 积分项带限幅防积分饱和 self.integral error * self.dt self.integral max(-10.0, min(10.0, self.integral)) # 积分限幅±10° i self.ki * self.integral # 微分项用误差变化率非输出变化率更稳定 d self.kd * (error - self.last_error) / self.dt self.last_error error return p i d # 初始化两个PID控制器 yaw_pid PIDController(kp0.8, ki0.05, kd0.1, dt0.04) pitch_pid PIDController(kp0.7, ki0.04, kd0.08, dt0.04)参数物理意义与调参口诀kp0.8每1°误差产生0.8°舵机修正量值太小响应慢太大则震荡。从0.3开始试每次0.2。ki0.05消除稳态误差但过大导致缓慢发散震荡。先设0目标稳定后微调。kd0.1抑制超调值大则运动迟钝小则过冲。在P调好后从0.05开始加。dt0.04必须与实际帧率匹配用sensor.get_frame_rate()实测若帧率波动大改用pyb.micros()计时。4.3 运动学补偿为什么画面边缘的目标需要更大舵机增量几何关系决定当目标位于图像边缘时相同像素误差对应的物理角度误差更大。例如目标在画面最右端x319cx误差1px对应角度误差≈0.221°但若目标在中心x160同样1px误差仅对应0.01°。因此PID输出需乘以一个距离中心的增益系数def apply_gain_compensation(error_px, img_center, img_size): 基于目标离中心距离动态调整PID输出增益 error_px: 像素误差如 cx - img_center 返回: 增益系数中心1.0边缘≈1.8 norm_dist abs(error_px) / (img_size / 2.0) # 归一化距离 [0,1] # 指数增益中心平缓边缘陡升 gain 1.0 0.8 * (norm_dist ** 2) return min(1.8, gain) # 上限1.8防过激 # 主循环中应用 if last_blob: cx, cy last_blob.cx(), last_blob.cy() img_w, img_h img.width(), img.height() cx_err img_w//2 - cx cy_err cy - img_h//2 # 计算增益 gx apply_gain_compensation(cx_err, img_w//2, img_w) gy apply_gain_compensation(cy_err, img_h//2, img_h) # PID更新输入为角度误差 yaw_target cx_err * kx * gx pitch_target cy_err * ky * gy yaw_output yaw_pid.update(yaw_target) pitch_output pitch_pid.update(pitch_target) # 应用死区小误差不动作防舵机颤抖 if abs(yaw_output) 0.5: yaw_output 0.0 if abs(pitch_output) 0.5: pitch_output 0.0 # 限幅输出防止舵机打齿 yaw_output max(-30, min(30, yaw_output)) # 水平±30°安全范围 pitch_output max(-20, min(20, pitch_output)) # 俯仰±20°防撞底 # 执行舵机转动 set_servo_angle(yaw_output, pitch_output)死区与限幅的工程意义abs(output) 0.5°设为死区舵机最小步进约0.5°更小的指令无法执行反而因反复尝试导致高频抖动。max/min限幅云台机械结构限制水平超±30°易碰支架俯仰超±20°易撞镜头必须硬限。5. 避坑指南90%的OPENMV云台项目在这里翻车调试过程中绝大多数问题并非算法错误而是被OPENMV MicroPython的隐式行为、硬件特性或环境干扰掩盖。以下是某实验室连续三天实测总结的5个高频翻车点每个都附带现象、根因和可立即验证的解决方案。5.1 现象云台左右/上下规律性抖动频率约2Hz幅度5°~10°原因电源纹波干扰PWM信号。舵机启停时电流突变通过共地路径耦合到OPENMV的ADC参考电压导致get_histogram()返回的HSV值周期性跳变进而使PID输出震荡。解决立即断开舵机电源仅用OPENMV USB供电运行get_histogram()看H值是否稳定若稳定则加装1000μF电解电容跨接舵机电源正负极靠近舵机端必须使用磁珠0.1μF陶瓷电容对OPENMV的VCC-GND滤波非可选。5.2 现象目标静止时云台缓慢漂移10秒后偏离中心15°原因PID积分项累积了未被清除的微小误差如图像噪声导致的虚假blob质心偏移且integral变量未在目标丢失时清零。解决在if blobs:分支内每次成功更新last_blob后重置PID积分器if blobs: largest_blob max(blobs, keylambda b: b.pixels()) last_blob largest_blob # 关键目标稳定时重置积分防漂移 yaw_pid.integral 0.0 pitch_pid.integral 0.0 else: last_blob None # 目标丢失时保持积分器值便于快速重捕5.3 现象白天识别正常傍晚灯光下目标完全消失原因固定V阈值如v_min100在低照度下失效。傍晚环境V值降至60~70目标V虽相对高但仍100被整体滤除。解决放弃全局V阈值改用局部对比度增强# 在snapshot后立即执行 img sensor.snapshot() img.lens_corr(1.8) # 校正桶形畸变提升边缘清晰度 img.gamma_corr(gamma0.5, contrast1.2, brightness0) # 提升暗部对比度 # 再进行HSV处理gamma0.5压缩高亮、拉伸暗部contrast1.2增强边缘此组合在LED灯下效果显著。5.4 现象云台转动时图像明显卡顿帧率从25fps暴跌至8fps原因set_servo_angle()中pyb.delay(50)阻塞了整个MicroPython主线程导致sensor.snapshot()无法及时触发。解决删除所有pyb.delay()改用非阻塞舵机控制# 在boot.py中增加全局时间戳 last_move_time pyb.millis() # 修改set_servo_angle为异步 def set_servo_angle_async(yaw_deg, pitch_deg): global last_move_time now pyb.millis() if now - last_move_time 30: # 至少30ms间隔给舵机响应时间 # 执行PWM设置无delay ch_yaw.pulse_width_percent(...) ch_pitch.pulse_width_percent(...) last_move_time now此方案牺牲单次转动精度但保障帧率稳定视觉伺服更看重实时性而非单步绝对精度。5.5 现象同一目标不同距离下舵机转动幅度不一致近处猛转、远处微调原因未考虑目标距离对像素/角度映射的影响。近距离时目标占据大量像素cx误差小但物理角度误差大远距离反之。解决引入面积反馈补偿目标面积越大距离越近增大PID增益面积越小距离越远减小增益。if last_blob: area_ratio last_blob.area() / (img.width() * img.height()) # 归一化面积 [0,1] # 面积越大增益越高近处需更激进 dist_gain 0.8 1.2 * area_ratio # [0.8,2.0] yaw_output * dist_gain pitch_output * dist_gain实测表明面积比0.05中距离时增益1.00.2特近时增益1.8完美匹配人眼追踪直觉。6. 进阶技巧用“历史轨迹拟合”预判目标运动方向当目标做匀速直线运动时仅靠当前帧误差的PID控制仍有滞后。我们可以利用过去N帧的blob质心序列拟合一条直线预测下一帧目标位置从而提前调整舵机——这本质上是前馈控制Feedforward与PID反馈形成互补。6.1 实现轨迹拟合用最小二乘法求解运动矢量OPENMV内存有限无法存储长序列我们只保留最近5帧的(cx, cy, timestamp)用线性回归求斜率# 全局变量main.py顶部 trajectory_history [] # [(cx, cy, ms), ...] 最多存5组 def predict_next_position(): 基于最近5帧质心拟合直线 y kx b预测tdt时刻位置 返回: (pred_cx, pred_cy) if len(trajectory_history) 3: return None, None # 提取时间戳和坐标 ts [p[2] for p in trajectory_history] cxs [p[0] for p in trajectory_history] cys [p[1] for p in trajectory_history] # 时间归一化以首帧为t0 t0 ts[0] t_norm [t - t0 for t in ts] # 线性拟合cx(t): cx vx * t cx0 # 最小二乘解vx Σ(ti*ci)/Σ(ti²) - cx0*Σ(ti)/Σ(ti²)但OPENMV无浮点矩阵库用简化公式 n len(t_norm) sum_t sum(t_norm) sum_t2 sum(t*t for t in t_norm) sum_cx sum(cxs) sum_t_cx sum(t*c for t,c in zip(t_norm, cxs)) # vx (n*sum_t_cx - sum_t*sum_cx) / (n*sum_t2 - sum_t*sum_t) # 标准公式 # 为防除零加epsilon denom n * sum_t2 - sum_t * sum_t 1e-6 vx (n * sum_t_cx - sum_t * sum_cx) / denom # 预测t40ms1帧位置 next_t t_norm[-1] 40.0 pred_cx vx * next_t (sum_cx - vx * sum_t) / n # 截距 mean(cx) - vx*mean(t) # 同理拟合cy(t) sum_cy sum(cys) sum_t_cy sum(t*c for t,c in zip(t_norm, cys)) vy (n * sum_t_cy - sum_t * sum_cy) / denom pred_cy vy * next_t (sum_cy - vy * sum_t) / n return int(pred_cx), int(pred_cy) # 主循环中更新轨迹历史 if last_blob: now_ms pyb.millis() trajectory_history.append((last_blob.cx(), last_blob.cy(), now p a hrefhttps://download.csdn.net/download/m0_73931287/88783335 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑