저는 따로 시장을 전망하는 사람도 아니며, 다양한 article을 읽지도 않았고, 해당 분야의 사람들과 소통을 많이 하지도 않습니다. 따라서 아래글에는 오류가 상당할 수 있으며, 단순히 한 개인의 생각임을 먼저 말씀드립니다.
MCU나 CPU(이하 MCU로 통일하여 명명)는 ARM등의 Core와 다양한 Peripheral들로 구성됩니다. 각각의 Peripheral들은 하나의 Chipset으로 구현되어 판매될 정도이니, MCU는 그 자체로 매우 큰 'SYSTEM'이라 할 수 있습니다. 이에 저는 MCU의 모든 기능을 파악한 후에 개발하기 보다는, 기본 예제를 기반으로 그때 그때 필요한 기능들을 찾아서 개발하고 있습니다. MCU를 전체적으로 이해하는 몇 안되는 천재들은 당연히 존재하겠습니다만, 대부분의 개발자는 저와 비슷하리라 생각합니다.
이렇듯 MCU의 모든 기능을 파악하지 않고도 개발이 가능한 이유는, MCU 제공 회사들이 다양한 예제 혹은 SDK를 같이 배포하고, Community를 제공하며, MCU 사용자들이 github등에 자신의 결과물을 배포하기 때문입니다. 다만, MCU 사용자들이 제공하는 기능은, 개별적으로 제공되다 보니 각각의 기능을 직접 찾아야 할 수는 있겠습니다.
Espressif사가 제공하는 ESP32는 WiFi를 제공하는 값싼 전용 MCU로, 다양한 분야에 사용 가능한 범용 MCU라고 생각하지는 않았습니다. 그런데, 시간이 지날수록 다양한 예제들이 나오고 LCD 이후 Camera까지 제공되기에 이르렀습니다. 특히나 Linux가 아닌 small embedded OS(예를 들면 freeRTOS)에서 powerful한 network 예제를 제공하는 것은 매우 큰 장점입니다. 최근에는 Multi core도 제공합니다. Espressif는 이미 WiFi 시장을 석권하였고, 이제는 범용 MCU로서 low end 범용 MCU 시장까지도 진입 하려는 것으로 보입니다.
다만, 다른 Major MCU 회사들과 달리, Espressif의 향후 MCU line up을 확인하기는 어렸습니다. 물론 MCU line up이 인터넷에 그냥 open하는 정보가 아니기는 합니다. 대개 새로운 line up은 core의 진화에 의한 것이 많은데, 사용자가 많은 ARM과 달리 Espressif가 사용하는 Xtensa Core는 매우 특화된 시장을 가지고 있으며, 상대적으로 진화 속도는 느릴 것으로 생각됩니다. 이에 Core의 진화보다는 periperal의 보강으로 line up을 가져가려는 것으로 보입니다. 사실 이 방법이 보다 현명할 것 같습니다. ESP32의 개발 환경은 IDF이며, 이는 IoT Development Framework의 약자입니다. WiFi 이후 IoT 시장을 지향하는 것이 Espressif의 방향이라면 Core의 Operating clock이나 불필요하게 여러가지의 Periperal을 추가하기 보다는, IoT에 필요한 Periperal들을 추가하면서 점점 mid/high end 시장으로의 확대를 방향성으로 가져가는 것은 현명해 보입니다. (개인 뇌피셜입니다.)
ESP32의 개발 환경으로 Arduino이 있습니다. ESP32가 Arduino 환경을 지원하기에 Arduino open hardware처럼 open hardware를 지향하려나 생각했습니다만, Arduino는 그 자체로 훌륭한 개발 환경입니다. 처음에는 예제로 초보자들이 따라하기 쉬우라고 제공하는 단순한 프로그램이라고 생각했습니다. 그런데, ESP32 CAM 예제를 실행하다 보니, 다양한 라이브러리와 Core들을 제공하는 어엿한 개발환경이더군요. Arduino 이외에 ESP32는 PlatformIO도 개발 환경으로 지원하고 있습니다.
ESP32 관련해서는 이미 많은 내용이 인터넷상에 있기는 합니다만, 이후에는 hardware 보다는 software (or firmware) 위주로, ESP32 CAM과 관련하여 좀더 읽기 편하게 정리해 보려고 합니다.
원할한 수행을 위해서는 numpy, matplotlib, opencv-python 패키지가 필요합니다.
virtualenv를 이용한 가장 기본적인 환경 설정은 다음과 같습니다.
# virtualenv 환경 설치
virtualenv venv --python=python3
# virtualenv 실행
source venv/bin/activate
# 필요한 패키지 설치
pip install numpy
pip install matplotlib
pip install opencv-python
이후에는 단순히 아래 명령어를 수행하면 됩니다.
python main.py
프로그램이 실행되면서 화면이 계속 변경되는 것을 확인할 수 있습니다.
최종적인 디렉토리 구조는 아래와 같습니다.
├── input
│ ├── detection-results # detection 결과 txt 파일들
│ ├── ground-truth # object 정보 txt 파일들 (정답)
│ └── images-optional # 원본 이미지
└── output # 통계 이미지, output.txt
├── classes # class별 AP 그림
└── images # 원본과 detection 결과를 같이 보여 주는 이미지
└── detections_one_by_one # 각각의 object별 이미지
소스를 처음 받으면 input 폴더와 하위 폴더만 있고, 실행 후에 output 폴더가 생성됩니다.
각 폴더 및 폴더에 포함된 파일들에 대한 상세 설명은 아래와 같습니다.
input/detection-results : 모델을 통해서 얻어진 detection 결과값
input/ground-truth : ground-truth data. 일종의 정답
input/images-optional : 실제 사용한 이미지들
output : 통계 이미지. AP, mAP를 위한 실제 데이터 값 (output.txt)
output/classes : class별 AP 이미지
output/images : detection 정보 및 ground-truth data가 box 형태로 실제 이미지에 표시된 최종 결과 이미지
output/images/detections_one_by_one : detection된 내용이 개별적으로 이미지에 표시된 파일
detection-results에 포함된 "이미지파일명.txt" 파일들의 구성은 다음과 같습니다.
class명 confidence xtl ytl xbr ybr
confidence : 모델이 해당 class로 확신하는 정도. float. %
xtl, ytl : box의 좌측상단 좌표(top left)
xbr, ybr : box의 우측하단 좌표(bottom right)
ground-truth에 포함된 "이미지파일명.txt"는 위와 동일한데, confidence만 없습니다.
이글은 train하고 모델을 변경해서 성능을 향상시키는 것이 목표가 아닙니다. Train을 완료한 config 파일과 weights 파일을 이용해서 테스트 파일을 시험하고, 모델의 성능을 평가하는 것이 주요 목표입니다.
평가를 위해서는 test를 통해서 object를 detect하고, 해당 object로 인식하는 confidence, 위치, 크기등의 정보가 필요합니다. 이를 위해서는 "./darknet detector test" 명령어를 사용하며, git 소스의 src/detector.c에 있는 test_detector() 함수를 직접 호출하게 되는데, 함수의 원형은 다음과 같습니다.
void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filename,
float thresh, float hier_thresh, int dont_show, int ext_output, int save_labels,
char *outfile, int letter_box, int benchmark_layers)
Arguments들에 대해서 확인한 내용을 정리하면 다음과 같습니다.
datacfg : 데이터 파일
cfgfile : cfg 파일
weightfile : weights 파일
filename : 시험에 사용할 테스트 파일
thesh : object를 인식하기 위한 minimum threshold
hier_thresh : -
dont_show : OpenCV가 있는 경우, 찾은 object를 box로 표시한 그림을 볼 수 있는데, 이를 보지 않도록 하는 옵션
ext_output : 찾은 object의 위치 정보를 console에 출력하기 위한 옵션
save_labels : 찾은 object의 위치 정보를 txt 파일로 자동 저장 (예를 들어 data/dog.jpg를 시험하면, data/dog.txt 파일이 자동으로 생성됨)
outfile :찾은 object의 확률 및 정보등을 json 파일 형식으로 저장하기 위한 옵션
letter_box : box를 letterbox로 그리는 것으로 보임(확인해 보지 않음)
benchmark_layers : -
옵션을 사용하기 위해서 입력해야 하는 변수는 위의 이름과 틀릴 수 있습니다. (hier_thresh가 아니라 -hier을 사용하는등... 자세한 사항은 소스 참조)
이글에서는 save_labels와 outfile 옵션을 소개합니다.
앞선 글에서 설명했듯이, -ext_output 옵션을 사용하면 화면에 detect한 object들의 위치를 출력합니다.
-save_labels 옵션을 사용하면 이미지 파일이 존재하는 폴더에 이미지 파일에서 확장자를 'txt'로 변경한 파일명을 가지는 label 파일이 생성됩니다.
이 label 파일에는 -ext_output의 출력 내용을 map 옵션에서 사용하기 위해서 변경한 최종 내용이 포함됩니다.
예를 들어 data/dog.jpg에서 data/dog.txt 파일이 생성되고, 그 내용은 아래와 같습니다.
- 여기서 'detecor test' 명령에는 data 파일이 사용되는데, data 파일의 형식은 다음과 같습니다. (coco.data 파일에 주석을 달았습니다.)
# #는 주석 표시
# class의 갯수
classes= 80
# train에 사용하는 파일
train = /home/pjreddie/data/coco/trainvalno5k.txt
# validation이나 test에 사용하는 파일
valid = coco_testdev
#valid = data/coco_val_5k.list
# class와 class id를 matching시키기 위한 names 파일
names = data/coco.names
backup = /home/pjreddie/backup/
eval=coco
- 시험에 사용할 파일명( (여기서는 data/dog.jpg)을 맨 마지막에 넣어 줬는데, 파일명이 없는 경우 아래와 같은 메시지가 출력되면서 파일 경로 입력을 기다립니다.
"Enter Image Path:
- 인식 확률이 출력되는데, '-ext_output' 옵션을 추가하면, 검출된 objects의 위치와 크기 정보를 출력합니다.
.txt-file for each .jpg-image-file - in the same directory and with the same name, but with .txt-extension, and put to file: object number and object coordinates on this image, for each object in new line: <object-class> <x> <y> <width> <height>
Where:
<object-class> - integer number of object from 0 to (classes-1)
<x> <y> <width> <height> - float values relative to width and height of image, it can be equal from (0.0 to 1.0]
for example: <x> = <absolute_x> / <image_width> or <height> = <absolute_height> / <image_height>
atention: <x> <y> - are center of rectangle (are not top-left corner)
For example for img1.jpg you will be created img1.txt containing:
- 여기서 X, Y는 이미지의 start point가 아니라, 전체 이미지의 center 값입니다. 내용을 이해하기 어려운데 git source의 script/voc_lable.py의 convert() 함수에 다음 내용이 있습니다.
x = (box[0] + box[1])/2.0 - 1
y = (box[2] + box[3])/2.0 - 1
x = x*dw
w = w*dw
여기서 box[0], box[1]은 xmin, xmax로, box[2], box[3]은 ymin, ymax로 이해하면 되며, 이를 수식으로 표시하면 다음과 같습니다.
import tarfile
import os
# 폴더 생성 예제
# Linux의 경우 '/'로 시작해야 하며, '~'로 home page를 표시할 수는 없음
srcFolderName = os.path.join('/home','apple', 'Downloads')
for i in range(1, 14):
# Documents 폴더에 'Output_01', 'Output_03', ... , 'Output_13'을 생성
dstFolderName = os.path.join('/home','apple', 'Documents', 'Output_'+'%02d'%i)
# 예외 처리
try:
if not(os.path.isdir(dstFolderName)):
os.makedirs(os.path.join(dstFolderName))
except OSError as e:
if e.errno != errno.EEXIST:
print("Failed to create directory!!!!!")
raise
" srcFolder에 있는 summary.tar.bz2 파일을 dstFolder에 extract
tar = tarfile.open(os.path.join(srcFolderName, 'summary.tar.bz2'), "r:bz2")
tar.extractall(dstFolderName)
tar.close()