banner
Vinking

Vinking

你写下的每一个BUG 都是人类反抗被人工智能统治的一颗子弹

樹莓派、早安、我和夏天

前段時間買了個樹莓派 4B 回來做畢業設計,畢業答辯過了之後樹莓派就閒置了下來。除了搭建一個局域網雲盤以外,想着之前學過一段時間的 OpenCV,就買了個攝像頭和人體傳感器回來接在樹莓派上面做了個簡單的早安推送功能,可以每天早上在桌子前坐下的時候微信推送一條早安問候還有天氣預報。

早安問候 & 天氣預報

總體的設計思路是每天早上七點樹莓派運行 py 腳本,通過人體傳感器檢測有沒有人在桌子前,如果有人就打開攝像頭去判斷是不是本人,是本人就給微信推送消息。思路很簡單,代碼寫下來也很順利沒有遇到什麼大的問題,就只有接人體傳感器的時候正負極不小心接反了燒壞了一個人體傳感器 + 燙了一下手∠(ᐛ」∠)_

Note

前期準備

🖥️ 一塊安裝好 OpenCV + Mediapipe 環境的樹莓派

📹 一個攝像頭

✋ 一個人體傳感器 (或者更多)

🔌 若干條母對母杜邦線

🎤 一個搭建了 Wecom 醬 的伺服器(或者也可以在樹莓派裡面搭建,隨意)

人體傳感器#

人體傳感器淘寶上隨便一個都可以,但是推薦買感應距離小一點的(大概 1 米左右),因為這裡買的傳感器是感應距離是 5 到 10 米,即使調到最小的感應距離,日常使用下來還是有一點點的太過靈敏了。

樹莓派引腳圖 & 人體傳感器引腳

人體傳感器的 VCC 引腳接樹莓派的 4 號 5V 引腳、GND 引腳接樹莓派的 6 號 GND 引腳、至於 OUT 引腳可以接在樹莓派的任何 GPIO 引腳,這裡選擇接在 38 號的 GPIO.28 引腳。

Important

注意不要把 VCC 和 GND 給接反了,否則直接燒壞傳感器。VCC 引腳接樹莓派 3.3V 引腳還是 5V 引腳看傳感器的工作電壓。

傳感器接線

接著把下面代碼扔進樹莓派裡面運行一下:

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM) #引腳編號設置為 BCM 模式
GPIO.setup(20, GPIO.IN) #將 GPIO.28 (BCM 編號是 20) 設置為輸入模式
while True:
    if GPIO.input(20):
        print("HIGH")
    elif GPIO.input(20) == 0:
        print("LOW")
    time.sleep(1)

如果一切順利,當把手放到人體傳感器前面的時候會輸出 HIGH,而且會 HIGH 一段時間(傳感器設置了延時),沒有檢測到的話就輸出 LOW。這樣的話第一步調試人體傳感器的工作就完成了。

人體傳感器輸出

人臉識別的前期準備#

人臉識別方面採用 OpenCV + Mediapipe 抓取攝像的人臉圖像,再把抓取到的人臉通過 API 上傳到旷視的 Face++ 平台 進行人臉識別得到結果。

Face++ 人臉檢測#

首先準備一張自拍的照片用來做數據庫的人臉,通過 multipart/form-data 方法 POST 調用 人臉檢測 API 得到 face_token。face_token 代表這張圖片的每個檢測出來的人臉,具有唯一性,後續可以用來進行人臉對比。

import urllib.request
import urllib.error
import time
import json

def uploadImage(postURL,imgURL):
    #構建 multipart/form-data 請求體
    border = '----------%s' % hex(int(time.time() * 1000))
    postData = []
    postData.append('--%s' % border)
    postData.append('Content-Disposition: form-data; name="%s"\r\n' % 'api_key')
    postData.append('XXXXXXXXXXX') #api_key
    postData.append('--%s' % border)
    postData.append('Content-Disposition: form-data; name="%s"\r\n' % 'api_secret')
    postData.append('XXXXXXXXXXX') #api_secret
    postData.append('--%s' % border)
    fr = open(imgURL, 'rb')
    postData.append('Content-Disposition: form-data; name="%s"; filename=" "' % 'image_file')
    postData.append('Content-Type: %s\r\n' % 'application/octet-stream')
    postData.append(fr.read())
    fr.close()
    postData.append('--%s--\r\n' % border)
    for i, d in enumerate(postData):
        if isinstance(d, str):
            postData[i] = d.encode('utf-8')
    http_body = b'\r\n'.join(postData)
    req = urllib.request.Request(url=postURL, data=http_body)
    req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % border)
    try:
        res = urllib.request.urlopen(req, timeout=5)
        qrcont = res.read()
        resJson = json.loads(qrcont.decode('utf-8'))
        print(resJson['faces'][0]['face_token'])
    except urllib.error.HTTPError as e:
        print(e.read().decode('utf-8'))
        pass

