일사분란착착착 - 영상인식을 통한 RVM 시스템 개발

MIE capstone
이동: 둘러보기, 검색

프로젝트 소개

프로젝트 명

영상인식을 통한 RVM 시스템 개발
Developing Reverse Vending Machine Using Image Detection

프로젝트 기간

2020.3 ~ 2020.6

팀 소개

서울시립대학교 기계정보공학과 (20144300**) (유*영) (팀장)
서울시립대학교 기계정보공학과 (20144300**) (강*찬)
서울시립대학교 기계정보공학과 (20144300**) (박*석)
서울시립대학교 기계정보공학과 (20144300**) (심*헌)
서울시립대학교 기계정보공학과 (20144300**) (윤*상)

소스코드

https://github.com/YeonsangYoon/embedded-system-project

프로젝트 개요

프로젝트 요약

프로젝트의 배경 및 기대효과

페트병은 재활용 자원 중에서도 재활용 가능성이 높고 또 재활용 후에도 품질이 크게 떨어지지 않는다. 하지만 라벨 및 뚜껑이 있는 경우 재활용 전처리 과정에서 비용과 시간이 크게 늘어가게 된다. 하지만 페트병의 라벨 및 뚜껑을 제거하여 버림이 올바른 방법임을 모르는 경우 많다. 이를 위해 올바른 재활용 방법 홍보가 필요하지만 실용적으로 이루어지지 않는 상태이다. 우리는 사람들에게 조금 더 흥미로운 방법을 통해서 재활용 방법을 홍보하기 위해 독일 덴마크 등에서 활용되는 RVM을 모티브로 삼았다. 또한 제도적으로 RVM이 확립되어 있지않은 상태에서 더 많은 페트와 캔을 수거하기 위해 영상인식 RVM을 생각하였다. RVM(reverse vending machine)은 일반 자판기와는 다르게 돈을 넣고 물건을 받는 것이 아닌, 물건은 넣으면 돈이 나오는 구조이다. 페트나 캔을 넣으면 소정의 보상을 받게되고 이 과정에서 자연스럽게 흥미가 생긴다. 동시에 올바른 재활용 방법을 익히는 교육적인 효과도 불러올 것이다. 우리는 기존의 RVM과 달리 임베디드 시스템을 활용하여 더 경제적이고 이동식인 시스템을 구축함을 목표로 했다. 기존의 RVM이 크기 및 무게 문제로 인해 설치 장소에 고정이 되어있는것과 달리 우리가 개발한 시스템은 여러장소(초등학교, 주거단지 등)에 유연하게 배치하여 소량의 기기로 큰 교육효과를 누릴수 있을 것이다.

동작 시나리오

그림 1. 사용자 시나리오

본 프로젝트에서 구현을 목표로 한 부분은 검은색 글씨로 표기하였다. 기존의 RVM 시스템은 사용자들의 적극적인 재활용쓰레기 수거를 위하여 투입한 쓰레기에 비례해 일정부분 현금이나 포인트로 보상을 주었다. 하지만 본 프로젝트에서 짧은 기간내 기본적인 RVM 기능 외 어플리케이션과 사용자 포인트 적립을 구현하는 것이 힘들다고 판단하여 부가기능은 추후에 개발하고 기본적인 RVM의 기능을 수행하는 시스템을 개발을 목표로 잡았다. 기본적인 사용자 시나리오는 다음과 같다. 사용자가 시작버튼을 누르면 내부적으로 기계의 상태가 ON으로 바뀌어 동작 시퀀스가 실행된다. 기본적으로 한번에 1개의 쓰레기를 처리하도록 동작을 하며 내부적으로 투입 -> 이동 -> 판별 -> 분류 의 순서로 동작한다. 사용자가 모든 재활용 쓰레기를 투입하여 종료버튼을 누르면 내부적으로 기계의 상태가 OFF로 바뀌어 동작 시퀀스가 완료된 후 idle 상태로 바뀌고 투입 결과가 UI에 표시된다.

구현 내용

역할분담 및 추진체계

