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;
}
➢ 로봇팔 동작 영상
⧫ 프로젝트를 진행하는 과정 중간 중간 기록을 하면 더 깔끔 했을텐데 빨리 완성 시키느라 기록 하지 못하여 글이 매끄럽지 못 한게 아쉽다.
공식 홈페이지 : 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;
}
➢ 로봇팔 동작 영상
⧫ 프로젝트를 진행하는 과정 중간 중간 기록을 하면 더 깔끔 했을텐데 빨리 완성 시키느라 기록 하지 못하여 글이 매끄럽지 못 한게 아쉽다.
댓글
댓글 쓰기