안녕하세요!
YOLO 설치 및 객체탐지 본문
YOLO란 무엇인가?
YOLO(You Only Look Once)
한개의 네트워크(계충, 모델 같은 의미로 칭함.)에서 객체(물체, 사물)을 탐지
탐지된 객체의 영역(바운딩 박스 - 사각형)과 객체의 이름(사람, 고양이 등...)을 표시해 주는 기능을 수행.
YOLO 설치 방법
0. 파일관리 폴더 생성
- yolo/config
1. YOLO 가중치(weights) 파일 다운로드 받기
- https://pjreddie.com/media/files/yolov3.weights
- https://pjreddie.com/media/files/yolov2-tiny.weights
- config 폴더 안에 다운받은 2개 파일 위치시키기
2. YOLO 환경설정 파일 다운받기
- https://github.com/pjreddie/darknet 접속 후 [다운로드] // darknet-master.zip
- cfg폴더 안에 → "yolov3.cfg", "yolov2-tiny.cfg" 2개 파일을 config 폴더 안에 위치 시키기
- data 폴더 안에 → "coco.names" 1개 파일을 config 폴더 안에 위치 시키기
3. 테스트 데이터 다운로드 하기
- https://www.kaggle.com/sshikamaru/car-object-detection
- 우측 상단 [다운로드]클릭 > 다운로드 > 압축풀기
- 다운받은 폴더 내 "data"폴더를 사용
- data폴더 이름을 cardataset으로 변경.
- yolo/ 폴더 밑으로 위치
4. OpenCV 프레임워크 설치하기
- 가상환경 활성화 → pip install opencv-python
이렇게하면 된다.
오늘도 필요한것을 임포트하며 시작하겠다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#OpenCV 프레임워크 라이브러리
import cv2
우선 불러오기
box = pd.read_csv("./yolo/cardataset/train_solution_bounding_boxes (1).csv")
box.head(5)
인공지능의경우는 결측치가 있으면 안되니 이상치 및 결측치 확인.
box.info()
box.describe()
# 샘플 자동차 이미지 1개 가지고 오기
sample1 = cv2.imread("./yolo/cardataset/training_images/vid_4_1000.jpg")
# 380x676 컬러 <높이, 너비, 채널(흑백은 0, 컬러는 3(RGB or BGR))> yolo의 원조는 BGR타입을 썼기 때문.
print(sample1.shape)
imread() : 이미지 파일을 읽어들이는 함수이고 반환값은 이미지 데이터(3차원 이미지 픽셀 데이터)이다.
(380, 676, 3)
array([[[255, 211, 128],
[254, 210, 127],
[254, 210, 127],
...,
[252, 184, 101],
[251, 183, 100],
[250, 182, 99]],
...
[ 66, 42, 20],
[ 66, 42, 20],
[ 66, 42, 20]]], dtype=uint8)
# 색상 채널 순서 정의하기
sample = cv2.cvtColor(sample1, cv2.COLOR_BGR2RGB)
Yolo에서는 현재 색상순서를 RGB를 사용한다. 저장된 이미지 데이터의 순서는 옛날 이미지라서 BGR을 사용하고 있기 때문에 색상순서를 RGB로 바꿔주는 작업이 필요하다.
array([[[128, 211, 255],
[127, 210, 254],
[127, 210, 254],
...
[ 20, 42, 66],
[ 20, 42, 66],
[ 20, 42, 66]]], dtype=uint8)
이미지의 바운딩박스의 x,y 좌표값을 추출한다.
# 사용하는 이미지 파일이름에 해당하는 바운딩 박스의 x,y 좌표값 추출하기
# - vid_4_1000.jpg 이미지 파일이름은 0번째 인덱스에 있음.
# - 0번째 인덱스의 모든 컬럼값 가져오기
point = box.iloc[0]
point, type(point)
(image vid_4_1000.jpg
xmin 281.259045
ymin 187.035071
xmax 327.727931
ymax 223.225547
Name: 0, dtype: object,
pandas.core.series.Series)
해당하여 시작좌표와 종료좌표를 추출해야 사용할 수 있기때문에 추출한다.
# 시작 좌표(xmin, ymin)와 종료좌표 (xmax,ymax) 추출하기
pt1 = (int(point["xmin"]), int(point["ymin"]))
pt2 = (int(point["xmax"]), int(point["ymax"]))
pt1, pt2
((281, 187), (327, 223))
plt.title("RGB")
plt.imshow(sample)
plt.show()
cv2.rectangle(sample, pt1, pt2, color=(255,0,255), thickness=2)
plt.title("marked bounding box")
plt.imshow(sample)
rectangle() : 사각형(바운딩박스는 사각형으로 표시하기 위해)을 그리는 함수
- sample : 원본 이미지 데이터
- pt1 : 박스의 시작점 좌표
- pt2 : 박스의 종료 좌표
- color : 박스 선의 색상 지정
- thickness = 2 : 선의 굵기
여기까지 완료했다면 이제 YOLO에서 제공하는가중치 모델을 불러와서 적용해볼 시간이다.
적용시작
# readNet(): 가중치 데이터 및 환경설정 파일 읽어들이기
# - DNN(심층신경망) 모델을 사용하여 모델 세팅하기
# - 첫번째 인자 : 가중치 파일
# - 두번째 인자 : 모델 설정 파일
net = cv2.dnn.readNet("./yolo/config/yolov3.weights",
"./yolo/config/yolov3.cfg")
net
< cv2.dnn.Net 0000021876289FB0>
net이란곳에 dnn모델을 적용했으니 나누는 naming작업을 해보자.
# 저장할 변수 정의
classes = []
# open(): 파일 열기, r: 읽기, w:쓰기, b가붙으면 바이너리
# f가 곧 open(내용물) 을 뜻하는 변수
with open("./yolo/config/coco.names", "r") as f :
classes = [line.strip() for line in f.readlines()]
# strip() : 왼쪽 공백 제거
# readlines() : 파일 내의 문장들을 행단위로 모두 읽어들이기
print(classes), len(classes)
['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat',
'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat',
'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack',
'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair',
'sofa', 'pottedplant', 'bed', 'diningtable', 'toilet', 'tvmonitor', 'laptop', 'mouse',
'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator',
'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
(None, 80)
#YOLO가 사용하는 계층 구조 확인하기
layer_names = net.getLayerNames()
print(layer_names), len(layer_names)
('conv_0', 'bn_0', 'leaky_1', 'conv_1', 'bn_1', 'leaky_2', 'conv_2', 'bn_2', 'leaky_3',
'conv_3', 'bn_3', 'leaky_4', 'shortcut_4', 'conv_5', 'bn_5', 'leaky_6', 'conv_6', 'bn_6',
..., ' 'leaky_105', 'conv_105', 'permute_106', 'yolo_106')
net.getUnconnectedOutLayers() : 다음 계층과 연결되지 않은(UnConnect) 마지막 계층(OutLayer)을 추출하는 함수
(출력계층 추출함수로, 출력계층의 인덱스 위치를 반환해줌.)
* layer_names[i-1] : -1을 한 이유는?
net.getUnconnectedOutLayers()에서 추출한 번호는 1부터 시작하기 때문에 i-1을 해줘야 한다.
output_layer = [layer_names[i-1] for i in net.getUnconnectedOutLayers()]
print(net.getUnconnectedOutLayers())
output_layer
[200 227 254]
['yolo_82', 'yolo_94', 'yolo_106']
이 뒤는 초반에 했던것의 반복이긴하지만 한번 더 하도록 하겠다.
# 샘플이미지 데이터 가져오기
img = cv2.imread("./yolo/cardataset/training_images/vid_4_10000.jpg")
# 샘플이미지 데이터 가져오기
img = cv2.imread("./yolo/cardataset/training_images/vid_4_10000.jpg")
#높이 너비 채널(색상)로 분리
height, width, channels = img.shape
- 바운딩박스의 시작좌표와 종료좌표값을 계산할 때 사용할 높이와 너비 추출
- yolo 출력계층에서 예측한 좌표값은 객체의 중심점 좌표에 대한 비율값을 추출 함
- 비율에 실제 높이와 너비를 이용해서 예측된 중심점 좌표와 계산, 바운딩 박스의 시작 좌표와 종료 좌표를 정의해야 함.
YOLO 모델이 이미지 데이터를 처리하기 위한 Blob 형태의 데이터로 구조화하기
<Blob(Binary Large Object) 처리>
- Blob는 이미지 처리 및 딥러닝에서 사용되는 데이터 구조임
- 이미지나 동영상에서 추출된 특정 부분이나 물체를 나타내기 위한 데이터 구조형태로 되어 있음.
- 주로 딥러닝 모델에 이미지를 전달하거나 이미지 프로세싱 작업에서 특정 부분을 추출하여 처리하는데 사용되는 구조임(객체 탐지용 데이터 구조)
- YOLO 모델에서 사용되는 데이터 구조가 Blob 구조를 따름.
→ 사용할 이미지를 Blob 데이터 구조로 변환한 후, YOLO 네트워크(모델)에 전달하여 객체를 검출하게 됨
- Blob 데이터 구조에 포함될(된) 수 있는 값들
→ 이미지 데이터 : 이미지 또는 영상 프레임(이미지)에서 추출된 특정 영역에 대한 이미지 데이터
→ 채널 정보 : 컬러 이미지인 경우 RGB 또는 BGR과 같은 컬러 정보
→ 공간 차원 정보 : 높이와 너비
→ 픽셀 값 범위 : 0~255까지의 값을 갖는 흑백 이미지 데이터 또는 -1~1, or 0~1 사이의 정규화된 이미지 값
- YOLO 모델(네트워크)에서는 이미지 데이터를 바로 사용할 수 없음.
→ 먼저 이미지를 Blob 데이터 형태로 변환해야함
→ Blob 데이터를 통해 YOLO가 예측 할 수 있도록 이미지에서 특징을 찾아내고, 크기를 저장하는 작업을 수행합니다.
→ 이미지 크기 저장을 → 이미지 크기 정규화라고 합니다.
→ 이미지 크기 정규화 사용되는 높이와 너비의 이미지 사이즈를 통일 시키는 작업
- YOLO로 사용되는 이미지 크기
→ 320 x 320 : 이미지가 작고, 정확도는 떨어지지만 속도가 빠름
→ 609 x 609 : 이미지가 크고, 정확도는 높지만 속도가 느림
→ 416 x 416 : 이미지 중간크기, 정확도 및 속도가 적당함(주로 사용되는 크기임)
blob = cv2.dnn.blobFromImage(img, 1/256,(416,416),(0, 0, 0),swapRB = True,crop = False)
(1, 3, 416, 416)
사용 함수 : cv2.dnn.blobFromImage() 이것을 사용하면 4차원 함수로 반환된다.
1/256으로 정규화를 해준 뒤, 위에서 말했듯 제일 많이쓰는 416을 사용한다는 뜻.
(0, 0, 0) : 원본 이미지의 채널 값을 흑백으로 변환하는 용도.
swapRGB : RGB에서 R값과 B값을 바꿀 것인지 결정 (기본값 False) : True인 경우 BGR을 RGB로 변경
- crop : 크기를 조정한 후에 이미지를 자를지 여부 결정, 일반적으로 자르면 안되기 때문에 False 지정 (기본값 False)
크기가 변경된 사이즈로 이미지를 변환해서 사용할지, 아니면 변경 사이즈 부분을 제외하고 자를지 결정한다.
하지만 이미지를 자르면 훼손되므로 자르지는 않는다.
# YOLO 모델에 입력 데이터로 넣어주기
net.setInput(blob)
# YOLO 모델의 출력계층
output_layer
['yolo_82', 'yolo_94', 'yolo_106']
# 입력데이터를 이용해서 예측된 출력결과 받아오기
# - net.forward() : YOLO 모델이 이미지 내에 있는 객체를 인식하여 정보를 출력해 줍니다.
# : 이때, 출력계층의 이름을 넣어서, 해당 출력계층의 값이 반환되게 됩니다.
outs = net.forward(output_layer)
# 인식된 객체(물체)의 인덱스 번호를 담을 변수
class_ids = []
# 인식된 객체의 인식률(정확도)를 담을 변수
confidences = []
# 인식된 객체의 좌표값을 담을 변수
boxes= []
# 출력계층이 반환한 값들을 처리하기 위하여 반복문 사용
for out in outs :
#print(out)
# 인식된 객체에 대한 정보가 담겨 있음.
for detection in out :
# 0번 인덱스 값 : 인식된 객체의 x 중심좌표 비율 값 (4.6797868e-02)
# 1번 인덱스 값 : 인식된 객체의 y 중심좌표 비율 값 (3.4333743e-02)
# 2번 인덱스 값 : 바운딩 박스의 너비 비율 값 (2.8790700e-01)
# 3번 인덱스 값 : 바운딩 박스의 높이 비율 값 (1.6676338e-01)
# 4번 인덱스 값 : 물체 인식률(4.5147601e-09)
# 5번 인덱스부터 전체 : 바운딩 박스에 대한 클래스(레이블 명칭(이름)) 확률 값들
# → 5번 이후의 개수는 레이블의 개수(클래스 수) 만큼
# → 레이블 이름은 실제 레이블의 값들과 비교하여 가장 높은 값을 가지는 인덱스의 값을 이용하여 실제 레이블 이름 추출함.
# print(detection)
# 인식된 객체에 대한 정보 추출하기(클래스=레이어 명칭) 확률 정보
scores = detection[5:]
# print(len(score),score)
"""scores 값이 가장 큰 인덱스 번호 찾기
- 0은 인식 못했다는 의미
- 가장 큰 인덱스 값 : 렝리블 명칭(이름)이 있는 리스트 배열의 인덱스 값을 의미함
"""
class_id = np.argmax(scores)
# print(class_id)
"""score 값이 가장 큰 위치의 인덱스 번호에 해당하는 값은 인식률(정확도)를 의미"""
confidence = scores[class_id]
# print(confidence)
# 정확도(인식률) 50% 이상인 데이터에 대해서 처리하기
if confidence > 0.5 :
# print(f"score : {scores}")
# print(f"class_id : {class_id}")
# print(f"class_id : {classes[class_id]}")
# print(f"confidence : {confidence}")
""" 바운딩 박스의 상태적 x, y 좌표비율 추출하여
- 실제 중심점 길이 좌표(절대 좌표)로 변환하기
"""
# 실제 중심점 x와 y 좌표
center_x = int(detection[0] * width)
center_y = int(detection[1] * height)
"""바운딩 박스의 상대적 너비와 높이 비율 추출하기"""
# 실제 너비, 높이로 변환
w = int(detection[2] * width)
h = int(detection[3] * height)
print(center_x, center_y, w, h)
# 이미지 좌표계는 좌상단이 0,0 그래프 좌표계는 좌하단이 0,0
"""시작점 좌표 계산하기"""
x = int(center_x - w / 2)
y = int(center_y - h / 2)
print(x, y)
"""실제 x,y, 너비, 높이 값을 리스트로 담기"""
boxes.append([x, y, w, h])
"""객체 인식률(정확도) 실수형 타입으로 담기"""
confidences.append(float(confidence))
"""레이블 명칭(이름) 인덱스 담기"""
class_ids.append(class_id)
print(boxes)
print(confidences)
print(class_ids)
여기서는 중간중간 프린트 문이 껴있는곳에서 따로따로 복사해서 확인을 해보시면 어떤 결과가 나오는지 알 수 있으니 확인하면서 해보시길 권합니다.
68 213 92 32
22 197
[[22, 197, 92, 32]]
[0.9849126935005188]
[2]
이후 indexes 에서 특성을 적용해준다.
# 중복된 바운딩 박스 제거하기
# - 인식된 객체별로 → 1개의 바운딩 박스만 남기기
# cv2.dnn.NMSBoxes():중복 바운딩 박스 제거하는 함수
# - boxes : 추출된 바운딩 박스 데이터
# - confidences : 바운딩 박스별 정확도
# - 0.5 : 정확도에 대한 임계값, 바운딩 박스 정확도가 0.5보다 작으면 박스 제거
# - 0.4 : 비최대 억제 임계값 이라고 칭한다. 이 값보다 크면 박스 제거 시킨다.
# 임계치 값을 주고 바운딩박스가 0.5보다 작으면 제거시켜라(개별의 객체의 크기).
# 0.4보다 크면 제거시키는데(겹쳐있는 박스의 크기에서 둘다 0.4보다 크다면 정확도가 낮은걸 제거한다.)
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)
indexes
원본 이미지의 예측한 위치에 바운딩 박스와 레이블(이름), 정확도 출력하기
indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)
# <폰트스타일 지정>
font = cv2.FONT_HERSHEY_PLAIN
"""
<바운딩 박스의 색상 지정>
- 인식된 객체가 많은 경우, 각각 색상을 지정해서 구분해 줄 필요성이 있기 때문에
→ 랜덤하게 색상을 추출하여 정의
- np.random.uniform() : 랜덤한 값 추출 함수
- 색상을 RGB형태로 추출하기 위해 값의 범위는 0~255를 사용
- 0, 255 : RGB 각 값의 랜덤 범위
- size=(len(boxes), 3) : 추출할 사이즈 : 행, 열 정의
→ 인식된 각 바운딩 박스의 개수만큼, 3개씩의 RGB값 추출을 의미함
"""
#인식된 개수 #R,G,B
colors = np.random.uniform(0,255, size=(len(boxes), 3))
# 인식된 객체가 있는 경우
if len(indexes) > 0 :
# 무조건 1차원으로 변환
print(indexes.flatten())
for i in indexes.flatten() :
#x,y,w,h 값 추출하기
x, y, w, h = boxes[i]
print(x, y, w, h)
# 실제 레이블 명칭(이름) 추출하기
label = str(classes[class_ids[i]])
print(label)
# 인식률(정확도) 추출하기
confidence = str(round(confidences[i], 3))
print(confidence)
# 바운딩 박스의 색상 추출하기
color = colors[i]
print(color)
""" 바운딩 박스 그리기
- 마지막 값 2 : 바운딩 박스 선의 굵기(thickness)
"""
cv2.rectangle(img, (x, y), ((x+w), (y+h)), color, 2)
"""인식된 객체의 레이블 명칭(이름)과 정확도 넣기(그리기)
* putText() : 원본 이미지에 텍스트 넣는 함수
- img : 원본이미지
- label : 인식된 레이블 명칭(이름)
- confidence : 인식률(정확도)
- (x, y+20) : 텍스트 시작 좌표
- font : 폰트 스타일
- font 다음 2 : 폰트 사이즈
- (0,255,0) : 폰트 색상
- 마지막 2 : 폰트 굵기
"""
cv2.putText(img, label + " " + confidence,
(x, y+20), font, 2, (0,255,0), 2)
plt.imshow(img)
#인식된 객체가 없는 경우
else :
print("I can't detecting!!! No Object!!!")
[0]
22 197 92 32
car
0.985
[227.54633838 118.47210528 2.73723236]
다른 그림으로 적용해보자면...
이런식으로도 뽑을 수 있다.
다음 게시글에선 간단히 함수화해보고 이것저것 그림을 넣어보도록 하겠다.
'개발일지 > ML(Machine Learning),DL(Deep Learning)' 카테고리의 다른 글
YOLO모델로 영상에서 객체 탐지 (2) | 2024.01.11 |
---|---|
YOLO 함수화 및 여러 그림으로 확인해보기 (3) | 2024.01.11 |
DNN dropout, callback함수 (2) | 2024.01.09 |
심층훈련망 훈련(DNN) (0) | 2024.01.08 |
신경망계층_추가방법_및_성능향상방법(2) (4) | 2024.01.08 |