이후 아래 코드에 대해 설명해보겠다. - 5~13행: YOLO 모델 구성함수 - 15~38행: YOLO모델로 img영상에서 물체탐지 후 반환하는 함수
def construct_yolo_v3():
f=open('coco_names.txt', 'r')
class_names=[line.strip() for line in f.readlines()]
model=cv.dnn.readNet('yolov3.weights','yolov3.cfg')
layer_names=model.getLayerNames()
out_layers=[layer_names[i-1] for i in model.getUnconnectedOutLayers()]
return model,out_layers,class_names
def yolo_detect(img,yolo_model,out_layers):
height,width=img.shape[0],img.shape[1]
test_img=cv.dnn.blobFromImage(img,1.0/256,(448,448),(0,0,0),swapRB=True)
yolo_model.setInput(test_img)
output3=yolo_model.forward(out_layers)
box,conf,id=[],[],[] # 박스, 신뢰도, 클래스 번호
for output in output3:
for vec85 in output:
scores=vec85[5:]
class_id=np.argmax(scores)
confidence=scores[class_id]
if confidence>0.5: # 신뢰도가 50% 이상인 경우만 취함
centerx,centery=int(vec85[0]*width),int(vec85[1]*height)
w,h=int(vec85[2]*width),int(vec85[3]*height)
x,y=int(centerx-w/2),int(centery-h/2)
box.append([x,y,x+w,y+h])
conf.append(float(confidence))
id.append(class_id)
ind=cv.dnn.NMSBoxes(box,conf,0.5,0.4)
objects=[box[i]+[conf[i]]+[id[i]] for i in range(len(box)) if i in ind]
return objects
model,out_layers,class_names=construct_yolo_v3() # YOLO 모델 생성
colors=np.random.uniform(0,255,size=(len(class_names),3)) # 클래스마다 색깔
05~13행의 construct_yolo_v3 함수를 살펴보자. ∙ 06~07행은 COCO 데이터셋의 클래스이름을 담고 있는 coco_names. txt 파일에서 클래스 이름을 읽어 class_names에 저장한다.
∙ 09행은 YOLO v3 모델 정보를 파일에서 읽어 yolo_model 객체에 저장한다.
yolov3.weights 파일에서는 신경망의 가중치 정보를 읽어오고 yolov3.cfg 파일에서는 신경망의 구조 정보를 가져온다.
∙ 10~11행은 getUnconnectedOutLayers 함수를 이용하여 yolo 82, yolo 94, yolo_106 층을 알아내어 out_layers 객체에 저장한다.
∙ 13행은 모델, 층, 클래스 이름을 담은 객체를 반환한다.
15~38행의 yolo_detect 함수를 살펴보자. ∙ 16행은 원본 영상인 img의 높이와 너비 정보를 height와 width에 저장한다.
∙ 17행은 OpenCV의 blobFromImage 함수로 영상을 YOLO에 입력할 수 있는 형태로 변환해 test_img에 저장한다. 이 함수는 [0,255] 범위의 화솟값을 [0,1]로 변환하고 영상 크기를 448 X 448로 변환하며 BGR 순서를 RGB로 바꾼다.원본 영상은 img에 남아있다.
∙ 19행은 test_img에 저장되어 있는 영상을 신경망에 입력한다.
∙ 20행은 신경망의 전방 계산을 수행하는데, out layers가 출력한 텐서를 output3 객체에 저장한다. 이로 인해 Ouput3 객체는 아래 3가지 텐서를 갖는다. 14×14×85×3, 28×28×85×3, 56×56×85×3
∙22~34행은 output3 객체로부터 물체 위치를 나타내는 박스 정보와 함께 물체 클래스와 신뢰도 정보를 추출한다. 22행은 박스와 신뢰도, 클래스정보를 저장할 리스트를 생성한다. 23행은 세 개의 텐서를 각각반복 처리하며 24행은 85차원 벡터를 반복 처리한다. 85 차원 벡터는 (x, y, w, h,o, p1. p2, … p80)으로 표현되며 앞의 네 요소는 박스, o는 신뢰도, 뒤의 80개 요소는 클래스확률이다.
∙25~27행은 뒤의 80개 요소 값에서 최고 확률에 해당하는 클래스를 알아내 클래스 번호는 class_id, 확률은 confidence에 저장한다.
∙28행은 confidence가 0.5보다 크지 않으면 버린다. 0.5보다 크면 29~31행에서 [0,1] 범위로 표현된 박스를 원래 영상 좌표 계로 변환해 왼쪽 위의 위치를 x와 y, 너비와 높이를 w와 h에 저장한다.
∙32~34행은 박스와 신뢰도, 클래스 정보를 리스트에 추가한다. 박스는 왼쪽 위와 오른쪽 아래 구석 좌표를 저장한다.
∙22~34행으로 검출한 박스들에는 상당한 중복성이 있다. 즉, 이전시간에 설명한 그림에서 빨간색 유니폼 선수를 검출한 박스가 검은색 칸에만 나타나지 않고 그 주위에 여럿 나타나는 현상이다.
∙36행의 NMSBoxes 함수는 박스를 대상으로 비최대 억제를 적용해 중복성을 제거한다.
∙37행 은 비최대 억제에서 살아남은 박스의 위치, 신뢰도, 클래스 이름을 모아 objects 객체에 저장한다.
∙38행은 objects를 반환한다.
∙ 40행은 contruct yolo_v3 함수로 YOLO 모델을 구성한다.
∙ 41 행은 물체 클래스를 고유한 색으로 표시하기 위해 컬러 목록을 만들어 colors 객체에 저장한다.
1. YOLO v3로 정지영상에서 Object Detection하기
메인 프로그램이 시작되는 ∙ 43행은 입력 영상을 읽어 img에 저장한다. ∙ 46행은 yolo_detect 함수로 원본 영상 img에서 물체를 검출해 res에 저장한다. ∙ 48~52행은 res에 있는 박스와 클래스 이름, 신뢰도를 영상에 표시한다.
프로그램 실행 결과를 보면, 왼쪽과 오른쪽 선수를 각각 100%와 94.1% 신뢰도의 person 클래스로 제대로 검출했다. 또한 축구공을 99.9% 신뢰도로 sports ball 클래스로 옳게 검출했다.
img=cv.imread('soccer.jpg')
if img is None: sys.exit('파일이 없습니다.')
res=yolo_detect(img,model,out_layers) # YOLO 모델로 물체 검출
for i in range(len(res)): # 검출된 물체를 영상에 표시
x1,y1,x2,y2,confidence,id=res[i]
text=str(class_names[id])+'%.3f'%confidence
cv.rectangle(img,(x1,y1),(x2,y2),colors[id],2)
cv.putText(img,text,(x1,y1+30),cv.FONT_HERSHEY_PLAIN,1.5,colors[id],2)
plt.imshow(img[...,::-1])
2. YOLO v3로 비디오에서 Object Detection하기 (랩탑 캠사용)
∙ 43~64행은 웹 캠에서 비디오를 읽어 디스플레이하는 코드에 YOLO를 적용한다. ∙ 50행은 비디오에서 획득한 현재 프레임을 yolo_detect 함수에 입력해 물체를 검출하고 결과를 res에 저장한다. ∙ 52~56행은 검출한 물체 정보를 영상에 표시한다.
cap=cv.VideoCapture(0)
if not cap.isOpened(): sys.exit('카메라 연결 실패')
while True:
ret,frame=cap.read()
if not ret: sys.exit('프레임 획득에 실패하여 루프를 나갑니다.')
res=yolo_detect(frame,model,out_layers)
for i in range(len(res)):
x1,y1,x2,y2,confidence,id=res[i]
text=str(class_names[id])+'%.3f'%confidence
cv.rectangle(frame,(x1,y1),(x2,y2),colors[id],2)
cv.putText(frame,text,(x1,y1+30),cv.FONT_HERSHEY_PLAIN,1.5,colors[id],2)
cv.imshow("Object detection from video by YOLO v.3",frame)
key=cv.waitKey(1)
if key==ord('q'): break