구성원 착착착.JPG

개발일정표

개발일정 착착착.JPG

시스템 구성

그림2. 시스템 구성도
  • RVM Controller
  • Image Processing Server
  • Rail Controller

RVM 시스템은 크게 3개의 프로세스로 구동된다. RVM Controller는 UI를 통해 사용자 이벤트를 처리하고 Status와 Main Cycle을 통해 시스템 상태를 관리하고 기계를 동작시키는 역할을 한다. RVM Controller는 하나의 프로세스로서 라즈베리파이에서 작동하며 Main Cycle과 UI를 2개의 Thread로 나누어 동시에 실행된다. UI는 사용자의 Event를 받아 Machine Status를 바꾸고 Main Cycle은 현재 status에 따라 전체 시스템을 동작시킨다. Rail Controller는 모든 모터를 제어하여 쓰레기를 판별하는 위치까지 이동시키고 각 결과에 따라 분류하도록 하는 프로세스이다. RVM Controller에게 Serial 통신을 통해 명령을 전달받아 아두이노에서 작동한다. Rail Controller는 총 4가지 동작 Case를 처리한다. 마지막으로 Image Processing Server는 판별부까지 이동한 쓰레기의 사진을 찍어 영상처리를 통해 판별하는 프로세스이다. RVM Controller와 Htttp 통신을 하기 위해 Web Server를 구축하였다. RVM Controller에서 판별 request를 보내면 Image Server에서는 사진을 찍고 영상처리를 하여 결과를 다시 보내준다.

하드웨어 설계 및 구현

RVM의 아두이노 메가 2560은 전반적인 모터제어 및 라즈베리파이와 시리얼 통신을 통해 동작 요청과 완료 신호를 송수신을 한다.

그림3. 하드웨어 연결도
  • 그림4.1 기어박스 카티아_1
  • 그림4.1 기어박스 카티아_2
  • 그림4.2 기어박스
  • 여러 회사들의 기어박스 설계도면들을 찾아보고 필요한 구동을 위한 기어박스를 3D프린터기로 제작

  • 그림4.3 레일 & 투입부
  • 그림4.4 페트병 분류기

아두이노 메인루프 : 서버역할을 하는 라즈베리파이와 시리얼 통신을 하고 들어오는 신호에 따라 정해진 기구부를 작동 및 제어한 후 해당 동작을 완료하면 서버에 동작 완료 신호를 보냅니다.

void loop()
{
    if(Serial.available() > 0)
    {
      in_data = Serial.read();
      switch(in_data)
      {
    
          case '1' :  // 입구 동작
            M2_duration = 0;
            M3_duration = 0;
            stepM(stepsPerRevolution*-1.3);
            M1_CW(225,1500);
            Serial.write('y');
            stepM(stepsPerRevolution*1.3);
            Flush();
            break;
    
          case '2' : // pet 분류 처리
            M2_duration = 0;
            M3_duration = 0;
            M3_CW(900);
            M2_duration = 0;
            M3_duration = 0;
            M1_CW(220,800);
            M3_CCW(900);
            Serial.write('y');
            Flush();
            break;
  
          case '3' :  // can 분류 처리
            M2_duration = 0;
            M3_duration = 0;
            M2_CW(5500,250);
            delay(100);
            M2_duration = 0;
            M3_duration = 0;
            M2_CCW(5400,250);
            Serial.write('y');
            Flush();
            break;
  
          case '4':  // 반송 처리
            M2_duration = 0;
            M3_duration = 0;
            M1_CCW(255,2500);
            Serial.write('y');
            Flush();
            break;
    
          default :
            Serial.write('E');
            Flush();
            break;
      }
  }
}

void Flush()    //Serial통신 버퍼 제거
{
    while(Serial.available() > 0)
    {
        Serial.read();
    }
}

3개의 dc모터, 1개의 스탭모터, 2개의 모터 드라이버가 사용됐습니다. 각각의 모터의 속도, 방향 그리고 엔코더 값에따라 모터를 제어 할 수 있습니다. 4가지의 모터의 방향 및 속도를 제어, 두 개의 dc모터는 엔코더센서를 통해 회전 정도를 제어할 수 있습니다.

