본문 바로가기

ESP32 CAM

ESP32-CAM과 YOLO를 이용한 영상 스트리밍 객체 탐지

반응형

ESP32-CAM과 YOLO를 이용한 객체 탐지

 

이 튜토리얼에서는 ESP32-CAM 모듈과 객체 감지용 딥러닝 시스템인 YOLO를 사용하여 객체를 감지하고 분류하는 방법을 배웁니다.

 

ESP32-CAM을 사용하여 이미지를 캡처하고, 웹 서버 역할을 하며, 캡처한 이미지를 분석을 위해 컴퓨터로 전송하는 프로젝트를 구축하는 과정을 안내해 드리겠습니다. 컴퓨터는 YOLO를 사용하여 객체를 감지하고 분류합니다. 하드웨어 조립, 카메라 설정, HTTP를 통한 JPEG 이미지 전송 방법을 배우게 됩니다.

 

이 과정을 마치면 ESP32-CAM으로 촬영한 사진을 볼 수 있는 웹 인터페이스와 80가지 유형의 물체를 감지할 수 있는 객체 감지 시스템을 갖추게 될 것입니다.

 

 

목차는 아래와 같아요.

 

개요

필수 부품

ESP32-CAM

FTDI USB-TTL 어댑터

USB 데이터 케이블

아두이노 IDE

시스템 아키텍처

ESP32-CAM 개발 보드

FTDI 프로그래머 연결하기

ESP32 코어 설치

ESP32-CAM 보드 선택

ESP32-CAM 라이브러리 설치

ESP32-CAM 웹 서버 코드

라이브러리 및 상수

카메라 해상도

웹 서버 초기화

JPEG 기능을 제공합니다

JPEG 함수 처리

카메라 초기화

Wi-Fi 초기화

서버 초기화

설정 기능

반복문

ESP32-CAM 웹 서버를 테스트하세요

YOLO 객체 탐지

프로젝트 폴더 구조

YOLO 파일을 다운로드하세요

가상 환경 생성

객체 감지 코드

라이브러리 가져오기

카메라 URL

YOLO 모델 파일

YOLO 모델 로딩 중

출력 레이어

클래스에 사용할 색상 생성

객체 감지 기능

처리 감지

비최대 억제

드로잉 감지

주요 기능

실행 진입점

객체 탐지기 실행

결론

 

필수 부품

 

아래에는 프로젝트 구축에 필요한 구성 요소가 나와 있습니다. FTDI 프로그래머 대신 ESP32-CAM용 프로그래밍 쉴드를 사용할 수도 있지만, FTDI 프로그래머를 사용하는 것을 권장합니다.

  • ESP32-CAM
  • FTDI USB-TTL 어댑터
  • USB 데이터 케이블
  • 아두이노 IDE 2.x

 

시스템 아키텍처

 

우리가 구축할 시스템은 크게 두 가지 구성 요소로 이루어져 있습니다.

1) 이미지를 캡처하고 웹 서버 역할을 하여 WiFi를 통해 이미지를 전송하는 ESP32-CAM 모듈.

2) YOLO 객체 탐지 ​​시스템이 실행되는 PC.

 

이 시스템은 이미지를 분석하고 탐지된 객체에 주석을 추가합니다. 아래 다이어그램은 시스템 아키텍처의 개요를 보여줍니다.

 

시스템 아키텍처 객체 탐지 ​​시스템

 

 

다음 그림은 시스템의 물체 감지 예시를 보여줍니다. 어수선한 제 책상 위에서 YOLO는 컵을 88%의 정확도로, 가위를 68%의 정확도로, 노트북을 59%의 정확도로 감지했습니다. 그림 왼쪽 상단에는 물체 주변의 경계 상자와 물체 이름, 감지 정확도가 표시되어 있습니다.

 

 

YOLO로 감지된 객체

 

 

다음 섹션에서는 ESP32-CAM에서 웹 서버를 프로그래밍하는 방법과 PC에서 YOLO 감지 시스템을 설정하는 방법을 배우게 됩니다.

 

ESP32-CAM 개발 보드

 

ESP32-CAM 개발 보드는 ESP32-S 칩, 카메라, 내장 플래시 및 microSD 카드 슬롯을 결합한 소형 모듈입니다. 이 보드는 Wi-Fi와 블루투스가 내장되어 있으며 최대 200만 화소 해상도의 OV2640 또는 OV7670 카메라를 지원합니다.

 

 

