YOLO(영상처리)와 6DoF 로봇 팔(window)

➢ 전에 사용한 tensorflow가 아닌 YOLO( You Only Look Once)를 사용하여 영상을 촬영하고 미리 학습시켜둔 이미지와 매칭하여 물체를 찾는 방법을 사용했다.

공식 홈페이지 : https://pjreddie.com/darknet/yolo/


➢ 기존 다른 Object Detection보다 처리 속도가 빠르다.



➢ YOLO 설치는 https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects 를 참고하여 설치를 진행하였다.


➢ 각자 원하는 환경과 구성된 환경이 다르기에 설치 시 설정를 정확히 해야한다.
나는 opencv와 cuda가 깔려 있는 상황에서 설치를 시작하였다.
확인후 설치를 시작한다.

✫gpu를 사용한다면 cuda 설치를 해주어야 하며 이때가 가장 중요하다.
cuda는 각 gpu가 지원하는 드라이버가 다른데 nvidia 홈페이지에서 자신에게 맞는 드라이버를 선택하고 설치를 하여야 한다.
(지금은 환경은 window지만 ubuntu 환경에서 설치시에 고생을 많이 했다.)

https://www.nvidia.com/Download/Find.aspx?lang=en-us에서 자신에게 맞는 드라이버를 선택하여 설치를 하여주면 된다.



➢ 다운로드 후 컴파일은 https://github.com/AlexeyAB/darknet#how-to-compile-on-windows-legacy-way 방법을 사용했다.


➢컴파일도 완료하였으면 이제 training을 실시하여야 한다.
training에 필요한 사진은 많으면 많을 수록 좋으며, 다양한 환경(다른 색의 배경 및 바닥, 다른 밝기의 조명)에서 찍은 사진이 많을 수록 좋은 것으로 보인다. 수는 최소 200장 정도 하면 적당한 것으로 생각된다.

필요한 사진들을 수집 완료 했으면 각 사진에 자신이 원하는 이미지를 마킹 해 주어야 한다. https://github.com/AlexeyAB/Yolo_mark 를 참고하여 사진에서 자신이 원하는 이미지를 마킹하면 될것이다.

자신이 찍어두거나 구글링하여 찾은 사진 말고도 동영상을 이용하여 프레임 마다 사진을 저장하는 방법도 있으니 참고하면 좋을듯 하다. https://github.com/AlexeyAB/Yolo_mark#how-to-get-frames-from-videofile

원하는 이미지와 라벨링을 마치면 이제 training을 실시하면 된다. yolov2냐 yolov3냐에 따라 수정해야 하는 부분이 있기에 원하는 버전을 선택하면 그에 따라 수정을 실시하면 된다. https://github.com/AlexeyAB/darknet#how-to-train-to-detect-your-custom-objects

학습된 모델을 실행하는데에는 Linux는 명령어를 입력하여 실행하여야 하지만 Window는 /build/darknet/x64에서 실행을 하면 된다. https://github.com/AlexeyAB/darknet#how-to-use-on-the-command-line

학습이 잘 되었다면 박싱이 되는 것을 확인 할 수 있다.


➢ 이제 박싱이 잘 되는 것을 확인 하였으니 카메라를 통해 얻은 좌표값을 시리얼 통신을 통해 아두이노로 넘겨주면 된다. 아래는 소스코드에 추가한 부분 중 일부이다.