uploadImage(postURL='https://api-cn.faceplusplus.com/facepp/v3/detect',imgURL='./face.jpg')

拿到 face_token 之後,由於我們並沒有將其存放在 faceset 中,72 小時之後會自動失效。所以下一步需要創建一個人臉庫用來存放 face_token 以便長期使用。

Face++ 創建人臉庫#

通過 POST 方法調用創建 人臉庫 API 即可創建人臉庫。

import requests

CreatFacesetdata = {
    'api_key': 'XXXXXXXXXXX', #api_key
    'api_secret': 'XXXXXXXXXXX' #api_secret
}
CreatFacesetRes = requests.post('https://api-cn.faceplusplus.com/facepp/v3/faceset/create', data=CreatFacesetdata)
CreatFacesetResJson = CreatFacesetRes.json()
faceset_token = CreatFacesetResJson['faceset_token']
print(faceset_token)

得到 faceset_token 後就可以向人臉庫添加人臉了。

Face++ 添加人臉#

同樣的,向人臉庫添加人臉同樣非常簡單,與上面的步驟無異。

import requests

UploadFacedata = {
    'api_key': 'XXXXXXXXXXX', #api_key
    'api_secret': 'XXXXXXXXXXX', #api_secret
    'faceset_token': 'XXXXXXXXXXX', #faceset_token
    'face_tokens': 'XXXXXXXXXXX' #face_token
 }
 UploadFaceRes = requests.post('https://api-cn.faceplusplus.com/facepp/v3/faceset/addface', data=UploadFacedata)
 UploadFaceResJson = UploadFaceRes.json()
 print(UploadFaceResJson)

至此人臉識別的前期準備就已經完成, Face++ 的文檔中心 有各種 API 更詳細的介紹,具體的使用方法可以點進去看看。

主程序#

在獲取天氣方面,這裡使用的是 和風天氣的實時天氣 API。通過向 https://devapi.qweather.com/v7/weather/now?location={城市 ID}&key={key} 發送 GET 請求獲取天氣狀況。人臉比對通過調用 人臉比對 API 實現,因為同樣需要上傳文件,所以還是使用 multipart/form-data 方法 POST 調用 API。完整的程序如下:

import cv2
import time
import mediapipe as mp
import RPi.GPIO as GPIO
import requests
import urllib.request
import urllib.error
import time
import datetime
import json
import os

GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.IN)

def getWeather(location, Key):
    # 獲取天氣
    weatherData = requests.get(
        'https://devapi.qweather.com/v7/weather/now?location=' + location + '&key=' + Key
    )
    weatherJson = weatherData.json()
    nowTemperature = weatherJson['now']['temp']
    nowWeather = weatherJson['now']['text']
    nowFeelsLike = weatherJson['now']['feelsLike']
    nowWindDir = weatherJson['now']['windDir']
    nowWindScale = weatherJson['now']['windScale']
    return nowWeather, nowFeelsLike, nowTemperature, nowWindDir, nowWindScale

def bodyCheak():
    # 人體傳感器信息
    if GPIO.input(20):
        return 1
    else:
        return 0

def faceCompare(imgURL):
    # 人臉比對
    border = '----------%s' % hex(int(time.time() * 1000))
    postData = []
    postData.append('--%s' % border)
    postData.append('Content-Disposition: form-data; name="%s"\r\n' % 'api_key')
    postData.append('XXXXXXXXXXX') # api_key
    postData.append('--%s' % border)
    postData.append('Content-Disposition: form-data; name="%s"\r\n' % 'api_secret')
    postData.append('XXXXXXXXXXX') # api_secret
    postData.append('--%s' % border)
    fr = open(imgURL, 'rb')
    postData.append('Content-Disposition: form-data; name="%s"; filename=" "' % 'image_file1')
    postData.append('Content-Type: %s\r\n' % 'application/octet-stream')
    postData.append(fr.read())
    fr.close()
    postData.append('--%s' % border)
    postData.append('Content-Disposition: form-data; name="%s"\r\n' % 'face_token2')
    postData.append('XXXXXXXXXXX') # face_token
    postData.append('--%s--\r\n' % border)
    for i, d in enumerate(postData):
        if isinstance(d, str):
            postData[i] = d.encode('utf-8')
    http_body = b'\r\n'.join(postData)
    req = urllib.request.Request(
        url='https://api-cn.faceplusplus.com/facepp/v3/compare',
        data=http_body
    )
    req.add_header('Content-Type', 'multipart/form-data; boundary=%s' % border)
    try:
        res = urllib.request.urlopen(req, timeout=5)
        qrcont = res.read()
        resJson = json.loads(qrcont.decode('utf-8'))
        if 'confidence' in resJson:
            return resJson['confidence'], resJson['thresholds']['1e-5']
        else:
            print('No Face Detected')
            return 0, 100
    except urllib.error.HTTPError as e:
        print(e.read().decode('utf-8'))