ESP32-CAM의 앞면과 뒷면

 

 

이 튜토리얼에서는 오리지널 AI-Thinker ESP32-CAM 보드 모델을 기준으로 설명 하지만, 사양이 완전히 동일한 복제품들이 많이 있습니다. 프로그래밍 및 사용 방법은 동일하며, 필수 부품 목록에 있는 복제품도 마찬가지입니다.

 

FTDI 프로그래머 연결하기

 

ESP32-CAM은 프로그래밍 쉴드 또는 FTDI 프로그래머를 통해 프로그래밍할 수 있습니다 . FTDI 프로그래머는 사용이 더 간편하고 유연성이 뛰어납니다. USB 신호를 시리얼 신호로 변환하여 UART 인터페이스를 통해 아두이노 및 ESP32와 같은 마이크로컨트롤러를 프로그래밍할 수 있도록 해줍니다. 다음 그림은 FTDI 프로그래머를 ESP32-CAM 모듈에 연결하는 방법을 보여줍니다.

 

 

FTDI 프로그래머와 ESP32-CAM의 배선

 

연결은 간단합니다. 먼저 프로그래머의 접지선(GND)과 ESP32-CAM 모듈의 접지선(파란색 선)을 연결합니다. 그런 다음 5V 전원 공급 장치(빨간색 선)도 같은 방식으로 연결합니다. 일부 FTDI 프로그래머에는 3.3V와 5V를 전환하는 점퍼 또는 스위치가 있을 수 있으므로, 가능하면 5V를 사용하십시오.

 

다음으로 ESP32-CAM의 U0T(U0TXD) 핀을 프로그래머의 RXD 핀(노란색 선)에 연결합니다. 마찬가지로 U0R 핀은 TXD 핀(녹색 선)에 연결합니다. 이렇게 하면 시리얼 통신이 설정됩니다.

 

ESP32-CAM을 프로그래밍 모드로 전환하려면 IO0 핀을 접지(GND)에 연결해야 합니다. 하지만 프로그램을 실행하려면 IO0 핀은 연결하지 않은 상태로 두어야 합니다. 따라서 프로그래밍 모드와 실행 모드를 전환할 수 있도록 IO0 핀과 GND 핀 사이에 스위치(보라색 선)를 추가했습니다. 아래 사진은 스위치와 FTDI 프로그래머를 연결한 ESP32-CAM의 배선 모습입니다.

 

 

ESP32-CAM의 프로그래밍 모드를 활성화 스위치를 켜십시오.

 

 

ESP32 코어 설치

 

ESP32 시리즈 보드를 처음 사용하는 프로젝트라면 먼저 보드를 설치해야 합니다. 이미 아두이노 IDE에 ESP32 보드가 설치되어 있다면 이 설치 단계를 건너뛸 수 있습니다.

 

먼저 "파일" 메뉴에서 "환경설정..."을 선택하여 환경설정 대화 상자를 엽니다. 그러면 아래와 같은 환경설정 대화 상자가 나타납니다.

 

설정 탭에서 대화 상자 하단에 " 추가 보드 관리자 URL "이라고 표시된 편집 상자를 찾을 수 있습니다.

 

이 입력란에 다음 URL을 복사하세요: “ https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json“

 

 

 

 

이렇게 하면 아두이노 IDE가 ESP32 코어 라이브러리의 위치를 ​​알게 됩니다. 다음으로 보드 관리자를 사용하여 ESP32 코어 라이브러리를 실제로 설치하겠습니다.

 

"도구 -> 보드 -> 보드 관리자"를 통해 보드 관리자를 엽니다. 왼쪽 사이드바에 보드 관리자가 나타납니다. 상단의 검색 필드에 "ESP32"를 입력하면 "Arduino ESP32 보드"와 "esp32 by Espressif" 두 종류의 ESP32 보드가 표시됩니다. 우리는 Espressif에서 제공하는 ESP32 라이브러리를 설치해야 합니다. "설치" 버튼을 클릭하고 다운로드 및 설치가 완료될 때까지 기다립니다.

 

 

ESP32 코어 라이브러리를 설치합니다.

 

 

ESP32-CAM 보드 선택

 

드롭다운 메뉴를 클릭한 다음 "다른 보드 및 포트 선택..."을 클릭하십시오.

 

 

보드 선택을 위한 드롭다운 메뉴

 

 

