新手学习实记(八、在树莓派上实现云台舵机目标追踪)



【前言】

校内实习制作——基于树莓派的云台人脸追踪系统。本文主要是记录我的操作和执行过程。

由于要同时准备考研,所以只能学习做一个简易的系统啦,希望能有收获叭。

【个人情况(供看文章的同学参考):

①树莓派初学者,第一次操作全新树莓派(包括选购器件、安装硬件、烧录镜像、配置所有本次实验需要的树莓派通信环境等等)。

②python初学者,但是有其他语言基础。

文中有大量试错过程可供参考。】

九月份补充说明:整体合计约一个月零十天的时长,因为答主考研,所以只能花这么长时间学习相关知识并实践。

除了最开始的集中实践,后续基本都是零零散散的调试。

基础配置我卡了很久很久,花了非常多的时间摸索。我决定把我的操作和遇到的问题都记录下来。希望能对需要的小伙伴有一点帮助。

时长记录 
事件时长原因
器材选购约四天选择商家并购买(半天左右),快递(三天左右)
树莓派环境配置约一周镜像下载、烧录及更换(半天左右)SD卡内存不够,更换大内存SD卡(快递三天左右)。找不到ip地址pc端无法远程控制树莓派(半天左右解决)尝试更改成静态ip,尝试多种方法无果,镜像崩溃,重新烧录镜像(折腾了一天多)。尝试使用手机app端控制,效果不佳,采用了一种很冷门的瞄定方法,成功了(约一天)。选择、下载及学习了如何配置文件传输工具sshClient、远程控制工具vnc。
pip和opencv配置约三天主要难点:学习并实践opencv和pip的配置和简单使用。

学习python和opencv

始终抽空学习。目前还是菜鸟水平。我在博客推荐了一些亲测比较适合新手学习的资源教程,欢迎讨论。
LBP特征提取算法两天左右

LBP特征提取算法是我本次校内实习人脸检测选用的算法,也是opencv目标检测使用的算法。

在树莓派上实现人脸检测一周左右

难点:修改haar算法伪代码,并找相关教程,用python实现(接近一周)。

摄像头的连接和调试(不到一天,但该算法捕捉图像的精度有限)。

PID算法一天左右

主要是理解,需要一定数学基础。

在树莓派上实现云台舵机目标追踪 

—周半左右难点:舵机接线(因为担心烧坏树莓派,买了一块PCA9685板子,发现板子没焊好排针,换了一块控制板,中途浪费了不少时间)。修改T——积分控制算法(很难调到理想精度)


在树莓派上实现云台舵机目标追踪

新手菜鸡遇到了各种问题,这里特别感谢一位大佬的帮助和指导(dr.young)。

主要参考:

【OpenCV基础教程】3.人脸追踪-舵机云台比例控制(1Z实验室)

https://www.bilibili.com/video/BV1UW411R7jk?from=search&seid=11854798723965069450

这个真的写的好好,向大佬学习。

不过这个教程用的是ESP32,我用的是树莓派,具体使用树莓派来人脸追踪的代码是不一样的,于是本文章基于1Z实验室代码进行了调整。

NOTE
1. 为保证人脸识别的帧率分辨率控制在320x240
BUG
1. 刚开始的时候缓存区视频流的问题, 导致舵机云台会乱转 向大佬低头  数值越界
ESP32出解析的数据:Bottom: 6553600 Top: 6556160
2. Reset 舵机云台,串口数据发送之后,ESP32有时候不执行
TODO
1. 获得舵机旋转角度的反馈数据
2. 创建两个Trackbar, 设置两个Kp取值
3. 绘制上下臂舵机的波形图

