当前位置: 首页 > news >正文

【电赛学习笔记】MaxiCAM 项目实践——二维云台追踪指定目标

前言

本文是对视觉模块MaixCam实现二维云台人脸跟踪_哔哩哔哩_bilibili大佬的项目实践整理与拓展,侵权即删。

单路舵机基本控制

#导入必要模块
from maix import pwm, time , pinmap#定义全局变量,设初值
SERVO_FREQ = 50         #主频
SERVO_MIN_DUTY = 2.5    #最小角度占空比
SERVO_MAX_DUTY = 12.5   #最大角度占空比
#选择pwm通道
pwm_id = 7
#引脚功能映射
pinmap.set_pin_function("A19", "PWM7")#定义角度设置函数
def angle_to_duty(angle):return (SERVO_MAX_DUTY - SERVO_MIN_DUTY) / 180 * angle + SERVO_MIN_DUTY     #固定公式无需记忆#创建PWM对象
out = pwm.PWM(pwm_id, freq = SERVO_FREQ, duty = angle_to_duty(0), enable = True)for i in range(180):out.duty(angle_to_duty(i))time.sleep_ms(10)

上述代码实现了舵机从0°到180°的运动

舵机类的定义

class Servo:#设置属性SERVO_FREQ = 50         #主频SERVO_MIN_DUTY = 2.5    #最小角度占空比SERVO_MAX_DUTY = 12.5   #最大角度占空比SERVO_MAX_ANGLE = 180   #最大旋转角#初始化函数def __init__(self, pwm_id:int, angle:int) -> None:angle = Servo.SERVO_MAX_ANGLE if angle > Servo.SERVO_MAX_ANGLE else angleangle = 0 if angle < 0 else angleif pwm_id == 7:pinmap.set_pin_function("A19", "PWM7")self.pwm = pwm.PWM(pwm_id, freq = Servo.SERVO_FREQ, duty = self._angle_to_duty_(angle), enable = True)elif pwm_id == 6:pinmap.set_pin_function("A18", "PWM6")self.pwm = pwm.PWM(pwm_id, freq = Servo.SERVO_FREQ, duty = self._angle_to_duty_(angle), enable = True)elif pwm_id == 5:pinmap.set_pin_function("A17", "PWM5")self.pwm = pwm.PWM(pwm_id, freq = Servo.SERVO_FREQ, duty = self._angle_to_duty_(angle), enable = True)elif pwm_id == 4:pinmap.set_pin_function("A16", "PWM4")self.pwm = pwm.PWM(pwm_id, freq = Servo.SERVO_FREQ, duty = self._angle_to_duty_(angle), enable = True)def __del__(self) -> None :self.pwm.disable()def _angle_to_duty_(self,angle:int) -> float :return (Servo.SERVO_MAX_DUTY - Servo.SERVO_MIN_DUTY) / 180 * angle + Servo.SERVO_MIN_DUTY def angle(self, angle:int) -> None:angle = Servo.SERVO_MAX_ANGLE if angle > Servo.SERVO_MAX_ANGLE else angleangle = 0 if angle < 0 else angleself.pwm.duty(self._angle_to_duty_(angle))

关于Python中“类”的简介

下面以这一段 Servo 舵机控制类 为例子,把 Python 中“类的定义规则、各参数/变量的作用域与访问规则” 逐条拆开讲清。只要记住 3 句话就能不迷路:

  1. 类里定义的变量分 类变量实例变量

  2. 函数参数和返回值可以写“类型注解”,但运行时不强制检查。

  3. self. 的是实例自己的;不带的是类或局部临时的。


一、类的“壳子”怎么写

class Servo:...
  • class 关键字 + 类名(首字母大写,PEP8 规范)。

  • 冒号后缩进 4 空格,内部放 类变量、方法


二、类变量(Class Variables)

SERVO_FREQ      = 50
SERVO_MIN_DUTY  = 2.5
SERVO_MAX_DUTY  = 12.5
SERVO_MAX_ANGLE = 180
  • 写在类体里、任何方法外

  • 所有实例共享同一份;通过 类名.变量实例.变量 都能读

    Servo.SERVO_MAX_ANGLE   # 推荐
    my_servo.SERVO_MAX_ANGLE
  • 如果某个实例想“私自”改值,会变成该实例自己的同名属性,不会动到类变量。


三、实例变量(Instance Variables)

实例变量在 __init__ 里用 self.名字 = ... 绑定:

self.pwm = pwm.PWM(...)
  • 每个对象各有一份,生命周期随对象。

  • 访问必须通过实例:my_servo.pwm


四、构造函数 __init__

def __init__(self, pwm_id: int, angle: int) -> None:
位置含义
self固定第 1 参数,指向当前正在创建的对象本身
pwm_id: int形参 + 类型注解(告诉人/IDE 该传 int)。
angle: int同上。
-> None返回值注解:构造函数固定返回 None

