본문 바로가기

Embedded LAB Semina

특정 객체 구별 및 로봇의 시야 추정

# 오늘의 고민 : 로봇이 놓인 방향을 어떻게 구별할 것인가

코드 해석

    def __init__(self, camera):
        self.camera = camera
        self.blue_lower = np.array([100, 150, 50])  # 파란색 하한값
        self.blue_upper = np.array([140, 255, 255])  # 파란색 상한값
        self.red_lower = np.array([170, 140, 150])  # 빨간색 하한값
        self.red_upper = np.array([180, 255, 255])  # 빨간색 상한값

    def get_image(self):
        ret, frame = self.camera.read()
        if not ret:
            print("Failed to capture image")
            return None
        return frame

    def preprocess_image(self, image, lower, upper):
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, lower, upper)
        return mask

 

camera 파라미터를 받아오고, 파란색과 빨간색을 검출하기 위한 HSV 범위를 설정한다.

__init__ 매서드는 객체 초기화를 수행.

 

get_image 매서드에서,

read() 매서드는 cv2.VideoCapture 객체의 메서드로, 비디오 스트림으로부터 프레임을 읽어와서 두 개의 값을 반환 두 개의 값을 반환한다. 

- ret : 프레임이 성공적으로 읽혔는지를 나타내는 불리언(boolean) 값 (True or False)

- frame :  읽어온 비디오 프레임(이미지)

 

preprocess_image 매서드에서,

전처리를 위해 이미지를 HSV 색 공간으로 변환하고, 주어진 색 범위(lower, upper)에 해당하는 영역을 마스크로 추출한다.

여기에서 mask 부분은 코드 아래의 run 매서드에서 preprocess_image 메서드를 호출하여 파란색과 빨간색 마스크를 생성하는데 사용된다.

 

    def find_dots(self, mask):
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        dots = []
        for contour in contours:
            if cv2.contourArea(contour) > 10:  # 작은 노이즈 제거
                M = cv2.moments(contour)
                if M['m00'] != 0:
                    cX = int(M['m10'] / M['m00'])
                    cY = int(M['m01'] / M['m00'])
                    dots.append((cX, cY))
        return dots, contours

cv2.RETR_EXTERNAL은 중첩된 윤곽선은 무시하고 외부 윤곽선만 찾는다.

(counters가 찾은 윤곽선들의 리스트이고, _는 계층구조,, 부분인데 사용하지 않으므로 저렇게 해둔 것이다.)

 

for contour in contours: 내에서는,

윤곽선의 면적을 계산하여 작은 노이즈를 제거하고, 면적이 10보다 큰 윤곽선만 처리하도록 했다.

윤곽선의 모멘트를 계산하여 중심 좌표를 구하고, 이를 dots 리스트에 추가하여 중심 좌표들과 윤곽선 리스트를 반환한다.

cv2.moments()는 모멘트 값들을 dictionary형태로 반환하고,

if M['m00'] != 0:: 모멘트의 영(moment of order zero) 값이 0이 아닌 경우에만 중심 좌표를 계산한다. ( M['m00']는 윤곽선의 면적)

- M['m10']와 M['m01']는 각각 첫 번째 모멘트로, x와 y 좌표의 합계

- cX = int(M['m10'] / M['m00']): x 좌표의 합계를 면적으로 나누어 중심 x 좌표를 계산

- cY = int(M['m01'] / M['m00']): y 좌표의 합계를 면적으로 나누어 중심 y 좌표를 계산

 

    def determine_direction(self, blue_dots, red_dot):
        if len(blue_dots) < 2 or not red_dot:
            return "Cannot determine direction. Not enough dots detected."

        # 모든 점들을 포함한 직선 검출
        dots = blue_dots + [red_dot]

        # x 좌표 차이와 y 좌표 차이를 계산하여 방향 결정
        # 리스트 컴프리헨션->반복문과 조건식을 한 줄로 작성하여 새로운 리스트를 생성
        x_coords = [dot[0] for dot in dots]
        y_coords = [dot[1] for dot in dots]

        x_diff = max(x_coords) - min(x_coords)
        y_diff = max(y_coords) - min(y_coords)

        if x_diff > y_diff:
            return "2번 방향"  # 가로로 정렬됨
        else:
            return "1번 방향"  # 세로로 정렬됨

determine_direction 메서드는 파란색 점이 최소 두 개 이상이고 빨간색 점이 하나 있을 때 점들의 위치를 바탕으로 방향을 결정한다. x 좌표 차이(x_diff)와 y 좌표 차이(y_diff)를 비교하여 점들이 가로로 정렬되었는지 세로로 정렬되었는지 판단하도록 했다.

dots = blue_dots + [red_dot]: 파란색 점들과 빨간색 점을 하나의 리스트로 결합하는데, 이렇게 한 이유는 파란색 점이 빨간색 공에 의해 가려지는 것을 고려하여 총 세 지점의 배치를 기준으로 방향을 결정할 것이기 때문이다.

cf. blue_dots는 리스트였고, red_dot은 튜플이었음

 

방향 정렬 계산 예시

ex. 세 점의 좌표가 [(30, 50), (70, 80), (50, 60)]일 때,

x_coords = [30, 70, 50],

y_coords = [50, 80, 60] 이므로

 

x_diff = max([30, 70, 50]) - min([30, 70, 50]) = 70 - 30 = 40,

