Gemini Raspberry Pi Robot

실물 로봇 영상과 공개 코드 사이의 차이

IoT 수업의 과제 대체 프로젝트를 준비하면서 주제를 꽤 오래 못 정했다. 시각장애인용 지팡이, 음주 측정기, 사람을 감지하면 웹으로 알려 주는 장치까지 후보는 계속 나왔는데, 어느 것도 끝까지 만들고 싶다는 생각은 들지 않았다.

그러다 Raspberry Pi에 LLM API를 붙이면 어떨까 싶었다. 정해진 버튼대로만 움직이는 RC카가 아니라, 사람의 말을 듣고 지금 무엇을 해야 하는지 고르는 로봇. ‘스스로 생각하고 판단하고 움직인다’는 말이 정확한 기술 설명은 아니어도, 그때 나를 작업대로 끌고 간 이미지는 그거였다.

음성 명령과 카메라, 초음파 센서로 동작하는 Raspberry Pi 로봇 시연
수업 발표에 사용한 실물 로봇. 마이크로 명령을 받고 카메라와 초음파 센서를 확인한 뒤 모터를 움직인다.

친구와 본체를 조립하고 LLM을 붙였다

5월 초 친구와 함께 프로젝트를 시작했다. 본체 조립은 친구가 맡았고, 나는 조립을 보조하면서 Raspberry Pi에 LLM을 연동하고 제어 코드를 작성했다.

라즈베리파이에 모터 드라이버와 DC 모터, HC-SR04 초음파 센서, USB 카메라와 마이크를 연결했다. 음성 명령은 한국어로 인식하고, Gemini가 명령을 JSON 행동 순서로 바꾸면 프로그램이 허용된 동작만 골라 실행한다. 카메라가 필요한 질문이나 물체 찾기에는 Gemini Vision을 사용했다.

말로 설명하면 한 줄로 이어지는 흐름이지만, 실제로는 서로 다른 장치와 네트워크 작업이 연결된 작은 파이프라인이었다.

호출어와 명령 듣기
  → 한국어 음성 인식
  → Gemini가 JSON 행동 계획 생성
  → 행동명과 반복 횟수 검사
  → 초음파 거리 확인
  → GPIO 모터 / OpenCV 카메라 / 음성 출력 실행
Raspberry Pi와 모터 제어 부품을 준비한 모습
Raspberry Pi와 제어 부품을 먼저 펼쳐 놓고 장치별 동작부터 확인했다.
로봇 차체에 모터와 바퀴를 조립하는 과정
차체와 바퀴를 조립한 뒤에는 좌우 모터의 방향이 코드와 맞는지 따로 시험했다.
Raspberry Pi 로봇의 센서와 모터 배선
센서, 모터, 오디오와 카메라가 한 차체에 모이면서 코드 밖 변수가 급격히 늘었다.
카메라와 초음파 센서를 장착한 완성된 Raspberry Pi 로봇
카메라와 초음파 센서까지 장착한 발표용 최종 형태.

코드에 오류가 없다는 말은 실물에서는 별 의미가 없었다

가장 곤란했던 것은 배터리 전압이었다. 모터는 돌아가는데 힘이 부족하거나, 같은 시간 동안 전원을 줬는데 회전 각도가 달라졌다. 화면에서는 정상으로 보이는 GPIO 출력도 바닥 마찰, 하중, 배선 상태와 전압이 바뀌면 전혀 다른 움직임이 됐다.

USB 장치 번호가 바뀌거나 마이크가 주변 소음을 명령으로 받아들이는 일도 있었다. 카메라가 보는 각도는 사람이 정면에서 보는 것과 달랐고, 초음파 센서는 좁거나 비스듬한 물체를 항상 안정적으로 잡지 못했다. 코딩할 때는 조건문 하나로 끝났다고 생각한 부분을 실물에서는 계속 다시 확인해야 했다.

그보다 힘들었던 건 될지 안 될지 확신이 없었다는 점이다. 한 부분의 실패가 전체 시연을 멈추게 하는 구조라서, 작업하는 동안 스스로 주제를 너무 크게 잡은 것은 아닌지 여러 번 의심했다. 결국 한꺼번에 로봇을 완성하려고 하지 않고 모터, 센서, 음성, 카메라를 하나씩 붙이는 식으로 버텼다.

Gemini Vision을 붙이니 ‘생각하는 시간’도 생겼다

