Recent Posts
printf("ho_tari\n");
[지능] Vision AI 기반 컨베이어 벨트 객체 인식 딥러닝 모델 최적화 2일차 본문
2024.12.04
2일차에는 1일차 때 라벨링 한 샘플 데이터를 YOLOv6-n 모델을 이용하여 학습시킨 뒤 prediction을 해보았다.
샘플 데이터의 화질이 매우 좋지 않아서 그런지 확실히 객체를 정확하게 탐지하지 못하는 것 같다.
파이썬 코드를 작성하여 수집한 이미지 데이터에 학습하여 생성한 YOLOv6 모델을 적용시켜 객체 탐지 박스가 생성된 이미지를 생성하여 저장하도록 하였다.
import cv2
import requests
from requests.auth import HTTPBasicAuth
import os
import glob
import json
import random
from collections import Counter
# ------------------------------
# 1. 클래스별 색상 매핑 및 동적 색상 생성 함수 정의
# ------------------------------
# 초기 클래스별 색상 매핑 (원하는 색상으로 변경 가능)
class_colors = {
'RASPBERRY PICO': (255, 0, 0), # 파란색
'USB': (0, 255, 0), # 초록색
'OSCILLATOR': (0, 0, 255), # 빨간색
'HOLE': (255, 255, 0), # 시안색
'CHIPSET': (255, 0, 255), # 자홍색
'BOOTSEL': (0, 255, 255) # 노란색
}
def get_class_color(class_name):
"""
클래스 이름에 따라 색상을 반환합니다.
새로운 클래스가 등장하면 랜덤 색상을 생성하여 추가합니다.
"""
if class_name in class_colors:
return class_colors[class_name]
else:
# 랜덤 색상 생성
color = tuple(random.randint(0, 255) for _ in range(3))
class_colors[class_name] = color
return color
# ------------------------------
# 2. 레전드(legend) 그리기 함수 정의
# ------------------------------
def draw_legend(img, class_counts, class_colors, font, font_scale, text_thickness):
"""
이미지에 클래스별 객체 수를 표시하는 레전드를 그립니다.
"""
box_size = 20
padding = 5
margin = 10
num_classes = len(class_counts)
legend_height = num_classes * (box_size + padding) + padding
legend_width = 0
for class_name, count in class_counts.items():
label = f"{class_name} ({count})"
(text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, text_thickness)
if text_width + box_size + 3 * padding > legend_width:
legend_width = text_width + box_size + 3 * padding
legend_width += margin # 추가 여백
# 레전드 배경 사각형 그리기 (흰색)
legend_bg_start = (margin, margin)
legend_bg_end = (margin + legend_width, margin + legend_height)
cv2.rectangle(img, legend_bg_start, legend_bg_end, (255, 255, 255), cv2.FILLED)
# 레전드 항목 그리기
current_y = margin + padding
for class_name, count in class_counts.items():
color = get_class_color(class_name)
label = f"{class_name} ({count})"
# 색상 박스 그리기
box_start = (margin + padding, current_y)
box_end = (margin + padding + box_size, current_y + box_size)
cv2.rectangle(img, box_start, box_end, color, cv2.FILLED)
# 텍스트 그리기
text_position = (margin + padding + box_size + padding, current_y + box_size - 5)
cv2.putText(img, label, text_position, font, font_scale, (0, 0, 0), text_thickness, cv2.LINE_AA)
# 다음 항목을 위해 y 위치 조정
current_y += box_size + padding
# ------------------------------
# 3. API 요청을 통해 박스 데이터 가져오기
# ------------------------------
def get_box_data(image_path, url, username, access_key):
"""
이미지를 API에 전송하여 박스 데이터를 가져옵니다.
"""
try:
with open(image_path, "rb") as image_file:
image_data = image_file.read()
response = requests.post(
url=url,
auth=HTTPBasicAuth(username, access_key),
headers={"Content-Type": "image/jpeg"},
data=image_data,
)
response.raise_for_status() # HTTP 오류가 발생하면 예외를 발생시킵니다.
return response.json().get("objects", [])
except Exception as e:
print(f"API 요청 중 오류 발생 ({image_path}): {e}")
return []
# ------------------------------
# 4. 이미지에 박스 및 레이블 그리기
# ------------------------------
def draw_boxes_on_image(img, boxes, font, font_scale, text_thickness, confidence_threshold):
"""
이미지에 박스와 레이블을 그립니다.
"""
occupied_text_regions = []
class_counts = Counter(obj['class'] for obj in boxes if obj['score'] >= confidence_threshold)
for obj in boxes:
class_name = obj['class']
score = obj['score']
box = obj['box'] # [x1, y1, x2, y2]
if score < confidence_threshold:
continue
start_point = (box[0], box[1]) # 좌측 상단 (x1, y1)
end_point = (box[2], box[3]) # 우측 하단 (x2, y2)
color = get_class_color(class_name)
thickness = 2 # 선 두께
# 박스 그리기
cv2.rectangle(img, start_point, end_point, color, thickness)
# 텍스트 준비 (클래스명과 점수 표시)
label = f"{class_name}: {score:.2f}"
# 텍스트 크기 계산
(text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, text_thickness)
# 텍스트 배경 사각형 그리기 (가독성을 위해)
# 텍스트를 박스 위쪽에 먼저 배치, 공간이 없으면 아래쪽에 배치
text_bg_x1 = start_point[0]
text_bg_y1 = start_point[1] - text_height - 10
if text_bg_y1 < 0:
text_bg_y1 = start_point[1] + text_height + 10
text_bg_x2 = start_point[0] + text_width + 4
text_bg_y2 = start_point[1] if text_bg_y1 < start_point[1] else start_point[1] + text_height + 20
text_bg_start = (text_bg_x1, text_bg_y1)
text_bg_end = (text_bg_x2, text_bg_y2)
# 텍스트 배경 사각형 영역이 겹치지 않도록 조정
overlap = False
for region in occupied_text_regions:
if not (text_bg_end[0] < region[0] or
text_bg_start[0] > region[0] + region[2] or
text_bg_end[1] < region[1] or
text_bg_start[1] > region[1] + region[3]):
overlap = True
break
if overlap:
# 텍스트를 박스 아래쪽으로 이동
text_bg_y1 = start_point[1] + text_height + 10
text_bg_y2 = start_point[1] + text_height + 20
text_bg_start = (text_bg_x1, text_bg_y1)
text_bg_end = (text_bg_x2, text_bg_y2)
# 다시 겹치는지 확인
overlap = False
for region in occupied_text_regions:
if not (text_bg_end[0] < region[0] or
text_bg_start[0] > region[0] + region[2] or
text_bg_end[1] < region[1] or
text_bg_start[1] > region[1] + region[3]):
overlap = True
break
if overlap:
# 추가적인 조정 (오프셋을 더 추가)
text_bg_y1 += text_height + 5
text_bg_y2 += text_height + 5
text_bg_start = (text_bg_x1, text_bg_y1)
text_bg_end = (text_bg_x2, text_bg_y2)
# 텍스트 배경 사각형 그리기
cv2.rectangle(img, text_bg_start, text_bg_end, color, cv2.FILLED)
# 텍스트 위치 설정
text_position = (text_bg_start[0] + 2, text_bg_end[1] - 5)
# 텍스트 그리기 (흰색)
cv2.putText(img, label, text_position, font, font_scale, (255, 255, 255), text_thickness, cv2.LINE_AA)
# 점유된 위치 저장 (텍스트 배경 사각형)
occupied_text_regions.append((text_bg_start[0], text_bg_start[1],
text_bg_x2 - text_bg_x1, text_bg_y2 - text_bg_y1))
# 레전드 그리기
draw_legend(img, class_counts, class_colors, font, font_scale, text_thickness)
return img
# ------------------------------
# 5. 메인 처리 루프
# ------------------------------
def main():
# API 설정
URL = ""
USERNAME = "" # API 사용자 이름
ACCESS_KEY = "" # API 액세스 키
# 입력 및 출력 디렉토리 설정
INPUT_DIR = "" # 실제 입력 디렉토리 경로로 변경
OUTPUT_DIR = "" # 실제 출력 디렉토리 경로로 변경
# 출력 디렉토리가 존재하지 않으면 생성
os.makedirs(OUTPUT_DIR, exist_ok=True)
# 지원되는 이미지 확장자
IMAGE_EXTENSIONS = ('*.jpg', '*.jpeg', '*.png', '*.bmp', '*.tiff')
# 모든 이미지 파일 경로 수집
image_paths = []
for ext in IMAGE_EXTENSIONS:
image_paths.extend(glob.glob(os.path.join(INPUT_DIR, ext)))
print(f"처리할 이미지 수: {len(image_paths)}")
# 폰트 설정
font = cv2.FONT_HERSHEY_COMPLEX_SMALL
font_scale = 0.5
text_thickness = 1
# 신뢰도 기준 설정 (필요에 따라 조정)
confidence_threshold = 0.3
# 각 이미지 처리
for idx, image_path in enumerate(image_paths, 1):
image_name = os.path.basename(image_path)
print(f"처리 중: {idx}/{len(image_paths)} - {image_name}")
# API를 통해 박스 데이터 가져오기
boxes = get_box_data(image_path, URL, USERNAME, ACCESS_KEY)
if not boxes:
print(f"박스 데이터를 가져올 수 없습니다: {image_name}")
# 필요에 따라 기본 이미지를 저장하거나 건너뜁니다.
output_path = os.path.join(OUTPUT_DIR, image_name)
cv2.imwrite(output_path, cv2.imread(image_path))
continue
# 이미지 불러오기
img = cv2.imread(image_path)
# 이미지 로드 확인
if img is None:
print(f"이미지를 불러올 수 없습니다: {image_path}")
continue
# 박스 및 레이블 그리기
img_with_boxes = draw_boxes_on_image(img, boxes, font, font_scale, text_thickness, confidence_threshold)
# 결과 이미지 저장
output_path = os.path.join(OUTPUT_DIR, image_name)
cv2.imwrite(output_path, img_with_boxes)
print("모든 이미지 처리가 완료되었습니다.")
if __name__ == "__main__":
main()
이 코드를 이용하여 GRADIO에 적용해 보았다.
사이트를 생성하여 이미지 데이터를 첨부하면 YOLOv6-n 모델을 기반으로 객체 탐지 박스를 생성하여 박스가 생성된 이미지를 출력시켜주도록 하였다.
import cv2
import gradio as gr
import requests
import numpy as np
from PIL import Image
from requests.auth import HTTPBasicAuth
# 가상의 비전 AI API URL (예: 객체 탐지 API)
VISION_API_URL = ""
TEAM = ""
ACCESS_KEY = ""
# 클래스별 색상 매핑
class_colors = {
'RASPBERRY PICO': (255, 0, 0), # 파란색
'USB': (0, 255, 0), # 초록색
'OSCILLATOR': (0, 0, 255), # 빨간색
'HOLE': (255, 255, 0), # 노란색
'CHIPSET': (255, 0, 255), # 자홍색
'BOOTSEL': (0, 255, 255) # 청록색
}
def get_class_color(class_name):
"""
클래스 이름에 따라 색상을 반환합니다.
새로운 클래스가 등장하면 랜덤 색상을 생성하여 추가합니다.
"""
if class_name in class_colors:
return class_colors[class_name]
else:
# 랜덤 색상 생성
color = tuple(np.random.randint(0, 256, size=3).tolist())
class_colors[class_name] = color
return color
def process_image(image):
# 이미지를 OpenCV 형식으로 변환
image = np.array(image)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# 이미지를 API에 전송할 수 있는 형식으로 변환
_, img_encoded = cv2.imencode(".jpg", image)
# API 호출 및 결과 받기
headers = {
"Content-Type": "application/octet-stream"
}
auth = HTTPBasicAuth(TEAM, ACCESS_KEY)
# API에 POST 요청 전송
response = requests.post(
VISION_API_URL,
data=img_encoded.tobytes(),
headers=headers,
auth=auth
)
# 요청이 성공했는지 확인
if response.status_code == 200:
results = response.json()
print("API 응답:", results) # 디버깅을 위해 추가
else:
print(f"Error: {response.status_code}")
results = {}
# API 결과를 바탕으로 박스 그리기
if 'objects' in results:
predictions = results['objects']
elif 'predictions' in results:
predictions = results['predictions']
elif 'detections' in results:
predictions = results['detections']
else:
print("API 응답에 예측 결과가 없습니다.")
predictions = []
if predictions:
height, width = image.shape[:2]
for pred in predictions:
label = pred.get('class', 'N/A')
score = pred.get('score', 0)
bbox = pred.get('box', [0, 0, 0, 0])
# 바운딩 박스 좌표 추출
xmin, ymin, xmax, ymax = bbox
# 좌표가 픽셀 값인 경우 스케일링 불필요
# 좌표가 이미지 범위를 벗어나지 않도록 조정
xmin = max(0, min(int(xmin), width - 1))
ymin = max(0, min(int(ymin), height - 1))
xmax = max(0, min(int(xmax), width - 1))
ymax = max(0, min(int(ymax), height - 1))
# 클래스별 색상 가져오기
color = get_class_color(label)
# 바운딩 박스 그리기
cv2.rectangle(image, (xmin, ymin), (xmax, ymax), color, 2)
# 텍스트 준비
text = f"{label}: {score:.2f}"
# 텍스트 크기 계산하여 배경 사각형 생성
(text_width, text_height), baseline = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2
)
cv2.rectangle(
image,
(xmin, ymin - text_height - baseline),
(xmin + text_width, ymin),
color,
thickness=cv2.FILLED,
)
# 바운딩 박스 위에 텍스트 표시
cv2.putText(
image,
text,
(xmin, ymin - baseline),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 0, 0),
2,
)
else:
print("이미지에서 객체를 감지하지 못했습니다.")
cv2.putText(
image,
"No objects detected",
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 0, 255),
2,
)
# BGR 이미지를 RGB로 변환
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
return Image.fromarray(image)
# Gradio 인터페이스 설정
iface = gr.Interface(
fn=process_image,
inputs=gr.Image(type="pil"),
outputs="image",
title="Vision AI Object Detection",
description="Upload an image to detect objects using Vision AI.",
)
# 인터페이스 실행
iface.launch()
인터페이스 실행 코드를 다음과 같이 바꾸면 링크를 생성하여 모바일로도 실행을 시킬 수 있다.
iface.launch(share=True)
그 후에는 동영상에서 YOLOv6-n 모델을 기반으로 객체를 탐지해보는 실습을 진행하였다.
동영상에 적용을 하기 위해서는 프레임 단위로 나누어 이미지에서 객체 탐지를 하도록 한다.
https://youtube.com/shorts/zxFuit2jMSM
https://youtube.com/shorts/4on4FaZsE44?feature=share
streamlit을 이용하여 실시간 YOLOv8 추론 사이트도 생성하였다.
'두산 로보틱스 부트캠프 ROKEY > 실무 프로젝트' 카테고리의 다른 글
[지능] Vision AI 기반 컨베이어 벨트 객체 인식 딥러닝 모델 최적화 4일차 (0) | 2024.12.10 |
---|---|
[지능] Vision AI 기반 컨베이어 벨트 객체 인식 딥러닝 모델 최적화 3일차 (0) | 2024.12.06 |
[지능] Vision AI 기반 컨베이어 벨트 객체 인식 딥러닝 모델 최적화 1일차 (0) | 2024.12.05 |
[주행] SLAM 모델 기반 다중이용시설 로봇 주행 환경 장애물 인식 모델 개발 5일차 (0) | 2024.12.03 |
[주행] SLAM 모델 기반 다중이용시설 로봇 주행 환경 장애물 인식 모델 개발 4일차 (0) | 2024.12.01 |