树莓派脸部追踪

硬件材料

树莓派4B、云台、摄像头

成品展示

思路

1、电脑上显示摄像头拍摄的视频,并得到人脸坐标,将人脸坐标发给树莓派。

2、树莓派来控制舵机旋转

3、电脑和树莓派之间和socket通信

4、树莓派上使用motion将摄像头内容输出到“192.168.6.179:8081”,从而让电脑获取视频源【192.168.6.179是树莓派地址】

注意:

1、树莓派可能需要关掉防火墙:ufw disable

2、树莓派要先启动motion:sudo motion【只用启动一次即可,一直在后台运行】

人脸跟踪的算法

第一种

获得人脸矩阵中心点坐标【x,y】,再获得视频中心坐标,计算两者误差,从而让摄像头旋转相应角度,旋转时要尽量一度一度的转,不要过激,否则容易让抖动。

当然,我写的只是简单的计算两个中心误差再旋转,缺点是旋转不平滑,改进方式是用PID算法

PID算法参考1:https://pyimagesearch.com/2019/04/01/pan-tilt-face-tracking-with-a-raspberry-pi-and-opencv/

PID算法参考2:https://bcxiaobai.eu.org/post/383.html

第二种

参考:https://blog.csdn.net/rikeilong/article/details/126446567

当人脸矩阵左边或右边快要超出视频边界时再旋转,也是要尽量一度一度的转

代码

电脑上

电脑上client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket

class connect_Raspberry():
def __init__(self,host,port):
print("客户端开启")
# 套接字接口
self.mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置ip和端口

try:
self.mySocket.connect((host, port)) #连接到服务器
print("连接到服务器")
except: #连接不成功,运行最初的ip
print('连接RASP不成功')

def send(self, words):
# 发送消息
msg = words
# 编码发送
self.mySocket.send(msg.encode("utf-8"))
# print("成功发送消息")

def close(self):
self.mySocket.close()
print("与树莓派丽连接中断\n")
exit()


电脑上main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import cv2
import mediapipe as mp
import numpy as np
import client

# 检测脸部
mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils

# 通信传输
myRaspConnection = client.connect_Raspberry('192.168.6.179', 8888)

if __name__ == "__main__":

capture = cv2.VideoCapture("http://192.168.6.179:8081")
ref, frame = capture.read()
fps = 0.0

while (True):
ref, frame = capture.read()
h, w, _ = np.shape(frame)
if not ref:
break
image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

# 脸部检测
with mp_face_detection.FaceDetection(model_selection=0, min_detection_confidence=0.8) as face_detection:
results = face_detection.process(image)

if results.detections:
for detection in results.detections:
box = detection.location_data.relative_bounding_box
# cx,cy,cw,ch=box
cx = box.xmin
cy = box.ymin
cw = box.width
ch = box.height

cv2.rectangle(image, (int(cx * w), int(cy * h)), (int((cx + cw) * w), int((cy + ch) * h)),
(0, 255, 0), 2)
# 控制云台
msg = str(abs(int(cx * w))) + " " + str(abs(int(cy * h))) + " " + str(abs(int((cx + cw) * w))) + " " + str(
abs(int((cy + ch) * h)))
print(msg)
myRaspConnection.send(msg)

frame = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# cv2.rectangle(frame, (int(cx*w) , int(cy*h)), (int((cx+cw)*w) , int((cy+ch)*h)),(0, 255, 0), 2)

cv2.imshow("video", frame)
c = cv2.waitKey(1) & 0xff

if c == 27:
capture.release()
break
print("Video Detection Done!")
capture.release()
cv2.destroyAllWindows()

树莓派上

树莓派上sever.py

1
2
3
4
5
6
7
8
9
import socket

print("服务开启")
mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "192.168.6.179"
port = 8888 #自己定义的端口号

mySocket.bind((host, port))
mySocket.listen(10)

树莓派上main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import time
import sever
import RPi.GPIO as GPIO
from PCA9685 import PCA9685
import math
pwm=PCA9685()
pwm.setPWMFreq(50)
pwm.setRotationAngle(5,0)


if __name__ == '__main__':
pid_X_P=0
pid_Y_P=0
print("等待连接")
client,address = sever.mySocket.accept()
print("新连接")
print("IP is %s" % address[0])
print("port is %d\n" % address[1])

