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

전체적인 흐름도

스크린샷 2022-06-21 오후 8 30 51

2가지 방식

  1. Socket 통신 방식 제어
  2. RESTfull Service 통신 방식 제어

개발환경 구성

  1. Arduino 설치 후 WeMos D1 R1 보드 선택 후 Blink 예제 실행

  2. 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();
}

  1. NodeJs 설치

센서 데이터를 DB에 저장 하고 DB에 저장된 센서 데이터를 웹/앱으로 전송을 해주는 중간 다리역할

  • 최신 LTS 파일 다운로드 후 설치 확인

설치된 버전 정보 확인

node --version

npm 프로그램 버전 확인 (Python pip와 비슷함)

npm --version
  • Visual Studio Code 설치 후 확장팩 다운로드 확장팩 다운로드는 선택 사항
    • prettier Extension 검색 후 설치
    • Ctrl + Shift + P
      • setting
      • Open Settings(UI) 선택
      • formatOnSave 입력 후 체크박스 선택
  1. 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에 접속 후 확인

  1. 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 파일을 만들어서 응답하는 방식

스크린샷 2022-06-21 오후 11 09 48

  1. 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>
  1. 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>
  1. 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");
});
  1. Nodejs Middleware 사용하기

특정 데이터를 처리하기전 서버에서 공통적으로 처해야 할 작업 클라이언트의 모든 요청은 미들웨어를 통과해야한다!

스크린샷 2022-06-25 오후 5 58 56

  1. 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>
  1. CSS사용을 위해 public 폴더생성 -> index.css 파일생성
.red {
  color: red;
}
  1. 미들웨어를 사용해서 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");
});
  1. 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) : 커널 상관 필터로 빠른 알고리즘이지만 빠른 영상에서는 작동이 잘 안된다.
    • 경계 상자가 객체를 놓치는 경우
  1. 초기 선택된 프레임이 파티클 필터라는 개념을 적용하여 더 큰 경계 상자 2개를 생성히여 이미지를 더 크게 포함
  2. 얼굴의 중앙점을 수학적 연산을 통해 계산
  3. 각각의 프레임들을 얼굴의 중앙점에 맞게 업데이트
  • CSRT(DISCRIMINATIVE CORRELATION FILTER WITH CHANNEL AND SPATIAL RELIABILITY) : 채널 및 공간 신뢰도를 통한 구분 상관 필터이며 다른 알고리즘보다 정확하지만 느리다.
  1. 첫 번째 박스에서 트래킹 하려는 객체를 탐지
  2. HOG 기법을 사용하여 학습
    • HOG : 이미지에서 중요 정보는 추출하고 나머지는 버림
  3. 랜덤 마르코프 테스를 적용
    • 트래킹 객체의 움직임을 감지
  4. 컨피던스 맵
    • 마스크로 가져린 객체만을 추출 (원본 이미지의 정보)
  5. 추적할 객체만을 추출

KCF로 객체 트래킹 구현

추적 트래킹을 사용하기 위해서 설치
pip install opencv-contib-python
  1. 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
  1. YOLOv4 가중치파일과 cfg파일을 다운로드
    [Github 주소]https://github.com/AlexeyAB/darknet

위 두 파일을 다운로드 해준다.

# 나는 IOT 사용을 위해 tiny모델을 다운로드함
  1. 가상환경 설치 및 필요 라이브러리 설치

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

#  =============== 라이브러리 설치 ============== 
  1. Yolo 모델을 Tensorflow 모델로 변환

변환하는 이유

  • 자유롭게 커스텀이 가능
  • tensorflow lite를 이용하여 모바일 개발이 가능
    • YOLO를 라즈베리파이에서 사용함에 있어서 많은 제약이 있다 IOT사용을 위해 모델을 가볍게 만드는과정이 필수
python save_model.py --model yolov4 

위 명령어로 쉽게 YOLO모델을 tensorflow모델로 변환이 가능하다.

  1. 객체 추적하기

변환된 모델을 이용하여 객체 추적

# 저장된 비디오에서 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
  1. 원하는 클래스만 추적하게 코드 수정

object_tracker.py 파일 수정
159번째 줄 코드 수정

# 원하는 클래스만을 넣어주면 해당 객체만 트랙킹한다. class정보는 coco.names 또는 자신이 학습한 names파일을 참고한다.
        allowed_classes = list(class_names.values())
        #ex
        # allowed_classes = ['person']