樹莓派救援機器人制作

2020-10-05 12:00:25

前言:

利用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裡面,這卻未能達到理想效果,因此這是一個待改進的地方。