参考Kp取值
1. Kp = 10  比例系数较大,来回抖动非常明显
2. Kp = 20  幅度过大,旋转后目标直接丢失
3. Kp = 5   幅度适中,有小幅抖动
4. Kp = 2   相应速度比较慢
'''
import cv2
import time
import RPi.GPIO as GPIO


def tonum(num):  # 用于处理角度转换的函数
    fm = 10.0 / 180.0
    num = num * fm + 2.5
    num = int(num * 10) / 10.0
    return num

servopin1 = 15   #舵机1,方向为左右转
servopin2 = 18   #舵机2,方向为上下转

GPIO.setmode(GPIO.BCM)
GPIO.setup(servopin1, GPIO.OUT, initial=False)
GPIO.setup(servopin2, GPIO.OUT, initial=False)
p1 = GPIO.PWM(servopin1,50) #50HZ
p2 = GPIO.PWM(servopin2,50) #50HZ



last_btm_degree = 100 # 最近一次底部舵机的角度值记录
last_top_degree = 100 # 最近一次顶部舵机的角度值记录

btm_kp = 5 # 底部舵机的Kp系数
top_kp = 5 # 顶部舵机的Kp系数

offset_dead_block = 0.1 # 设置偏移量的死区



# 载入人脸检测的Cascade模型
FaceCascade = cv2.CascadeClassifier('lbpcascade_frontalface.xml')

# 创建一个窗口 名字叫做Face
cv2.namedWindow('FaceDetect',flags=cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO | cv2.WINDOW_GUI_EXPANDED)


def set_cloud_platform_degree(last_btm_degree, last_top_degree):
    p1.start(tonum(last_btm_degree)) #初始化角度
    p2.start(tonum(last_top_degree)) #初始化角度
    time.sleep(0.1)
    p1.ChangeDutyCycle(0) #清除当前占空比,使舵机停止抖动
    p2.ChangeDutyCycle(0) #清除当前占空比,使舵机停止抖动
    time.sleep(0.01)
    
    
    
def set_btm_degree(degrees):
    p1.start(tonum(degrees)) #
    time.sleep(0.1)
    p1.ChangeDutyCycle(0) #清除当前占空比,使舵机停止抖动
    time.sleep(0.01)
    
def set_top_degree(degrees):
    p2.start(tonum(degrees)) #
    time.sleep(0.1)
    p2.ChangeDutyCycle(0) #清除当前占空比,使舵机停止抖动
    time.sleep(0.01)
    
    
# 舵机角度初始化
set_cloud_platform_degree(last_btm_degree, last_top_degree)



def update_btm_kp(value):
    # 更新底部舵机的比例系数
    global btm_kp
    btm_kp = value

def update_top_kp(value):
    # 更新顶部的比例系数
    global top_kp
    top_kp = value

# 创建底部舵机Kp的滑动条
cv2.createTrackbar('BtmServoKp','FaceDetect',0, 20,update_btm_kp)
# 设置btm_kp的默认值
cv2.setTrackbarPos('BtmServoKp', 'FaceDetect', btm_kp)
# 创建顶部舵机Kp的滑动条
cv2.createTrackbar('TopServoKp','FaceDetect',0, 20,update_top_kp)
# 设置top_kp的默认值
cv2.setTrackbarPos('TopServoKp', 'FaceDetect', top_kp)


# 摄像头的IP地址  
# http://用户名:密码@IP地址:端口/
ip_camera_url = 'http://admin:admin@192.168.43.1:8081/'
# 创建一个VideoCapture
cap = cv2.VideoCapture(0)
cap.set(320,240)
# 设置缓存区的大小 !!!
#cap.set(cv2.CAP_PROP_BUFFERSIZE,1)
print('IP摄像头是否开启: {}'.format(cap.isOpened()))



def btm_servo_control(offset_x):
    '''
    底部舵机的比例控制
    这里舵机使用开环控制
    '''
    global offset_dead_block # 偏移量死区大小
    global btm_kp # 控制舵机旋转的比例系数
    global last_btm_degree # 上一次底部舵机的角度
    
    # 设置最小阈值
    if abs(offset_x) < offset_dead_block:
        offset_x = 0

    # offset范围在-50到50左右
    delta_degree = offset_x * btm_kp
    # 计算得到新的底部舵机角度
    next_btm_degree = last_btm_degree + delta_degree
    # 添加边界检测
    if next_btm_degree < 0:
        next_btm_degree = 0
    elif next_btm_degree > 180:
        next_btm_degree = 180
    
    return int(next_btm_degree)

def top_servo_control(offset_y):
    '''
    顶部舵机的比例控制
    这里舵机使用开环控制
    '''
    global offset_dead_block
    global top_kp # 控制舵机旋转的比例系数
    global last_top_degree # 上一次顶部舵机的角度

    # 如果偏移量小于阈值就不相应
    if abs(offset_y) < offset_dead_block:
        offset_y = 0

    # offset_y *= -1
    # offset范围在-50到50左右
    delta_degree = offset_y * top_kp
    # 新的顶部舵机角度
    next_top_degree = last_top_degree + delta_degree
    # 添加边界检测
    if next_top_degree < 0:
        next_top_degree = 0
    elif next_top_degree > 180:
        next_top_degree = 180
    
    return int(next_top_degree)

def face_filter(faces):
    '''
    对人脸进行一个过滤
    '''
    if len(faces) == 0:
        return None
    
    # 目前找的是画面中面积最大的人脸
    max_face =  max(faces, key=lambda face: face[2]*face[3])
    (x, y, w, h) = max_face
    if w < 10 or h < 10:
        return None
    return max_face

def calculate_offset(img_width, img_height, face):
    '''
    计算人脸在画面中的偏移量
    偏移量的取值范围: [-1, 1]
    '''
    (x, y, w, h) = face
    face_x = float(x + w/2.0)
    face_y = float(y + h/2.0)
    # 人脸在画面中心X轴上的偏移量
    offset_x = float(face_x / img_width - 0.5) * 2
    # 人脸在画面中心Y轴上的偏移量
    offset_y = float(face_y / img_height - 0.5) * 2

    return (offset_x, offset_y)



def set_btm_servo_angle(degree):
    
        '''
        设定云台底部舵机的角度
        '''
        
        btm_min_angle = 0 # 底部舵机最小旋转角度
        btm_max_angle = 180 # 底部舵机最大旋转角度
        btm_init_angle = 100 # 底部舵机的初始角度
        
        if degree < btm_min_angle:
            degree = btm_min_angle
        elif degree > btm_max_angle:
            degree = btm_max_angle
        
        #self.servos.position(self.btm_servo_idx, degrees=degree)

        set_btm_degree(degrees=degree)
        
        
        
def set_top_servo_angle(degree):
    
        '''
        设定云台底部舵机的角度
        '''
        
        top_min_angle = 0 # 底部舵机最小旋转角度
        top_max_angle = 180 # 底部舵机最大旋转角度
        top_init_angle = 100 # 底部舵机的初始角度
        
        if degree < top_min_angle:
            degree = top_min_angle
        elif degree > top_max_angle:
            degree = top_max_angle
        
        #self.servos.position(self.btm_servo_idx, degrees=degree)

        set_top_degree(degrees=degree)
        
        

while cap.isOpened():
    # TODO 阅读最后一帧
    ret, img = cap.read()
    # 手机画面水平翻转
    img = cv2.flip(img, 1)
    # 将彩色图片转换为灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 检测画面中的人脸
    faces = FaceCascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5
    )
    # 人脸过滤
    face = face_filter(faces)
    if face is not None:
        # 当前画面有人脸
        (x, y, w, h) = face
        # 在原彩图上绘制矩形
        cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 4)

        img_height, img_width,_ = img.shape
        print("img h:{} w:{}".format(img_height, img_width))
        # 计算x轴与y轴的偏移量
        (offset_x, offset_y) = calculate_offset(img_width, img_height, face)
        # 计算下一步舵机要转的角度
        next_btm_degree = btm_servo_control(offset_x)
        next_top_degree = top_servo_control(offset_y)
        # 舵机转动
        #set_cloud_platform_degree(next_btm_degree, next_top_degree)
        set_btm_servo_angle(next_btm_degree)
        set_top_servo_angle(next_top_degree)
        # 更新角度值
        last_btm_degree = next_btm_degree
        last_top_degree = next_top_degree
        print("X轴偏移量:{} Y轴偏移量:{}".format(offset_x, offset_y))
        print('底部角度: {} 顶部角度:{}'.format(next_btm_degree, next_top_degree))
    # 在窗口Face上面展示图片img
    cv2.imshow('FaceDetect', img)
    # 等待键盘事件
    key = cv2.waitKey(1)
    if key == ord('q'):
        # 退出程序
        break
    elif key == ord('r'):
        print('舵机重置')
        # 重置舵机
        # 最近一次底部舵机的角度值记录
        last_btm_degree = 100
        # 最近一次顶部舵机的角度值记录
        last_top_degree = 100
        # 舵机角度初始化
        set_cloud_platform_degree(last_btm_degree, last_top_degree)

# 释放VideoCapture
cap.release()
# 关闭所有的窗口
cv2.destroyAllWindows()

这里非常非常非常不建议用树莓派3b+做深度图像处理,这个检测帧的延时真的是慢到让人崩溃。

鸽子发言:以后再深入学习了再试试能不能优化。

  • 12
    点赞
  • 128
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值