// DC Motor 1 : rail
//a = enc , b = speed (0~255)
void M1_CW(int b, int c)
{
    digitalWrite(in1,HIGH);
    digitalWrite(in2,LOW);
    analogWrite(analog, b);      
    delay(c);
    M1_stop();
}

//a = enc , b = speed (0~255)
void M1_CCW(int b, int c) {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    analogWrite(analog, b);      
    delay(c);

    M1_stop();
}

void M1_stop()
{
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(500);
}


//DC Motor 2
//a = enc , b = speed (0~255)
void M2_CW(int a, int b) {

    int temp = 0;
    int temp1 = 0;
    int count = 0;
    while(abs(M2_duration) <= a)
    {
        digitalWrite(M2_in1,HIGH);
        digitalWrite(M2_in2,LOW);
        analogWrite(analog2, b);   
        
        delay(5);

        temp = M2_duration;
        if(temp==temp1)
        {
          count++;
          if(count>150)
              break;
        }
        else
        {
          count = 0;
        }
        temp1 = M2_duration;
    }
    M2_stop();
}

//a = enc , b = speed (0~255)
void M2_CCW(int a, int b) 
{
    int temp = 0;
    int temp1 = 0;
    int count = 0;

    digitalWrite(M2_in1, LOW);
    digitalWrite(M2_in2, HIGH);
    
    while(abs(M2_duration) <= a)
    {
        analogWrite(analog2, b);
        delay(5);

        temp = M2_duration;
        if(temp==temp1)
        {
            count++;
            if(count>150)
                break;
        }
        else
        {
            count = 0;
        }

        temp1 = M2_duration;
    }
    M2_stop();
}

void M2_stop() {
    digitalWrite(M2_in1, LOW);
    digitalWrite(M2_in2, LOW);
    M2_duration = 0;      
}


//DC Motor 3 : Exit
//a = enc 
void M3_CW(int a)
{
    int temp = 0;
    int temp1 = 0;
    int count = 0;

    digitalWrite(M3_in1,HIGH);
    digitalWrite(M3_in2,LOW);
    
    while(abs(M3_duration) <= a){
        delay(5);
        temp = M3_duration;
        if(temp==temp1)
        {
            count++;
            if(count>100)
                break;
        }
        else
        {
            count = 0;
        }
        temp1 = M3_duration;
    }
    M3_stop();
}

//a = enc , b = speed (0~255)
void M3_CCW(int a)
{
    int temp = 0;
    int temp1 = 0;
    int count = 0;

    digitalWrite(M3_in1,LOW);
    digitalWrite(M3_in2,HIGH);

    while(abs(M3_duration) <= a){
         //Serial.print("M3_duration : ");
         //Serial.println(M3_duration);
         
        delay(5);
        temp = M3_duration;
        if(temp==temp1)
        {
            count++;
            if(count>100)
              break;
        }
        else
        {
            count = 0;
        }
        temp1 = M3_duration;
    }
    M3_stop();
}

void M3_stop() 
{
  digitalWrite(M3_in1, LOW);
  digitalWrite(M3_in2, LOW);
  // Serial.println("3 : stop");
  // delay(500);
  M3_duration = 0;      
}

//Step Motor1 : Entrance
void stepM(int stepsPerRevolution)
{
  myStepper.step(stepsPerRevolution);
}


소프트웨어 설계 및 구현

RVM의 소프트웨어는 기본적으로 python, C, C++을 사용하여 프로그래밍하였다. 아래의 그림은 내부적으로 3개의 프로세스들이 동작하는 시퀀스를 나타낸다. 사용자가 시작 버튼을 누르면 기계 상태가 On으로 바뀌며 Main Cycle을 시작하게 된다.

그림5. 내부 동작 시퀀스 다이어그램


RVMController