그러면 검색창에 "ESP32-CAM"을 입력하는 대화 상자가 열립니다. 보드 목록에서 "AI Thinker ESP32-CAM" 보드를 찾을 수 있습니다. 해당 보드를 클릭하고 COM 포트를 선택하여 활성화한 다음 확인을 클릭하세요.

 

 

AI Thinker ESP32-CAM을 이용한 보드 선택 대화 상자

 

 

ESP32-CAM이 FTDI 프로그래머를 통해 USB 포트에 연결되었는데도 포트를 선택할 수 없다면 CP210X 드라이버가 설치되어 있지 않은 것입니다. SILICON LABS 소프트웨어 다운로드 페이지에서 사용 중인 운영체제에 맞는 CP210x 드라이버를 다운로드하십시오. 예를 들어 Windows의 경우 "CP210x VCP Windows"를 다운로드하면 됩니다.

 

 

CP210X 드라이버 다운로드

 

 

그러면 ZIP 파일이 다운로드됩니다. 압축을 풀고 설치 프로그램을 실행하세요. 설치가 완료되면 ESP32-CAM이 USB 포트에 연결된 것으로 표시됩니다. 문제가 계속 발생하는 경우 FTDI 드라이버도 설치해야 할 수 있습니다 .

 

ESP32-CAM 라이브러리 설치

 

웹 서버를 위해 esp32cam 라이브러리를 사용할 예정입니다. GitHub 저장소로 이동하여 녹색 '코드' 버튼을 클릭한 후 'ZIP 다운로드'를 선택하여 라이브러리를 다운로드하세요:

 

 

esp32cam 라이브러리를 다운로드하세요

 

 

그런 다음 "스케치->라이브러리 포함->.Zip 라이브러리 추가"를 클릭합니다.

 

 

다운로드한 ZIP 파일의 경로를 선택하여 라이브러리를 설치하세요. 다음 섹션에서는 ESP32-CAM에서 웹 서버를 실행하는 코드를 작성하고 설명합니다.

 

ESP32-CAM 웹 서버 코드

 

다음 코드는 ESP32-CAM 모듈을 설정하여 Wi-Fi 네트워크를 통해 이미지를 전송합니다. 이미지를 캡처하여 요청하는 클라이언트에게 JPEG 파일로 제공합니다. 서버는 기본 HTTP 포트인 80번 포트에서 실행됩니다.

 

#include "WebServer.h"
#include "WiFi.h"
#include "esp32cam.h"

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";
const char* URL = "/cam.jpg";

static auto RES = esp32cam::Resolution::find(800, 600);

WebServer server(80);

void serveJpg() {
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAILED!");
    server.send(503, "", "");
    return;
  }
  Serial.printf("CAPTURE OK %dx%d %db\n",
                frame->getWidth(), frame->getHeight(),
                static_cast<int>(frame->size()));

  server.setContentLength(frame->size());
  server.send(200, "image/jpeg");

  WiFiClient client = server.client();
  frame->writeTo(client);
}

void handleJpg() {
  if (!esp32cam::Camera.changeResolution(RES)) {
    Serial.println("CAN'T SET RESOLUTION!");
  }
  serveJpg();
}

void initCamera() {
  {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setBufferCount(2);
    cfg.setJpeg(80);

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
  }
}

void initWifi() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
    ;
  Serial.printf("http://%s%s\n",
                WiFi.localIP().toString().c_str(), URL);
}

void initServer() {
  server.on(URL, handleJpg);
  server.begin();
}

void setup() {
  Serial.begin(115200);
  initWifi();
  initCamera();
  initServer();
}

void loop() {
  server.handleClient();
}

 

 

코드가 어떻게 작동하는지 이해하기 위해 구성 요소별로 분석해 보겠습니다.

 

라이브러리 및 상수

 

코드의 시작 부분에는 웹 서버, Wi-Fi 기능 및 카메라 제어에 필요한 라이브러리를 포함합니다.

 

#include "WebServer.h"
#include "WiFi.h"
#include "esp32cam.h"

 

또한 Wi-Fi 자격 증명과 카메라 이미지에 접근하기 위한 URL 엔드포인트에 대한 상수를 정의합니다.

 

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";
const char* URL = "/cam.jpg";

 

당연히 자격 증명은 사용하시는 Wi-Fi 네트워크의 SSID와 비밀번호로 바꿔야 합니다.

 

카메라 해상도

 