weather, feelsLike, temperature, windDir, windScale = getWeather(
    location='XXXXXXXXXXX',
    Key='XXXXXXXXXXX'
)
pushText = 'Vinking,早安~ 今天是' + str(datetime.date.today().year) + '年' \
            + str(datetime.date.today().month) + '月' \
            + str(datetime.date.today().day) + '日' \
            + ',天氣' + weather + ',溫度' + temperature + '°,體感溫度' \
            + feelsLike + '°,吹' + windDir + windScale + '級,新的一天要加油~'

while True:
    if bodyCheak():
        Capture = cv2.VideoCapture(0)
        mp_face_mesh = mp.solutions.face_mesh
        faceMesh = mp_face_mesh.FaceMesh(max_num_faces=1)
        faceDetectionTime, videosTime = 0, 0
        while True:
            success, videos = Capture.read()
            imgRGB = cv2.cvtColor(videos, cv2.COLOR_BGR2RGB)
            results = faceMesh.process(imgRGB)
            faces = []
            if results.multi_face_landmarks:
                for faceLms in results.multi_face_landmarks:
                    face = []
                    for id, lm in enumerate(faceLms.landmark):
                        ih, iw, ic = videos.shape
                        x, y = int(lm.x * iw), int(lm.y * ih)
                        # 臉左輪廓特徵點
                        faceLeft = faceLms.landmark[234]
                        faceLeft_X = int(faceLeft.x * iw)
                        # 臉上輪廓特徵點
                        faceTop = faceLms.landmark[10]
                        faceTop_Y = int(faceTop.y * ih)
                        # 臉右輪廓特徵點
                        faceRight = faceLms.landmark[454]
                        faceRight_X = int(faceRight.x * iw)
                        # 臉下輪廓特徵點
                        faceBottom = faceLms.landmark[152]
                        faceBottom_Y = int(faceBottom.y * ih)
                        face.append([x, y])
                    faces.append(face)
            videosTime += 1
            if len(faces) != 0:
                videosTime = 0
                faceDetectionTime += 1
            if faceDetectionTime >= 20:
                cv2.imwrite("./Face.jpg", videos[faceTop_Y: faceBottom_Y, faceLeft_X: faceRight_X])
                confidence, thresholds = faceCompare(imgURL='./Face.jpg')
                if confidence >= thresholds:
                    print('Success')
                    requests.get(
                        # 微信推送
                        'https://speak.vinking.top/?text=[早安推送]' + pushText
                    )
                    if os.path.exists('./Face.jpg'):
                        os.remove('./Face.jpg')
                    Capture.release()
                    cv2.destroyAllWindows()
                    GPIO.cleanup()
                    exit(0)
                else:
                    print('False')
                    if os.path.exists('./Face.jpg'):
                        os.remove('./Face.jpg')
                    break
            elif videosTime >= 60:
                print('Timeout')
                break
        Capture.release()
    time.sleep(1)

要想每天七點定時執行,只需要輸入 crontab -e 命令,在文件裡面輸入 0 7 * * * python3 {文件路徑} 然後 Ctrl + O 保存,Ctrl + X 退出即可。

最後#

一直以來都想要一整套智能家居,但是價格有一點點貴,所以只能自己換個方式來實現類似功能了,而且當功能成功跑起來的時候還是非常有成就感的,或許也是一種直接買回來體驗不到的快樂吧。

Ps. 如果想同時控制智能家居(例如小米智能插座)開啟關閉的話,這裡找到一個 小米智能設備的 python 庫,或許可以實現類似功能。

此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.vinking.top/posts/daily/raspberry-pi-morning-alarm-weather-forecast

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。