RVM Controller는 라즈베리파이에서 작동하는 프로세스이다. 파이썬으로 작성되었으며 pyqt5 파이썬 GUI 라이브러리를 사용하여 기본 골격을 만들었다. 거기에 현재 기계의 상태를 나타내주는 RVM Status Class와 각 동작을 실행하게 하는 Main Cycle을 구현하였다. 각 프로세스들이 여러가지 방법을 통해 통신하기 때문에 RVM Controller는 처음 시작할 때 여러가지의 통신 인터페이스를 초기화하고 각 센서 측정을 위한 GPIO setting을 해야한다. 아래의 코드는 RVM Controller의 main thread로서 기계를 처음 킬 때 Status class 인스턴스를 생성하고 각종 인터페이스를 초기화하고 로드셀 영점을 조절한다.

# stat class init
RVM_status = RVM_Stat() 

if not debug:
    # USB serial interface
    port = '/dev/ttyACM0'                           
    ser = serial.Serial(port, 9600, timeout = 2)

    # Load Cell GPIO setting
    GPIO.setwarnings(False)
    hx711 = HX711(LC_DT_Pin, LC_SCK_Pin)
    hx711.reset()

    w = []
    for i in range(20):
        w.append(hx711._read())

        if False in w:
            w.remove(False)
        
    w.remove(max(w))
    w.remove(min(w))
    init_avg = sum(w) / len(w)

    # IR Sensor GPIO setting
    GPIO.setup(IR_Pin1,GPIO.IN)
    GPIO.setup(IR_Pin2,GPIO.IN)
    GPIO.setup(IR_Pin3,GPIO.IN)
    GPIO.setup(IR_Pin4,GPIO.IN)

# main Cycle init
t1 = threading.Thread(target = main_Cycle)
t1.daemon = False
t1.start()

# 유저 인터페이스 init
app = QApplication(sys.argv) 
phone_window = phoneWindow() 
main_window = mainWindow()
main_window.showFullScreen()
app.exec_()
그림6. RVM Status & Main Cycle

RVM Controller는 전체 시스템을 관리하는 python 프로세스로서 UI, Main cycle, stat class의 인스턴스를 가지고 있다. Main cycle에서 단계별로 각 모듈을 함수 형태로 호출하며, 현재 stat을 관리한다. RVM Status는 기계의 온오프를 나타내는 Machine Status, 현재 실행 상태를 나타내는 Execute Status, 현재 투입된 재활용 쓰레기 정보인 Recycling Status, 에러 발생 여부를 나타내는 Error Status를 맴버로 가지고 있다. 또한 Main Cylce은 총 7단계의 실행 단계를 가지며 각 단계를 모두 실행해야 한개의 쓰레기가 처리된다.

def main_Cycle():
    # main cycle
    while 1:

        #0 machine stat check
        if RVM_status.machine_stat != RVM_STATE_ON:
            time.sleep(0.1)
        else :
            #1 Check IR sensor
            if checkObjectCond() < 0:
                if RVM_status.machine_stat == RVM_STATE_OFF :
                    resultD = 0;
                else : 
                    errorExit()

            #2 Check Load cell 
            elif checkLoadCell() < 0:
                errorExit()

            #3 rail move command 
            elif moveCommand('Dzone') < 0:
                errorExit()

            #4 Request discrimination
            else :
                resultD = requestD()
                print(resultD)

                #5 rail move command 
                if moveCommand(resultD)<0:
                    errorExit()

            if RVM_status.error_stat == retValOK:
                # Update Status
                RVM_status.updateStatus(resultD)
                main_window.can_pet()
                main_window.button_text()

            elif RVM_status.error_stat == Error :
                #debug msg
                while RVM_status.error_stat == Error :
                    continue

                printU("Error fixed.. restart")
                main_window.button_text()