카메라에 원하는 해상도를 설정합니다. 이 경우 800×600 픽셀 해상도를 원합니다.

 

static auto RES = esp32cam::Resolution::find(800, 600);

 

 

웹 서버 초기화

 

다음으로 80번 포트에서 수신 대기하는 웹 서버 인스턴스를 생성합니다.

 

WebServer server(80);

 

 

JPEG 기능을 제공합니다

 

이 serveJpg()함수는 카메라에서 이미지를 캡처하여 JPEG 파일 형식으로 클라이언트로 전송합니다. 캡처에 실패하면 "503 서비스 사용 불가" 응답을 보냅니다.

 

void serveJpg() {
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAILED!");
    server.send(503, "", "");
    return;
  }
  Serial.printf("CAPTURE OK %dx%d %db\n",
                frame->getWidth(), frame->getHeight(),
                static_cast<int>(frame->size()));

  server.setContentLength(frame->size());
  server.send(200, "image/jpeg");

  WiFiClient client = server.client();
  frame->writeTo(client);
}

 

 

여기서 먼저 프레임 캡처를 시도합니다. 캡처에 성공하면 이미지의 크기와 치수를 기록하고, 콘텐츠 길이를 설정한 후 이미지를 클라이언트로 전송합니다.

 

JPEG 함수 처리

 

이 handleJpg()함수는 카메라 해상도를 변경하고 serveJpg()이미지를 제공하는 함수를 호출합니다.

 

void handleJpg() {
  if (!esp32cam::Camera.changeResolution(RES)) {
    Serial.println("CAN'T SET RESOLUTION!");
  }
  serveJpg();
}

 

 

이 기능은 이미지를 제공하기 전에 카메라가 원하는 해상도로 설정되도록 합니다.

 

카메라 초기화

 

이 initCamera()기능은 핀 할당, 해상도, 버퍼 개수 및 JPEG 품질을 포함한 카메라 설정을 구성합니다.

 

void initCamera() {
  {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setBufferCount(2);
    cfg.setJpeg(80);

    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
  }
}

 

 

구성 객체를 생성하고 필요한 매개변수를 설정한 후 카메라를 초기화합니다. 카메라 초기화 성공 여부는 시리얼 모니터에 메시지로 표시됩니다.

 

Wi-Fi 초기화

 

이 initWifi()함수는 ESP32를 지정된 Wi-Fi 네트워크에 연결합니다.

 

void initWifi() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
    ;
  Serial.printf("http://%s%s\n",
                WiFi.localIP().toString().c_str(), URL);
}

 

 

지속적인 Wi-Fi 연결을 비활성화하고, 모드를 스테이션으로 설정한 후 Wi-Fi 네트워크에 연결을 시도합니다. 연결이 완료되면 카메라 이미지에 접근할 수 있는 URL을 출력합니다.

 

서버 초기화

 

이 initServer()함수는 카메라 이미지 요청을 처리하도록 서버를 설정합니다.

 

void initServer() {
  server.on(URL, handleJpg);
  server.begin();
}

 

 

URL 엔드포인트를 정의하고 이를 handleJpg()함수와 연결한 다음 서버를 시작합니다.

 

setup 함수

 

이 setup()함수는 직렬 통신, Wi-Fi, 카메라 및 서버를 초기화합니다.

 

void setup() {
  Serial.begin(115200);
  initWifi();
  initCamera();
  initServer();
}

 

 

loop 함수

 

마지막으로, 해당 loop()함수는 들어오는 클라이언트 요청을 지속적으로 처리합니다.

 

void loop() {
  server.handleClient();
}

 

 

이 기능은 서버가 클라이언트 요청에 응답하도록 보장하여 클라이언트가 카메라로 촬영한 이미지를 가져올 수 있도록 합니다.

 

ESP32-CAM 웹 서버를 테스트

 

이제 웹 서버를 테스트해 보겠습니다. 위의 코드를 컴파일하고 업로드하세요. ESP32-CAM에 코드를 업로드하려면 스위치를 켜서 보드를 프로그래밍 모드로 전환한 다음, 보드의 리셋 버튼을 짧게 누르고 아두이노 IDE에서 업로드 버튼을 클릭하세요.

 

ESP32-CAM에 코드를 업로드하는 데 더 자세한 도움이 필요하시면, ESP32-CAM 프로그래밍 튜토리얼을 참조하세요 .

 

