# 電路學期末專題- Low-cost Facial Recognition Doorlock System ## 功能介紹 1. 透過AMB82開發版可連網的特性,可於同區域網內時實查看錄像。 2. 電磁鎖解鎖(開門),可以透過以下兩種方法。 * 第一種方法為"臉部辨識解鎖"。AMB82本就是Realtek專門設計來跑相關辨識模型的開發版,透過跑臉部辨識模型,將實時影像與資料庫做2D圖像對比,如果比對正確,則LCD會顯示"Welcome Home!",電磁鎖會開啟3s,並於3s後重新上鎖,如比對錯誤,則不會作動。 * 第二種方法則是"密碼解鎖"。輸入密碼後,會進行密碼比對,若正確會於LCD顯示 "Welcome Home!",電磁鎖會開啟3s,並於3s後重新上鎖,如比對錯誤則會顯示"Wrong Password!",並於3s後可重新輸入。 * 電磁鎖開啟時外部綠色LED會亮,關閉時則紅色LED會亮。 * 以上兩種解鎖方法只能擇一進行,不可同時! ## 所需材料 1. Realtek AMB82-mini AI Camera:[link](https://www.icshop.com.tw/product-page.php?29111) ![](https://i.imgur.com/frY9A8n.png) 2. Arduino Uno-自備 ![](https://i.imgur.com/qRizk9S.png) 4. 電磁鎖:[link](https://shopee.tw/%E2%96%BA1528%E2%97%846V-12V%E5%B0%8F%E5%9E%8B%E5%BE%AE%E5%9E%8B%E6%AB%83%E9%96%80%E9%9B%BB%E6%8E%A7%E9%8E%96-%E9%9B%BB%E7%A3%81%E9%8E%96-%E9%9B%BB%E6%8F%92%E9%8E%96-%E9%9B%BB%E5%AD%90%E9%8E%96-i.4877344.2268211356?sp_atk=a4bbb49d-50f4-4e19-b6ee-c2102e5011d2&xptdk=a4bbb49d-50f4-4e19-b6ee-c2102e5011d2) * 觸發電源大小:6V/0.6A ![](https://i.imgur.com/RyvMhLl.png) 5. 繼電器:[link](https://shopee.tw/Relay-1%E8%B7%AF-5V-12V-24V-%E7%B9%BC%E9%9B%BB%E5%99%A8%E6%A8%A1%E7%B5%84-%E6%95%B8%E4%BD%8D%E8%A8%8A%E8%99%9F-%E4%B8%80%E4%BD%8D-%E6%90%AD%E8%BC%89%E5%85%89%E8%80%A6-%E9%9B%BB%E6%BA%90%E9%9A%94%E9%9B%A2-%E9%AB%98%E9%9B%BB%E4%BD%8D%E8%A7%B8%E7%99%BC-%E4%BD%8E%E9%9B%BB%E4%BD%8D%E8%A7%B8%E7%99%BC%E5%8F%AF%E8%AA%BF-i.656213378.15057338988?sp_atk=faa60d40-24c8-462b-8467-1e41da22cb2f&xptdk=faa60d40-24c8-462b-8467-1e41da22cb2f) ![](https://i.imgur.com/2xQN1vN.png) 6. 杜邦線、跳線數條-自備 ![](https://i.imgur.com/Lb6j15P.png) 8. 麵包板-自備 ![](https://i.imgur.com/UM1tV4b.png) 10. 電磁鎖電源供應:[link](https://shopee.tw/%E3%80%90%E7%94%B0%E6%95%85%E9%87%8E%E3%80%91AC-%E6%95%B4%E6%B5%81%E8%AE%8A%E5%A3%93%E5%99%A8%E9%81%A9%E9%85%8D%E5%99%A8-DC-12V-500mA-1A-1.5A-2A-%E9%96%8B%E9%97%9C%E9%9B%BB%E6%BA%90%E4%BE%9B%E6%87%89%E5%99%A8-US-4.0mm-*-i.795509971.19873883251?sp_atk=8eeafa79-884b-41e2-bb80-49c78e874eff&xptdk=8eeafa79-884b-41e2-bb80-49c78e874eff) ![](https://i.imgur.com/IbWKqAp.png) 9. AMB82 電源供應:行動電源 or 5V 1A 插座 10. DC母頭:[link](https://shopee.tw/%E3%80%8A597%E3%80%8BDC%E6%AF%8D%E9%A0%AD-%E7%9B%A3%E6%8E%A7-%E6%94%9D%E5%BD%B1%E6%A9%9F%E9%9B%BB%E6%BA%90%E6%8E%A5%E9%A0%AD-%E5%85%8D%E7%84%8A%E6%8E%A5-12V%E5%85%AC%E6%8F%92%E9%A0%AD-%E8%9E%BA%E7%B5%B2%E9%9B%BB%E6%BA%90%E6%8F%92%E9%A0%AD-i.105691234.1707075293?sp_atk=5efa8450-7578-425f-97fb-5ce10634ca66&xptdk=5efa8450-7578-425f-97fb-5ce10634ca66) ![](https://i.imgur.com/41s1Fgh.png) 11. LCD模組with i2c included:[link](https://shopee.tw/LCD1602%E9%A1%AF%E7%A4%BA%E6%A8%A1%E7%B5%84-I2C%E4%BB%8B%E9%9D%A2-16x2%E8%97%8D%E5%BA%95%E7%99%BD%E5%AD%97%E6%B6%B2%E6%99%B6LCD-%E5%B7%B2%E7%84%8A%E5%A5%BDIIC%E8%BD%89%E6%8E%A5%E6%9D%BFPCF8574-%E9%81%A9Arduino-%E6%A8%B9%E8%8E%93-i.4491023.2503817529?sp_atk=090870b5-4a8d-4ce3-955a-1cbbb92cd63f&xptdk=090870b5-4a8d-4ce3-955a-1cbbb92cd63f) ![](https://i.imgur.com/CVo8IEk.png) 12. 3x3 keypad:[link](https://shopee.tw/%E8%B6%85%E5%A4%A7%E6%8C%89%E9%8D%B5-1*4-3*4-4*4-4*5-%E7%9F%A9%E9%99%A3%E9%8D%B5%E7%9B%A4%E5%A4%96%E6%93%B4%E8%96%84%E8%86%9C%E9%8D%B5%E7%9B%A4%E5%96%AE%E7%89%87%E6%A9%9F%E6%8C%89%E9%8D%B5%E6%A8%A1%E5%A1%8A-i.311618785.22241732309?sp_atk=09e520c3-1aa2-4fe0-8b5d-3b8a13d1ef57&xptdk=09e520c3-1aa2-4fe0-8b5d-3b8a13d1ef57) ![](https://i.imgur.com/fw6HPkV.png) 13. LED-自備 ![Imgur](https://i.imgur.com/R4M9rKT.png) 15. 木板數片 ## 電路連接 * 圖片待補 1. Arduino * LCD * VCC->Arduino_5V * GND->Arduino_GND * SDA->Arduino_A4 * SDL->Arduino_A5 * Relay_Module * Positive->Arduino_5V * Negative->Arduino_GND * S(訊號控制線)->Arduino_13 * COM->電源供應 * NumberPad * 面相NumberPad,由左至右插在Arduino_5~Arduino_11 2. AMB82 * GND->Arduino_GND * 8->Arduino_A0 * 3->Led_red * 4->Led_green 3. 電磁鎖 * 記得先試接,確認作動方向。 * GND與DC母頭連接,正極接在繼電器NO(Normal Open)接口。 ## 程式編寫 * 需先設定AMB82環境 <iframe width="560" height="315" src="https://www.youtube.com/embed/PAyg0NEIgYY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> * 需安裝以下函式庫 * [LiquidCrystal_I2C](https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library) * [Keypad](https://github.com/Chris--A/Keypad) * 程式碼 * AMB82 ```arduino= #include "WiFi.h" #include "StreamIO.h" #include "VideoStream.h" #include "RTSP.h" #include "NNFaceDetectionRecognition.h" #include "VideoStreamOverlay.h" #include "AmebaFatFS.h" //鏡頭相關腳位定義 #define CHANNELVID 0 #define CHANNELJPEG 1 #define CHANNELNN 3 //設定臉部辨識解析度 #define NNWIDTH 576 #define NNHEIGHT 320 #define RECTTEXTLAYER0 OSDLAYER0 #define RECTTEXTLAYER1 OSDLAYER1 //腳位定義 #define RED_LED 3 #define GREEN_LED 4 #define DOORLOCK 8 VideoSetting configVID(VIDEO_FHD, 30, VIDEO_H264, 0); VideoSetting configJPEG(VIDEO_FHD, CAM_FPS, VIDEO_JPEG, 1); VideoSetting configNN(NNWIDTH, NNHEIGHT, 10, VIDEO_RGB, 0); NNFaceDetectionRecognition facerecog; RTSP rtsp; StreamIO videoStreamer(1, 1); StreamIO videoStreamerFDFR(1, 1); StreamIO videoStreamerRGBFD(1, 1); char ssid[] = "HaHaPiYan"; //設定 wifi ssid char pass[] = "00000000"; //設定 wifi password int status = WL_IDLE_STATUS; int resultSize = 0; uint32_t img_addr = 0; uint32_t img_len = 0; String fileName; long counter = 0; //檔案初始化 AmebaFatFS fs; void setup() { pinMode(RED_LED, OUTPUT); pinMode(GREEN_LED, OUTPUT); pinMode(DOORLOCK, OUTPUT); Serial.begin(115200); //wifi連線 while (status != WL_CONNECTED) { Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); status = WiFi.begin(ssid, pass); // wait 2 seconds for connection: delay(2000); } //鏡頭初始化 Camera.configVideoChannel(CHANNELVID, configVID); Camera.configVideoChannel(CHANNELJPEG, configJPEG); Camera.configVideoChannel(CHANNELNN, configNN); Camera.videoInit(); rtsp.configVideo(configVID); rtsp.begin(); //臉部辨識模型設定 facerecog.configVideo(configNN); facerecog.modelSelect(FACE_RECOGNITION, NA_MODEL, DEFAULT_SCRFD, DEFAULT_MOBILEFACENET); facerecog.begin(); facerecog.setResultCallback(FRPostProcess); videoStreamer.registerInput(Camera.getStream(CHANNELVID)); videoStreamer.registerOutput(rtsp); if (videoStreamer.begin() != 0) { Serial.println("StreamIO link start failed"); } //開始影片串流 Camera.channelBegin(CHANNELVID); Camera.channelBegin(CHANNELJPEG); videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN)); videoStreamerRGBFD.setStackSize(); videoStreamerRGBFD.setTaskPriority(); videoStreamerRGBFD.registerOutput(facerecog); if (videoStreamerRGBFD.begin() != 0) { Serial.println("StreamIO link start failed"); } Camera.channelBegin(CHANNELNN); OSD.configVideo(CHANNELVID, configVID); OSD.begin(); digitalWrite(DOORLOCK, LOW); } void loop() { if (Serial.available() > 0) { String input = Serial.readString(); input.trim(); //基本指令 if (input.startsWith(String("REG="))) { String name = input.substring(4); facerecog.registerFace(name); } else if (input.startsWith(String("EXIT"))) { facerecog.exitRegisterMode(); } else if (input.startsWith(String("RESET"))) { facerecog.resetRegisteredFace(); } else if (input.startsWith(String("BACKUP"))) { facerecog.backupRegisteredFace(); } else if (input.startsWith(String("RESTORE"))) { facerecog.restoreRegisteredFace(); } } delay(1000); OSD.createBitmap(CHANNELVID,RECTTEXTLAYER0); OSD.createBitmap(CHANNELVID,RECTTEXTLAYER1); OSD.update(CHANNELVID, RECTTEXTLAYER0); OSD.update(CHANNELVID, RECTTEXTLAYER1); } void FRPostProcess(std::vector<FaceRecognitionResult> results) { uint16_t im_h = configVID.height(); uint16_t im_w = configVID.width(); printf("Total number of faces detected = %d\r\n", facerecog.getResultCount()); OSD.createBitmap(CHANNELVID, RECTTEXTLAYER0); OSD.createBitmap(CHANNELVID, RECTTEXTLAYER1); if (facerecog.getResultCount() > 0) { if (1) { if (facerecog.getResultCount() > 1) { // Door remain close when more than one face detecteD digitalWrite(RED_LED, HIGH); digitalWrite(GREEN_LED, LOW); } else { FaceRecognitionResult singleItem = results[0]; if (String(singleItem.name()) == String("unknown")) { //臉部辨識結果錯誤,紅色警示開啟 digitalWrite(RED_LED, HIGH); digitalWrite(GREEN_LED, LOW); } else { //臉部辨識結果正確,綠色通行燈開啟,並進行解鎖訊號傳輸工作 digitalWrite(RED_LED, LOW); digitalWrite(GREEN_LED, HIGH); fileName = String(singleItem.name()); digitalWrite(DOORLOCK, HIGH); Serial.println("Door Opened!"); delay(3000); digitalWrite(DOORLOCK,LOW); Serial.println("Door Locked!"); } } } } for (uint32_t i = 0; i < facerecog.getResultCount(); i++) { FaceRecognitionResult item = results[i]; int xmin = (int)(item.xMin() * im_w); int xmax = (int)(item.xMax() * im_w); int ymin = (int)(item.yMin() * im_h); int ymax = (int)(item.yMax() * im_h); uint32_t osd_color; int osd_layer; if (String(item.name()) == String("unknown")) { osd_color = OSD_COLOR_RED; osd_layer = RECTTEXTLAYER0; } else { osd_color = OSD_COLOR_GREEN; osd_layer = RECTTEXTLAYER1; } printf("Face %d name %s:\t%d %d %d %d\n\r", i, item.name(), xmin, xmax, ymin, ymax); OSD.drawRect(CHANNELVID, xmin, ymin, xmax, ymax, 3, osd_color, osd_layer); char text_str[40]; snprintf(text_str, sizeof(text_str), "Face:%s", item.name()); OSD.drawText(CHANNELVID, xmin, ymin - OSD.getTextHeight(CHANNELVID), text_str, osd_color, osd_layer); } OSD.update(CHANNELVID, RECTTEXTLAYER0); OSD.update(CHANNELVID, RECTTEXTLAYER1); } ``` * Arduino ```arduino= #include "Keypad.h" #include <Wire.h> #include <LiquidCrystal_I2C.h> const byte ROWS = 4; // 列數(橫的) const byte COLS = 3; // 行數(直的) int i=0, j=0; #define LCD_ROWS 2 // LCD顯示器的列數 #define LCD_COLS 16 // LCD顯示器的行數 //鍵盤上每一個按鍵的名稱 char keys[ROWS][COLS] = { {'1','2','3'}, {'4','5','6'}, {'7','8','9'}, {'*','0','#'} }; byte rowPins[ROWS] = {11, 10, 9, 8}; //定義列的腳位 byte colPins[COLS] = {7, 6, 5}; //定義行的腳位 String passcode = "1234"; // 預設密碼 String inputCode = ""; // 暫存用戶的按鍵字串 bool acceptKey = true; // 是否接受用戶按鍵輸入的boolean value int value_A0; //初始化鍵盤 Keypad customKeypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS); // LCD顯示器 LiquidCrystal_I2C lcd(0x27, 16, 2); // 設定模組位址0x27,以及16行, 2列的顯示形式 void clearRow(byte n) { byte last = LCD_COLS - n; lcd.setCursor(n, 1); // 移動到第2行,"PIN:"之後 for (byte i = 0; i < last; i++) { lcd.print(" "); } lcd.setCursor(n, 1); } //重設LCD顯示文字和輸入狀態。 void resetLocker() { lcd.clear(); lcd.print("Enter Password"); lcd.setCursor(0, 1); lcd.print("PIN:"); lcd.cursor(); acceptKey = true; inputCode = ""; } // 比對用戶輸入的密碼 void checkPinCode() { acceptKey = false; // 暫時不接受用戶按鍵輸入 clearRow(0); // 從第0個字元開始清除LCD畫面 lcd.noCursor(); lcd.setCursor(0, 1); // 比對密碼 if (inputCode == passcode) { lcd.print("Welcome Home!"); digitalWrite(13, HIGH); delay(3000); digitalWrite(13, LOW); } else { lcd.print("Wrong Password!"); } delay(3000); resetLocker(); } void setup() { pinMode(13, OUTPUT); pinMode(A0, INPUT); digitalWrite(13, LOW); Serial.begin(9600); lcd.init(); lcd.backlight(); resetLocker(); } void loop() { value_A0=analogRead(A0); //以A0接收amb82開發板的訊號接收 //Serial.println(value_A0); char key = customKeypad.getKey(); // 數字鍵盤的解鎖 if (acceptKey && key != NO_KEY) { if (key == '*') { // 清除畫面 clearRow(4); // 從第4個字元開始清除 inputCode = ""; } else if (key == '#') { checkPinCode(); // 比對輸入密碼 } else { inputCode += key; // 儲存用戶的按鍵字元 lcd.print('*'); } } // 臉部辨識的解鎖 if(value_A0>900){ acceptKey = false; lcd.clear(); lcd.noCursor(); lcd.print("Welcome Home!"); digitalWrite(13, HIGH); delay(3000); digitalWrite(13, LOW); resetLocker(); } } ``` ## 遇到的問題及注意事項 1. 一開始我們想使用Esp32來進行臉部辨識,但辨識度、流暢度及穩定度皆不佳。 * 解決方法: 改用AMB82 2. 電腦讀取不到AMB82。 * 解決辦法: 安裝CH340接口的驅動程式([link](https://www.wch.cn/download/CH341SER_EXE.html)) 3. Arduino無法讀取到AMB82的訊號。 * 解決辦法: 兩塊開發板間GND要接GND,形成完整迴路。 4. 程式編譯錯誤 * 可能原因 1. 未正確安裝函式庫。 2. 未正確設定開發環境。 ## 外觀設計 * 待補 ## 成品展示 ![](https://i.imgur.com/Ksx0FWK.png) ## 參考資料(References) * 企劃靈感來源 <iframe width="560" height="315" src="https://www.youtube.com/embed/_VOmfJ4x-Fg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> * 元件使用 * KeyPad使用:[link](https://blog.jmaker.com.tw/arduino-keypad-4x4/) * AMB82使用:[link](https://www.amebaiot.com/zh/amebapro2-amb82-mini-arduino-getting-started/) * 繼電器接法:[link](https://blog.jmaker.com.tw/arduino-relay/) * LCD i2c使用:[link](https://crazymaker.com.tw/arduino-lcd-i2c-tutorial/)