AIoT Esp32cam教師研習
開發環境設定
下載Arduino IDE https://www.arduino.cc/en/Main/Software 點選zip免安裝包
下載ESP32核心套件 https://dl.espressif.com/dl/package_esp32_index.json 1.0.4版
設定Arduino IDE for ESP32
簡單測試程式:HelloWorld
ESP32函式庫安裝
USB-驅動安裝
下載驅動程式:
CH340:https://bit.ly/2HcGi3g
CP210x:Arduino/Driver有附帶
或從網路下載:https://t.ly/cD8j
下載後,執行安裝確認無驚嘆號即可
修改開發版設定
ESP32 Wrover Module,此為ESP32通用版本。
修改Port為您電腦的port。
最後修改partition Scheme為Huge APP
程式上傳步驟(CP2102)
拿一條母母對接IO0+GND
按Rst一下,此時進入燒錄(下載)模式
(序列視窗會出現waiting for download)
Arduino IDE按程式上傳
等候上傳完畢
出現Leaving…
拆除IO0
按下rst重開機,此時晶片進入工作模式
等候序列視窗資訊
程式上傳步驟(For CH340)
先開序列通訊視窗
按著Flash按鈕不放,按Rst一下放開,再放開Flash,此時進入燒錄模式
(序列視窗會出現waiting for download或者亂碼...)
Arduino IDE按程式上傳
等候上傳完畢
出現Leaving…
按下RST重開機,此時晶片進入工作模式
等候序列視窗資訊
esp32cam 取消Brownout警告
使用cp2102常會出現Brownout電流不足警告且無法連線wifi,可使用以下程式加入在上傳程式中
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
}
ESP32cam閃光燈測試
void setup() {
// put your setup code here, to run once:
pinMode(4,OUTPUT);//pin4->flashlight
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(4,HIGH);
delay(500);
digitalWrite(4,LOW);
delay(5000);
}
影像伺服器CameraWebServer
開啟範例檔esp32->camera->camerawebserver
修改10及14行
將10註解,14開啟
18、19行修改自己的WIFI設定
依據P.11的上傳步驟,將程式上傳
int IRpin = 13;
void setup() {
Serial.begin(115200);
pinMode(13, INPUT);
}
void loop() {
if (digitalRead(IRpin) == 1) {
//偵測人體感應high時
Serial.println("1");
}
else {
Serial.println("0"); }
delay(500);}
拍照存SD卡
修改WIFI設定
有人經過時會自動拍照並存擋在SD卡
當然也可以上傳Line或其他服務
程式下載:https://t.ly/wvl7
SD卡拔除後,必須重開才會寫入
編號為1,2,3...也可改用UNIX時間作為檔名,就不會覆蓋。請自行修改
Line 通知
連接超音波利用超音波測試距離
echoPin接在GPIO14 trigPin接在GPIO15
超音波測試程式 https://bit.ly/2PR4AB2
每1分鐘(超音波或人體感測),傳送一張照片
串接LINE機器人
LINE的自動化傳訊工具目前來說分成兩種,BOT跟Notify
LINE Notify 製作過程
一、申請LINE Notify服務 https://notify-bot.line.me/zh_TW/
二、取得使用者Token
三、自動化訊息
簡易測試LINE機器人
將LINE機器人加入設定的群組
先到:https://reqbin.com/
1.修改傳遞方式為POST
2.輸入Notify傳訊網址:https://notify-api.line.me/api/notify
3.Post data,輸入message=hello Eric
4.點選Header頁簽
5.新增一個Header參數,請注意Bearer與Token之間有一個空白
超音波+Line通知範例程式
超音波範例程式:https://bit.ly/341s4Mf
紅外線範例程式:https://t.ly/Re6o
請注意,Line接收規定一小時內50張,且最大1024x768
記得改網路設定,並填入Line代號
MQTT串流
範例程式:https://t.ly/u4id
安裝程式庫:PubsubClient (Nick)
手機安裝MQTT APP:MQTT Dash
https://play.google.com/store/apps/details?id=net.routix.mqttdash&hl=zh_TW
AIoT Esp32cam教師研習工作坊(三)新增課程
TensorFlow Lite for Micro
Python環境建構
Anaconda虛擬環境管理
https://www.anaconda.com/products/individual
何謂虛擬環境:程式設計師會執行很多個專案,每一個專案可能都會使用一些不同的開發模組,這些開發模組可能會互相衝突,第二種情況是,程式會改版,而以前寫的程式與現有的版本不相容。
此時我們可以位這些專案建立獨立的開發環境,這些環境完全隔離,便可以避免上述的問題,只要我們在開發專案時,指定使用哪一個環境即可。
新增虛擬環境
點選左側的Enviroment
點選下方的Create
輸入新的環境名稱
例如ESP32CAM
按下Create後
等候一段時間即可看到虛擬環境
Python安裝opencv模組
開啟anaconda環境管理工具
點選右側environment,找到base(root),若你有其他環境,可以自行選擇
點選執行符號▲/open terminal
安裝opencv模組 輸入 pip install opencv-contrib-python --user
完成安裝後,開啟VSCode
安裝VS.code編輯程式
下載:https://code.visualstudio.com/Download
詳細解說:http://blog.tonycube.com/2018/11/visual-studio-code.html
修改語系
ctrl+shift+p 輸入 display language ->修改成 "locale":"zh-TW" 儲存後重開就完成了。
安裝擴充功能:ctrl+shift+p 輸入 ext inst 在 Sidebar 中輸入 zh-TW 選擇 Chinese (Traditional) Language Pack for Visual Studio Code (VS Code 的中文(繁體)語言套件) 按下 Install 然後重載。
安裝python模組及自動建議的其他模組
測試第一支程式
檔案/開啟資料夾,建立一個python程式碼專用資料夾,例如取名為python,然後開啟該資料夾
進入資料夾畫面後,點選新增資料夾作為分類,例如 201908_Basic
在201908_Basic資料夾中新增HelloWorld.py (注意副檔名)
msg1 ="Hello"
msg2 ="World"
print(msg1,msg2)
執行,中斷,監看
按F5即可立即編譯執行,F10下一行(step),F5到下一個中斷
中斷:在程式前方點紅色小點,執行會暫停
監看:觀察變數 現在狀態與數值
設定執行組態
設定組態,以後就不會問
點選左上新增組態
會開啟預設的lunch.json
直接存檔即可
使用自己的webcam測試
Webcam範例程式:https://bit.ly/3h12bj1
Python ESP32CAM範例程式:https://bit.ly/343pB3U
Arduino ESP32CAM範例程式:https://bit.ly/30Xvygx
結合Teachable Meachine AI辨識
先於TM訓練(剪刀,石頭,布),下載訓練後model檔存於程式目錄(keras_model.h5及labels.txt)
Esp32cam上傳之前的影像伺服器CameraWebServer範例程式,接著電腦端執行以下python程式
#TM_python_esp32cam.py
import tensorflow.keras as keras
from PIL import Image, ImageOps, ImageFont , ImageDraw
from urllib.request import urlopen
import numpy as np
import cv2
#顯示判斷結果
font = ImageFont.truetype('msjhbd.ttc', 40)
fillColor = (255,0,0)
# 選擇攝影機
url="http://【IPcamera address】:81/stream"
CAMERA_BUFFRER_SIZE=2048
stream=urlopen(url)
bts=b''
cap = cv2.VideoCapture(0)
np.set_printoptions(suppress=True)
#請自行修改h5檔案位置
model = keras.models.load_model('keras_model.h5')
data = np.ndarray(shape=(1, 224, 224, 3), dtype=np.float32)
while True:
# 從攝影機擷取一張影像
try:
bts+=stream.read(CAMERA_BUFFRER_SIZE)
jpghead=bts.find(b'\xff\xd8')
jpgend=bts.find(b'\xff\xd9')
if jpghead>-1 and jpgend>-1:
jpg=bts[jpghead:jpgend+2]
bts=bts[jpgend+2:]
img=cv2.imdecode(np.frombuffer(jpg,dtype=np.uint8),cv2.IMREAD_UNCHANGED)
#v=cv.flip(img,0)
#h=cv.flip(img,1)
#p=cv.flip(img,-1)
frame=img
h,w=frame.shape[:2]
img=cv2.resize(frame,(640,480))
image = Image.fromarray(frame)
size = (224, 224)
image = ImageOps.fit(image, size, Image.ANTIALIAS)
image_array = np.asarray(image)
normalized_image_array = (image_array.astype(np.float32) / 127.0) - 1
data[0] = normalized_image_array
#預測結果
prediction = model.predict(data)
#顯示結果,請將label內容依照次序排列
maxindex = np.argmax(prediction)#找出機率最大的輸出
if prediction.max()>0.5: #如果最大的機率都<0.5就當作無法判斷
if maxindex==0 :
print("seissor")
showtext="seissor"
elif maxindex==1 :
print("rock")
showtext="rock"
elif maxindex==2 :
print("paper")
showtext="paper"
elif maxindex==3 :
print("none")
showtext="none"
else:
print('無法辨識')
print(prediction)
#在圖上顯示結果
img_PIL = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_PIL)
draw.text((0,0), showtext, font=font, fill=fillColor)
frame = cv2.cvtColor(np.asarray(img_PIL),cv2.COLOR_RGB2BGR)
cv2.imshow('frame', frame)
k=cv2.waitKey(1)
# 按q離開
if k & 0xFF == ord('q'):
break
k=cv2.waitKey(1)
except Exception as e:
print("Error:" + str(e))
bts=b''
stream=urlopen(url)
continue
#cv2.imwrite("photo.jpg", frame)
# 釋放攝影機
cap.release()
# 關閉所有 OpenCV 視窗
cv2.destroyAllWindows()
解決經常出錯卡住狀況問題
以上執行結果如右,雖然成功但經常卡檔3~5秒
Error:OpenCV(4.5.3) C:\Users\runneradmin\AppData\Local\Temp\pip-req-build-u4kjpz2z\opencv\modules\imgcodecs\src\loadsave.cpp:818: error: (-215:Assertion failed) !buf.empty() in function 'cv::imdecode_'
教授建議http比較不穩,可以改rtsp
rtsp在esp32cam端上傳程式
首先連線到函式庫網址:https://github.com/geeksville/Micro-RTSP
下載打包zip後,利用arduino內的功能表/草稿碼/匯入函式庫/加入.zip檔案方式安裝本函式庫。
程式燒錄完畢之後,就開通兩個功能1.web mjpeg串流 及 2.RTSP串流,目前的程式兩者同時只能有一個連入,使用web連上後,rtsp會自己斷掉,當web關閉後,rtsp會重新啟動。
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>
#include "OV2640.h"
#include "SimStreamer.h"
#include "OV2640Streamer.h"
#include "CRtspSession.h"
char *ssid = "ssid"; // Put your SSID here
char *password = "password"; // Put your PASSWORD here
WebServer server(80);
WiFiServer rtspServer(554);
OV2640 cam;
CStreamer *streamer;
//mjpeg串流
void handle_jpg_stream(void)
{
WiFiClient client = server.client();
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
server.sendContent(response);
while (1)
{
cam.run();
if (!client.connected())
break;
response = "--frame\r\n";
response += "Content-Type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
server.sendContent("\r\n");
if (!client.connected())
break;
}
}
//靜態影像
void handle_jpg(void)
{
WiFiClient client = server.client();
cam.run();
if (!client.connected())
{
return;
}
String response = "HTTP/1.1 200 OK\r\n";
response += "Content-disposition: inline; filename=capture.jpg\r\n";
response += "Content-type: image/jpeg\r\n\r\n";
server.sendContent(response);
client.write((char *)cam.getfb(), cam.getSize());
}
//錯誤處理
void handleNotFound() {
String message = "Server is running!\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
server.send(200, "text/plain", message);
}
//WiFi連線
void WifiConnecte() {
//開始WiFi連線
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi連線成功");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
}
void setup()
{
Serial.begin(115200);
//設定影像大小:UXGA(1600x1200),SXGA(1280x1024),XGA(1024x768),SVGA(800x600),VGA(640x480),CIF(400x296),QVGA(320x240),HQVGA(240x176),QQVGA(160x120)
esp32cam_aithinker_config.frame_size = FRAMESIZE_UXGA;
esp32cam_aithinker_config.jpeg_quality = 10;
cam.init(esp32cam_aithinker_config);
//開始WiFi連線
WifiConnecte();
server.on("/", HTTP_GET, handle_jpg_stream);
server.on("/jpg", HTTP_GET, handle_jpg);
server.onNotFound(handleNotFound);
server.begin();
rtspServer.begin();
streamer = new OV2640Streamer(cam);//啟動服務
Serial.println("WiFi connected");
Serial.print("Use 'http://");
Serial.print(WiFi.localIP());
Serial.println("/' to watch mjpeg stream");
Serial.print("Use 'http://");
Serial.print(WiFi.localIP());
Serial.println("/jpg' to see still jpg");
Serial.print("Use RTSP:'");
Serial.print(WiFi.localIP());
Serial.println("', URL:'/mjpeg/1' and port:554 to start rtsp stream");
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
WifiConnecte();
}
server.handleClient();
uint32_t msecPerFrame = 100;
static uint32_t lastimage = millis();
// If we have an active client connection, just service that until gone
streamer->handleRequests(0); // we don't use a timeout here,
// instead we send only if we have new enough frames
uint32_t now = millis();
if (streamer->anySessions()) {
if (now > lastimage + msecPerFrame || now < lastimage) { // handle clock rollover
streamer->streamImage(now);
lastimage = now;
// check if we are overrunning our max frame rate
now = millis();
if (now > lastimage + msecPerFrame) {
printf("warning exceeding max frame rate of %d ms\n", now - lastimage);
}
}
}
WiFiClient rtspClient = rtspServer.accept();
if (rtspClient) {
Serial.print("client: ");
Serial.print(rtspClient.remoteIP());
Serial.println();
streamer->addSession(rtspClient);
}
}
在電腦端的python程式是個人自行修改,測試正常
#自行測試修改RTSP TM_python_esp32camRTSP.py
import tensorflow.keras as keras
from PIL import Image, ImageOps, ImageFont , ImageDraw
import numpy as np
from cv2 import cv2
#顯示判斷結果
font = ImageFont.truetype('msjhbd.ttc', 40)
fillColor = (255,0,0)
# 選擇攝影機
CAMERA_BUFFRER_SIZE=2048
vcap = cv2.VideoCapture('rtsp://【rtsp address】/mjpeg/1')
np.set_printoptions(suppress=True)
#請自行修改h5檔案位置
model = keras.models.load_model('keras_model.h5')
data = np.ndarray(shape=(1, 224, 224, 3), dtype=np.float32)
while(1):
try:
ret, frame = vcap.read()
h,w=frame.shape[:2]
img=cv2.resize(frame,(640,480))
image = Image.fromarray(frame)
size = (224, 224)
image = ImageOps.fit(image, size, Image.ANTIALIAS)
image_array = np.asarray(image)
normalized_image_array = (image_array.astype(np.float32) / 127.0) - 1
data[0] = normalized_image_array
#預測結果
prediction = model.predict(data)
#顯示結果,請將label內容依照次序排列
maxindex = np.argmax(prediction)#找出機率最大的輸出
if prediction.max()>0.5: #如果最大的機率都<0.5就當作無法判斷
if maxindex==0 :
print("seissor")
showtext="seissor"
elif maxindex==1 :
print("rock")
showtext="rock"
elif maxindex==2 :
print("paper")
showtext="paper"
elif maxindex==3 :
print("none")
showtext="none"
else:
print('無法辨識')
print(prediction)
#在圖上顯示結果
img_PIL = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_PIL)
draw.text((0,0), showtext, font=font, fill=fillColor)
frame = cv2.cvtColor(np.asarray(img_PIL),cv2.COLOR_RGB2BGR)
cv2.imshow('frame', frame)
k=cv2.waitKey(1)
# 按q離開
if k & 0xFF == ord('q'):
break
k=cv2.waitKey(1)
except Exception as e:
print("Error:" + str(e))
vcap = cv2.VideoCapture('rtsp://10.203.62.69/mjpeg/1')
continue
k=cv2.waitKey(1)
# 按a拍照存檔
if k & 0xFF == ord('a'):
cv2.imwrite('test.jpg', frame)
# 按q離開
if k & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
# 釋放攝影機
vcap.release()
# 關閉所有 OpenCV 視窗
cv2.destroyAllWindows()