beangle = 90 #每个人的初始角度不同,建议先自己测试好角度
beangle0 = 45

#舵机插的通道口
channel1 = 4 #上下
channel2 = 8 #左右

#变化幅度(这个越大,舵机动的幅度就越大)
angleFreq = 1
#超出屏幕范围(这个调大后,脸部离视频边界检测更灵敏)
changeFreqX = 100
changeFreqY = 20

error_x=500 #当前误差值
last_error_x=100 #上一次误差值
error_y=250
last_error_y=50
wight=900
height=480
piv_x=90
piv_y=45

step=1
try:
print("开始")
while True:
msg = client.recv(1024)
msg = msg.decode("utf-8")
if msg != "":
mess = msg.split(' ')

x0 = int(mess[0])#左上角x
y0 = int(mess[1])#左上角y
x1 = int(mess[2])#右下角x
y1 = int(mess[3])#右下角y


# 方法1:超出中间就偏转
x_mean=int((x0+x1)/2)
y_mean=int((y0+y1)/2)
print("x_mean",x_mean,"y_mean",y_mean)
error_x=int(x_mean-wight/2)
error_y=int(y_mean-height/2)
print("error_x",error_x,"error_y",error_y)

# 误差大于100,要向左偏
if error_x<0 and abs(error_x)>100:
# temp_x=abs(error_x)/(wight/2)*45

step_x=math.exp(abs(error_x)/(wight/2))
print(step_x)
beangle+=step
if beangle >= 180:
beangle = 180
print("向左偏",beangle)
pwm.setRotationAngle(1,beangle)
# 向右偏
if error_x>0 and abs(error_x)>100:
step_x=math.exp(abs(error_x)/(wight/2))
print(step_x)
beangle-=step
if beangle <=10:
beangle = 10
print("向右偏",beangle)
pwm.setRotationAngle(1,beangle)

# 误差大于50,要向上偏
if error_y<0 and abs(error_y)>70:
# if abs(error_y)>=100:
# error_y=100
# temp_x=abs(error_x)/(wight/2)*45
try:
step_y=math.exp(abs(error_y)/(height/2))
except:
step_y=2
print(step_y)
beangle0-=step
if beangle0 <=10:
beangle0 = 10
print("向上偏",beangle0)
pwm.setRotationAngle(0,beangle0)
# 向下偏
if error_y>0 and abs(error_y)>70:
# if abs(error_y)>=100:
# error_y=100
try:
step_y=math.exp(abs(error_y)/(height/2))
except:
step_y=2
print(step_y)
beangle0+=step
if beangle0 >= 85:
beangle0 = 95
print("向下偏",beangle0)
pwm.setRotationAngle(0,beangle0)



# 方法2:快超出屏幕时再旋转
# if x0 < changeFreqX:
# beangle += angleFreq
# if beangle >= 180:
# beangle = 180
# pwm.setRotationAngle(1,beangle)
# #set_servo_angle(channel1,beangle)

# if y0 < changeFreqY:
# beangle0 -= angleFreq
# if beangle0 <= 10:
# beangle0 = 10
# pwm.setRotationAngle(0,beangle0)
# #set_servo_angle(channel2,beangle0)

# if x1 > 640 - changeFreqX: #窗口宽为640
# beangle -= angleFreq
# if beangle <= 10:
# beangle = 10
# pwm.setRotationAngle(1,beangle)
# #set_servo_angle(channel1,beangle)

# if y1 > 480 - changeFreqY: #窗口高为480
# beangle0 += angleFreq
# if beangle0 >= 85:
# beangle0 = 85
# pwm.setRotationAngle(0,beangle0)
# set_servo_angle(channel2,beangle0)
# print("beangle",beangle,"beangle0:",beangle0)
except ValueError as e:
pwm.exit_PCA9685()
print("退出")
print(e)
exit()

运行

1、树莓派上先运行main.py

2、电脑上再运行main.py,电脑上可见一个视频窗口,此时摄像头开始追踪人脸

参考

PID算法:https://pyimagesearch.com/2019/04/01/pan-tilt-face-tracking-with-a-raspberry-pi-and-opencv/

思路:https://blog.csdn.net/rikeilong/article/details/126446567?spm=1001.2014.3001.5502