if (start - end > 1) {
                end = time(NULL);
                char sendMes[10];
                u_char caString[20];
                sprintf(caString, "%.0f,%.0f", x_cent, y_cent);
                printf(caString);
                DWORD dwWritten;    //double world written 쓰기 후 실제 쓴 바이트 수 저장 공간
                DCB sPState;   //시리얼 통신 포트상태 저장 구조체  //세팅 되는 값을 기억해둬야함
                HANDLE hComm = CreateFile("COM6",
                    GENERIC_READ | GENERIC_WRITE,          //읽기 권한 | 쓰기 권한
                    0,                                                          //0은 포트당 한명의 사용자만 사용 가능. 공유모드,점유모드
                    NULL,                                                //화일의 보안성을 설정 ,포인터 주소로 들어가야함 0은 정수 NULL은 포인터 0
                    OPEN_EXISTING,                               //파일이 존재하면 열고, 없으면 에러
                    FILE_ATTRIBUTE_NORMAL,
                    0);                                                    //파일을 생성 - 존재하는 파일을 열기때문에 0 
              //createfile 은 윈도우 기반에서만 사용가능
              //리눅스에서 쓰던 read write등은 표준 c기반이라 다양한 곳에서 사용 가능

                if (INVALID_HANDLE_VALUE == hComm)
                {
                    printf("포트를 열수 없습니다.\n");
                    return 0;
                }
                if (0 == PurgeComm(hComm, PURGE_TXABORT | PURGE_TXCLEAR))                     //입출력 버퍼 초기화, 보류중인 입출력 연산 종료
                {
                    printf("버퍼 초기화 에러 \n");
                    CloseHandle(hComm);
                    return 0;
                }

                sPState.DCBlength = sizeof(sPState);                                //구조체의 크기를

                if (0 == GetCommState(hComm, &sPState))                    //시리얼 포트의 구조체를 읽어 오는거
                {
                    printf("시리얼 상태 읽기 에러\n");
                    CloseHandle(hComm);
                    return 0;
                }

                sPState.BaudRate = CBR_9600;                      //속도
                sPState.ByteSize = 8;                                //바이트 크기, 한번에 보낼수있는 크기
                sPState.Parity = NOPARITY;                       //패리티
                sPState.StopBits = ONESTOPBIT;

                if (0 == SetCommState(hComm, &sPState))               //포트설정 값을 저장  우리가 필요하 부분만 저장하고 다른부분은 손대지 않는다
                {
                    printf("시리얼 상태 설정 에러\n");
                    CloseHandle(hComm);
                    return 0;
                }
                if (0 == WriteFile(hComm, caString, sizeof(caString), &dwWritten, 0))
                {
                    printf("쓰기에러\n");
                }
                else
                {
                    printf("쓰기 성공\n");
                }
                CloseHandle(hComm);
                return 0;
            }

시리얼 통신을 통해 좌표값을 날리는 것은 time함수를 사용해 1초마다 신호를 보내도록 하였다.


➢ 시리얼 통신을 통해 들어온 값을 계산하고 로봇 팔이 그에 맞게 움직이게 한다. 아래는 소스코드 중 일부

void loop(){
  //카메라를 통해 들어온 좌표값으로 로봇팔 제어 2
  if(session == 0){
      serialTest6();
  }
  else if(session == 1){
    offObject2();
  }
}

void CalAngle2(int x, int y){
  long int a = 500 - x;
  if(a < 0 ) return 0;
  long int b = y-20;
  float c = atan2(b, a);
  c = c / PI * 180;
  int alpha =  (int)c;
  saveAngle = alpha;

  double r = (a*a)+(b*b);
  r = sqrt(r);
  distance = map(r,150,375,80,160);
  distance -= 5;
  distance2 = map(distance,90,160,0,70);
}

void serialTest6() {//카메라에서 받은 좌표값을 이용해 로봇팔 작동(4축)
  int x_int,y_int;
  if (Serial.available()) {
    String contString = Serial.readString();//시리얼 통신으로 읽어 들임
    int divindex = contString.indexOf(',');//,의 위치를 찾음
    int lastindex = contString.length();//읽어 들인 값의 전체 길이 측정
    String x_String = contString.substring(0, divindex); //처음부터 , 까지 문자 추출
    String y_String = contString.substring(divindex + 1, lastindex); //, 부터 끝까지 문자 추출
    x_int = x_String.toInt();
    y_int = y_String.toInt();
    if( ((x_int_pre -10 < x_int) && (x_int_pre + 10 > x_int)) &&
    ((y_int_pre - 10 < y_int) && (y_int_pre + 10 > y_int)) ){
      offObject2();
      return 0;
    }
    else if((x_int == 0) && (y_int == 0)) offObject2();
    else {
      CalAngle2(x_int, y_int);
      x_int_pre=x_int;
      y_int_pre=y_int;
    }
  }
  if((x_int > 0) && (y_int > 0)){
  servo1.write(saveAngle, 60);
  delay(3000);
  servo4.write(45-(distance2*0.2),60);
  delay(1000);
  servo3.write(45+(distance2*1.2)-12,60);
  delay(1000);
  servo2.write(distance-12,60);
  delay(1000);
  servo6.write(167,60);
  delay(1000);
  session = 1;
  }
}
void offObject2(){
  servo2.write(90, 30);
  delay(2000);
  servo3.write(178.60);
  delay(2000);
  servo1.write(165, 60);
  delay(2000);
  servo2.write(150, 60);
  delay(2000);
  servo4.write(0, 60);
  delay(2000);
  servo6.write(100, 60);
  delay(2000);
  session = 0;
}


➢ 로봇팔 동작 영상





⧫ 프로젝트를 진행하는 과정 중간 중간 기록을 하면 더 깔끔 했을텐데 빨리 완성 시키느라 기록 하지 못하여 글이 매끄럽지 못 한게 아쉽다.

댓글

이 블로그의 인기 게시물

python 에서 resize 이벤트 만들기

PLC와 아두이노 그리고 온도 센서를 이용하여 램프 점등

C언어로 로또 프로그램 만들기