AI 부트캠프 - 26일차

Cookie Baking·2024년 11월 7일

AI 부트 캠프 TIL

목록 보기
20/42

공원산책 (출처 : 프로그래머스)

🧚
문제는 간단하나 생각보다 시간이 오래걸렸다.
오래 걸린 이유 또한 간단했다.
단계적 풀이를 하지 않아서였다.

이렇게 방향키(?)같은 문제를 접근할 때에는 한 개의 방향키 당 접근해야 한다는 것을 깨달았다.

또한 여러 에러들을 마주했는데

return값이 없을 경우

  • 모든 조건에 return값을 명시해주었는지 확인해야 한다.
TypeError: object of type 'NoneType' has no len()

IndexError 가 날 경우

  • len() 사용시에 행/열을 올바르게 선택했는지 찾으려는 대상이 len을 올바르게 갖고 있는지를 확인해야 한다.

POINT 1

또한 해당 문제에서는 행접근, 열접근을 유의해서 진행했어야 했다.
해당 문제에서는 열 접근할 때에는 문자열 형태로 되어있기 때문에 행 접근보다는 easy하게 그저 해당하는 행을 찾아 열 값들을 변경했는데

행 접근 시에는 모든 행/열을 방문해 결과적으로는 모든 값들을 변경했어야 했다.

열 접근 시에

new = ""
        
        for i in range(len(park[now[0]])):
            if i == y:
                new += "S"
            elif i == now[1]:
                new += "O"
            else:
                new += park[now[0]][i]
            
        park[now[0]] = new

행 접근 시에

new_arr = []
        
        for i in range(len(park)):
            new = ""
            for j in range(len(park[i])):
                if i == x and j == now[1]:
                    new += "S"
                elif i == now[0] and j == now[1]:
                    new += "O"
                else:
                    new += park[i][j]
            
            new_arr.append(new)
        
        park = new_arr

POINT 2

  • range의 끝은 항상 포함되지 않기 때문에 끝까지 검사하고자 한다면 +1을 잊지 말자
  • 방향키 문제는 함수로 빼자

📌

def position(park):
    for i in range(len(park)):
        for j in range(len(park[i])):
            if park[i][j] == "S":
                return (i, j)

def moving(direction, move, park):
    
    now = position(park)
    int_move = int(move)
    
    if direction == "E":
        y = now[1] + int_move
        if y >= len(park[now[0]]):
            return park
        
        for i in range(now[1], y+1):
            if park[now[0]][i] == "X":
                return park
        
        new = ""
        
        for i in range(len(park[now[0]])):
            if i == y:
                new += "S"
            elif i == now[1]:
                new += "O"
            else:
                new += park[now[0]][i]
            
        park[now[0]] = new
        return park
        
        
    elif direction == "S":
        x = now[0] + int_move
        if x >= len(park):
            return park
        
        for i in range(now[0], x+1):
            if park[i][now[1]] == "X":
                return park
        
        new_arr = []
        
        for i in range(len(park)):
            new = ""
            for j in range(len(park[i])):
                if i == x and j == now[1]:
                    new += "S"
                elif i == now[0] and j == now[1]:
                    new += "O"
                else:
                    new += park[i][j]
            
            new_arr.append(new)
        
        park = new_arr
        return park
        
            
    elif direction == "N":
        x = now[0] - int_move
        if x < 0:
            return park
        
        for i in range(x, now[0]+1):
            if park[i][now[1]] == "X":
                return park
        
        new_arr = []
        
        for i in range(len(park)):
            new = ""
            for j in range(len(park[i])):
                if i == x and j == now[1]:
                    new += "S"
                elif i == now[0] and j == now[1]:
                    new += "O"
                else:
                    new += park[i][j]
            
            new_arr.append(new)
        
        park = new_arr
        return park
        
    elif direction == "W":
        y = now[1] - int_move
        if y < 0:
            return park
        
        for i in range(y, now[1]+1):
            if park[now[0]][i] == "X":
                return park
        
        new = ""
        
        for i in range(len(park[now[0]])):
            if i == y:
                new += "S"
            elif i == now[1]:
                new += "O"
            else:
                new += park[now[0]][i]
            
        park[now[0]] = new
        return park
    

def solution(park, routes):
    answer = []
    
    for route in routes:
        direction = route.split()[0]
        move = route.split()[1]
        
        park = moving(direction, move, park)
        
    
    answer.append(position(park)[0])
    answer.append(position(park)[1])
    return answer