업로드가 성공적으로 완료되면 카메라 사진의 URL이 시리얼 모니터에 출력되고 "CAMERA OK"라는 텍스트도 표시됩니다.

 

http://192.168.1.146/cam.jpg
CAMERA OK

 

이 URL을 웹 브라우저의 주소창에 복사해서 붙여넣으면 카메라가 촬영한 사진을 볼 수 있습니다.

 

 

URL이 표시된 주소 표시줄

 

웹 브라우저에서 새로고침 버튼을 누를 때마다 웹 서버는 이 요청을 받아 ESP32-CAM에 새 사진을 찍도록 요청하고, 찍은 새 사진을 웹 브라우저로 전송합니다. 아래는 이런 방식으로 찍은 제 책상 사진입니다.

 

ESP32-CAM으로 촬영한 이미지를 웹 서버를 통해 브라우저에 제공합니다.

ESP32-CAM으로 촬영한 이미지를 웹 서버를 통해 브라우저에 제공합니다.

 

 

다음 섹션에서는 YOLO 객체 탐지 ​​모델에 사진을 전송하여 장면 속 객체를 인식하도록 합니다.

 

YOLO 객체 탐지

 

YOLO (You Only Look Once)는 속도와 정확도가 뛰어난 딥러닝 기반 객체 탐지 ​​모델입니다. 2016년 조셉 레드먼(Joseph Redmon) 등이 처음 소개했으며 , 이후 여러 차례 개선된 버전이 출시되었습니다. 2025년 2월 기준, YOLO11은 울트라리틱스(Ultralytics )에서 개발한 최신 버전입니다.

 

하지만 우리는 구형 모델인 YOLOv3v를 사용할 예정입니다. 크기가 작고 사용하기 쉽지만, 최신 모델만큼 정확도가 높지는 않습니다. 

 

 

YOLO 모델의 아키텍처 (출처)

 

YOLO 모델은 448x448x3 크기의 RGB 이미지를 입력으로 받아, 감지된 객체의 경계 상자와 신뢰도 점수를 7x7x30 크기의 텐서로 출력하는 심층 컨볼루션 네트워크입니다. 우리는 80가지 객체를 감지하도록 학습된 YOLO 모델 버전을 사용할 것입니다 . 예를 들면 다음과 같습니다.

 

사람

자전거

자동차

오토바이

가위

장난감 곰

헤어드라이어

칫솔

 

이 글에서는 모델의 세부 사항을 다루지 않지만, 더 자세히 알고 싶으시다면 YOLO 원문 논문, YOLO 3 버전 개선 사항 설명, 유용한 정보가 담긴 응용 논문 링크를 참고하세요.

 

  • YOLOv3: 점진적 개선
  • 단 한 번의 관찰로 모든 것을 해결하세요: 통합 실시간 객체 탐지
  • YOLO v3: 스마트 감시 시스템을 위한 시각적 및 실시간 객체 탐지 ​​모델(3초)

 

 

PC에서 프로젝트 폴더 구조

 

PC에서 YOLO 객체 감지 시스템을 실행하려면 먼저 프로젝트 폴더를 생성해야 합니다. 예를 들어 "esp32-cam-yolo-object-detection"이라는 폴더를 만들고, 그 안에 "YOLO"라는 하위 폴더와 "detect.py"라는 파이썬 파일을 생성합니다. 폴더 구조는 다음과 같아야 합니다.

 

 

프로젝트 폴더 구조

 

YOLO 파일을 다운로드하세요

 

다음으로 필요한 YOLO 파일(가중치, 아키텍처 설정, 클래스 이름)을 다운로드하여 프로젝트의 "YOLO" 폴더에 넣어주세요. 다음은 해당 파일 링크입니다.

 

https://pjreddie.com/media/files/yolov3.weights

https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg

https://github.com/pjreddie/darknet/blob/master/data/coco.names

 

그러면 "YOLO" 폴더의 내용은 다음과 같아야 합니다.

 

 

YOLO 폴더

 

 

가상 환경 생성

 

파이썬 라이브러리도 몇 가지 설치해야 하는데, venv를 사용하여 가상 환경에 설치하겠습니다 . 명령 셸을 열고 다음 명령어를 실행하세요.

 

가상 환경에 관한 상세 설명은 다음 블로그 포스팅을 참고하세요. 

 

cd esp32-cam-object-detection
python -m venv venv
venv\Scripts\activate.bat    
pip install opencv-python opencv-python-headless numpy torch torchvision

 

 

