选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2020-08-20
  • 更新:2020-08-31
在 K210 上使用MaixPy 编写一个简单的云台程序, 实现人脸跟随


效果视频

硬件

  • 可以运行 MaixPy 的 k210 开发板(此处用了maixbit 开发板)
  • 屏幕,是买 maixbit 套餐里面带的,屏幕排线容易折断,要小心使用
  • 两轴舵机,扭力尽量大一点,会更稳,上面视频使用的 20KG 扭力的舵机, 参数:
    • 频率: 50 (Hz)
    • 周期 T: 1/50 = 0.02s = 20ms
    • 占空比 duty: [0.5ms, 2.5ms] -> [0.025, 0.125] -> [2.5%, 12.5%]
  • 硬件连接
    • IO24 <—> pitch(舵机俯仰轴)
    • IO25 <—> roll (舵机横滚轴,这里实际是控制的航向轴)

原理

  1. 使用人脸检测模型来检测人脸,获取到人脸的中心坐标。
  2. 将当前的坐标与中心点坐标进行对比,获得当前与目标的差值。
  3. 根据这个与目标的差值,k210 驱动舵机给舵机一定的驱动量,使其将摄像头转动,达到中心对准人头。

这里的难点在第三步,根据与目标位置的差值如何驱动舵机转动使摄像头中心对准人头

  • 如果没有任何控制理论的同学会想,用判断语句,一点一点移动就好了。但这样看起来舵机非常僵硬,一卡一卡的。
  • 稍微有点控制理论基础的应该都知道,这种情况使用 经典 PID 控制即可,简单地说就是根据误差值,通过一个带有三个参数(P I D)的公式得到一个输出值,将这个值作为增量输出到舵机控制舵机转动,比如公式这样:

    这里的 Kp Tt Td 就是 PID 参数,看起来很复杂,实际可以先从简单的 只用 P 控制(其它两个值为0)的情况做起就比较简单,即输出增量= 和目标的差值 * Kp。I是积分即把每次的误差累积起来,对于小误差很有用,随着时间累积会放大误差作用,比如离目标差几个像素,但是输出量太小不能让舵机转动,随着时间累积,输出量会越来越大直到让舵机动起来;D是微分,可以防止过冲,这里是将本次的误差和上次的误差变化参与输出值的计算,来让转动更加平滑。
    这里只是大概解释一下,具体可以自行搜索 经典PID控制 学习,结合看代码就很好懂。

实现

源代码