YOLO 란

YOLO는 객체 탐지 모델 중 하나로, 이미지에서 객체의 위치와 종류를 동시에 예측하는 강력한 딥러닝 모델임.
YOLO는 한 번의 신경망 전파만으로 객체를 탐지하기 때문에 실시간 처리가 가능할 정도로 매우 빠름

  • YOLO는 이미지가 들어오면 그리드로 분할함
  • 바운딩 박스 : 탐지된 객체를 둘러싸는 직사각형
  • confidence score : 바운딩 박스가 객체를 포함할 가능성
    객체가 바운딩 박스 내에 존재할 확률 (Objectness Score)
    — 객체가 해당 바운딩 박스 내에 있을 확률을 나타냅니다.
    객체의 종류에 대한 확률 (Class Score)
    — 바운딩 박스 내에 객체가 존재할 경우, 그 객체가 특정 클래스에 속할 확률을 나타냅니다.
    둘을 곱한 값이 confidence score임
  • 이미지 전체에서 객체가 특정 클래스에 속할 가능성을 예측하는 것이 목적
  • 만약 여러 개의 바운딩 박스가 하나의 객체를 탐지한 경우? 중복된 바운딩 박스를 제거하고 가장 신뢰도가 높은 박스만 남기는 과정이 포함됨, 즉 중복된 예측을 줄임

YOLO는 CNN기반으로 여러 개의 합성곱층과 풀링층을 거쳐 이미지의 특징을 추출하고 바운딩 박스와 클래스 확률을 예측하는 레이어로 통과하게 됨

기본 코드

  • YOLO : YOLOv8 객체 탐지 모델을 사용
  • cv2 : OpenCV 라이브러리로, 영상처리 및 웹캠 제어를 담당
from ultralytics import YOLO
import cv2
from matplotlib import pyplot as plt

# 웹캠을 통해 실시간 객체 탐지
cap = cv2.VideoCapture(0)  # 웹캠 캡처 시작

# 웹캠 -> 

while True:
    ret, frame = cap.read()
    if not ret:
        break

    # 프레임을 YOLOv8 모델에 입력하여 객체 탐지 수행
    results = model(frame)
                
                # 이후 처리코드
  
    # 'q' 키를 누르면 종료
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 웹캠 종료 및 창 닫기
cap.release()
cv2.destroyAllWindows()

응용 코드

  • PyQt5 : GUI를 생성하고 웹캠을 시작, 중지할 수 있는 버튼을 추가

  • FLOW 설명 :

  • 앞서 conda를 이용해 설치한 PyQt5의 GUI 앱을 실행하고 VideoCaptureWidget객체를 띄움

  • VideoCaptureWidget 객체 클래스 생성
    해당 클래스에서는 모델을 로드 한 후 yolov8 모델 파일로 초기화 함

  • UI를 띄움

  • 웹캠 설정 : 웹캠 초기화와 timer 설정이 들어감

  • 웹캠 start버튼 누를 시에 : 웹캠 장치가 열리며 timer를 20ms로 업데이트 함

  • 웹캠 stop 버튼 누를 시에 : 웹캠 중지 및 타이머 멈춤

  • 웹캠 연결과 함께 (초기화와 함께) : 객체 탐지 결과를 UI에 표시함
    YOLOv8이 객체 탐지를 수행하고
    바운딩 박스가 포함된 이미지를 가져옴 : results[0].plot()
    가져온 이미지를 QImage로 변환함
    QImage를 QLabel에 표시하기 위해 QPixmap으로 변환

  • app.exec_() : 앱 종료, GUI의 이벤트 루프를 실행함
from ultralytics import YOLO
import cv2
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QPushButton
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QImage, QPixmap