y_diff = max([50, 80, 60]) - min([50, 80, 60]) = 80 - 50 = 30 이다.

 

이때, x 좌표의 범위가 y 좌표의 범위보다 크다면 점들이 가로로 더 넓게 퍼져 있음을 의미한다. 즉, 2번 방향이다.

 

    def run(self):
        while True:
            image = self.get_image()
            if image is None:
                break

            blue_mask = self.preprocess_image(image, self.blue_lower, self.blue_upper)
            blue_dots, blue_contours = self.find_dots(blue_mask)
            
            red_mask = self.preprocess_image(image, self.red_lower, self.red_upper)
            red_dots, red_contours = self.find_dots(red_mask)
            # 빨간색 점이 검출된 경우 첫 번째 점을 red_dot에 할당하고, 그렇지 않으면 None을 할당
            red_dot = red_dots[0] if red_dots else None

            direction = self.determine_direction(blue_dots, red_dot)

            print(f"Robot is facing the {direction} of the field.")
            
            # Draw contours around the blue and red dots
            cv2.drawContours(image, blue_contours, -1, (0, 255, 0), 2)
            cv2.drawContours(image, red_contours, -1, (0, 0, 255), 2)
            
            for dot in blue_dots:
                cv2.circle(image, dot, 5, (0, 255, 0), -1)
            
            if red_dot:
                cv2.circle(image, red_dot, 5, (0, 0, 255), -1)
            
            cv2.imshow('Direction', image)
            #cv2.imshow('Blue Mask', blue_mask)
            #cv2.imshow('Red Mask', red_mask)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        cv2.destroyAllWindows()

run 메서드가 메인 실행 루프인데, 이 루프는 계속해서 이미지를 캡처하고, 전처리하고, 점들을 찾아 방향을 결정한다.

결정된 방향은 콘솔에 출력(print)하도록 하여, 어떤 방향인지 확인할 수 있다.

또한, 점들의 윤곽선과 중심을 이미지에 표시하고 화면에 출력하는 부분도 포함되어 있다.

camera = cv2.VideoCapture(1)
detector = DirectionDetector(camera)
detector.run()
camera.release()

DirectionDetector 객체를 생성하고 run 메서드를 호출하여 프로그램을 실행하는 부분 (python)

실행 결과

좌측이 1번 방향, 우측이 2번 방향이다.

 

전체 코드

import cv2
import numpy as np

class DirectionDetector:
    def __init__(self, camera):
        self.camera = camera
        self.blue_lower = np.array([100, 150, 50])  # 파란색 하한값
        self.blue_upper = np.array([140, 255, 255])  # 파란색 상한값
        self.red_lower = np.array([170, 140, 150])  # 빨간색 하한값
        self.red_upper = np.array([180, 255, 255])  # 빨간색 상한값

    def get_image(self):
        ret, frame = self.camera.read()
        if not ret:
            print("Failed to capture image")
            return None
        return frame

    def preprocess_image(self, image, lower, upper):
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, lower, upper)
        return mask

    def find_dots(self, mask):
        contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        dots = []
        for contour in contours:
            if cv2.contourArea(contour) > 10:  # 작은 노이즈 제거
                M = cv2.moments(contour)
                if M['m00'] != 0:
                    cX = int(M['m10'] / M['m00'])
                    cY = int(M['m01'] / M['m00'])
                    dots.append((cX, cY))
        return dots, contours

    def determine_direction(self, blue_dots, red_dot):
        if len(blue_dots) < 2 or not red_dot:
            return "Cannot determine direction. Not enough dots detected."

        # 모든 점들을 포함한 직선 검출
        dots = blue_dots + [red_dot]

        # x 좌표 차이와 y 좌표 차이를 계산하여 방향 결정
        x_coords = [dot[0] for dot in dots]
        y_coords = [dot[1] for dot in dots]

        x_diff = max(x_coords) - min(x_coords)
        y_diff = max(y_coords) - min(y_coords)

        if x_diff > y_diff:
            return "2번 방향"  # 가로로 정렬됨
        else:
            return "1번 방향"  # 세로로 정렬됨

    def run(self):
        while True:
            image = self.get_image()
            if image is None:
                break

            blue_mask = self.preprocess_image(image, self.blue_lower, self.blue_upper)
            blue_dots, blue_contours = self.find_dots(blue_mask)
            
            red_mask = self.preprocess_image(image, self.red_lower, self.red_upper)
            red_dots, red_contours = self.find_dots(red_mask)
            red_dot = red_dots[0] if red_dots else None

            direction = self.determine_direction(blue_dots, red_dot)

            print(f"Robot is facing the {direction} of the field.")
            
            # Draw contours around the blue and red dots
            cv2.drawContours(image, blue_contours, -1, (0, 255, 0), 2)
            cv2.drawContours(image, red_contours, -1, (0, 0, 255), 2)
            
            for dot in blue_dots:
                cv2.circle(image, dot, 5, (0, 255, 0), -1)
            
            if red_dot:
                cv2.circle(image, red_dot, 5, (0, 0, 255), -1)
            
            cv2.imshow('Direction', image)
            cv2.imshow('Blue Mask', blue_mask)
            cv2.imshow('Red Mask', red_mask)
            
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        cv2.destroyAllWindows()

camera = cv2.VideoCapture(1)
detector = DirectionDetector(camera)
detector.run()
camera.release()