cd 명령어는 프로젝트 폴더로 이동합니다. venv 명령어는 가상 환경을 생성하며, 이 안에서 pip install을 통해 필요한 라이브러리를 설치합니다. 이로 인해 프로젝트 폴더 내에 라이브러리를 포함하는 "venv" 폴더가 생성됩니다:

 

 

프로젝트 폴더 내의 venv 폴더

 

 

객체 감지 코드

 

마지막으로, 다음 코드를 프로젝트 폴더의 detect.py 파일에 복사하세요.

 

import cv2
import numpy as np
import urllib.request

# Camera URL
url = "http://192.168.1.146/cam.jpg"

# YOLO model files
weights_path = r"./YOLO/yolov3.weights"
config_path = r"./YOLO/yolov3.cfg"
names_path = r"./YOLO/coco.names"

# Load the YOLO model and COCO class names
net = cv2.dnn.readNet(weights_path, config_path)
with open(names_path, "r") as f:
    classes = [line.strip() for line in f.readlines()]

layer_names = net.getLayerNames()

# Handling the return value of getUnconnectedOutLayers()
out_layers = net.getUnconnectedOutLayers()
if isinstance(out_layers[0], list):
    output_layers = [layer_names[i[0] - 1] for i in out_layers]
else:
    output_layers = [layer_names[i - 1] for i in out_layers]

# Generate random colors for each class
colors = np.random.uniform(0, 255, size=(len(classes), 3))


def detect_objects(frame):
    height, width, _ = frame.shape
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)

    layer_outputs = net.forward(output_layers)

    boxes = []
    confidences = []
    class_ids = []

    for output in layer_outputs:
        for detection in output:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > 0.3:
                center_x = int(detection[0] * width)
                center_y = int(detection[1] * height)
                w = int(detection[2] * width)
                h = int(detection[3] * height)

                x = int(center_x - w / 2)
                y = int(center_y - h / 2)

                boxes.append([x, y, w, h])
                confidences.append(float(confidence))
                class_ids.append(class_id)

    indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.3, 0.4)

    # Draw detections on the frame
    if len(indexes) > 0 and isinstance(indexes, np.ndarray):
        indexes = indexes.flatten()
        for i in indexes:
            x, y, w, h = boxes[i]
            label = str(classes[class_ids[i]])
            confidence = confidences[i]
            color = colors[class_ids[i]]
            print(f"Detected: {label} with confidence {confidence:.2f}")

            cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
            cv2.putText(
                frame,
                f"{label} {confidence:.2f}",
                (x, y - 10),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.5,
                color,
                2,
            )

    return frame


def main():
    cv2.namedWindow("Object Detection", cv2.WINDOW_AUTOSIZE)

    while True:
        try:
            img_resp = urllib.request.urlopen(url)
            imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
            frame = cv2.imdecode(imgnp, -1)
            frame = detect_objects(frame)

            cv2.imshow("Object Detection", frame)

            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
        except Exception as e:
            print(f"Error occurred: {e}")
            break

    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

 

 

위 코드는 OpenCV와 YOLO 모델을 이용한 객체 탐지 ​​시스템을 구현한 것입니다. 카메라 영상에서 이미지를 캡처하여 실시간으로 객체를 탐지하고 결과를 화면에 표시합니다. 다음은 코드 동작 설명입니다.

 

라이브러리 가져오기

 

필요한 라이브러리를 먼저 임포트합니다: 컴퓨터 비전 작업을 위한 cv2, 수치 연산을 위한 numpy, 그리고 URL 요청 처리를 위한 urllib.request입니다.

 

import cv2
import numpy as np
import urllib.request

 

카메라 URL

 

여기서는 이미지를 캡처할 카메라 피드의 URL을 정의합니다. 이 상수를 웹 서버가 이미지를 전송하는 URL로 바꿔야 합니다.

 

url = "http://192.168.1.146/cam.jpg"

 

YOLO 모델 파일

 

다음으로, YOLO 모델 파일의 경로를 지정합니다. 여기에는 가중치 파일, 구성 파일, 그리고 모델이 감지할 수 있는 객체의 이름이 포함됩니다.

 

weights_path = r"./YOLO/yolov3.weights"
config_path = r"./YOLO/yolov3.cfg"
names_path = r"./YOLO/coco.names"

 

YOLO 모델 로딩

 

