前言:
利用APP inventor构建一个APP作为客户端程序,利用Flask框架结合树莓派构建一个服务器端程序,两者间通信,制作出一个木质外壳结构、带有摄像头和机械臂,同时具备人脸检测和红外目标搜索功能的救援机器人。
材料准备:
横截面为边长1.5cm正方形的木条若干米、树莓派4B、BST-4WD拓展板、金属TT电机X4、金属舵机及必要配件X6、12.6V动力锂电池、3D打印齿轮X8、PCA9685舵机驱动板、人体热释红外传感器、手机X2、杜邦线若干条、废弃瓶盖若干
硬件结构:
软件原理:
实物图:
实现功能:
1.通过点击APP上的方向按钮和速度调节滑动条来操纵机器人前、后、左、右、转向的运动以及速度调节。
2.通过点击APP的上摄像头云台控制按钮实现对摄像头方向的水平和垂直调节,并通过APP图像显示区域实时显示opencv采集并处理过的视频流,如果检测到人脸则对人脸进行矩形框标记,从而实现对环境和人脸的感知。
3.通过点击人脸检测按钮,opencv采集单张图像,然后调用百度人脸检测接口进行人脸检测,将返回的数据处理后发送到手机,最终实现在信息显示框查看年龄、性别、表情、是否佩戴口罩、配戴眼镜类型等检测数据,APP调用百度语音合成接口朗读以上数据的效果。
4.点击红外目标搜索按钮,开始进行生命体搜索,如果搜索到红外目标则APP语音合成提示信息。
5.通过点击机械臂控制按钮实现对4自由度机械臂的控制,从而达到机械臂抓取物体并放置到车体上带回的目的。
程序实现:
树莓派Python代码:
# main.py
from flask import Flask, render_template, Response,request
from camera import VideoCamera
from urllib.parse import urlencode
import urllib
import RPi.GPIO as GPIO
import Adafruit_PCA9685
import requests
import base64
#引脚定义
left_moto1=20
left_moto2=21
left_pwm=16
right_moto1=19
right_moto2=26
right_pwm=13
hongwai_pin=22
#变量定义
speed=0
pwm_left=None
pwm_right=None
servo_min = 150
servo_max = 600
pwm_servo=None
face_check_flag='0'
#图片存储路径
pic_path='/home/pi/wifi_car/test.jpg'
#百度AI appkey secretkey
ak="qTKX7mY59YeZ1GfiW0HYv1mK"
sk="UHu5yYuQahn7L4DGxPYhi1WL6v5tjnXm"
data_str='收到此检测消息表明人脸检测功能正常,请正式开始使用!'
#初始化函数
def init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(left_pwm,GPIO.OUT,initial=GPIO.HIGH)
GPIO.setup(left_moto1,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(left_moto2,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(right_pwm,GPIO.OUT,initial=GPIO.HIGH)
GPIO.setup(right_moto1,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(right_moto2,GPIO.OUT,initial=GPIO.LOW)
GPIO.setup(hongwai_pin,GPIO.IN)
global pwm_left
global pwm_right
pwm_left = GPIO.PWM(left_pwm, 2000)
pwm_right = GPIO.PWM(right_pwm, 2000)
global pwm_servo
pwm_servo = Adafruit_PCA9685.PCA9685()
pwm_servo.set_pwm_freq(60)
#前进函数
def car_forward():
GPIO.output(left_moto1,GPIO.HIGH)
GPIO.output(left_moto2,GPIO.LOW)
GPIO.output(right_moto1,GPIO.HIGH)
GPIO.output(right_moto2,GPIO.LOW)
pwm_left.start(speed)
pwm_right.start(speed)
#后退函数
def car_back():
GPIO.output(left_moto1,GPIO.LOW)
GPIO.output(left_moto2,GPIO.HIGH)
GPIO.output(right_moto1,GPIO.LOW)
GPIO.output(right_moto2,GPIO.HIGH)
pwm_left.start(speed)
pwm_right.start(speed)
#左转函数
def car_left():
GPIO.output(left_moto1,GPIO.LOW)
GPIO.output(left_moto2,GPIO.HIGH)
GPIO.output(right_moto1,GPIO.HIGH)
GPIO.output(right_moto2,GPIO.LOW)
pwm_left.start(speed)
pwm_right.start(speed)
#右转函数
def car_right():
GPIO.output(left_moto1,GPIO.HIGH)
GPIO.output(left_moto2,GPIO.LOW)
GPIO.output(right_moto1,GPIO.LOW)
GPIO.output(right_moto2,GPIO.HIGH)
pwm_left.start(speed)
pwm_right.start(speed)
#停止函数
def car_stop():
GPIO.output(left_moto1,GPIO.LOW)
GPIO.output(left_moto2,GPIO.LOW)
GPIO.output(right_moto1,GPIO.LOW)
GPIO.output(right_moto2,GPIO.LOW)
#获取百度AI access_token
def getAccess_token(AK,SK):
host = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id="+AK+"&client_secret="+SK
response = requests.get(host)
access_token=''
if response:
dict=response.json()
access_token=dict.get("access_token","none")
#print(dict.get("access_token","none"))
return access_token
#图片进行base64编码函数
def Base64(img_path):
with open(img_path, 'rb') as f:
image_data = f.read()
base64_data = base64.b64encode(image_data) # base64编码
string=str(base64_data,"utf-8")
# print(string)
return string
#请求数据函数
def request_post(base64_code,access_token):
request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect"
#请求参数 年龄 性别 表情 口罩 眼镜
params={ 'image':''+base64_code+'','image_type':'BASE64','face_field':'age,gender,expression,mask,glasses'}
params=urlencode(params)
request_url = request_url + "?access_token=" + access_token
request = urllib.request.Request(url=request_url,data=params.encode("utf-8"))
request.add_header('Content-Type', 'application/json')
response = urllib.request.urlopen(request)
content = response.read()
return content
#返回数据处理
def baidu_api(path,ak,sk):
global data_str
base64_code = Base64(path)
token=getAccess_token(ak,sk)
data_set=request_post(base64_code,token)
print('**********************')
print(data_set)
print('**********************')
string=bytes.decode(data_set)
#print(string)
dict_data=eval(string)
dict_data2=dict_data.get("result","none")
dict_data3=dict_data2.get("face_list","none")
dict_data4=dict_data3[0]
age=dict_data4.get("age","none")
age_str="年龄:"+str(age)+","
print(age_str)
# beauty=dict_data4.get("beauty","none")
# beauty_str="beauty:"+str(beauty)
# print(beauty_str)
gender=dict_data4.get("gender","none").get("type","none")
gender_str="性别:"+str(gender)+","
print(gender_str)
glasses=dict_data4.get("glasses","none").get("type","none")
glasses_str="眼镜类型:"+str(glasses)+","
print(glasses_str)
mask=dict_data4.get("mask","none").get("type","none")
mask_str="是否佩戴口罩:"+str(mask)
print(mask_str)
expression=dict_data4.get("expression","none").get("type","none")
expression_str="表情:"+str(expression)+","
print(expression_str)
data_str=age_str+gender_str+glasses_str+expression_str+mask_str
#flask
app = Flask(__name__)
#默认路由
@app.route('/')
def index():
return render_template('index.html')
def gen(camera):
global face_check_flag
while True:
if face_check_flag=='1':
camera.save_pic()#保存图像
print("save pic OK")
baidu_api(pic_path,ak,sk)
face_check_flag='0'
frame = camera.get_frame()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
#获取视频流路由
@app.route('/video_feed')
def video_feed():
return Response(gen(VideoCamera()),
mimetype='multipart/x-mixed-replace; boundary=frame')
#人脸检测路由
@app.route('/face_check',methods=['GET'])
def face_check():
global face_check_flag
data=request.args.get('data')
print('The data is :',data)
#print("Type is :",type(data))
face_check_flag=data
return data_str
#运动控制路由
@app.route('/sport',methods=['GET'])
def sport():
data=request.args.get('data')
if data=='forward':
car_forward()
if data=='back':
car_back()
if data=='left':
car_left()
if data=='right':
car_right()
if data=='stop':
car_stop()
print("the data is :",data)
#print(type(data))
return 'Sport OK'
#速度调节路由
@app.route('/speed',methods=['GET'])
def getSpeed():
data=request.args.get('data')
global speed
speed=float(data)
return 'Speed OK'
#舵机1控制路由
@app.route('/servo1',methods=['GET'])
def getServo1():
data=request.args.get('data')
angle=int(data)
servo_val=int(450/270*angle)+150
pwm_servo.set_pwm(1,0,servo_val)
return 'Servo1 OK'
#舵机2控制路由
@app.route('/servo2',methods=['GET'])
def getServo2():
data=request.args.get('data')
angle=int(data)
servo_val=int(450/270*angle)+150
pwm_servo.set_pwm(2,0,servo_val)
return 'Servo2 OK'
#舵机3控制路由
@app.route('/servo3',methods=['GET'])
def getServo3():
data=request.args.get('data')
angle=int(data)
servo_val=int(450/270*angle)+150
pwm_servo.set_pwm(3,0,servo_val)
return 'Servo3 OK'
#舵机4控制路由
@app.route('/servo4',methods=['GET'])
def getServo4():
data=request.args.get('data')
angle=int(data)
servo_val=int((servo_max-servo_min)/270*angle)+150
pwm_servo.set_pwm(4,0,servo_val)
return 'Servo4 OK'
#摄像头云台水平调节路由
@app.route('/camera_horizon',methods=['GET'])
def get_cam_horizon():
data=request.args.get('data')
angle=int(data)
servo_val=int(450/270*angle)+150
pwm_servo.set_pwm(5,0,servo_val)
return 'camera_horizon OK'
#摄像头云台垂直调节路由
@app.route('/camera_vertical',methods=['GET'])
def get_cam_vertical():
data=request.args.get('data')
angle=int(data)
servo_val=int(450/270*angle)+150
pwm_servo.set_pwm(6,0,servo_val)
return 'camera_vertical OK'
#红外检测路由
@app.route('/hongwai',methods=['GET'])
def hongwai():
if GPIO.input(hongwai_pin)==True:
print("hongwai_OK")
return "hongwaiok"
else:
print("hongwai_ERROR")
return "hongwaierror"
if __name__ == '__main__':
#初始化函数调用
init()
#flask运行
app.run(host='192.168.43.180' ,port=8123, debug=True)
# camera.py
import cv2 as cv
#IP摄像头地址
camera_url='http://admin:admin@192.168.43.73:8081'
class VideoCamera(object):
#实例视频流获取对象
def __init__(self):
self.video = cv.VideoCapture(camera_url)
def __del__(self):
self.video.release()
#图像保存函数
def save_pic(self):
ret, image = self.video.read()
cv.imwrite('/home/pi/wifi_car/test.jpg',image)
#获取视频流帧 处理
def get_frame(self):
success, frame = self.video.read()
gray=cv.cvtColor(frame,cv.COLOR_BGR2GRAY)
#opencv级联分类器检测
face_cascade = cv.CascadeClassifier("data/haarcascade_frontalface_alt.xml")
faces=face_cascade.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=4,flags=cv.CASCADE_SCALE_IMAGE,minSize=(100, 100),maxSize=(250,250))
# print(faces)
#矩形框标记人脸
for (x,y,w,h) in faces:
frame= cv.rectangle(frame,(x,y),(x+w,y+h),(255,255,0),2)
ret, jpeg = cv.imencode('.jpg', frame)
return jpeg.tobytes()
<!--index.html 视频显示页面-->
<html>
<head>
<title>Video Streaming Demonstration</title>
</head>
<body>
<img src="{ { url_for('video_feed') }}" width="100%" height="120%">
</body>
</html>
APP inventor代码块(部分):
结束语:
受树莓派引脚和拓展板的限制,加装更多的传感器很不方便,在Arduino上安装传感器,利用串口将数据发送给树莓派理论上应当可行,但是在实际的编程中要将读取功能放在Flask里面,这却未能达到理想效果,因此这是一个待改进的地方。