Image Processing Server는 판별부에 도착한 물체를 영상인식을 통해 판별하는 역할을 한다. RVM Controller는 python requests 모듈을 통해 간단하게 Http request를 보낼 수 있다. 따라서 RVM Controller로부터 Http request를 받아 판별을 하기 위해 간단한 웹서버를 구축하였다. 웹서버는 임베디드 보드에서 충분히 사용할 수 있는 Flask라는 웹 프레임워크를 사용하였다. 서버는 Http request를 받게되면 총 3가지 단계를 통해 판별을 수행한다. 카메라 촬영 & 전처리 -> Yolo 영상인식 -> 결과 parsing의 순서로 동작하며 이에 대한 자세한 내용은 영상인식 파트에서 설명하겠다.

영상인식 구현

본 프로젝트에서 투입 물건은 카메라로 촬영할 수 있는 장소에 페트병, 캔 두 종류가 오게된다. 페트병과 캔의 분류는 실시간 물체 감지 시스템인 YOLOv3를 사용한다. 물건의 분류는 크게 3가지 단계로 이루어진다. 사진촬영 및 전처리, YOLOv3 실행, 파싱을 통한 결과값 확인

1단계 - 사진촬영 및 전처리

우선 카메라를 통해 촬영되는 이미지를 그대로 사용하기에는 문제가 있었다. 카메라를 통해 보이는 물체는 분류를 하려는 물건만 있는 것이 아니고 모터와 기어박스 등 여러가지 물체들이 존재했다. 때문에 일말의 오류를 방지하고자 오픈소스인 OpenCV를 사용하여 이미지를 필요한 부분만 추출하는 전처리 과정을 넣었다.
(1) 사진 촬영
nvgstcapture 라이브러리를 활용하여 Jetson Nano 보드에서 CSI 카메라를 사용할 수 있었다.

cmd_capture = "rm -rf *.jpg && nvgstcapture-1.0 --cus-prev-res=1920x1080 -A"

(2) 촬영 사진 전처리

import os
import cv2

def imagepreprocess():
    """
    확장자가 jpg인 파일을 찾아서 불러오고 원하는 크기로 이미지를 자른다.
    자른 이미지는 기존 이미지를 덮어씌워서 저장한다.
    """
    path = "./"
    filenames = os.listdir(path)
    image_name = ""
    for filename in filenames:
        full_filename = os.path.join(path, filename)
        ext = os.path.splitext(full_filename)[-1]
        if ext == '.jpg': # find jpg
            image_name = full_filename[2:]

    image = cv2.imread(image_name, cv2.IMREAD_COLOR) # image load
    image_cut = image[76:390, :, :] # image cut
    cv2.imwrite(image_name, image_cut)


2단계 - YOLOv3

YOLOv3의 실행은 파이썬의 subprocess를 이용하였다. 전처리가 끝난 이미지를 불러와서 촬영 사진에 대한 검출을 실시한다. 검출 threshold는 0.4로 정확도가 0.4 아래인 물체는 물체가 아니라 판단을 하게 설정하였다. 검출 결과는 detect.txt 파일에 저장하도록 설정하였고 이를 후에 파싱하여 결과를 정리한다.

cmd_yolo = "./darknet detector test data/obj.data ./yolov3.cfg backup/pet_or_can.weights ./*.jpg -threshold 0.5 | tee detect.txt"

# Run Yolo
try:
    subprocess.call(cmd_yolo, shell=True)
except subprocess.TimeoutExpired: 
    print("Timeout during execution")
    return -1


3단계 - 파싱을 통한 결과값확인

일정패턴으로 나오는 텍스트를 통해서 원하는 문자를 파싱하여 결과값을 확인하였다.threshold 라는 변수를 두어 pet와 can의 정확도의 평균의 기준을 설정하였고 결과로 'pet', 'can', 'return'을 내어 이후 이 값을 라즈베리파이의 메인 서버로 전송한다.

