IOT(사물인터넷)
Raspberry 기본설정
라즈베리파이 한글 설치
Step.1
sudo apt-get install fonts-unfonts-core
sudo apt-get install ibus ibus-hangul
sudo reboot
Step.2
설정 -> 기본 설정 -> IBus 환경 설정 -> IBus 데몬 실행
라즈베리파이 아두이노 설치
sudo apt-get install arduino
라즈베리파이 웹캠 설치
- 웹캠 설정
sudo raspi-config
Interfacing Options -> Camera -> enable 설정
- 웹캠 정보확인
v4l2-ctl -V
- 패키지 설치
sudo apt-get update
sudo apt-get install fswebcam
- 사진 촬영
fswebcam -r 1280*960 --no-banner image2.jpg
home/pi에서 해당 이미지 확인
MQTT 통신
MQTT 사물인터넷 통신 프로젝트
-
선수 지식
- Arduino
- Node.js
- MongoDB
- C언어
-
준비물
- Wifi 모듈이 있는 보드 (WemosD1)
- 온/습도 센서 (DH11)
- LED
전체적인 흐름도
2가지 방식
- Socket 통신 방식 제어
- RESTfull Service 통신 방식 제어
개발환경 구성
-
Arduino 설치 후 WeMos D1 R1 보드 선택 후 Blink 예제 실행
-
Wifi 연결 & 웹서버 구축
Wifi 연결
#include <ESP8266WiFi.h>
// 헤더파일 include
const char * ssid = "";
const char * password = "";
// 접속할 Wifi 정보 입력
WiFiServer server(80);
// 80 포트로 연결
void setup(){
Serial.begin(9600);
// 9600의 속도를 가진 시리얼 통신
// 시리얼 모니터와 같은 값을 지정하며 해당 보드는 보통 115200
delay(10);
Serial.println();
Serial.print("Connection to");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Wifi 연결 시도
while(WiFi.status()!= WL_CONNECTED){
delay(500);
Serial.print(".");
} // 연결이 성공할때까지 실행
Serial.println("");
Serial.println("WiFi connected");
server.begin();
Serial.println("Server started");
// Server Start!!
Serial.println(WiFi.localIP());
} //Setup
void loop(){ // Client 요청이 올때마다 웹페이지 전송
WiFiClient client = server.available(); // Client 접속 체크
if(!client){ // 요청이 올때까지 계속 반복
return;
}
Serial.println("New Client");
String req = client.readStringUntil('\r');
Serial.println(req);
client.flush(); // 정보 비우기
String s = "<html>";
s = s+ "<meta name= 'viewport' content = 'width=device-width, initial-scale = 1.0'/>";
s = s+ "<meta http-equiv='Content-Type' content= 'text/html;charset=utf-8'/>";
s = s+"<head></head><body>Hello World!</body></html>";
client.print(s);
delay(1);
Serial.println("Client disonnected");
}
웹서버 구축
사용 함수 설명
ESP8266WebServer()
- 접속 포트를 설정
- HTTP 프로토콜은 기본적으로 80번 포트를 사용하며 디폴트값으로 지정되어있음
ESP8266WebServer(int port = 80)
인자 | 설명 |
---|---|
port | 접속 포트 |
on()
- 클라이언트의 요청에 대한 처리 함수
- 서버의 웹 페이지를 표시하는 URL은 컴퓨터의 파일과 마찬가지로 계층적인 디렉토리 구조
- 클라이언트의 요청 처리 함수는 서버에 접속할 수 있는 주소에 따라 달리 지정한다.
void on(const char * url, ThandlerFunction handler)
인자 | 설명 |
---|---|
url | 주소 |
handler | 처리 함수 |
onNotFound()
- 존재하지 않는 주소로 접속하였을 경우 처리 함수
void onNotFound(ThandlerFunction fn)
인자 | 설명 |
---|---|
fn | 처리 함수 |
begin()
- 웹 서버를 시작
void begin()
handleClient()
- 서버가 시작된 후 클라이언트의 요청을 받을 수 있다, 요청에 대한 처리는 handleClient()를 사용
- handleClient() 함수는 클라이언트의 요청이 있는 경우 클라이언트와 연결으 생성하고 요청을 처리
- 클라이언트가 접속하는 주소에 따라 해당하는 처리 함수 호출
void handleClient()
send()
- 클라이언트로 데이터 전송
void send(int code, char* content_type, String content)
인자 | 설명 |
---|---|
code | HTTP 응답 코드 |
content_type | 전송 내용의 종류 |
content_type | 전송 내용 |
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
const char* ssid = "";
const char* password = "";
ESP8266WebServer server(80); const int led = 14;
String s,s_head;
void handleRoot() {
digitalWrite(led, HIGH);
s=s_head+"<h1>켜짐</h1><br>";
server.send(200, "text/html", s);
//server.send(200, "text/plain", "hello from esp8266!");
}
void handleNotFound(){
digitalWrite(led, 1);
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET)?"GET":"POST"; message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i=0; i<server.args(); i++){
message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; }
server.send(404, "text/plain", message);
digitalWrite(led, 0); }
// WIFI_STA (Station mode, Stand-alone mode)
// 다른 공유기에 접속해서 IP를 할당받고, HTTP 통신을 사용하는 모드입니다
void setupWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print("."); }
Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP());
}
void setup(void){
pinMode(led, OUTPUT);
digitalWrite(led, LOW); Serial.begin(115200);
// 여기 프로그램 부분을 함수로처리
setupWifi();
// 스마트폰에 맟게 크기 조정, html에서 한글 출력하게 설정
s_head="<meta name='viewport' content='width=device-width, initial-scale=1.0'/>";
//s=s+"<meta http-equiv='refresh' content='5'/>";
s_head=s_head+"<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />";
server.on("/", handleRoot); server.on("/inline", [](){
//server.send(200, "text/plain", "this works as well");
digitalWrite(led, LOW);
s=s_head+"<h1>꺼짐</h1><br>";
server.send(200, "text/html", s);
});
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started"); }
void loop(void){
server.handleClient();
}
- NodeJs 설치
센서 데이터를 DB에 저장 하고 DB에 저장된 센서 데이터를 웹/앱으로 전송을 해주는 중간 다리역할
- 최신 LTS 파일 다운로드 후 설치 확인
설치된 버전 정보 확인
node --version
npm 프로그램 버전 확인 (Python pip와 비슷함)
npm --version
- Visual Studio Code 설치 후 확장팩 다운로드
확장팩 다운로드는 선택 사항
- prettier Extension 검색 후 설치
- Ctrl + Shift + P
- setting
- Open Settings(UI) 선택
- formatOnSave 입력 후 체크박스 선택
- NodeJs http 서버 생성
- Project 폴더 생성 후 app.js 파일 생성
// http 모듈을 이용
const http = require("http");
// require를 이용하여 http 모듈을 가져오기
// 람다식을 이용 (익명 함수)
const server = http.createServer((req, res) => {
if (req.url === "/") {
res.write("<h1>Hello from nodejs</h1>");
} else {
// 백틱을 사용하면 ${}을 사용해서 문자열과 변수를 적절하게 같이 사용할 수 있다.
res.wrtie(`<h1>You have entered this urel : ${req.url}</h1>`);
}
res.end();
});
server.listen(3000, () => {
console.log("The server is listening on port 3000");
});
- 서버구동 및 실행
node app.js
3000 port가 개방되고 서버가 구동
http://localhost:3000에 접속 후 확인
- express 모듈로 서버만들기
express는 NodeJs에서 웹서버를 만들 때 가장많이 사용하는 모듈이다.
npm package (http://npmjs.com)에서 npm 패키지 정보를 알 수 있음
- express 설치 후 실습 코드 입력
express 모듈 설치
npm i express
실습 코드
const express = require("express");
const Server = express();
// GET , POST , DELETE, PUT
Server.get("/", (req, res) => {
res.send("<h1>Hello from NodeJs</h1>");
});
Server.listen(3300, (err) => {
if (err) return console.log(err);
console.log("The Server is Listening on Port 3300");
});
- express HTML로 응답
별도의 HTML 파일을 만들어서 응답하는 방식
- index.html 파일 생성
<!-- html:5을 입력하면 자동으로 기본 골격을 만들어준다 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Main</title>
</head>
<body>
<h1>This is MainPage</h1>
</body>
</html>
- about.html 파일 생성
<!-- html:5을 입력하면 자동으로 기본 골격을 만들어준다 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About</title>
</head>
<body>
<h1>About</h1>
</body>
</html>
- app.js 파일 수정
const express = require("express");
const Server = express();
// GET , POST , DELETE, PUT
// __dirname을 사용하면 현재 파일이 실행되는 경로를 자동으로 맞춰준다.
Server.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
Server.get("/about", (req, res) => {
res.sendFile(__dirname + "/about.html");
});
Server.listen(3300, (err) => {
if (err) return console.log(err);
console.log("The Server is Listening on Port 3300");
});
- Nodejs Middleware 사용하기
특정 데이터를 처리하기전 서버에서 공통적으로 처해야 할 작업 클라이언트의 모든 요청은 미들웨어를 통과해야한다!
- 404.html 파일 생성
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>Page Not Found 404</h1>
</body>
</html>
- CSS사용을 위해 public 폴더생성 -> index.css 파일생성
.red {
color: red;
}
- 미들웨어를 사용해서 public 경로를 공통적으로 적용 app.js 수정
const express = require("express");
const Server = express();
// GET , POST , DELETE, PUT
Server, use(express.static(__dirname + "public"));
Server.get("/", (req, res) => {
res.sendFile(__dirname + "/index.html");
});
Server.get("/about", (req, res) => {
res.sendFile(__dirname + "/about.html");
});
Server.use((req, res) => {
res.sendFile(__dirname + "404/html");
});
Server.listen(3300, (err) => {
if (err) return console.log(err);
console.log("The Server is Listening on Port 3300");
});
- index.html과 css 파일 연동
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/index.css" />
<title>Main</title>
</head>
<body>
<h1 class="red">This is MainPage</h1>
</body>
</html>
Object Tracking
실시간 객체 트랙킹
등록된 사용자를 제외한 외부침입자를 인식하여 트랙킹하는 방법
트래킹 알고리즘과 객체 감지와의 차이점
- 트래킹 알고리즘
- 감지 알고리즘보다 훨씬 빠름
- 기존 데이터를 사용하여 다음 탐지에 재사용이 가능하기 때문에
- 하지만 급격한 변화에는 대응하기 힘들다.
만약 100개의 프레임이 있는 영상의 경우
- 감지 알고리즘 프레임별로 100번의 객제 감지 실행
- 트래킹 알고리즘은 첫 번째 프레임에서 객체를 탐지 후 이 정보를 영상 끝까지 사용
2. KCF 및 CSRT 알고리즘
- KCF(KERNAL CORRELATION FILTERS) : 커널 상관 필터로 빠른 알고리즘이지만 빠른 영상에서는 작동이 잘 안된다.
- 경계 상자가 객체를 놓치는 경우
- 초기 선택된 프레임이 파티클 필터라는 개념을 적용하여 더 큰 경계 상자 2개를 생성히여 이미지를 더 크게 포함
- 얼굴의 중앙점을 수학적 연산을 통해 계산
- 각각의 프레임들을 얼굴의 중앙점에 맞게 업데이트
- CSRT(DISCRIMINATIVE CORRELATION FILTER WITH CHANNEL AND SPATIAL RELIABILITY) : 채널 및 공간 신뢰도를 통한 구분 상관 필터이며 다른 알고리즘보다 정확하지만 느리다.
- 첫 번째 박스에서 트래킹 하려는 객체를 탐지
- HOG 기법을 사용하여 학습
- HOG : 이미지에서 중요 정보는 추출하고 나머지는 버림
- 랜덤 마르코프 테스를 적용
- 트래킹 객체의 움직임을 감지
- 컨피던스 맵
- 마스크로 가져린 객체만을 추출 (원본 이미지의 정보)
- 추적할 객체만을 추출
KCF로 객체 트래킹 구현
추적 트래킹을 사용하기 위해서 설치
pip install opencv-contib-python
- New file 생성 (Object_Treacking.py)
import cv2
tracker = cv2.TrackerKCF_create()
video = cv2.VideoCapture(0)
ok, frame = video.read()
## ok -> 올바르게 읽었는지
## 영상의 첫번째만 확인
bbox = cv2.selectROI(frame)
## 첫번째 프레임에 대한 정보만 저장
## 관심영역 선택
print(bbox)
ok = tracker.init(frame, bbox)
# print(ok)
while True:
## 영상의 각 프레임 통과
ok, frame = video.read()
if not ok: ## 처리할 프레임이 없는경우
break
ok, bbox = tracker.update(frame)
# print(bbox)
if ok:
(x, y, w, h) = [int(v) for v in bbox]
# (420, 24, 390, 519)
cv2.rectangle(frame, (x,y), (x+w, y+h), (0,255,0), 2,1)
else:
cv2.putText(frame, "Error", (100,100), cv2.FONT_HERSHEY_SIMPLEX, 1,(0,0,255),2)
cv2.imshow("Tracking", frame)
if cv2.waitKey(1) & 0xFF == 27:
break ### ESC
CSRT 객체 트래킹 구현
KCF 알고리즘에서는 따라가지 못했던 객체를 따라가는 모습을 볼 수 있다.
import cv2
tracker = cv2.TrackerCSRT_create()
## 위 모델에서 KCF -> CSRT로 바꿔주기만 하면 된다.
video = cv2.VideoCapture(0)
ok, frame = video.read()
## ok -> 올바르게 읽었는지
## 영상의 첫번째만 확인
bbox = cv2.selectROI(frame)
## 첫번째 프레임에 대한 정보만 저장
## 관심영역 선택
print(bbox)
ok = tracker.init(frame, bbox)
# print(ok)
while True:
## 영상의 각 프레임 통과
ok, frame = video.read()
if not ok: ## 처리할 프레임이 없는경우
break
ok, bbox = tracker.update(frame)
# print(bbox)
if ok:
(x, y, w, h) = [int(v) for v in bbox]
# (420, 24, 390, 519)
cv2.rectangle(frame, (x,y), (x+w, y+h), (0,255,0), 2,1)
else:
cv2.putText(frame, "Error", (100,100), cv2.FONT_HERSHEY_SIMPLEX, 1,(0,0,255),2)
cv2.imshow("Tracking", frame)
if cv2.waitKey(1) & 0xFF == 27:
break ### ESC
3. Deep Sort 알고리즘
위 두개의 알고리즘 보다 더 좋아 보임
DeepSORT : 기존 SORT Tracker의 (Detection + Kalman Filter + Hungarian Algorithm) 구조에서 ‘딥러닝’을 복합한 구조로, Appearance를 이용한 거리 측정 등을 통해 기존 SORT보다 더 뛰어난 성능을 보여주는 추적기이다.
- Kalman filter(칼만 필터)
쉽게 말해 이전 프레임에 등장한 개체를 이용하여 다음 움직임을 예측하는 것이다.
- Hungarian algorithm
Deep sort가 이전등장한 객체와 예측한 객체가 동일하다라고 판별하는 기준
설명을 봐도 어렵다 일단 구현해보자..
유투버 The AI Guy의 영상과 Github을 참고하혔음
선수 지식
- YOLOv4
- Tensorflow
- Google Colab
가상환경 및 GPU 설정이 어렵다면 COLAB 사용을 추천
Colab -> 런타임 변경 -> GPU 설정
Coalb 또는 프로젝트를 시작할 폴더에서 Git clone
git clone https://github.com/theAIGuysCode/yolov4-deepsort.git
- YOLOv4 가중치파일과 cfg파일을 다운로드
[Github 주소]https://github.com/AlexeyAB/darknet
위 두 파일을 다운로드 해준다.
# 나는 IOT 사용을 위해 tiny모델을 다운로드함
- 가상환경 설치 및 필요 라이브러리 설치
Tensorflow 버전은 2.3.0이어야 한다
만약 아닌경우 해당 코드 실행
#### 이 코드를 추가하지않으면 실행 불가
!pip uninstall tensorflow -y
!pip install tensorflow==2.3.0
import tensorflow
tensorflow.__version__
가상환경 및 라이브러리 설치
# Tensorflow CPU
conda env create -f conda-cpu.yml
conda activate yolov4-cpu
# Tensorflow GPU
conda env create -f conda-gpu.yml
conda activate yolov4-gpu
# =============== 가상환경설치 ==============
# TensorFlow CPU
pip install -r requirements.txt
# TensorFlow GPU
pip install -r requirements-gpu.txt
# =============== 라이브러리 설치 ==============
- Yolo 모델을 Tensorflow 모델로 변환
변환하는 이유
- 자유롭게 커스텀이 가능
- tensorflow lite를 이용하여 모바일 개발이 가능
- YOLO를 라즈베리파이에서 사용함에 있어서 많은 제약이 있다 IOT사용을 위해 모델을 가볍게 만드는과정이 필수
python save_model.py --model yolov4
위 명령어로 쉽게 YOLO모델을 tensorflow모델로 변환이 가능하다.
- 객체 추적하기
변환된 모델을 이용하여 객체 추적
# 저장된 비디오에서 yolo4 deep sort 객체 추적
python object_tracker.py --video ./data/video/test.mp4 --output ./outputs/demo.avi --model yolov4
# 노트북 webcam에서 yolo4 deep sort 객체 추적 (video 0으로 설정)
python object_tracker.py --video 0 --output ./outputs/webcam.avi --model yolov4
tiny 모델 사용법
# save yolov4-tiny model
python save_model.py --weights ./data/yolov4-tiny.weights --output ./checkpoints/yolov4-tiny-416 --model yolov4 --tiny
# Run yolov4-tiny object tracker
python object_tracker.py --weights ./checkpoints/yolov4-tiny-416 --model yolov4 --video ./data/video/test.mp4 --output ./outputs/tiny.avi --tiny
- 원하는 클래스만 추적하게 코드 수정
object_tracker.py 파일 수정
159번째 줄 코드 수정
# 원하는 클래스만을 넣어주면 해당 객체만 트랙킹한다. class정보는 coco.names 또는 자신이 학습한 names파일을 참고한다.
allowed_classes = list(class_names.values())
#ex
# allowed_classes = ['person']