코딩헤딩

Deep learning[딥러닝] YOLO 객체탐지 네트워크 가중치 모델 사용 본문

머신러닝 | 딥러닝

Deep learning[딥러닝] YOLO 객체탐지 네트워크 가중치 모델 사용

멈머이 2024. 1. 10. 21:26
728x90

https://coding-heading.tistory.com/103

 

Deep learning[딥러닝] YOLO 객체탐지 네트워크 기초

- "욜로"라고 칭한다. - 한 개의 네트워크(계층, 모델 같은 의미로 칭함)에서 객체(물체, 사물)를 탐지 - 탐지된 개체의 영역(바운딩 박스-사각형)과 객체의 이름(사람, 고양이, ...)을 표시해 주는

coding-heading.tistory.com

이전 글과 이어집니다.


<사용되는 파일>
  - yolov3.weights : 이미 훈련된 모델의 가중치 데이터 파일
  - yolov3.cfg : yolo모델 매개변수 설정 파일
  - coconames : 인식(감지)된 객체의 레이블 명칭(이름)이 저장된 파일


*    DNN(심층신경망) 모델을 사용하여 모델 세팅하기

net = cv2.dnn.readNet("./yolo/config/yolov3.weights",
                      "./yolo/config/yolov3.cfg")

net

결과 : < cv2.dnn.Net 000001A2F38BA030>

- readNet() : 가중치 데이터 및 환경설정 파일 읽어 들이기  {네트워크 = 계층}
    - 첫 번째 인자 : 가중치 파일
    - 두 번째 인자 : 모델 설정 파일

 

* 레이블 명칭(이름) 데이터 읽어 들이기

    - 인식한 객체에 대한 이름을 표시하기 위해

### 저장할 변수 정의
classes = []
### open() : 파일 열기
### "r" : 읽기모드 / "b" : 쓰기모드 / "b" : 바이너리
with open("./yolo/config/coco.names", "r") as f:
    ### strip() : 왼쪽 오른쪽 공백 제거
    ### readlines() : 파일 내에 문장들을 행단위로 모두 읽어들이기
    classes = [line.strip() for line in f.readlines()]

classes

결과 :