def parse_result(file_name, threshold):
    with open(file_name, 'r') as f:
        lines = f.readlines()
    lines = lines[1:]
    can = []
    pet = []

    for i in range(len(lines)):
        lines[i] = lines[i].replace(":", "")
        lines[i] = lines[i].replace("%", "")
        lines[i] = lines[i].replace("\n", "")
        each = lines[i].split()
        if(each[0] == 'pet'):
            pet.append(int(each[1]))
        else:
            can.append(int(each[1]))

    if not pet:
        pet.append(0)
    if not can:
        can.append(0)

    can_possibility = sum(pet)/len(pet)
    pet_possibility = sum(pet)/len(pet)

    if (max(can_possibility, pet_possibility) < threshold or can_possibility==pet_possibility):
        return 'return'
    elif (can_possibility > pet_possibility):
        return 'pet'
    elif (can_possibility < pet_possibility):
        return 'can'

UI구현

RVM은 기계의 상태를 모니터링 하고 조작하기 위한 터치스크린 UI를 포함하고 있다. 라즈베리 파이에서도 안정적으로 동작하는 가벼운 프로그램으로 제작하기 위해 python pyqt5를 이용하여 제작하였다. python으로 작성하여 메인 프로세스인 RVM_controller과 같은 프로세스에 동작하며 데이터 통신 비용을 낮추고 pyqt5를 이용하여 그래픽 부담이 낮은 UI를 구현하였다. UI는 아래에 표시된 3개의 화면으로 구성된다.


  • 그림7.1 시작화면
  • 그림7.2 동작화면
  • 그림7.3 종료화면


왼쪽화면은 시작화면으로 RVM이 동작 중이지 않다는 것을 나타낸다. 시작 버튼으로 RVM을 동작 상태로 만들 수 있다. 가운데 화면은 RVM의 동작중 화면으로 RVM의 현재 동작 단계를 표시하는 메시지창과 투입된 페트 또는 캔의 개수를 표시하는 창으로 구성된다. 종료 버튼을 눌러 종료화면으로 넘아 갈 수 있다. 오른쪽 화면은 종료화면으로 시작부터 종료까지 넣은 페트병과 캔의 총 개수를 표시해주며 기계를 종료한다. 종료후에는 시작화면으로 돌아간다.

프로젝트 결과

최종 결과물

그림8. 최종 결과물

영상인식 결과

그림9. 학습 결과 손실함수 및 IOU

판별 정확도

그림10. 판별정확도

시연영상

미구현 내용

현재 미구현이 된 것으로는 LED플래시가 있다. 기존 구상에는 물건 분류를 위한 사진을 찍을 때, 암실에서 LED 플래시를 켜고 찍는 것이였다. 하지만 시간상 하드웨어 완성과 YOLOv3 학습에 집중하느라 LED 플래시 대신 천장을 열어두는 것으로 대체하였다. 추후에는 암실에서 LED플래시만의 조명으로 분류를위한 사진을 찍어서 물건 분류에 대한 변수를 줄일 생각이다.

현재에는 모든 페트병과 캔을 분류하는 것이 아니고 일부분의 페트병과 캔만을 분류할 수 있다. 데이터 수집에 있어서 페트병과 캔의 종류를 늘리는데 시간상 부족하였다. 추후에 이 프로젝트를 개량하여 진행하게 된다면 더 많은 종류의 페트병과 캔의 데이터를 수집하여 학습시킬 계획이다.

미구현이라기 보다는 추가되면 좋은 사항으로는 물건 분류 방법의 추가가 있다. 현재에는 내용물이 들어 있거나 유리병같은 무거운 물건을 제외하기 위해 사용하는 무게 센서 이외에는 분류를 모두 YOLOv3의 영상처리에만 의존하게 된다. Netson Nano에서 YOLOv3를 돌리기에는 생각보다 부담스러웠던 점도 있고 몇가지 센서를 병행한다면 더 좋은 정확도를 얻을 수 있을 것이다. 예를들어 빛을 투과하는 페트병과 투과하지 못하는 캔의 성질을 이용하면 빛을 이용해서 좀 더 손쉽게 페트병과 캔의 분류에 도움을 줄 수 있을 것이다.

본 프로젝트를 진행하면서 Netson Nano에서 YOLOv3를 직접 실행시키는 것보다 Netson Nano에서 촬영한 사진 혹은 영상을 외부 PC로 전송하여 외부 PC에서 YOLOv3를 실행시키는 것이 좀 더 빠를 것이라고 생각되었다. 따라서 외부 기기로 영상을 손쉽게 송출하도록 도와주는 라이브러리인 Motion을 사용한다면 좀 더 빠른 시간 안에 물건 분류를 처리할 수 있을 거라고 생각한다.