五、形参、局部变量、类变量的区分示例

angle = Servo.SERVO_MAX_ANGLE if angle > Servo.SERVO_MAX_ANGLE else angle
  • 左边 angle局部变量(形参名被重新绑定)。

  • Servo.SERVO_MAX_ANGLE类变量

  • 没有 self. 前缀,所以不会存成实例属性。


六、私有“工具函数”的命名惯例

def _angle_to_duty_(self, angle: int) -> float:
  • 单下划线开头 _name 表示“内部使用”,Python 不会强制隐藏,仅提示程序员。

  • self → 实例方法,能访问实例变量 self.pwm

  • angle: int -> float 再次使用类型注解。


七、析构函数 __del__

def __del__(self) -> None:self.pwm.disable()
  • 对象被垃圾回收前自动调用;常用于释放硬件资源。

  • 同样带 self,但不建议依赖它做关键清理,CPython 不保证时机。


八、实例方法 angle

def angle(self, angle: int) -> None:
  • 调用方式:servo.angle(90)

  • 内部通过 self.pwm.duty(...) 修改实例自己的 PWM。


九、变量/属性的完整访问路径总结

写法指向
Servo.SERVO_FREQ类变量(所有实例共享)
self.pwm实例变量(当前对象私有)
angle(无前缀)局部变量(函数内临时)

十、快速记忆表

概念定义位置访问方式生命周期
类变量类体,方法外类.变量 / 实例.变量随类
实例变量__init__ 里用 self.实例.变量随实例
形参/局部变量函数参数或内部直接变量名函数调用期间

照以上规则,你就能看懂并写出任何类似的 Python 类。

项目实战——二位云台色块追踪

from maix import camera, display, image, app
import servo### 初始化 ###
# 舵机初始角度
INIT_POS_X = 90
INIT_POS_Y = 100
# 滤波系数(越小越平滑,响应越慢)
FILTER_FACTOR = 0.15
# PID 系数(已调好,可微调)
KP = 0.018
KD = 0.20# 摄像头与显示
cam = camera.Camera(320, 240)        # 分辨率可改,但需与后续一致
dis = display.Display()# 舵机(PWM6→水平,PWM7→垂直)
servo_x = servo.Servo(6, INIT_POS_X)
servo_y = servo.Servo(7, INIT_POS_Y)# 目标角度初值
target_x_pos = INIT_POS_X
target_y_pos = INIT_POS_Y
last_err_x_pos = 0
last_err_y_pos = 0# 图像中心
IMAGE_WIDTH  = 320
IMAGE_HEIGHT = 240# 红色色块的 LAB 阈值(需根据实际环境调整)
# 格式:(L_min, L_max, A_min, A_max, B_min, B_max)
color_threshold = [(0, 80, 30, 70, 10, 60)]while not app.need_exit():img = cam.read()# 查找色块:merge=True 合并相邻块,pixels_threshold 过滤小面积blobs = img.find_blobs(color_threshold, merge=True, pixels_threshold=300)if not blobs:          # 没检测到dis.show(img)continue# 取最大色块作为目标blob = max(blobs, key=lambda b: b.pixels())# 画框和中心十字img.draw_rect(blob.x(), blob.y(), blob.w(), blob.h(), color=image.COLOR_GREEN)img.draw_cross(blob.cx(), blob.cy(), color=image.COLOR_RED, size=5)# ---------- 横向 PID ----------err_x_pos = IMAGE_WIDTH / 2 - blob.cx()err_x_pos = FILTER_FACTOR * err_x_pos + (1 - FILTER_FACTOR) * last_err_x_posdelta_x_pos = KD * (err_x_pos - last_err_x_pos) + KP * err_x_poslast_err_x_pos = err_x_postarget_x_pos += delta_x_pos# ---------- 纵向 PID ----------err_y_pos = IMAGE_HEIGHT / 2 - blob.cy()err_y_pos = FILTER_FACTOR * err_y_pos + (1 - FILTER_FACTOR) * last_err_y_posdelta_y_pos = KD * (err_y_pos - last_err_y_pos) + KP * err_y_poslast_err_y_pos = err_y_postarget_y_pos += delta_y_pos# 舵机角度限幅(0°~180°)target_x_pos = max(0, min(180, target_x_pos))target_y_pos = max(0, min(180, target_y_pos))# 驱动舵机servo_x.angle(int(target_x_pos))servo_y.angle(int(target_y_pos))dis.show(img)

PID部分解释

零基础也能听懂的 PID 小车比喻
(把“色块追踪”想成“让小汽车自动开到路中间”)

────────────────────

  1. 先认识三个字母
    P —— Proportional 比例
    I —— Integral 积分
    D —— Derivative 微分