로봇이 카메라로 물체를 찾을 때는 사진을 찍고, 이미지를 API로 보내고, 결과를 받아 다시 행동으로 바꾼다. 그래서 일반적인 전진이나 회전보다 반응이 확실히 느리다. 시연 영상에서 로봇이 잠깐 멈추는 구간은 분위기를 위한 연출이 아니라 네트워크와 이미지 추론을 기다리는 시간이다.

그래도 Vision을 선택한 이유는 단순했다. 물체마다 별도 인식 모델을 학습하지 않고도 ‘앞에 무엇이 있는지’, ‘목표가 어느 쪽에 있는지’를 같은 명령 흐름에서 다룰 수 있었기 때문이다. 과제 기간 안에 실제로 움직이는 결과를 만드는 데는 이 장점이 컸고, 지연 시간은 그 대가였다.

발표에서는 내부 구조보다 움직였다는 사실이 먼저 보였다

7월 9일 수업 시간에 실물 로봇을 발표했다. 나는 어느 요청에서 API가 불리고 어느 거리에서 모터가 멈추는지를 보고 있었지만, 원리를 모르는 사람들에게는 말한 대로 카메라를 보고 움직이는 장면 자체가 먼저 들어왔다. 반응은 AI가 처음 제품 시연으로 등장했을 때의 발표회처럼 느껴졌다. 내부에서 무엇이 느리고 불안정한지보다, 기계가 말을 듣고 움직였다는 사실에 시선이 모였다.

발표와 동작 영상

아래 영상은 같은 실물 프로토타입에서 촬영했다. 명령 종류와 촬영 길이가 달라 짧은 영상도 함께 남겼다.

그런데 영상과 GitHub 코드가 맞지 않았다

발표 뒤 저장소를 공개할 때 최종 통합본이 아니라 축약된 실험 코드가 올라갔다. 공개본의 이동 함수는 실제 GPIO 대신 문장을 출력했고, 카메라와 센서 코드도 주 실행 경로에 연결되어 있지 않았다. 나중에 README를 정리하며 공개 코드만 다시 읽었을 때는 영상의 기능을 현재 저장소로 재현할 수 없다는 결론이 맞았다.

처음에는 실제로 돌렸던 코드가 어딘가에 그대로 있을 거라고 생각했다. 하지만 ‘있을 것’과 ‘지금 공개된 코드에서 확인된다’는 전혀 다른 말이었다. 그래서 없는 구현을 기억으로 채워 쓰지 않고, 그 시점의 README에는 공개 코드의 범위를 제한사항으로 남겼다.

발표용 최종 통합본을 저장했다

robot_pj_long_listen 폴더에 GPIO 모터, 초음파 센서, 카메라, Gemini Vision, 음성 입력과 TTS가 연결된 버전을 남겼다.

수업 발표와 첫 공개

실물은 최종 통합본으로 발표했지만 GitHub에는 축약된 코드가 올라갔다. 이때 영상과 공개 코드의 기준이 갈라졌다.

원본을 찾아 코드와 문서를 복원했다

5월 28일 파일의 내용과 날짜를 다시 대조하고, 폐기된 키가 남은 과거 백업은 제외한 뒤 최종 통합 코드를 재커밋했다.

복원하면서 바꾼 기준

원본을 찾았다고 폴더를 그대로 덮어쓰지는 않았다. 과거 백업 중에는 이미 폐기한 API 키가 들어간 파일도 있었다. 키 값은 복원 대상에서 제외하고, 현재 저장소는 .env와 환경변수만 사용하도록 정리했다. 문서도 “로봇이 무엇을 할 수 있다”는 소개보다 어떤 파일이 어떤 장치를 부르고, 어디까지 안전 조건이 있으며, 무엇이 아직 위험한지를 먼저 확인할 수 있게 고쳤다.

현재 공개 코드는 영상과 같은 최종 통합본의 흐름을 담고 있다. 그래도 남은 한계는 분명하다. 센서 타임아웃을 더 보수적으로 처리해야 하고, 뒤와 옆은 보지 못하며, 물리 긴급 정지와 배터리 감시도 없다. Gemini Vision의 지연도 여전하다.

이번에 가장 오래 남은 교훈은 로봇보다 기록에 관한 것이었다. 실물이 한 번 움직였다는 영상, 그날 사용한 코드, 나중에 정리한 공개 저장소는 자동으로 같은 상태가 되지 않는다. 차이가 생겼을 때 기억으로 메우지 않고 날짜와 파일을 대조해 복원하는 일도 프로젝트의 일부였다.