class VideoCaptureWidget(QWidget):
    def __init__(self):
        super().__init__()

        # YOLOv8x 모델 로드 (YOLOv8x)
        self.model = YOLO('yolov8x.pt')

        # UI 설정
        self.setWindowTitle("실시간 객체 탐지")
        self.image_label = QLabel(self)
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.image_label)

        self.start_button = QPushButton("Start Webcam", self)
        self.start_button.clicked.connect(self.start_webcam)
        self.layout.addWidget(self.start_button)

        self.stop_button = QPushButton("Stop Webcam", self)
        self.stop_button.clicked.connect(self.stop_webcam)
        self.layout.addWidget(self.stop_button)
        self.setLayout(self.layout)

        # 웹캠 초기화
        self.capture = None
        # QTimer를 통해 주기적으로 새로운 프레임을 읽고 업데이트 할 수 있음
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_frame)
    
    def start_webcam(self):
        """웹캠을 시작하고, 타이머를 시작하여 프레임을 주기적으로 읽음"""
        self.capture = cv2.VideoCapture(0)  # 웹캠 장치 열기
        self.timer.start(20)  # 20ms마다 프레임 업데이트 (50fps)

    def stop_webcam(self):
        """웹캠을 중지하고 타이머를 멈춤"""
        self.timer.stop()
        if self.capture is not None:
            self.capture.release()

    def update_frame(self):
        """웹캠에서 프레임을 읽어와서 YOLO 객체 탐지를 수행한 후 UI에 표시"""
        ret, frame = self.capture.read()
        if ret:
            # YOLOv8 객체 탐지 수행
            results = self.model(frame)
            result = results[0]

            # 바운딩 박스가 포함된 이미지를 가져옴
            img_with_boxes = result.plot()

            # OpenCV 이미지를 QImage로 변환
            rgb_image = cv2.cvtColor(img_with_boxes, cv2.COLOR_BGR2RGB)
            h, w, ch = rgb_image.shape
            bytes_per_line = ch * w
            convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)

            # QImage를 QLabel에 표시하기 위해 QPixmap으로 변환
            self.image_label.setPixmap(QPixmap.fromImage(convert_to_Qt_format))

    def closeEvent(self, event):
        """윈도우 닫을 때 웹캠 해제"""
        if self.capture is not None:
            self.capture.release()

if __name__ == "__main__":
    app = QApplication([])
    window = VideoCaptureWidget()
    window.show()
    app.exec_()

YOLO 응용

처음 접근 : 사람인식 -> 인종, 나이 맞춰보기
수정한 접근 : 데이터셋에 노이즈가 많이 낌을 포착 -> 화질 개선 모델 생성 우선 접근으로 수정

  1. 데이터 셋 준비
  1. 데이터 셋 전처리
    이미지들마다 크기가 제각각이다. 모델 학습을 위해 같은 크기인 400 X 400으로 resize해주는 작업을 해준다.
    resize된 사진들을 확인하기 위해 resize된 파일을 하위 디렉터리를 생성해 저장해 주었다.

괜찮은 resize 찾기 위해 subplot 생성해 비교

path = SAMPLE_PATH
label = os.path.basename(os.path.dirname(path))

# 이미지 읽기
img = cv2.imread(path)

# 비율 계산
height, width = img.shape[:2]
aspect_ratio = width / height

# 여러 크기 정의
new_sizes = [(100, 100), (200, 200), (300, 300), (400, 400)]

# 서브플롯 크기 설정
fig, axes = plt.subplots(1, len(new_sizes), figsize=(15, 5))

for idx, new_size in enumerate(new_sizes):
    # 리사이즈 계산
    if aspect_ratio > 1:
        new_width = new_size[0]
        new_height = int(new_width / aspect_ratio)
    else:
        new_height = new_size[1]
        new_width = int(new_height * aspect_ratio)
    
    # 리사이즈
    resized_img = cv2.resize(img, (new_width, new_height))

    # 결과 출력 (RGB로 변환 후 표시)
    axes[idx].imshow(cv2.cvtColor(resized_img, cv2.COLOR_BGR2RGB))
    axes[idx].set_title(f"Size: {new_size}")
    axes[idx].axis('off')  # 축 숨기기

# 화면에 출력
plt.tight_layout()
plt.show()

학습 데이터와 검증 데이터 분할

resized_path = RESIZE_DIR_PATH

images = []
labels = []

def preProcessing(file_path, images, lables):
    label = os.path.basename(os.path.dirname(file_path))

    img = cv2.imread(file_path)
    
    # 리사이즈
    resized_img = cv2.resize(img, (400, 400))

    images.append(img)
    labels.append(label)

    # 리사이즈된 이미지를 저장할 경로 설정
    # resized_path에 바로 저장
    # filename = os.path.basename(file_path)
    # resized_filename = f"resized_{filename}"

    # save_file_path = os.path.join(resized_path, resized_filename)

    # # 리사이즈된 이미지를 저장
    # cv2.imwrite(save_file_path, resized_img)

error handling - dataset pickle 화 실패

  • 이미지 데이터이고 데이터의 수가 컸기 때문에 pickle화를 실패했다.

0개의 댓글