['person',
 'bicycle',
 'car',
 'motorbike',
 'aeroplane',

         .

         .

         .

 

* YOLO가 사용하는 계층 구조 확인하기
     - 우리가 사용했던 summery() 함수의 기능과 유사

layer_names = net.getLayerNames()
layer_names

결과 :

('conv_0',
 'bn_0',
 'leaky_1',
 'conv_1',
 'bn_1',

      .

      .

      .

 

* YOLO에서 사용하는 출력계층 확인하기

   - YOLO는 객체 검증을 위해 여러 개의 출력 계층을 사용하고 있음
   - 객체탐지시에 출력계층에서 출력해 준 값들을 이용해서 사용
      => 바운딩 박스의 좌표값, 객체 인자 정확도, 인지된 객체의 레이블 명칭(이름) 등의 출력을 담당함

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']

net.getUnconnectedOutLayers()

결과 : array([200, 227, 254])

 

* 샘플 이미지 데이터 가져오기

img = cv2.imread("./yolo/cardataset/training_images/vid_4_10000.jpg")
img

결과 :

array([[[254, 217, 161],
        [254, 217, 161],
        [253, 216, 158],
        ...,
        [252, 199, 142],
        [252, 199, 142],
        [251, 198, 141]],

                   .

                   .

                   .

 

* BGR을 RGB로 변환하기

img=cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img

결과 :
array([[[161, 217, 254],
        [161, 217, 254],
        [158, 216, 253],
        ...,
        [142, 199, 252],
        [142, 199, 252],
        [141, 198, 251]],

                   .

                   .

                   .

 

* 바운딩박스의 시작 좌표와 종료좌표값을 계산할 때 사용할 높이와 너비 추출

height, width, channels = img.shape
height, width, channels

결과 : (380, 676, 3) 

- yolo 출력계층에서 예측한 좌표값은 객체의 중심점 좌표에 대한 비율값을 추출함
 - 비율에 실제 높이와 너비를 이용해서 예측된 중심점 좌표와 계산하여 바운딩박스의 시작좌표와 종료좌표를 정의해야 함.

 

 

* yolo모델이 이미지 데이터를 처리하기 위한 Blob형태의 데이터로 구조화하기


<Blob(Binary Large Object)>
  - Blob은 이미지 처리 및 딥러닝에서 사용되는 데이터 구조임
  - 이미지나 동영상에서 추출된 특정 부분이나 물체를 나타내기 위한 데이터 구조형태로 되어 있음
  - 주로 딥러닝 모델에 이미지를 전달하거나 이미지 프로세싱 작업에서 특정 부분을 추출하여 처리하는 데 사용되는 

     구조임(객체 탐지용 데이터 구조라고 이해하면 된다.)
  - YOLO 모델에서 사용되는 데이터 구조가 Blob 구조를 따름
     => 사용할 이미지를 Blob 데이터 구조로 변환한 후에
     => YOLO 네트워크(모델)에 전달하여 객체를 검출하게 됨
 
  - Blob 데이터 구조에 포함될(된) 수 있는 값들
     => 이미지 데이터 : 이미지 또는 영상 프레임(이미지)에서 추출된 특정 영역에 대한 이미지 데이터
     => 채널 정보 : 컬러 이미지인 경우 RGB 또는 BRG과 같은 컬러 정보
     => 공간 차원 정보 : 높이와 너비
     => 픽셀 값 범위 : 0~255까지의 값을 가지는 흑백 이미지 데이터 또는 -1~1 또는 0~1 사이의 정규화된 이미지 값

  - YOLO모델(네트워크)에서는 이미지 데이터를 바로 사용할 수 없음
     => 먼저 이미지를 Blob 데이터 형태로 변환해야 함
     => Blob 데이터를 통해 YO:O가 예측하면서 이미지에 특징을 찾아내고, 크기를 저장하는 작업을 수행한다.
     => 이미지 크기 저장을 -> 이미지 크기 정규화 라고 한다.
         ==> 이미지 크기 정규화 : 사용되는 높이와 너비의 이미지 사이즈를 통일시키는 작업

  - YOLO에서 사용되는 이미지 크기
     => 320 X 320 : 이미지가 작고, 정확도는 떨어지지만 속도가 빠름
     => 609 X 609 : 이미지가 크고, 정확도는 높지만 속도가 느림
     => 416 X 416 : 이미지 중간크기, 정확도와 속도가 적당함 (주로 사용되는 크기임)


* 원본 이미지 데이터를 Blob으로 변환하면서, 동시에 정규화(사이즈 통일) 하기

blob = cv2.dnn.blobFromImage(
              img,
              1/256,
              (416, 416),
              (0, 0, 0),
              swapRB=True,
              crop=False
)

blob.shape

결과 : (1, 3, 416, 416)

  - 사용 함수 : cv2.dnn.blobFromImage()
  - 4차원으로 반환됨
  - img : 원본 이미지 데이터
  - 1/256 : 픽셀값에서 256으로 나누어서 데이터 정규화 진행
          : 0~1 사이의 값으로 정규화
  - (416, 416) : 높이와 너비의 사이즈를 통일시키는 정규화 진행
  - (0, 0, 0) : 원본 이미지의 채널 값을 흑백으로 변환하기
  - swapRB=True : GRB에서 R값과 B값을 바꿀 것인지 결정(기본값 False)
                : True인 경우 BRG를 RGB로 변경
  - crop : 크기를 조정한 후에 이미지를 자를지 여부 결정
         : 일반적으로 자르면 안 되기에 False지정 (기본값 False)
         : 크기가 변경된 사이즈로 이미지를 변환해서 사용할지 아니면, 변경 사이즈 부분을 제외하고 자를지 결정

 

* YOLO 모델에 데이터 넣어주기(입력계층에 들어가게 됨)

"""YOLO 모델에 입력 데이터로 넣어주기"""
net.setInput(blob)

 

* YOLO 모델의 출력 계층을 이용해서 결과받아오기

"""YOLO모델의 출력계층"""
output_layer

결과 : ['yolo_82', 'yolo_94', 'yolo_106']

 

* 입력데이터를 이용해서 예측된 출력결과받아오기

outs = net.forward(output_layer)

len(outs)

결과 : 3 

- net.forward() : YOLO 모델이 이미지 내에 있는 객체를 인식하여 정보를 출력해 준다.
                           : 이때, 출력계층의 이름을 넣어서 해당 출력계층의 값이 반환되게 된다. 

- array 배열의 3개 데이터를 튜플형태로 반환함
           -> 각 튜플은 출력계층 3개가 출력해 준 값들임

 

* 인식된 객체(물체) 좌표, 레이블명칭(이름)과 정확도 확인하기 

### 인식된 객체(물체)의 인덱스 번호를 담을 변수
class_ids = []
### 인식된 객체의 인식률(정확도)를 담을 변수
confidences = []
### 인식된 객체의 좌표값을 담을 변수
boxes = []

### 출력계층이 반환한 값들을 처리하기 위하여 반복문 사용
for out in outs:
    # print(out)
    # 인식된 객체에 대한 정보가 담겨있음
    for detection in out:
        """
        - 0번 인덱스 값 : 인식된 객체(바운딩박스)의 x 중심좌표 비율값
        - 1번 인덱스 값 : 인식된 객체(바운딩박스)의 y 중심좌표 비율값
        - 2번 인덱스 값 : 바운딩 박스의 너비 비율값
        - 3번 인덱스 값 : 바운딩 박스의 높이 비율값
        - 4번 인덱스 값 : 물체 인식률
        - 5번 인덱스부터 전체 : 바운딩 박스에 대한 클래스(레이블명칭(이름)) 확률 값들
        -   => 5번 이후의 갯수는 레이블의 갯수(클래스 수) 만큼
            => 레이블 이름은 실제 레이블과 값들과 비교하여 가장 높은 값을 가지는 
                인덱스의 값을 이용하여 실제 레이블 이름 추출함
        """
        # print(detection)
        # 인식된 객체에 대한 정보 추출하기(클래스=레이어 명칭) 확률정보
        scores = detection[5:]
        # print(len(scores), scores)

        """
        scores값이 가장 큰 인덱스 번호 찾기
          - 0은 인식 못했다는 의미
          - 가장 큰 인덱스 값 : 레이블 명칭(이름)이 있는 리스트 배열의 인덱스 값을 의미함
        """
        class_id = np.argmax(scores)
        # print(class_id)

        ### scores 값이 가장 큰 위치의 인덱스 번호에 해당하는 값은 인식률(정확도)를 의미
        confidence = scores[class_id]
        # print(confidence)

        ### 정확도 50% 이상인 데이터에 대해서 처리하기
        if confidence > 0.5:
            # print(f"scores : {scores}")
            # print(f"class_id : {class_id}")
            # print(f"confidence : {confidence}")

            """
            바운딩박스의 상대적 x, y 좌표 추출하여
             - 실제 길이 좌표(절대좌표)로 반환하기
            """
            # 실제 중심점 x 좌표
            center_x = int(detection[0] * width)
            # 실제 중심점 y 좌표
            center_y = int(detection[1] * height)
            print(center_x, center_y)

            ### 바운딩 박스의 상대적 너비와 높이 비율 추출하기
            # 실제 너비로 반환
            w = int(detection[2] * width)
            # 실제 높이로 반환
            h = int(detection[3] * height)
            print(w, h)

            ### 시작점 좌표 계산하기
            x = int(center_x -w / 2)
            y = int(center_y - h / 2)
            print(x, y)

            """
            이미지 죄표계
              - 최상단이 0, 0 이다.
              - 그래프의 좌표계는 최하단이 0, 0 
            """

            ### 실제 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]

 

* * 중복된 바운딩박스 제거하기
  - 인식된 객체별로  =>  1개의 바운딩박스만 남기기

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

결과 : array([0])
  - cv2.dnn.NMSBoxes() : 중복 바운딩박스 제거하는 함수
     => boxes : 추출된 바운딩 박스 데이터
     => confidences : 바운딩박스별 정확도
     => 0.5 : 정확도에 대한 임계값, 바운딩박스 정확도가 0.5보다 작으면 박스 제거
     => 0.4 : 비최대 억제 임계값이라고 칭한다. / 이 값보다 크면 박스를 제거시킨다.

  - NMSBoxes() 함수가 반환하는 값은 제거된 후 남은 바운딩 인덱스의 번호값 리스트


 

* 원본 이미지에 예측한 위치에 바운딩박스와 레이블(이름), 정확도 출력하기

### <폰트 스타일 지정>
font = cv2.FONT_HERSHEY_PLAIN

"""
<바운딩박스 색상 지정하기>
  - 인식된 객체가 많은경우, 각각 생상을 지정해서 구분해 줄 필요성이 있기 때문에  
        =>  랜덤하게 추출하여 정의
  - np.random.uniform() : 랜덤한 값 추출 함수
  - 색상을RGB의 형태로 추출하기 위해 값의 범위는 0~255를 사용
  - 0, 255 : RGB 각 값의 랜덤 범위
  - size=(len(boxes), 3) : 추출할 사이즈 : 행, 열 정의
     => 인식된 각 바운딩 박스의 갯수만큼, 3개씩의 RGB값 추출을 의미함
"""
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],2))
        print(confidence)
        ### *무조건 문자타입으로 바꾸기*

        """바운딩박스의 색상 추출하기"""
        color = colors[i]
        print(color)

        """
        바운드박스 그리기
          - 마지막 값 "2" : 바운딩박스 선의 굵기
        """
        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("인식된 객체가 없습니다.")

 

 

 

결과 : 

[0]
22 197 92 32
car
0.98
[182.1187388  211.92274426  61.69087878]

728x90