OpenCV dnn모듈을 사용하여 YOLO 모델을 로드하고 지정된 파일에서 클래스 이름을 읽어옵니다. 레이어 이름도 나중에 사용할 수 있도록 가져옵니다.

 

net = cv2.dnn.readNet(weights_path, config_path)
with open(names_path, "r") as f:
    classes = [line.strip() for line in f.readlines()]

layer_names = net.getLayerNames()

 

 

출력 레이어

 

우리는 네트워크의 출력 레이어를 결정합니다. 이는 어떤 레이어가 최종 탐지 결과를 제공하는지 이해하는 데 매우 중요합니다.

 

out_layers = net.getUnconnectedOutLayers()
if isinstance(out_layers[0], list):
    output_layers = [layer_names[i[0] - 1] for i in out_layers]
else:
    output_layers = [layer_names[i - 1] for i in out_layers]

 

 

클래스에 사용할 색상 생성

 

감지된 객체를 시각화하고 구분하기 위해 경계 상자에 무작위 색상을 생성합니다.

 

colors = np.random.uniform(0, 255, size=(len(classes), 3))

 

 

객체 감지 기능

 

이 detect_objects()함수는 이미지 프레임을 입력으로 받아 처리하고 YOLO 모델을 사용하여 객체를 감지합니다. 함수가 반환하는 객체에는 경계 상자와 레이블이 표시됩니다.

 

def detect_objects(frame):
    height, width, _ = frame.shape
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)

    layer_outputs = net.forward(output_layers)

    boxes = []
    confidences = []
    class_ids = []

 

 

이 함수에서는 먼저 입력 프레임에서 블롭(blob)을 생성합니다. 블롭은 모델에 적합하도록 이미지를 전처리한 버전입니다. 그런 다음 순방향 전달을 수행하여 모델의 출력을 얻습니다.

 

처리 감지

 

출력 결과를 순회하며 탐지된 객체에 대한 경계 상자, 신뢰도 점수 및 클래스 ID를 추출합니다. 신뢰도가 0.3을 초과하는 탐지만 유효한 것으로 간주됩니다. 이 매개변수(0…1)를 자유롭게 변경하여 더 적거나 더 많은 신뢰도의 탐지 결과를 표시할 수 있습니다.

 

for output in layer_outputs:
    for detection in output:
        scores = detection[5:]
        class_id = np.argmax(scores)
        confidence = scores[class_id]
        if confidence > 0.3:
            center_x = int(detection[0] * width)
            center_y = int(detection[1] * height)
            w = int(detection[2] * width)
            h = int(detection[3] * height)

            x = int(center_x - w / 2)
            y = int(center_y - h / 2)

            boxes.append([x, y, w, h])
            confidences.append(float(confidence))
            class_ids.append(class_id)

 

 

비최대 억제 Non-Maximum Suppression

 

중복되는 박스를 제거하기 위해 최적의 바운딩 박스만 남기는 비최대 억제 (NMS) 기법을 적용합니다.

 

indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.3, 0.4)

 

드로잉 감지

 

감지된 각 객체에 대해 경계 상자와 레이블을 프레임에 그립니다. 감지된 클래스/객체 이름과 신뢰도 점수가 표시됩니다.

 

if len(indexes) > 0 and isinstance(indexes, np.ndarray):
    indexes = indexes.flatten()
    for i in indexes:
        x, y, w, h = boxes[i]
        label = str(classes[class_ids[i]])
        confidence = confidences[i]
        color = colors[class_ids[i]]
        print(f"Detected: {label} with confidence {confidence:.2f}")

        cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
        cv2.putText(
            frame,
            f"{label} {confidence:.2f}",
            (x, y - 10),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5,
            color,
            2,
        )

 

 

메인 함수

 

이 main()함수는 감지 결과를 표시할 창을 설정하고 카메라 영상에서 프레임을 지속적으로 캡처합니다. 캡처된 각 프레임을 detect_objects()함수 내에서 처리하고 결과를 표시합니다.

 

def main():
    cv2.namedWindow("Object Detection", cv2.WINDOW_AUTOSIZE)

    while True:
        try:
            img_resp = urllib.request.urlopen(url)
            imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
            frame = cv2.imdecode(imgnp, -1)
            frame = detect_objects(frame)

            cv2.imshow("Object Detection", frame)

            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
        except Exception as e:
            print(f"Error occurred: {e}")
            break

    cv2.destroyAllWindows()

 