프로젝트 평가

평가항목

평가항목 일사분란.JPG

평가결과

(1) 판별 정확도
앞서 소개한 4가지 단계를 거쳐 최종적으로 10개의 페트와 10개의 캔에 대해서 테스트를 진행하였다. 결과로는 페트병에 대해서는 0.80, 캔에 대해서는 0.60의 정확도를 보였다. 학습을 하는 과정에서 크롤링한 데이터의 판별 정확도가 좋지 않아 직접 페트병과 캔을 촬영하여 학습을 진행하였다. 주로 분리수거장에서 페트와 캔을 확보하였는데 페트는 온전한 형태로 버려지는 경우가 많지만 캔은 부피를 줄여 버리는 경우가 많아 데이터 확보에 어려움이 있었고 이에 적은 종류의 캔으로 많은 데이터를 만들어 학습의 결과가 페트에 비해 좋지 않다고 판단이 된다. 페트의 데이터양에 비해 캔의 데이터양이 적은 경우 데이터 비중의 불균형으로 학습의 결과가 전체적으로 저하될 수 있다고 판단하여 자연스럽게 페트의 데이터양도 줄여서 학습을 진행하였다.

(2) 모터 제어 정확도
판별부까지 30번 물체를 이동시킨 결과 총 27번 정확하게 이동시켰다. 이동에 실패한 3번의 경우 이동 자체는 성공했지만 페트나 캔이 가로로 돌아가버린 경우이기에 이는 모터 제어 정확도의 문제가 아닌 하드웨어적인 문제라 판단할 수 있을 것이다. 모터 제어는 성공적이라 판단하였다.

(3) 모듈 동작 정확도
라즈베리파이에 구축된 서버의 신호에 맞추어 각 모듈은 시나리오 순서대로 동작함을 알 수 있다. 모터 제어 정확도를 판별하기 위해 30번 물체를 이동시키며 동시에 모듈 동작의 정확도도 함께 측정하였다. 총 30번 중 30번 모두 시나리오에 맞추어 정확하게 동작하였다.

(4) 경제성
전체적으로 RVM 제작에 들어간 비용은 400,000이다. 물론 전체적으로 아크릴로 구성하고 압축기와 외형을 제작하지는 않았지만 기존의 RVM의 가격인 21,000,000원과 비교하면 충분히 경제성 면에서는 장점을 가진다 할 수 있다.

(5) 직관성
RVM의 상태는 모두 UI를 통해서 사용자에게 전달이 된다. 물체를 투입한 경우 현재 물체가 어느 단계를 거치고 있는지, 결과가 어떻게 나왔는지를 실시간으로 전달하여 직관성을 충분히 구성하였다.

(6) 신속성
사전조사를 하면서 YOLOv3를 Jetson Nano 에서 사용하는 경우 전체적으로 판별에 필요한 시간이 3~5초임을 알 수 있었다. 이를 고려하여 한 물체에 대하여 전체적인 응답시간을 10초로 설정하였다. 실제로도 영상처리서버인 Jetson Nano에서 판별에 들어가는 시간은 약 3초이다. 하지만 이를 위해 가중치를 불러오는데 필요한 시간이 15~20초 정도이고 우리의 시스템은 판별시마다 가중치를 불러오기에 매번 응답시간에 가중치 로딩 시간이 들어가고 이에 전체적인 응답시간은 25~30초 정도로 측정이 된다.

느낀점

