这篇文章属于此系列:
一个用树莓派做的会聊天,能人脸识别(支持云台追踪)和发送邮件的小玩具
https://blog.csdn.net/yonglisikao/article/details/82804318
树莓派及基本配件,摄像头(Picamera或Webcam),双舵机小云台
Python
请参考这篇文章:
http://shumeipai.nxez.com/2018/06/21/pan-tilt-multi-servo-control.html
我想到的算法是:
1.拍一张照片;
2.确定照片里脸的中心到照片中心的水平和垂直距离;
3.照片里的距离与实际的角度有一定的对应关系,根据这个关系得到相应的角度;
4.控制云台转动相应的角度,实现人脸追踪。
脸位置的确定:使用face_recognition库里的face_locations()函数,这个函数在我的其他文章里介绍过,它能够返回一个元组类型的数据,具体信息是这样的:(上,右,下,左)到图片右上顶点的距离,单位是像素。分别把上下和左右取平均即得到脸中心的位置。
对应关系的确定:因为我们要实现简易的人脸追踪,所以对精度要求不高。假设它们是线性关系,即
α(角度) = k(比例系数) * d(距离)
然后我们来确定比例系数,如果你知道你使用的摄像头的视野角度,直接用视野角度除以照片的对应维度的像素点的数量即得到比例系数,即
k(比例系数) = β(视野角度) / D(对应维度的像素点的个数)
如果不知道,可以像我这样粗略估计一下
1.制作一个像下图这样的东西,像对准待测摄像头;
2.用待测摄像头拍一张照片,如下图;
3.根据拍摄的照片,像下图这样作图,并测出角度,我得到的视野角度大致是50°;
4.我用摄像头拍摄的照片的分辨率是640×480,因为我刚测的水平的视野角度,所以像素点的个数取640;
5.50 / 640 = 0.078125 就是所求的比例系数。
需要注意,你可能会想在while语句前创建视频对象,然后在每次循环中读取一次,但是因为它实际上堆积了之前的数据,也就是说,它会读到很久之前的数据,而我们希望它读到实时的数据,所以我们只能在每次循环中都创建然后释放对象,这导致差不多一秒才能读取一次数据,将来或许可以想到其他办法来解决速度的问题。
# coding = utf-8
import face_recognition
import cv2
import RPi.GPIO as GPIO
import time
def setServoAngle(servo, angle):
'''
:param servo 控制舵机的引脚编号,这取决于你,我用的分别是17和27
:param angle 舵机的位置,范围是:0到180,单位是度
return: None
'''
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(servo, GPIO.OUT)
pwm = GPIO.PWM(servo, 50)
pwm.start(8)
dutyCycle = angle / 18. + 3.
pwm.ChangeDutyCycle(dutyCycle)
time.sleep(0.3)
pwm.stop()
GPIO.cleanup()
# 设置舵机的初始位置
x0, y0 = 80, 30
setServoAngle(27, x0)
setServoAngle(17, y0)
while True:
# 创建视频对象,打开摄像头
video_capture = cv2.VideoCapture(0)
ret, pframe = video_capture.read()
# 释放视频对象
video_capture.release()
frame = cv2.resize(pframe, (0, 0), fx=0.25, fy=0.25) # 这里将分辨率缩小为1/4,故比例系数增大为4倍,现在是0.078125*4 = 0.3125
output = frame[:, :, ::-1]
# 确定脸的位置
face_locations = face_recognition.face_locations(output)
if face_locations:
x = (face_locations[0][1] + face_locations[0][3])/2
y = (face_locations[0][0] + face_locations[0][2])/2
print(x, y) # 输出脸中心到右上顶点的水平和垂直距离
else:
x, y = 80, 60 # 如果没有脸则让舵机保持不动,相当于脸在中央(这时的分辨率为160*120)
# 计算出舵机应该移动的角度,正负与你舵机的安装方式有关
dx = (80 - x) * 0.3125
dy = -(60 - y) * 0.3125
if abs(dx) >= 3: # 设置一个阈值,当角度大于3时,才移动,避免舵机一直在原地抖动,下同
x0 += dx
if x0 > 180: # 设置界限,超出范围不再转动,下同
x0 = 180
elif x0 < 0:
x0 = 0
setServoAngle(27, x0) # 水平方向的舵机控制
if abs(dy) >= 3: # 设置阈值
y0 += dy
if y0 > 180:
y0 = 180
elif y0 < 0:
y0 = 0
setServoAngle(17, y0) # 垂直方向的舵机控制
总体效果:准确度上还不错,速度上较慢。
https://github.com/LoveThinkinghard/Raspibot