일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 소프트웨어아키텍처
- 미키7
- 메모리용량
- 임베디드소프트웨어경진대회
- YOLOv4
- 컴공대회
- 소프트웨어 아키텍처
- 오블완
- PYTHON
- sf소설
- sf소설책
- venv가상환경
- 임베디드
- EBS볼륨
- 책추천
- 단편선
- 휴머노이드
- OpenCV
- 티스토리챌린지
- 디스크용량
- 대회
- 소프트웨어대회
- AWS
- 미키17
- 로봇대회
- raspberry pi
- software quality
- 알고리즘대회
- raspbian
- EC2
- Today
- Total
mjello 님의 개발 블로그
[코드리뷰] 2023 임베디드 소프트웨어 경진대회 : 휴머노이드 골프 로봇 본문
https://github.com/KOSS-ROKO/Team_RoKo_2020
테스트는 다른 비공개 레포에서 진행했습니다.
대회 소개 및 목표는 1,2편을 참고해주시기 바랍니다.
로봇 스펙
2족 보행 로봇 MF-RAPI4
두뇌보드 : 라즈베리파이 4 (4G-64G) | OS : 라즈비안 리눅스
제어보드 : MR-C3024FX | OS : 로보베이직
테스트 방식 : VNC로 로봇 라즈베리파이와 연동
개발 방식 : VScode의 LiveShare 기능으로 다같이 화면 공유 후 작업,
로봇 라즈베리파이에 로그인 된 Github 계정으로 Commit
로직 및 클래스 다이어그램 소개
기본 로직입니다.
0. 준비
1. 시작 및 티샷 (*티샷=첫번째 퍼팅입니다.)
2. 공을 향해 걸어간다
3. 퍼팅 위치에 서기
4. 퍼팅
5. 홀인
이 일련의 과정들을 여러 조건에 따라 건너뛰기도 하고 되돌아가기도 하는 구조입니다.
처음에는 총 2개 파일로만 프로그램을 작성했습니다.
1. 라즈베리파이 비디오 영상을 받아와서 이미지를 한장씩 전달해주는 파일
2. 위 6개 알고리즘을 수행하는 메인 파일
메인 파일에는
1. 이미지 전처리
2. 다양한 조건문과 알고리즘
3. 로봇에게 동작 번호로 Motion 지시
의 과정이 모두 들어가야했는데,
< 문제점 >
이렇게 되니 중복되는 코드가 많아서 리뷰가 어렵고, 로직이 꼬일때 오류 검출도 힘들었습니다.
특히 2번을 수행하기위해서는 현재 로봇의 센서 값들을 저장해놓을 전역/지역 변수 관리와
거리 측정 알고리즘까지 고려해야했습니다.
< 해결 >
따라서 이 무거운 메인 파일을 쪼개서 코드를 객체화하고 역할별로 분류하기 위해 코드를 뒤엎었습니다.
위 사진이 최종 클래스 다이어그램입니다.
우선 목적에 따라 Core (알고리즘) / Motion (로봇제어) / Vision (데이터 값 추출) 의 총 3개의 폴더로 구성했습니다.
Motion 에는 로봇팀이 '로봇 모션 명령'을 정리했고,
Vision 의 Distance.py 에는 '서보 각도에 관한 전역변수'와 '최초 영점/가변 영점에 따른 거리 계산 알고리즘',
ImageProcessor.py 에는 '영상처리 및 분석(전처리) 클래스'를 담았습니다.
Vision에서 전처리는 OpenCV2 라이브러리를 활용했습니다.
전처리 과정을 간단히 설명하자면,
Thresholding과 Bitwise로 frame에서 특정 rgb frame을 추출하고,
Gaussian blur와 Erode(침식), Dilate(팽창) 값을 조정하여 노이즈를 잡았습니다.
이제 전반적인 알고리즘을 제가 개발한 코드 위주로 소개해보겠습니다.
목표물 인식을 위한 동작 최소화 Head.py
Head.py : 로봇의 고개 각을 조절하는 클래스
big_LR_head : 목표물이 화면상에 들어오도록 고개를 좌우 회전
small_LR_head : 목표물이 화면 수직 중앙선에 위치할 때까지 고개를 좌우로 미세 조정
big_UD_head(self, detect_object, big_ud_angle) : 목표물이 화면상에 들어오도록 고개를 상하 조절
head_for_dist(self, detect_object, small_ud_angle) : 거리 측정을 위해 목표물이 화면의 가운데에 위치하도록 고개를 상하로 미세 조정
straight() : 로봇, 공, 홀컵을 일직선으로 맞추기 위해 로봇의 좌우 이동 및 회전을 수행
로봇이 공과 목표지점을 찾을 수 있는 방법은 컴퓨터 비젼 밖에 없었지만,
라즈베리파이 카메라 특성상 시야각이 좁아서 퍼팅 후 바로 공을 찾을 수 없었습니다.
예를 들어서 설명해보겠습니다.
공 | |||||
최 | 대 | ||||
+ | |||||
시 | 야 |
공 | 상 | ||||
최 | 대 | ||||
좌 | + | 우 | |||
시 | 야 |
이와 같은 상황이라면, 공의 위치를 찾기위해 [ 1. 위아래 2. 좌우 ] 를 수행해야 하는데,
이 과정이 big_UD_head와 big_LR_head 입니다.
[ 1. 위아래 2. 좌우 ] 를 번갈아서 반복 시행해야 흰색 존의 빨간공을 찾을 수 있습니다.
그 후에 공이 최대시야 안에 들어오면,
시야 정중앙(+) 에 목표물을 위치시키기위해 small_LR_head을 수행하고 head_for_dist로 거리를 알아냅니다.
저는 최대한 효율적으로 공을 정중앙에 위치시키기위해
big으로 목표물을 화면상에서 발견만 하도록하고
small과 dist로 중심을 맞추는 방식으로 개발했습니다.
한 경기당 제한시간이 5분이기 때문에 시간을 효율적으로 쓰는것이 중요했습니다.
따라서, [ 1. 위아래 2. 좌우 ] 둘 중 어느걸 먼저 수행해야 더 빨리 공을 찾을 수 있는지도
경기 진행 단계와 로봇의 현재 자세에 따라서 다르게 설정했습니다.
홀컵 인식 및 홀인 판별 알고리즘
1. 홀컵 인식
가장 문제가 되었던 부분은 바닥의 화살표도 노란색이라는 점입니다.
카메라 화질로 인해 [ 노란 색상값 + 도형의 각이 n개 이상이면 원으로 취급 ]하는 방식으로는 홀컵만 인식하기 어려웠습니다.
두번째로 시도한 [삼각형 깃발 검출 방식] 은 너무 가까운 거리에서는 화면 내 삼각형 모양이 보이지않는 문제,
세번째로 시도한 [특정 면적 이상이면 홀컵으로 판단] 방식도 불완전했습니다.
그래서 처음부터 이 문제를 다시 생각해보게 되었습니다.
저희가 '홀컵'만 인식하기위한 이유는 퍼팅의 강도를 홀컵 거리에 따라 조절하기 위함이었고,
홀컵 대신 화살표가 인식된다고해서 로봇의 방향 인식에는 문제가 생기지않았습니다.
따라서, 화살표를 지나기 전인 초반에는
방향 : 화살표 or 홀컵 으로 인식
퍼팅강도 : 퍼팅 횟수와 해당 파에 따라서 가중을 두는 방식으로 변경했고,
후반에는 홀컵을 인식하여 정확한 퍼팅 세기를 결정하는 방식으로 문제를 해결했습니다.
2. 홀인 판별
로봇의 키는 대략 50cm로 그렇게 크지않습니다.
마지막 퍼팅 후 가까이에서 홀컵을 내려다보아도 아래 1번 사진처럼
홀인된 볼의 가장자리가 튀어나온 것처럼 보일 수 있습니다.
그래서 제가 처음 제안한 방법은 A ( B B' ) A' 처럼
좌우 가장자리로만 홀인여부를 판단(1회)하고, 홀컵 기준으로 90도 돌아서 한 번 더(2회) 판단하자는 의견이었습니다.
그런데, 기술교육에서 홀컵의 깊이를 재고,
실제 로봇으로 테스트를 해보니 우려한 상황은 발생하지않아서
한 번에 공과 홀컵의 상하좌우 점(총 8개)로 판별하고 세레머니하는 방식으로 코드를 수정하여 시간을 단축했습니다.
메인 알고리즘
Robo.py : 로봇 제어에서 동작과 이미지 처리 관련 기능을 초기화 하고 관리하는 클래스
Act.py : Controller 클래스 시작을 위한 Act 키 값 저장
Controller.py : 메인 알고리즘 클래스
저희가 설계한 초반 알고리즘입니다.
1. 시작 및 티샷
START = auto() # 시작
TEESHOT = auto() # 맨 처음 티샷
2. 공을 향해 걸어간다
FIND_BALL = auto() # 공이 중앙에 오도록 고개를 (left, right)
DISTANCE = auto() # 거리 측정 (거리)
WALK_BALL = auto() # 공까지 걸어가기 (걸음수)
3. 퍼팅 위치에 서기
big_ud_head() 목적 :화면 안에 공, 홀컵 둘다 검출되어야함 = oneframe
small_lr_head() ⇒ 공을 가운데에 맞춤
ball_hole_straight() ⇒ 좌우 Turn (홀컵-공-로봇 일직선을 위해 : 로봇이 좌상단, 우상단으로 원형으로 움직이도록)
⇒ True : 일직선이 맞춰지면, small LR UD하고 홀컵 거리 계산
기본 : 좌퍼팅 ( field의 블랙으로 좌우 퍼팅 결정 )
small LR(좌우) UD(상하) 후 공 거리 계산
if 공거리 안맞으면, 미세조정
LEFT_RIGHT = auto() # 홀컵이 중앙에 오도록 고개를 ( left&right ) (변수에 좌우 angle 값 저장)
UP_DOWN = auto() # 홀컵 중앙 맞춘 고개 각도 값 저장 (up&down) (변수에 상하 angle 값 저장)
DISTANCE = auto() # 홀컵 거리 측정 (holecup distance)
⇒ 여기서 홀컵 거리가 10 CM이하이면, hole in 알고리즘 호출
LEFT_RIGHT = auto() # 공이 중앙에 오도록 고개를 ( left&right ) (변수에 좌우 angle 값 저장)
UP_DOWN = auto() # 공 중앙 맞춘 고개 각도 값 저장 (up&down) (변수에 상하 angle 값 저장)
DISTANCE = 15 # 공 거리 측정
예외상황 : 홀컵이 최대 고개각도에서도 안보일경우
ANGLE_CAL= auto() # 각도, 오왼 계산 후 로봇 이동
4. 퍼팅 (전역변수로 몇번 퍼팅했는지 count)
DISTANCE = auto() # 홀컵 거리 측정 (holecup distance)
PUTTING = auto() # 퍼팅 강도 설정, 퍼팅 하기
5. 홀인
#2, 3을 하다가 3에서 홀컵까지의 거리가 10cm 이내에 들어오면 break 후
ONEFRAME = auto() # 홀컵-공 한 화면 안에 들어오는지 (True, False)
HOLEIN = auto() #홀인여부 판단 (True, False)
CEREMONY = auto() #세레머니
정확한 퍼팅을 위한 각도 조정
위 [메인 알고리즘] 에서 정확한 퍼팅을 위해서는 3. 퍼팅 위치에 서기 단계가 가장 중요했습니다.
2개의 샷만 친다고 가정하면,
처음 티샷 이후 2. 공을 향해 걸어가고, 3. 퍼팅 위치에 서기 에서 퍼팅각을 결정하고
4. 퍼팅 / 5. 홀인 /을 진행하기 때문입니다.
초반 알고리즘은 [홀컵 - 공 - 로봇] 이 일직선에 위치한 상태에서
로봇이 왼쪽으로 둥글게 돌아 공의 왼편 90도에 위치하고 퍼팅하는 순서였습니다. (아래 사진 참고)
그러나 로봇 영점 상태에 따라 모션이 달라져서 하드웨어의 '90도 Pose'만 믿고 퍼팅 각을 결정할 수는 없었습니다.
따라서 퍼팅 정확도를 높일 방법으로 'Set_holecup_left()'과 'ball_pos()' 알고리즘 반복을
[4. 퍼팅] 의 motion.putting 직전에 추가했습니다.
[ball_pos()]
로봇이 정면 아래 63도를 바라보고
공이 골프채에 맞을 수 있는 범위에 들어오도록 조정하는 과정입니다.
=> 공 중앙 x,y값과 목표 값 비교로 공 위치 조정
[Set_holecup_left()]
로봇 고개를 좌 90도로 돌린 후 홀컵의 깃대가 목표범위 안에 들도록 조정하는 과정입니다.
=> 좌퍼팅 기준, 로봇과 홀컵 각 조정
위 과정 후에도 퍼팅 정확도가 만족할만 하지 못해서
저와 로봇팀 팀장이 같이 다양한 상황에 대한 목표 범위를 측정해서 문제점을 파악했습니다.
홀컵과 로봇 사이 거리에 따라 로봇의 목표 각이 달라져야했습니다.
이를 수식으로 만들어서 로봇과 홀컵의 현재 거리(d)를 넣으면,
목표 각(a)를 도출하는 방식은 로봇이 작은 angle값을 조정할 수 있을만큼 정확하지 못해서 사용하지 않았습니다.
최종적으로 거리 범위를 3등분해서 목표 각 3개 중 하나를 리턴하는 방식으로 문제를 해결했습니다.
Dead Lock
운영체제 내용 추가 예정
time.sleep(0.1)
여기까지 알고리즘 설명을 마치고, 아쉬운 점과 느낀 점에 대해 이야기하겠습니다.
아쉬웠던점
1. 처음부터 객체지향으로 짰다면 어땠을까?
통 코드를 분할하고 객체화하는 과정에서 많은 시간과 노력이 들었다.
팀원들 모두 이렇게 큰 장기 프로젝트는 처음이었기에 거쳐간 시행착오라고 생각하고
다음 프로젝트부터는 코드의 가독성과 객체화에 신경써야겠다고 다짐했다.
2. 서투른 툴 사용
a. 처음엔 깃허브 커밋 메시지도 신경썼지만, 후반부로 갈수록 당장 눈앞의 테스트가 중요해서
커밋 메시지를 신경쓰지못했다. 이슈나 리뷰 등의 기능도 활용하지못해서 아쉽다.
b. CV팀에서 알고리즘을 개발할때 VScode의 '라이브 쉐어' 기능을 활용해서 화면공유로 다 같은 화면에서
각자 업무를 개발했었다. 당시에는 편리했지만, 결과적으로 누가 어떤 내용을 개발했는지 알아볼 수 없게 되었다.
나는 우리 팀원들 중 알고리즘 로직에 가장 많은 기여를 했다고 자신할 수 있지만,
결과적으로 내 계정의 커밋은 초반 CV 전처리 단계의 58 커밋 밖에... 남지않아서 아쉬움이 남는다.
3. 팀원들 간 약속
예선과 본선 초반에는 팀원들 모두 열정에 불타서 성실하게 참여했었는데,
아무래도 장기적으로 진행되다보니 개발 진행이 더뎌지고, 지각을 하는 등의 문제가 발생했다.
따라서 회의를 통해 매주 할일을 분배하고, 지각자는 커피 쏘기 등의 규칙을 통해 문제를 개선할 수 있었다.
4. 너무 연약한 로봇
이건 기술상의 문제인데, 대회가 제 21회차인 만큼 로봇이 노후화되어서
좋은 코드로도 원하는 결과를 내기 어려웠다.
로봇 힘이 약해지니 바닥재와의 마찰에 영향을 많이 받았고
동작과 공이 굴러가는 정도가 달라져서 대회 당일 바닥재질에 따라 로봇 동작을 다시 정의했다.
특히 우리 로봇은 영점 부품에 문제가 있어서 로봇팀에서 상당히 고생했다.
5걸음마다 영점이 틀어져서 넘어지고, 넘어지면 영점이 틀어지는 악순환을 반복하며 제대로 동작을 수행하지못했다.
이 문제를 해결하기위해 로봇팀에서도 수고해줬고,
CV팀에서는 로봇의 동작을 믿지않고
예외상황이 발생해도 처리할 수 있도록 거의 모든 예외상황에 대한 코드를 작성했다.
하드웨어의 부족한 점을 소프트웨어로 보완해야겠다는 생각이었다.
이 과정이 굉장히 오래걸렸는데,
결과적으로 본선 마지막 경기에서 이 예외상황에 해당하는 상황이 생겨서 뿌듯하게 홀인할 수 있었다.
주최에서도 문제를 인식하고 2024년도부터는 새 로봇으로 교체해준다고 하니
앞으로 대회에 참여하실 분들은 걱정하지않아도 될 것 같다.
느낀점
1. 로직 설계에 대한 흥미
대회에서 만난 모든 문제가 정답이 없었고,
해결 방법이 여러가지인 문제를 푸는 것 같아서 자유롭고 재미있었다.
오래 고민하던 문제에 대한 해결법을 찾았을 때에 느낀 뿌듯함과 보람을 잊을 수 없어서
이 분야를 계속해서 공부하고 싶다.
2. 임베디드 소프트웨어의 특징 & 우리팀의 노력
위 '아쉬웠던 점' - 4번에도 언급했듯이
임베디드 소프트웨어는 [하드웨어와 소프트웨어의 결합]에서 나오는 시너지가 매력적인 분야라고 생각한다.
하드웨어 분야는 소프트웨어와 현실 세계를 이어주는 역할을 하는 만큼 흥미롭지만 어려운 분야라고 느꼈다.
우리 팀은 이 어려움을 마주하는 과정에서 [임베디드 & 소프트웨어] 간 보완뿐만 아니라,
하나의 팀으로서 서로 간의 단점을 보완해주고 장점을 살렸다.
팀원들과 하나의 목표를 바라보며, 집요함과 끈기를 가지고 계속해서 문제를 파고든 경험이 굉장히 값졌고,
경기 당일에는 모두가 눈물을 보일정도로 다들 진심으로 임했다.
이 과정이 우리를 하나의 팀으로 만들어준 것 같다.
'개발 하자 🛠' 카테고리의 다른 글
AWS EC2 볼륨 늘리기 + 가상환경(venv)으로 디스크 공간 사용해서 메모리 부족 해결하기 (1) | 2025.06.04 |
---|---|
AWS EC2에서 Nginx를 이용한 무료 HTTPS 통신 설정 및 도메인 적용 (0) | 2025.03.31 |