(先不用背英文,记住它们各自干的事就行)

────────────────────
2. 把问题换成生活例子

• 你坐在一辆玩具小汽车里,车要停在一条长路的正中间。
• 你每隔 1 秒钟往窗外看一眼,测一下“车身离中线的距离”(这个距离就是误差 err)。
• 每一次看完,你就给方向盘一个“修正量”(delta),让车往中线靠。

PID 就是决定“修正量”的三兄弟。
────────────────────
3. 三兄弟分别做什么?

① 大哥 P(比例):
“离得越远,打得越猛!”
公式:P 部分 = KP × err
• KP 是“比例系数”,像方向盘灵敏度。
• 如果 KP 太小,车慢吞吞;KP 太大,车猛冲过头。

② 二哥 D(微分):
“快撞线了,赶紧松手!”
公式:D 部分 = KD × (err − last_err)
• 只看“误差变化的速度”。
• 当车快速接近中线时,D 会反向拉一把,避免冲过头。
• 相当于“阻尼”,让车不晃。

③ 小弟 I(积分):
“怎么老差一点?慢慢加把劲!”
• 把历史上的误差都加起来,再乘一个系数 KI。
• 对小误差做长期“补偿”。
• 本例为了简单,把 I 关掉(KI=0),所以代码里只有 P 和 D。

────────────────────
4. 代码逐句翻译

以横向为例:

err_x_pos = IMAGE_WIDTH/2 - blob.cx()

→ 看一眼:色块中心离画面中心有多少像素。

err_x_pos = FILTER_FACTOR*err_x_pos + (1-FILTER_FACTOR)*last_err_x_pos

→ 先做个“小滤波”,让测量值别太跳(和 PID 无关,只是让数据平滑)。

delta_x_pos = KD*(err_x_pos - last_err_x_pos) + KP*err_x_pos

→ 把 P 和 D 两个修正量合在一起:
• KPerr_x_pos → 大哥 P:离得多就转得多。
• KD
(err-last) → 二哥 D:如果误差变化很快,就减速。

last_err_x_pos = err_x_pos

→ 把这次误差存起来,下次算 D 时用。

target_x_pos += delta_x_pos

→ 方向盘最终转角 = 上次转角 + 本次修正量。

纵向同理,只是换了一个方向。

────────────────────
5. 调参口诀(小白速成)

  1. 先把 KD 设为 0,只调 KP:

    • 车慢 → 增大 KP

    • 车抖动 → 减小 KP

  2. 再加 KD:

    • 车冲到中线停不下来 → 增大 KD

    • 车变得迟钝 → 减小 KD

  3. 如果静止时总有固定误差,再加一点 KI(本例不需要)。

一句话总结
P 管“现在有多偏”,D 管“偏得有多快”,I 管“长期小偏差”,三兄弟一起用力,就能把色块牢牢地锁在画面正中央!

http://www.lryc.cn/news/599964.html

相关文章:

  • 嵌入式Linux裸机开发笔记8(IMX6ULL)主频和时钟配置实验(3)
  • vue 渲染 | 不同类型的元素渲染的方式(vue组件/htmlelement/纯 html)
  • linux配置ntp时间同步
  • 前端核心进阶:从原理到手写Promise、防抖节流与深拷贝
  • ERNIE-4.5-0.3B 实战指南:文心一言 4.5 开源模型的轻量化部署与效能跃升
  • Agentic RAG理解和简易实现
  • 计算机体系结构中的中断服务程序ISR是什么?
  • haproxy集群
  • Java测试题(上)
  • Spring之【Bean后置处理器】
  • sam2环境安装
  • JAVA语法糖
  • JAVA同城服务家政服务家政派单系统源码微信小程序+微信公众号+APP+H5
  • 探索 Sui 上 BTCfi 的各类资产
  • 在DolphinScheduler执行Python问题小记
  • DP4871音频放大芯片3W功率单通道AB类立体声/音频放大器
  • 3N90-ASEMI电源管理领域专用3N90
  • 【前端】JavaScript文件压缩指南
  • 文件包含学习总结
  • reflections:Java非常好用的反射工具包
  • 【linux】Haproxy七层代理
  • 如何理解泊松分布
  • 在 IntelliJ IDEA 中打开这个用于设置 Git 用户名(Name)和邮箱(Email)的特定弹窗
  • JAVA知识点(三):Spring与ORM框架
  • 【RDMA】Adapters PRM Mellanox Adapters Programmer’s Reference mellanox网卡编程手册0.52
  • Linux库——库的制作和原理(1)_回顾动静态库、制作使用库
  • 上位机程序开发基础介绍
  • OpenCV结合深度学习进行图像分类
  • 练习实践-基础设施-文件共享-windows和linux之间的文件共享-smb服务搭建
  • 解决angular与jetty websocket 每30s自动断连的问题