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的上傳步驟,將程式上傳

自動拍照與存檔

連接感應器

將人體感測器與ESP32-CAM連接,

VCC->5V或3.3V

OUT->IO13

GND->GND

紅外線感應簡易測試:https://t.ly/oQxL

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時間作為檔名,就不會覆蓋。請自行修改

ESP32錄影

下載範例程式:https://t.ly/MvPJ

關閉錄影:http://ip/stop

ftp下載檔案:開啟檔案總管,輸入ftp://esp:esp@ip,帳號密碼修改程式碼336

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

https://youyouyou.pixnet.net/blog/post/120801237

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到下一個中斷

中斷:在程式前方點紅色小點,執行會暫停

監看:觀察變數 現在狀態與數值

設定執行組態

設定組態,以後就不會問

  1. 點選左上新增組態

  2. 會開啟預設的lunch.json

  3. 直接存檔即可

使用自己的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

https://youyouyou.pixnet.net/blog/post/120778494-rtsp-esp32cam-qnap%E7%9B%A3%E8%A6%96%E5%99%A8-%E9%8C%84%E5%BD%B1%E6%B8%AC%E8%A9%A6

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()