- 클라우드 컴퓨팅으로, 구글에서 제공해주는 GPU를 사용해 객체 탐지 모델을 학습시키고 응용하는 방법을 정리합니다.
로컬 컴퓨터가 성능이 좋다면 그것을 사용하시면 되고, 아니라면 여기서 학습시킨 weight값을 가져가서 개발 및 사용할수 있습니다.
- 객체 탐지란,
이미지에서 어느 위치에 어느 객체가 있는지를 알아내는 것을 의미합니다.
무작정 모든 객체를 탐지하는 것이 아니라, 우리가 원하는 것을 검출하도록 학습시킬수도 있습니다.
예를들면 OCR에서는 글자가 이미지, 영상 속 어느 위치에 있는지, 어떤 글자를 나타내는지를 알아낼수 있고,
공사장 안전 탐지에선, 사람 객체와 안전모 객체를 검출해서, 사람이 안전모를 쓰고 있는지를 알아내거나, 혹은 특정 인물의 얼굴을 인식해서 보안으로 사용하거나, 여튼 다양한 것들을 검출하도록, 우리가 미리 학습을 시킬수 있습니다.
(쉽게 말하자면, 이미지 파일을 입력값으로 가지고, 해당 이미지에 객체로 인식되는 부분의 사각 영영의 bounding box의 좌표들과, 해당 영역들이 어떤 객체인지를 나타내는 클래스가 출력값으로 나옵니다.)
- 객체 탐지 기술의 내부는 무척 복잡합니다.
단일 이미지 분석이야 CNN에 대해 공부하신다면, 어느정도 이해할수도 있는데,
객체 탐지는 그에 더해 프로그래밍적이고 보다 구조적인 부분의 응용력이 필요합니다.
이 모델에 대해 자세히 아시고 싶으시면 RCNN부터 Fast RCNN, Faster RCNN 이런 순서로 공부해보세요.
저도 이전에 정리했었는데, Faster RCNN은 현재 코드 개선중입니다.
- 모델을 처음부터 만드는 것은 위에서 언급했듯 어려운 일인데, 그나마 프로그래머분들이 쉽게 객체탐지 정보를 얻어서 사용할수 있는 방법이 있습니다.
바로 YOLO라는 기술의 라이브러리를 사용한다면, 객체 탐지를 학습시키고, 객체를 탐지하고, 그 정보를 얻어오는 등의 일들에 대해, 인터페이스 단위로 매우 쉽게 사용이 가능합니다.
- YOLO는 RCNN계열에서 발전한 객체탐지 모델로, 현재 가장 빠르고 실용성 있는 탐지 모델로도 알려져있습니다.
(실시간 영상 분석이 가능할 정도의 속도에 더불어, 일정 이상의 정확성을 보유)
- 모델 자체에 대한 것은 추후에 알아가며 직접 모델링을 해보도록 하고, 일단 이를 이용하여 모델을 학습시키고 사용하는 방법부터 알아볼 것입니다.
참고로 코드는 https://colab.research.google.com/drive/1_GdoqCJWXsChrOiY8sZMr_zbr_fH-0Fg?usp=sharing#scrollTo=iZULaGX7_H1u가 출처로, 저는 실제로 돌려보며 약간의 개인 설명만 붙일 것입니다.
출처에 이미 사용법과 설명이 있으므로 참고하세요.
- 개발 준비
개발 환경은 GPU를 제공해주는 클라우드 서비스인 구글 Colab을 사용합니다.
https://colab.research.google.com/notebooks/welcome.ipynb?hl=ko#scrollTo=GJBs_flRovLc
!git clone https://github.com/AlexeyAB/darknet
깃에서 yolov4를 colab의 클라우드 컴퓨터에 다운받습니다.
로컬 컴퓨터에서 쉘이나 cmd에 시스템 명령어를 내리듯 코랩 너머의 컴퓨터에게 시스템 명령을 내리려면, !를 붙이면 됩니다.
%cd darknet
!sed -i 's/OPENCV=0/OPENCV=1/' Makefile
!sed -i 's/GPU=0/GPU=1/' Makefile
!sed -i 's/CUDNN=0/CUDNN=1/' Makefile
!sed -i 's/CUDNN_HALF=0/CUDNN_HALF=1/' Makefile
그리고 다운받은 모델의 구동환경 옵션을 위와 같이 설정합니다.
참고로 sed는 리눅스의 텍스트 변환 명령어로, Makefile이라는 파일 내부의 문자열을 조작합니다.
예를들어, s/는 치환을 나타내는 것이고, s!sed -i 's/OPENCV=0/OPENCV=1/' Makefile 는, Makefile 내의 OPENCV=0이라는 문장을 찾아서 OPENCV=1로 치환하는 것을 의미합니다.
자세한 것은 https://linuxstory1.tistory.com/entry/SED-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%82%AC%EC%9A%A9%EB%B2%95
!/usr/local/cuda/bin/nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2019 NVIDIA Corporation Built on Sun_Jul_28_19:07:16_PDT_2019 Cuda compilation tools, release 10.1, V10.1.243
이런식으로 CUDA의 버전 확인을 합니다.
참고로 CUDA는 GPU를 CPU처럼 프로그램 코드의 연산 처리장치로써 활용하도록 도와주는 api인데, Nvidia가 제공하는 것이라, Nvidia 그래픽카드에만 GPU 병렬 처리가 가능합니다.
!make
마지막으로 make를 하면, yolov4의 설치가 완료된 것입니다.
- 학습된 모델 가중치 적용
yolov4 개발자 측에서 미리 학습된 모델 가중치를 제공해줍니다.
!wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights
- 필요 함수 작성
모델과는 상관없지만, 여타 실습 환경에 필요한 유틸 함수를 미리 작성해둡시다.
# define helper functions
def imShow(path):
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
image = cv2.imread(path)
height, width = image.shape[:2]
resized_image = cv2.resize(image,(3*width, 3*height), interpolation = cv2.INTER_CUBIC)
fig = plt.gcf()
fig.set_size_inches(18, 10)
plt.axis("off")
plt.imshow(cv2.cvtColor(resized_image, cv2.COLOR_BGR2RGB))
plt.show()
# use this to upload files
def upload():
from google.colab import files
uploaded = files.upload()
for name, data in uploaded.items():
with open(name, 'wb') as f:
f.write(data)
print ('saved file', name)
# use this to download a file
def download(path):
from google.colab import files
files.download(path)
imshow는 path에 저장된 사진들을 가져와서 크기를 3배로 키워서 나타내주는 것이고, upload는 우리 로컬 컴퓨터에서 코랩 서버 컴퓨터로 파일을 전송하는 것, download는 그 반대입니다.
- 현재 YOLOv4 모델은 COCO Dataset에 맞춰서 미리 학습된 상태, 즉 객체탐지 대회에 참가했던 때의 상태로 준비되어있습니다.
이를 테스트해봅시다.
# run darknet detection on test images
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/person.jpg
위와 같은 명령어로, 테스트를 진행하면 됩니다.
darknet을 실행시키고, 그중 detector test를 실행시키며, 뒤로는 클래스명, 모델 설정, 모델 가중치, 그리고 테스트할 이미지 파일을 넣어주는 것인데, yolo api는 나중에 따로 자세히 정리하겠습니다.
지금은 간단하게 접해본다 생각하고 실습해봅시다.
# show image using our helper function
imShow('predictions.jpg')
직접 실행을 시켜보면, 위와 같이 결과 이미지를 얻어올수 있습니다.
다크넷은 잘 만들어진 객체탐지 모델이자 프로그램인데, 단순히 인공신경망 결과뿐 아니라 이처럼 결과를 합성시켜줘서 편합니다.
원래는 위에서 말했듯 객체가 존재하는 구역의 사각 좌표와 그 좌표의 클래스 인덱스를 반환하는 것으로, 그것을 합쳐서 실제 이미지 위에 투사하면 위와 같습니다.
- 코랩과 파일 교환하기.
로컬이면 별 설명할 것이 없을텐데, 코랩은 클라우드 컴퓨팅으로, 네트워크 너머의 컴퓨터를 조작하기에, 내가 원하는 파일을 올려서 그것을 사용하거나, 클라우드 너머의 파일을 가져오기 위해서 코랩이 네트워크 통신을 쉽게 할수 있는 방식을 제공해줍니다.
이전에 유틸 함수에서 지정해둔 함수를 사용하면 쉽습니다.
# try out the upload helper function! (I uploaded an image called highway.jpg, upload whatever you want!)
%cd ..
upload()
%cd darknet
위와 같이 파일 선택 버튼이 나올텐데, 이를 누르고, 원하는 파일을 선택해서 넣어주면 됩니다.
위에서는 highway.jpg를 넣어주면 된다고 하는데, 같은 이름의 사진을 하나 준비해서 넣어줍시다.
저의 경우에는,
이 사진을 사용할 것입니다.
# run darknet with YOLOv4 on your personal image! (note yours will not be called highway.jpg so change the name)
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights ../highway.jpg
imShow('predictions.jpg')
테스트와 동일하게, 이번에는 우리가 준비한 highway.jpg를 넣어주어 테스트를 진행하고, 거기서 나오는 결과이미지인 predictions.jpg를 나타내면,
복잡한 사진 역시 이런식으로 제법 잘 탐지가 됨을 알수 있습니다.
(오차값이 있는 것은, 보다 학습을 시키거나, 임계점을 높여도 됩니다. 참고로 객체탐지 모델은 '실시간성'을 바탕으로 둔 것이기에 정확도가 떨어지는 것은 당연합니다. 사람도 30프레임에서 실시간으로 영상 내 모든 객체를 판단하기는 어렵죠... 사람은 그 이상의 프레임에서 살고있긴 하지만, 위 모델처럼 이미지 전체를 훑지 않고, 중요부분에 집중하며, 맹점이라는 것이 존재합니다.)
- 구글 드라이브 사용.
구글 코랩이다보니, 파일 전송이라면 구글 드라이브와 연동할수 있는 방법이 있습니다.
%cd ..
from google.colab import drive
drive.mount('/content/gdrive')
위와 같이 원하는 드라이브를 mount하면 나타나는 URL에 들어가서, 자신의 구글 계정으로 로그인을 하고, 거기서 나오는 코드를 붙여넣으면 됩니다.
코랩에서는 content/gdrive에 접속하면 gdrive 내의 파일들에 접근할수 있다는 의미고,
내 드라이브에 대한 접근 권한을 코랩에게 준 것이 됩니다.
# this creates a symbolic link so that now the path /content/gdrive/My\ Drive/ is equal to /mydrive
!ln -s /content/gdrive/My\ Drive/ /mydrive
!ls /mydrive
이렇게, content/gdrive의 My\ Drive/라는 내 드라이브 접근 위치를 단순하게 /mydrive로 접근 가능하게 리눅스 심볼릭 링크를 설정하고,
ls로 해당 위치, 즉 내 구글 드라이브에 뭐가 있는지를, 마치 로컬 파일처럼 확인이 가능합니다.
이번에는 이곳에 접속해서 이미지를 사용해봅시다.
내 구글 드라이브에 images라는 폴더를 만들고, 그 안에 임의의 street.jpg 파일을 넣어두고 아래 코드를 실행시킵니다.
# run detections on image within your Google Drive!
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights /mydrive/images/street.jpg
imShow('predictions.jpg')
참고로 위의 이미지는 제가 위 출처에서 임의로 구한 이미지입니다.
일부러 어려워보이는 사진을 골랐는데, 겹쳐있는 부분의 자전거, 사람, 심지어 핸드백이나 백팩까지 전부 감지합니다.
성능이 매우 좋네요.
- 클라우드 컴퓨터에 업로드하는 것과 반대로, 클라우드 컴퓨터에서 작업한 내역을 내 로컬 컴퓨터로 다운로드 하려면,
# LOCAL MACHINE DOWNLOAD
# if you get an error first run then run it again and it should work
download('predictions.jpg')
위에서 작성한 download 함수를 사용해서 다운받고자하는 파일의 경로와 파일명을 적어주면 됩니다.
# GOOGLE DRIVE DOWNLOAD
# note that I can change what the image name is saved as (I am saving it as detection1.jpg)
!cp predictions.jpg /mydrive/images/detection1.jpg
구글 드라이브에 저장하려고 한다면,
# GOOGLE DRIVE DOWNLOAD
# note that I can change what the image name is saved as (I am saving it as detection1.jpg)
!cp predictions.jpg /mydrive/images/detection1.jpg
마치 실제 리눅스 로컬에서 파일을 다루듯, cp 명령어를 이용해서, 구글 드라이브의 링크로 지정한 mydrive로 옮겨주면 됩니다.
- 동영상 객체탐지
이미지 사진이 아니라 동영상에서 이를 사용해봅시다.
탐지할 영상 파일을 준비합시다.
영상 파일은 아무거나 찾아도 되지만, 원작자분께서 준비한 https://github.com/theAIGuysCode/YOLOv4-Cloud-Tutorial/blob/master/videos/test.mp4를 사용하면 편합니다.
# upload the video of your choosing! (Feel free to use the same video I do, it is in the Github repository)
upload()
test.mp4를 업로드하고,
!./darknet detector demo cfg/coco.data cfg/yolov4.cfg yolov4.weights -dont_show test.mp4 -i 0 -out_filename results.avi
이렇게 탐지기를 돌리면,
위 설정대로 results.avi가 나옵니다.
(다시말하지만 yolo에서 제공하는 api는 따로 정리하겠습니다.
여기선 간단한 것들 몇가지만 정리합니다.)
이렇게 얻어진 결과 영상을,
# download the video with detections shown
download('results.avi')
다운로드 하여 결과물을 확인하면 아래와 같습니다.
당연히 구글 드라이브도 사용 가능하며, 이 경우에는 간편하게,
!./darknet detector demo cfg/coco.data cfg/yolov4.cfg yolov4.weights -dont_show /mydrive/videos/test.mp4 -i 0 -out_filename /mydrive/videos/results.avi
이렇게 경로만 지정해주면 됩니다.
[YOLO v4 커스텀]
- 이제 본격적으로 모델을 커스텀해봅시다.
- 임계치 변경
임계치 Threshold란, 정확도 어디까지를 탐지할지에 대한 수치입니다.
# this is ran without the threshold flag set
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/dog.jpg
imShow('predictions.jpg')
먼저 위와 같이 아무것도 정하지 않을 경우에는, 0.33짜리 pottedplant도 검출이 되는데,
# same detections but ran with the threshold flag set to 0.5 (pottedplant is no longer detected!)
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/dog.jpg -thresh 0.5
imShow('predictions.jpg')
위와같이 threshold를 0.5로 설정해줄 경우,
정확도가 0.5 이상인 것만을 검출하고 나머지는 배경으로써 무시합니다.
- 좌표 출력
# darknet run with external output flag to print bounding box coordinates
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/person.jpg -ext_output
imShow('predictions.jpg')
위와 같이 -ext_output 옵션을 붙인다면, 탐색 결과에서 각 탐지 객체의 바운딩 박스 좌표를 얻어옵니다.
dog: 99% (left_x: 62 top_y: 265 width: 142 height: 80) person: 100% (left_x: 194 top_y: 98 width: 78 height: 281) horse: 98% (left_x: 406 top_y: 140 width: 196 height: 204) Unable to init server: Could not connect: Connection refused
결과는 대략 위와 같이 클래스: 정확도(좌표) 로 나타납니다.
- 사진 표시 x
# running darknet with dont show flag set (no longer get warnings)
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights data/person.jpg -dont_show
이건 코랩 환경에선 의미가 없을지도 모르는데,
로컬에서 yolo를 돌릴시, 콘솔창이 열리며, 그 결과가 나타납니다.
-dont_show를 입력시, 처리 결과 이미지를 보여주지 않습니다.
즉, 우리가 지금껏 코랩에서 처리한 것과 동일하죠.
위에서 동영상 탐지때 이를 사용했는데, 저도 정확히는 모르겠지만, 창에 따로 결과를 실시간 반영하는데 컴퓨팅 파워가 들어가기에 이를 사용하면 좋을것 같습니다.
다른 프로그램 등에서 yolo를 사용하여 영상 처리를 하기 위해선 이것에서 처리한 결과물의 메모리를 실시간으로 다른 프로그램에서 사용할수 있게 하는 방법을 yolo에서 제공할텐데, 이는 실제 개발을 하며 따로 정리하겠습니다.
- 여러 이미지 처리
여러 이미지 처리의 경우에는 리스트를 만들어놓으면 됩니다.
프로그래밍에서 말하는 리스트라기보다는 목록을 적어둔 텍스트 파일을 만들어둡시다.
/mydrive/images/plane.jpg
/mydrive/images/street.jpg
/mydrive/images/highway.jpg
이런식으로 안에 경로를 적어둔 images.txt 파일을 만들면, 한줄 한줄당 경로들의 이미지를 yolo가 가져올 것입니다.
구글 드라이브에 이를 업로드합시다.
그리고 위에 적힌 경로에 각 jpg 파일을 넣어두고,
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights -ext_output -dont_show -out result.json < /mydrive/images.txt
앞에서 배운 좌표 출력 옵션에, dont_show를 사용하고, -out을 이제까지처럼 이미지 파일이 아닌 json 형식으로 한 후, 위에서 작성한 텍스트 파일을 입력값으로 넣으면 됩니다. (아마 다크넷 안에서 입력값 확장자가 이미지면 그거 하나를 사용하고, txt라면, 내부 한줄 한줄을 경로로써 리스트에 담아서 처리하는 것이겠네요.)
download('result.json')
다운로드는 위와 같이 합니다.
참고로 json은 데이터 저장 방식을 의미합니다.
최근 프로그래밍에선 매우 유용하게 사용하는 방식이니 모르신다면 검색하세요.
보다 사람이 보기 쉽게 텍스트 파일로 다운받으시려면,
!./darknet detector test cfg/coco.data cfg/yolov4.cfg yolov4.weights -dont_show -ext_output < /mydrive/images.txt > result.txt
download('result.txt')
이렇게 하시고 내부를 보시면,
yolo를 돌리며 얻어올수 있는 훈련 데이터, 클래스 및 좌표 데이터를 얻을수 있습니다.
역시나 프로그래밍적으로 이용하려면 json 형식이 좋습니다.
- 커스텀 객체 탐지기 훈련시키기
드디어 해봅시다.
위에 까지는 yolo 개발자가 coco dataset에 특화된 매우 잘 훈련된 모델 가중치를 가지고 돌린 결과인데, 꽤나 성능이 좋았습니다.
다만, 우리가 원하는 카테고리가 위 데이터셋에 속하지 않을수도 있고, 특정한 정보만을 탐지하고 싶을 때도 있습니다.
바로 그러할때, yolo의 기본 구조를 가지고, 몇 클래스를 탐지할 것인지, 무슨 클래스를 탐지할 것인지를 스스로 학습시키고 커스텀 할수 있습니다.
yolo가 프로그램이 아닌 모델로 제공이 된다면,
그러니까 토치나 텐서플로 레이어로 제공이 된다면,
커스텀에서는, 클래스 분류 FC 레이어를 제거하고, 특징 추출 단계에서 원하는 클래스 수 + 1(배경)로 하여 새롭게 붙인 후, 학습을 시키면 되는데, Yolo는 일부러 그런 것인지 그런식으로 제공되지는 않더군요.
내부 구현을 은닉하려는지 아니면 모델 구조 커스텀을 막은건지...
하지만 대신 더 편한 api가 제공됩니다. (토치로 만든 인공신경망을 wrapping한 것이라 생각하면 됩니다.)
YOLO Detector에서 커스텀 학습에 필요한 정보는,
정답 레이블과 데이터셋,
cfg 파일,
obj.data와 obj.name 파일,
train.txt 파일
입니다.
이를 커스텀하시면 됩니다.
1. 레이블링 및 커스텀 데이터셋 준비
모든 지도학습에서 가장 중요한 작업입니다.
데이터 기반 머신러닝이 가진 특징이자, 아직까지 비자율적 머신러닝의 한계이기도 하죠.
데이터셋은 전세계 비전 머신러닝 사용자가 애용하는 https://storage.googleapis.com/openimages/web/index.html를 사용합니다.
여기서 데이터를 추출하고 레이블링까지 해주는 도구로는, https://github.com/theAIGuysCode/OIDv4_ToolKit이 있습니다.
이에대한 설명으론 https://www.youtube.com/watch?v=_4A9inxGqRM를 추천합니다.
여기선,
python main.py downloader --classes 'Vehicle registration plate' --type_csv train --limit 1500
위와 같이 train 데이터로 자동차 번호판을 추출하도록 할 것입니다.
python main.py downloader --classes 'Vehicle registration plate' --type_csv validation --limit 300
검증 데이터 역시 준비해줍시다.
그리고 클래스명을 준비합니다.
classes.txt에서, 예측할 클래스명을 적어주면 되는데,
한줄당 하나씩 클래스명을 적어주면 됩니다.
신경망에서 나오는 1 0 0 0
과 같은 원 핫 벡터에서, 각 컬럼이 뜻하는 것을 문자열로 나타낸 것입니다.
여기선, 첫줄에 Vehicle registration plate라고만 적어두면 됩니다.
이후 툴에서 다운받은 레이블을 yolo에 맞도록, convert 해줍니다.
python convert_annotations.py
convert가 되었다면 예전 레이블 폴더는 제거합니다.
rm -r OID/Dataset/train/'Vehicle registration plate'/Label/
rm -r OID/Dataset/validation/'Vehicle registration plate'/Label/
다운받은 이미지 파일이 들어있는 폴더에는, 각 이미지 파일과, 그 이미지의 클래스, 그리고 바운딩 박스의 좌표가 들어있는 정답 txt 파일이 한쌍씩 들어있습니다.
- 위 사이트에서 제공해주는 데이터셋에는 존재하지 않는 이미지를 탐지하고자 할수도 있을것입니다.
그럴때는 위와 같은 방식이 아니라, 직접 학습 데이터셋을 만들어야합니다.(노가다입니다... 비지도 학습으로 이를 해결하는 방식에 대한 아이디어가 있지만, 앞으로 최소 5년간은 시도도 안해볼 예정입니다...;;)
말 그대로 검색해서 하나하나 정리하는 방식도 있는데, 조금 이를 도와주는 툴이 있습니다.
https://www.youtube.com/watch?v=EGQyDla8JNU를 참고하세요.
이외에도 이미지를 크롤링 하는 도구나 바운딩 박스를 편하게 계산해주는 도구도 있으니 구글링 해서 그때그때 사용합시다.
2. 클라우드에 업로드
클라우드 환경에서 이를 사용할 것이기에 위에서 만들어낸 학습 데이터를 옮겨줘야합니다.
구글드라이브를 사용하면 편할 것입니다.
먼저 학습 데이터를 obj.zip으로 압축하고,
검증 데이터는 test.zip으로 압축합니다.
그리고 준비할 파일들이 있는데,
generate_test.py
import os
image_files = []
os.chdir(os.path.join("data", "test"))
for filename in os.listdir(os.getcwd()):
if filename.endswith(".jpg"):
image_files.append("data/test/" + filename)
os.chdir("..")
with open("test.txt", "w") as outfile:
for image in image_files:
outfile.write(image)
outfile.write("\n")
outfile.close()
os.chdir("..")
generate_train.py
import os
image_files = []
os.chdir(os.path.join("data", "obj"))
for filename in os.listdir(os.getcwd()):
if filename.endswith(".jpg"):
image_files.append("data/obj/" + filename)
os.chdir("..")
with open("train.txt", "w") as outfile:
for image in image_files:
outfile.write(image)
outfile.write("\n")
outfile.close()
os.chdir("..")
obj.data
classes = 1
train = data/train.txt
valid = data/test.txt
names = data/obj.names
backup = /mydrive/yolov4/backup
(클래스 수와 경로 설정)
obj.names
license_plate
(레이블 명을 한줄에 하나씩 지정)
위와 같은 파일들을 구글 드라이브에 넣어줍니다.
클라우드 환경에서 이를 빠르게 접근할수 있도록 복사해줍니다.
# copy over both datasets into the root directory of the Colab VM (comment out test.zip if you are not using a validation dataset)
!cp /mydrive/yolov4/obj.zip ../
!cp /mydrive/yolov4/test.zip ../
그리고 준비한 데이터를 unzip
# unzip the datasets and their contents so that they are now in /darknet/data/ folder
!unzip ../obj.zip -d data/
!unzip ../test.zip -d data/
3. yolo 설정
yolo의 구조나 학습에 대해 설정합시다.
여기선 1개 클래스, 특정한 이미지에 대한 학습을 할테니 그에 맞도록 설정을 해줘야합니다.
# download cfg to google drive and change its name
!cp cfg/yolov4-custom.cfg /mydrive/yolov4/yolov4-obj.cfg
yolo에서 제공해주는 cfg 기본 파일을 구글드라이브에 복사해줍니다.
# to download to local machine (change its name to yolov4-obj.cfg once you download)
download('cfg/yolov4-custom.cfg')
이를 편히 편집하기 위해 로컬 컴퓨터에 다운로드(혹은 구글 드라이브 텍스트 편집 실행) 하고,
편집을 합니다.
[net]
# Testing
#batch=1
#subdivisions=1
# Training
batch=64
subdivisions=16
width=416
height=416
channels=3
momentum=0.949
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
learning_rate=0.001
burn_in=1000
max_batches = 6000
policy=steps
steps=4800,5400
scales=.1,.1
#cutmix=1
mosaic=1
#:104x104 54:52x52 85:26x26 104:13x13 for 416
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=mish
# Downsample
[convolutional]
batch_normalize=1
filters=64
size=3
stride=2
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[route]
layers = -2
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=32
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[route]
layers = -1,-7
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
# Downsample
[convolutional]
batch_normalize=1
filters=128
size=3
stride=2
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[route]
layers = -2
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=mish
[route]
layers = -1,-10
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
# Downsample
[convolutional]
batch_normalize=1
filters=256
size=3
stride=2
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[route]
layers = -2
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=mish
[route]
layers = -1,-28
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
# Downsample
[convolutional]
batch_normalize=1
filters=512
size=3
stride=2
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[route]
layers = -2
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=mish
[route]
layers = -1,-28
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
# Downsample
[convolutional]
batch_normalize=1
filters=1024
size=3
stride=2
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[route]
layers = -2
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=mish
[shortcut]
from=-3
activation=linear
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=mish
[route]
layers = -1,-16
[convolutional]
batch_normalize=1
filters=1024
size=1
stride=1
pad=1
activation=mish
stopbackward=800
##########################
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
### SPP ###
[maxpool]
stride=1
size=5
[route]
layers=-2
[maxpool]
stride=1
size=9
[route]
layers=-4
[maxpool]
stride=1
size=13
[route]
layers=-1,-3,-5,-6
### End SPP ###
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[upsample]
stride=2
[route]
layers = 85
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[route]
layers = -1, -3
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[upsample]
stride=2
[route]
layers = 54
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[route]
layers = -1, -3
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
##########################
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky
[convolutional]
size=1
stride=1
pad=1
filters=18
activation=linear
[yolo]
mask = 0,1,2
anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401
classes=1
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
scale_x_y = 1.2
iou_thresh=0.213
cls_normalizer=1.0
iou_normalizer=0.07
iou_loss=ciou
nms_kind=greedynms
beta_nms=0.6
max_delta=5
[route]
layers = -4
[convolutional]
batch_normalize=1
size=3
stride=2
pad=1
filters=256
activation=leaky
[route]
layers = -1, -16
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky
[convolutional]
size=1
stride=1
pad=1
filters=18
activation=linear
[yolo]
mask = 3,4,5
anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401
classes=1
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
scale_x_y = 1.1
iou_thresh=0.213
cls_normalizer=1.0
iou_normalizer=0.07
iou_loss=ciou
nms_kind=greedynms
beta_nms=0.6
max_delta=5
[route]
layers = -4
[convolutional]
batch_normalize=1
size=3
stride=2
pad=1
filters=512
activation=leaky
[route]
layers = -1, -37
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky
[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky
[convolutional]
size=1
stride=1
pad=1
filters=18
activation=linear
[yolo]
mask = 6,7,8
anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401
classes=1
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1
scale_x_y = 1.05
iou_thresh=0.213
cls_normalizer=1.0
iou_normalizer=0.07
iou_loss=ciou
nms_kind=greedynms
beta_nms=0.6
max_delta=5
결론적으로 위와 같습니다.
중요한 것 몇가지를 찝어보자면,
[net]은 전체 신경망 학습 및 동작에 대한걸 설정하는 것입니다.
그리고 그 아래로는 각 레이어들에 대한 설명인데,
CNN 등 레이어들이 있죠.
yolo의 경우는 바운딩 박스를 예측하는 레이어라고 생각합시다.
주된 설정은,
batch = 64
subdivisions = 16
max_batches = 6000,
steps = 4800, 5400
classes = 1
filters = 18
batch = 64
subdivisions = 16
max_batches = 6000,
steps = 4800, 5400
classes = 1
filters = 18
width = 416
height = 416
max_batches = (클래스 수) * 2000 (6000 이하가 되어선 안됨. 고로 2클래스 까지는 그냥 6000개로 합시다.)
steps = (max_batches의 80%), (max_batches의 90%)
filters = (클래스 수 + 5) * 3
이런식으로 하시면 됩니다.
자신이 변경하고 싶은 환경에 따라 계산해서 적어주면 됩니다.
위와 같이 변경한 cfg 파일을 카피해줍시다.
# upload the custom .cfg back to cloud VM from Google Drive
!cp /mydrive/yolov4/yolov4-obj.cfg ./cfg
그리고 위에서 구글드라이브에 만들어둔 나머지 파일을 가져옵시다.
!cp /mydrive/yolov4/obj.names ./data
!cp /mydrive/yolov4/obj.data ./data
# upload the generate_train.py and generate_test.py script to cloud VM from Google Drive
!cp /mydrive/yolov4/generate_train.py ./
!cp /mydrive/yolov4/generate_test.py ./
4. test.txt, train.txt 제너레이트
yolo에게 어느 이미지를 사용할지에 대해 알려주는 것이 바로 위 두 txt 파일입니다.
이를 편히 작성하기 위해 위에서 미리 파이썬 유틸을 만들어두었죠.
이를 실행시킵시다.
!python generate_train.py
!python generate_test.py
5. CNN 계층 가중치 다운
전이학습을 통해 빠르고 성능 좋게 학습을 진행하기 위해,
이미지 특징을 추출하는 CNN 계층은 미리 학습된 가중치를 가져옵니다.
!wget https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.conv.137
6. 학습 시키기
# train your custom detector! (uncomment %%capture below if you run into memory issues or your Colab is crashing)
# %%capture
!./darknet detector train data/obj.data cfg/yolov4-obj.cfg yolov4.conv.137 -dont_show -map
darkent의 detector를 train해줍니다.
이미 준비해둔 obj.data와, cfg 파일, 그리고 미리 학습된 conv 가중치를 넣어서 학습시킵니다.
-map은, 학습시 검증 데이터셋이 있을 때 사용하며, Mean Average Precision을 구해줍니다.
# show chart.png of how custom object detector did with training
imShow('chart.png')
를 통해 학습 결과를 확인하면, 에폭당 학습 상태를 확인 가능합니다.
# kick off training from where it last saved
!./darknet detector train data/obj.data cfg/yolov4-obj.cfg /mydrive/yolov4/backup/yolov4-obj_last.weights -dont_show
위에선 선행 학습된 가중치로 학습을 한 것인데,
이에 더해서, 한번 학습을 마친 가중치를 가지고 학습을 하려면, backup/yolov4-obj_last.weights를 통해 이 가중치 값을 이용해 학습을 하면 됩니다.
7. 학습된 모델 사용
이제 마지막으로 학습된 모델을 사용해봅시다.
# need to set our custom cfg to test mode
%cd cfg
!sed -i 's/batch=64/batch=1/' yolov4-obj.cfg
!sed -i 's/subdivisions=16/subdivisions=1/' yolov4-obj.cfg
%cd ..
# run your custom detector with this command (upload an image to your google drive to test, thresh flag sets accuracy that detection must be in order to show it)
!./darknet detector test data/obj.data cfg/yolov4-obj.cfg /mydrive/yolov4/backup/yolov4-obj_last.weights /mydrive/images/car2.jpg -thresh 0.3
imShow('predictions.jpg')
그냥 편하게 detector test를 하여, 설정과 학습된 가중치를 사용해서 객체탐지를 하면 됩니다.
끝입니다.
// 이를 이용해 다른 프로그램에서 응용하여 사용하는 법...
darknet yolo를 통해 메모리상으로 데이터를 얻어와 사용하는 법은 여럿 있던데, 해당 코드들을 찾아보고 만들어봐야겠습니다.
그보다, torch를 이용해서 직접 yolo를 구현하고 사용하는 법이 있던데, 이를 먼저 공부할지 어떨지...
객체 탐지만 잘 다룰수 있어도 컴퓨터로 할수있는 것이 많아지니 재밌네요.
(ex : 생체 탐지, 상황 탐지, 코로나 관련 마스크 여부 확인 등...)