박*석 - 이번 프로젝트를 하면서 하드웨어 제작하는 것이 생각보다 어렵다는 것을 느꼈습니다. 아크릴, 풀리 등의 재료구매부터 각종 모터들과 센서들의 회로 구성까지 쉽지않았습니다. 팀원들과 같이 협력하면서 어려가지 문제들을 하나씩 해결하는 과정자체가 보람찼습니다. 페트병, 캔 분류에서는 YOLOv3 오픈소스를 사용하였는데 단순하게 데이터만 넣어서는 분류가 잘 되지않았습니다. 질 좋은 데이터들과 분류하려는 데이터들의 비율을 일정하게 맞추는 것이 물체 인식부분에 도움이 된다는 것을 새로 알았습니다. 이번 프로젝트 동안 같이한 팀원들에게 정말 고생 많았다고 말하고 싶습니다.

강*찬 - 프로젝트를 하면서 문제가 발생하면 소프트웨어 제작과 하드웨어 제작을 하는 과정에서 두 파트간에 많은 정보 공유가 필요하다는 것을 알 수 있었습니다. RVM이 오동작을 하는 경우 시스템의 문제인 경우도 있었지만 가장 크리티컬한 문제가 있었던 부분은 보드를 설치하고 배선을 연결하는 과정에서 있었던거 같습니다. 보드와 전선의 접촉 문제로 인한 오작동, 많은 수의 모터와 모터드라이버, 센서들로 인해 작동을 하다가 중간에 시스템이 다운 되는등 여러가지 문제점들이 있었고, 팀원들과 협력해 하나씩 해결해 나갈 수 있었습니다.

유*영 - 처음 주제 선정을 하는 단계부터 영상인식, 서버 구축, 전체적인 하드웨어 제작 이렇게 3가지 부분을 생각하며 정말 걱정이 많았습니다. 팀원들 모두가 정말 뛰어난 실력을 보여주어 이렇게 프로젝트를 잘 마무리할 수 있었습니다. 제작 과정에서 물품 구매와 제작 장소 선정에 여러 어려움이 있었습니다. 설계과정에서 적합하다 생각한 제작 재료도 제작을 하며 변경의 필요를 느꼈고 새로운 물품을 선정하는 과정이 쉽지 않았습니다. 또한 제작 환경이 보장되지 않아 힘든 부분도 있었습니다. 이런 힘든 과정 속에서도 아두이노와 모터제어를 처음 해보지만 정말 훌륭하게 제어를 구현한 강*찬형, 서버구축이라는 힘든 과정을 잘 해내준 윤*상, UI와 통신, 그리고 원포인트로 팀원들을 도와준 심*헌, 여러 프로젝트 경력과 영상인식을 잘해준 박*석 모두 너무나도 잘해주었습니다. 훌륭한 팀원들과 함께하며 배운것이 많았고 고맙습니다. 일사분란 착착착 화이팅!

윤*상 - RVM을 개발하면서 어느정도 수준의 하드웨어 제작이 필요한 시스템을 개발할 때에는 기본적인 하드웨어 시스템을 구현해놓고 소프트웨어를 개발하는 것이 효율적인 것을 느꼈습니다. 이번 프로젝트에선 대부분의 소프트웨어를 개발한 뒤 하드웨어를 제작하였는데 이 과정에서 예상치 못한 문제들이 발생하였습니다. 모터와 센서의 개수가 많아 전력 공급에서 문제가 생기고 배선과 같은 부분에서 제대로 작동하지 않아 오랜 시간이 걸렸습니다. 하지만 우리 팀원 모두 열심히 프로젝트에 임해주었고 소통도 활발히 되었으며 개개인의 능력 모두 뛰어났기 때문에 빠르게 문제를 해결하여 프로젝트를 마무리 할 수 있었습니다.

심*헌 - 이번 프로젝트에서 pyqt5를 통해 라즈베리파이와 같은 제한된 조건에서 유저 인터페이스를 개발하였습니다. 평소 자바스크립트를 통한 웹개발을 하고있어 ui 개발을 맡았지만 디자인과 같은 좀 더 전문성이나 예술에 가까운 영역에는 한계가 있어 팀원들과 소통을 통해 만들었습니다. 새로운 ui 플랫폼 개발의 기회가 된거 같아 도움이 되었고 팀원들과 하나의 큰 결과물을 내어 보람을 느낍니다.