그러면 창이 열리고, 그 창이 열려 있는 상태에서 "q" 키를 누르면 애플리케이션이 종료됩니다.

 

실행 진입점

 

마지막으로 스크립트가 직접 실행되고 있는지 확인하고, 실행되면 해당 main()함수를 호출하여 프로그램을 시작합니다.

 

if __name__ == "__main__":
    main()

 

 

다음 섹션에서는 모든 것을 통합하고 객체 탐지 ​​시스템을 실행합니다.

 

객체 탐지기 실행

 

먼저 ESP32-CAM 모듈에 웹 서버 코드를 실행하고 ESP32-CAM이 이미지를 캡처하여 시리얼 모니터에 출력된 URL을 통해 웹 브라우저에 표시하는지 확인하십시오. 또한 detect.py 파일에서 이 URL을 사용하고 있는지 확인하십시오. 예를 들어 제 경우에는 URL이 다음과 같습니다.

 

url = "http://192.168.1.146/cam.jpg"

 

다음으로 YOLO 객체 탐지기를 시작합니다. 프로젝트 폴더("esp32-cam-object-detection")로 이동하여 가상 환경을 활성화한 후 탐지기 코드 detect.py를 실행하세요:

 

cd esp32-cam-object-detection
venv\Scripts\activate.bat  
python detect.py

 

 

가상 환경을 비활성화하려면 다음 명령어를 실행하면 됩니다.

 

venv\Scripts\deactivate.bat

 

코드가 실행 중이면 감지된 객체의 이름과 신뢰도 점수가 콘솔에 출력됩니다.

 

Detected: cup with confidence 0.76
Detected: laptop with confidence 0.39
Detected: cup with confidence 0.51
Detected: laptop with confidence 0.33
Detected: cup with confidence 0.44
Detected: cup with confidence 0.65
Detected: cup with confidence 0.63

 

 

또한 "객체 감지"라는 창이 열리는데, 이 창에는 카메라가 보고 있는 현재 화면과 시스템이 감지할 수 있는 객체 주변의 경계 상자가 표시됩니다. 아래는 시스템이 컵, ​​리모컨, 노트북을 정확하게 감지한 예시입니다.

 

 

객체 감지 응용 프로그램의 창

 

 

YOLO 모델의 탐지 기능에 대한 더 많은 예시를 보려면 다음 YOLO 데모 비디오를 참조하세요 .

 

결론

 

이 튜토리얼에서는 객체 탐지 ​​시스템을 구축하는 방법을 배웠습니다. ESP32-CAM 모듈은 이미지를 캡처하고 해당 이미지를 처리하는 웹 서버를 실행하는 데 사용되었습니다. 캡처된 이미지는 Wi-Fi를 통해 YOLO 딥러닝 모델 기반의 객체 탐지 ​​소프트웨어가 실행되는 PC로 전송되었습니다.

 

ESP32-CAM용 코드를 컴파일하고 업로드하는 과정은 다소 까다로울 수 있습니다. 문제가 발생하면 더 자세한 지침이 포함된 ESP32-CAM 프로그래밍 튜토리얼 을 참조하세요 .

 

참고로, 저희의 간단한 객체 탐지 ​​시스템은 미리 정의된 80개의 객체(또는 클래스)로 제한되어 있습니다. 하지만, YOLO 모델을 사용자 지정 객체로 학습시킬 수도 있습니다. 이 작업을 원하시면 "YOLOv3를 사용하여 사용자 지정 객체를 탐지하도록 학습시키는 방법" 튜토리얼을 참고하세요.

 

더 궁금한 점이 있으시면 댓글란에 남겨주세요.

 

이 튜토리얼의 원문 참고 

네이버 영상 참고 

 

구현 자료 참고 https://handz.or.kr/library?vid=39

위 링크 핸즈 페이지 링크들

서점에서 절판으로 구입이 어려우면 핸즈(love@handz.or.kr)로 연락주세요.

관련 사이트

핸즈 유튜브 채널
https://youtube.com/c/handzat

핸즈 네이버카페 [핸즈자료창고]
https://cafe.naver.com/handzat

핸즈 블로그
https://blog.naver.com/handzat

핸즈 페이스북
http://facebook.com/handzat

핸즈 인스타그램
https://instagram.com/handz_at

자립하는 삶을 만드는 적정기술센터
http://cafe.naver.com/selfmadecenter

인스트럭터블스
http://www.instructables.com

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

당신의 성공을 위해 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