代码在这里: https://github.com/sipeed/MaixPy_scripts/tree/master/application/gimbal

  1. ‘’‘
  2. @author neucrack
  3. @license MIT
  4. ’‘’
  5. import time, sys
  6. from machine import Timer,PWM
  7. from fpioa_manager import board_info
  8. from math import pi
  9. from machine import UART
  10. class Servo:
  11. def __init__(self, pwm, dir=50, duty_min=2.5, duty_max=12.5):
  12. self.value = dir
  13. self.pwm = pwm
  14. self.duty_min = duty_min
  15. self.duty_max = duty_max
  16. self.duty_range = duty_max -duty_min
  17. self.enable(True)
  18. self.pwm.duty(self.value/100*self.duty_range+self.duty_min)
  19. def enable(self, en):
  20. if en:
  21. self.pwm.enable()
  22. else:
  23. self.pwm.disable()
  24. def dir(self, percentage):
  25. if percentage > 100:
  26. percentage = 100
  27. elif percentage < 0:
  28. percentage = 0
  29. self.pwm.duty(percentage/100*self.duty_range+self.duty_min)
  30. def drive(self, inc):
  31. self.value += inc
  32. if self.value > 100:
  33. self.value = 100
  34. elif self.value < 0:
  35. self.value = 0
  36. self.pwm.duty(self.value/100*self.duty_range+self.duty_min)
  37. class PID:
  38. _kp = _ki = _kd = _integrator = _imax = 0
  39. _last_error = _last_t = 0
  40. _RC = 1/(2 * pi * 20)
  41. def __init__(self, p=0, i=0, d=0, imax=0):
  42. self._kp = float(p)
  43. self._ki = float(i)
  44. self._kd = float(d)
  45. self._imax = abs(imax)
  46. self._last_derivative = None
  47. def get_pid(self, error, scaler):
  48. tnow = time.ticks_ms()
  49. dt = tnow - self._last_t
  50. output = 0
  51. if self._last_t == 0 or dt > 1000:
  52. dt = 0
  53. self.reset_I()
  54. self._last_t = tnow
  55. delta_time = float(dt) / float(1000)
  56. output += error * self._kp
  57. if abs(self._kd) > 0 and dt > 0:
  58. if self._last_derivative == None:
  59. derivative = 0
  60. self._last_derivative = 0
  61. else:
  62. derivative = (error - self._last_error) / delta_time
  63. derivative = self._last_derivative + \
  64. ((delta_time / (self._RC + delta_time)) * \
  65. (derivative - self._last_derivative))
  66. self._last_error = error
  67. self._last_derivative = derivative
  68. output += self._kd * derivative
  69. output *= scaler
  70. if abs(self._ki) > 0 and dt > 0:
  71. self._integrator += (error * self._ki) * scaler * delta_time
  72. if self._integrator < -self._imax: self._integrator = -self._imax
  73. elif self._integrator > self._imax: self._integrator = self._imax
  74. output += self._integrator
  75. return output
  76. def reset_I(self):
  77. self._integrator = 0
  78. self._last_derivative = None
  79. class Gimbal:
  80. def __init__(self, pitch, pid_pitch, roll=None, pid_roll=None, yaw=None, pid_yaw=None):
  81. self._pitch = pitch
  82. self._roll = roll
  83. self._yaw = yaw
  84. self._pid_pitch = pid_pitch
  85. self._pid_roll = pid_roll
  86. self._pid_yaw = pid_yaw
  87. def set_out(self, pitch, roll, yaw=None):
  88. pass
  89. def run(self, pitch_err, roll_err=50, yaw_err=50, pitch_reverse=False, roll_reverse=False, yaw_reverse=False):
  90. out = self._pid_pitch.get_pid(pitch_err, 1)
  91. # print("err: {}, out: {}".format(pitch_err, out))
  92. if pitch_reverse:
  93. out = - out
  94. self._pitch.drive(out)
  95. if self._roll:
  96. out = self._pid_roll.get_pid(roll_err, 1)
  97. if roll_reverse:
  98. out = - out
  99. self._roll.drive(out)
  100. if self._yaw:
  101. out = self._pid_yaw.get_pid(yaw_err, 1)
  102. if yaw_reverse:
  103. out = - out
  104. self._yaw.drive(out)
  105. if __name__ == "__main__":
  106. '''
  107. servo:
  108. freq: 50 (Hz)
  109. T: 1/50 = 0.02s = 20ms
  110. duty: [0.5ms, 2.5ms] -> [0.025, 0.125] -> [2.5%, 12.5%]
  111. pin:
  112. IO24 <--> pitch
  113. IO25 <--> roll
  114. '''
  115. init_pitch = 80 # init position, value: [0, 100], means minimum angle to maxmum angle of servo
  116. init_roll = 50 # 50 means middle
  117. sensor_hmirror = False
  118. sensor_vflip = False
  119. lcd_rotation = 2
  120. lcd_mirror = True
  121. pitch_pid = [0.23, 0, 0.015, 0] # P I D I_max
  122. roll_pid = [0.23, 0, 0.015, 0] # P I D I_max
  123. target_err_range = 10 # target error output range, default [0, 10]
  124. target_ignore_limit = 0.02 # when target error < target_err_range*target_ignore_limit , set target error to 0
  125. pitch_reverse = False # reverse out value direction
  126. roll_reverse = True # ..
  127. import sensor,image,lcd
  128. import KPU as kpu
  129. class Target():
  130. def __init__(self, out_range=10, ignore_limit=0.02, hmirror=False, vflip=False, lcd_rotation=2, lcd_mirror=True):
  131. self.pitch = 0
  132. self.roll = 0
  133. self.out_range = out_range
  134. self.ignore = ignore_limit
  135. self.task_fd = kpu.load(0x300000) # face model addr in flash
  136. anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
  137. kpu.init_yolo2(self.task_fd, 0.5, 0.3, 5, anchor)
  138. lcd.init()
  139. lcd.rotation(lcd_rotation)
  140. lcd.mirror(lcd_mirror)
  141. sensor.reset()
  142. sensor.set_pixformat(sensor.RGB565)
  143. sensor.set_framesize(sensor.QVGA)
  144. if hmirror:
  145. sensor.set_hmirror(1)
  146. if vflip:
  147. sensor.set_vflip(1)
  148. def get_target_err(self):
  149. img = sensor.snapshot()
  150. code = kpu.run_yolo2(self.task_fd, img)
  151. if code:
  152. max_area = 0
  153. max_i = 0
  154. for i, j in enumerate(code):
  155. a = j.w()*j.h()
  156. if a > max_area:
  157. max_i = i
  158. max_area = a
  159. img = img.draw_rectangle(code[max_i].rect())
  160. self.pitch = (code[max_i].y() + code[max_i].h() / 2)/240*self.out_range*2 - self.out_range
  161. self.roll = (code[max_i].x() + code[max_i].w() / 2)/320*self.out_range*2 - self.out_range
  162. # limit
  163. if abs(self.pitch) < self.out_range*self.ignore:
  164. self.pitch = 0
  165. if abs(self.roll) < self.out_range*self.ignore:
  166. self.roll = 0
  167. img = img.draw_cross(160, 120)
  168. lcd.display(img)
  169. return (self.pitch, self.roll)
  170. else:
  171. img = img.draw_cross(160, 120)
  172. lcd.display(img)
  173. return (0, 0)
  174. target = Target(target_err_range, target_ignore_limit, sensor_hmirror, sensor_vflip, lcd_rotation, lcd_mirror)
  175. tim0 = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
  176. tim1 = Timer(Timer.TIMER0, Timer.CHANNEL1, mode=Timer.MODE_PWM)
  177. pitch_pwm = PWM(tim0, freq=50, duty=0, pin=24)
  178. roll_pwm = PWM(tim1, freq=50, duty=0, pin=25)
  179. pitch = Servo(pitch_pwm, dir=init_pitch)
  180. roll = Servo(roll_pwm, dir=init_roll)
  181. pid_pitch = PID(p=pitch_pid[0], i=pitch_pid[1], d=pitch_pid[2], imax=pitch_pid[3])
  182. pid_roll = PID(p=roll_pid[0], i=roll_pid[1], d=roll_pid[2], imax=roll_pid[3])
  183. gimbal = Gimbal(pitch, pid_pitch, roll, pid_roll)
  184. target_pitch = init_pitch
  185. target_roll = init_roll
  186. t = time.ticks_ms()
  187. _dir = 0
  188. t0 = time.ticks_ms()
  189. stdin = UART.repl_uart()
  190. while 1:
  191. # get target error
  192. err_pitch, err_roll = target.get_target_err()
  193. # interval limit to > 10ms
  194. if time.ticks_ms() - t0 < 10:
  195. continue
  196. t0 = time.ticks_ms()
  197. # run
  198. gimbal.run(err_pitch, err_roll, pitch_reverse = pitch_reverse, roll_reverse=roll_reverse)
文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-pk9kve.jpg