Skip to main content
Summary

바닥을 읽는 로봇의 눈

로봇이 길을 따라간다고 하면 왠지 카메라가 먼저 떠오를 수 있습니다. 하지만 작은 라인트레이서 로봇은 대부분 카메라 없이도 검은 선을 따라 달립니다. 비밀은 아주 단순합니다. 바닥에 빛을 쏘고, 되돌아오는 빛의 양을 읽는 것입니다.

라인 센서와 색상 센서는 모두 "빛이 물체에서 어떻게 반사되는가"를 이용합니다. 흰 종이는 빛을 많이 반사하고, 검은 테이프는 빛을 적게 반사합니다. 빨간 물체는 빨간빛을 많이 반사하고, 파란 물체는 파란빛을 더 많이 반사합니다. 로봇은 이 차이를 숫자로 읽고, 그 숫자를 기준으로 판단합니다.

이번 장에서는 라인트레이서 로봇에 자주 쓰이는 적외선 반사 센서와, 물체의 색을 구분하는 색상 센서를 다룹니다. 여기서 중요한 것은 센서 자체보다 판단 방식입니다. 센서값은 늘 흔들립니다. 바닥 재질이 바뀌고, 형광등이 켜지고, 햇빛이 들어오고, 배터리 전압도 조금씩 달라집니다. 그래서 "검은색이면 0, 흰색이면 1"처럼 단순하게 생각하면 실제 로봇은 금방 길을 잃습니다.

좋은 로봇은 센서값을 외우는 로봇이 아니라, 지금 놓인 환경에서 기준을 다시 잡을 줄 아는 로봇입니다.

라인 센서와 색상 센서

적외선 반사 센서의 원리

라인 센서 모듈을 보면 보통 작은 LED처럼 생긴 부품과 검은색 또는 투명한 수광 소자가 나란히 붙어 있습니다. 한쪽은 빛을 쏘고, 다른 한쪽은 되돌아온 빛을 받습니다. 라인트레이서용 센서에서는 주로 적외선을 사용합니다. 적외선은 사람 눈에는 보이지 않지만, 센서 입장에서는 충분히 측정 가능한 빛입니다.

센서가 흰색 바닥 위에 있으면 쏜 빛이 많이 되돌아옵니다. 검은색 라인 위에 있으면 빛이 많이 흡수되어 적게 돌아옵니다. 이 차이가 전압 차이로 바뀌고, 제어 보드는 이 전압을 아날로그 입력이나 디지털 입력으로 읽습니다.

여기서 주의할 점이 있습니다. 센서 모듈마다 값의 방향이 다를 수 있습니다. 어떤 모듈은 흰색에서 큰 값, 검은색에서 작은 값을 냅니다. 반대로 비교기 회로가 들어간 디지털 출력 모듈은 검은색에서 LOW가 나오고 흰색에서 HIGH가 나오거나, 그 반대로 동작할 수 있습니다. 따라서 라인 센서를 처음 연결했을 때 해야 할 일은 하나입니다. 먼저 값을 직접 읽어보는 것입니다.

아두이노의 아날로그 입력은 보통 0부터 1023 사이의 값을 읽습니다. ESP32는 설정에 따라 다르지만 일반적으로 더 넓은 범위의 ADC 값을 읽을 수 있습니다. 숫자 범위는 달라도 핵심은 같습니다. 흰색에서의 값과 검은색에서의 값을 비교하면 됩니다.

const int sensorPin = A0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int value = analogRead(sensorPin);
  Serial.println(value);
  delay(100);
}

이 코드를 올린 뒤 센서를 흰 종이 위에 올려보고, 검은 테이프 위에 올려봅니다. 예를 들어 흰색에서 850 근처, 검은색에서 220 근처가 나온다면 두 값의 중간인 535 정도를 임계값으로 잡을 수 있습니다. 반대로 흰색에서 200, 검은색에서 800이 나온다면 판단식도 반대로 써야 합니다.

임계값은 센서값을 둘로 나누는 기준입니다. 기준보다 크면 흰색, 작으면 검은색처럼 판단할 수 있습니다. 하지만 이 숫자는 절대값이 아닙니다. 책상 위에서는 잘 되던 로봇이 경기장 바닥에서는 흔들릴 수 있습니다. 그래서 임계값은 나중에 반드시 보정해야 합니다.

디지털 출력 센서와 아날로그 출력 센서

라인 센서 모듈에는 크게 두 가지 출력 방식이 있습니다. 하나는 디지털 출력이고, 다른 하나는 아날로그 출력입니다.

디지털 출력 센서는 모듈 안에 작은 비교기 회로가 들어 있습니다. 사용자는 HIGH 또는 LOW만 읽으면 됩니다. 회로가 간단하고 코드도 쉽습니다. 모듈에 달린 가변저항을 돌려 기준값을 맞출 수 있는 제품도 많습니다. 처음 실습하기에는 편합니다.

하지만 디지털 출력은 세밀한 차이를 알기 어렵습니다. 센서가 라인의 한가운데 있는지, 라인 가장자리에 걸쳐 있는지, 바닥이 살짝 어두운 것인지 구분하기 힘듭니다. "검정 또는 흰색"만 알려주기 때문입니다.

아날로그 출력 센서는 반사된 빛의 양을 숫자로 알려줍니다. 이 값은 흔들릴 수 있지만, 대신 더 많은 정보를 담고 있습니다. 라인을 부드럽게 따라가고 싶다면 아날로그 값이 훨씬 유리합니다. 나중에 P 제어나 PID 제어를 적용할 때도 아날로그 센서 배열이 큰 힘을 발휘합니다.

정리하면 단순한 ON/OFF 판단에는 디지털 출력이 편하고, 빠르고 안정적인 주행에는 아날로그 출력이 좋습니다. 라인트레이서 로봇을 제대로 만들고 싶다면 가능하면 여러 개의 아날로그 라인 센서를 배열로 사용하는 방식을 추천합니다.

센서 하나로 라인을 찾기

가장 단순한 라인트레이서 로봇은 센서 하나만으로도 만들 수 있습니다. 센서를 로봇 앞쪽 중앙에 달고, 검은 라인이 감지되면 직진하고, 감지되지 않으면 한쪽으로 회전해서 다시 라인을 찾는 방식입니다.

const int linePin = A0;
int threshold = 500;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int value = analogRead(linePin);

  if (value < threshold) {
    Serial.println("LINE");
    // 예: 직진
  } else {
    Serial.println("FLOOR");
    // 예: 천천히 회전하며 라인 찾기
  }

  delay(50);
}

이 예제에서는 검은색에서 값이 작게 나온다고 가정했습니다. 만약 자신의 센서가 검은색에서 더 큰 값을 낸다면 value < threshold를 value > threshold로 바꾸어야 합니다.

센서 하나 방식은 구조가 쉽지만 한계가 분명합니다. 로봇이 라인의 왼쪽으로 벗어났는지 오른쪽으로 벗어났는지 알 수 없습니다. 센서가 보는 것은 오직 "지금 내 아래가 라인인가 아닌가"뿐입니다. 그래서 로봇은 좌우로 흔들리며 라인을 찾게 됩니다. 느린 장난감 로봇에는 괜찮지만, 빠르고 안정적인 주행에는 부족합니다.

라인의 위치를 제대로 알고 싶다면 센서를 여러 개 사용해야 합니다.

센서 배열로 라인의 위치 판단하기

라인트레이서 로봇의 앞쪽을 보면 보통 센서가 일렬로 여러 개 붙어 있습니다. 3개, 5개, 8개처럼 홀수 개를 쓰는 경우가 많습니다. 센서 배열을 사용하면 라인이 중앙에 있는지, 왼쪽에 치우쳤는지, 오른쪽에 치우쳤는지 알 수 있습니다.

예를 들어 센서 5개를 왼쪽부터 S0, S1, S2, S3, S4라고 합시다. 각 센서가 검은 라인을 감지하면 1, 아니면 0이라고 단순화해 볼 수 있습니다.

센서 상태해석로봇의 반응
0 0 1 0 0라인이 중앙직진
0 1 1 0 0라인이 약간 왼쪽왼쪽으로 살짝 조향
1 1 0 0 0라인이 많이 왼쪽왼쪽으로 크게 조향
0 0 1 1 0라인이 약간 오른쪽오른쪽으로 살짝 조향
0 0 0 1 1라인이 많이 오른쪽오른쪽으로 크게 조향

여기서 "왼쪽으로 조향한다"는 말은 바퀴 로봇에서는 보통 왼쪽 바퀴를 느리게 하고 오른쪽 바퀴를 빠르게 한다는 뜻입니다. 반대로 오른쪽으로 가고 싶으면 오른쪽 바퀴를 느리게, 왼쪽 바퀴를 빠르게 하면 됩니다.

가장 단순한 코드는 패턴을 직접 비교하는 방식입니다.

const int pins[5] = {A0, A1, A2, A3, A4};
int threshold[5] = {500, 500, 500, 500, 500};
int line[5];

void setup() {
  Serial.begin(9600);
}

void loop() {
  for (int i = 0; i < 5; i++) {
    int value = analogRead(pins[i]);
    line[i] = (value < threshold[i]) ? 1 : 0;  // 검은색에서 값이 작다고 가정
  }

  if (line[2] == 1 && line[1] == 0 && line[3] == 0) {
    Serial.println("CENTER");
  } else if (line[0] || line[1]) {
    Serial.println("LEFT");
  } else if (line[3] || line[4]) {
    Serial.println("RIGHT");
  } else {
    Serial.println("LOST");
  }

  delay(50);
}

이 방식은 이해하기 쉽습니다. 하지만 빠른 라인트레이서에서는 조금 거칠게 동작합니다. 센서값을 0과 1로 잘라버리면 라인이 센서 사이에 있을 때의 미묘한 위치 정보를 잃어버리기 때문입니다. 더 부드러운 제어를 위해서는 라인의 위치를 숫자 하나로 계산하는 편이 좋습니다.

가중 평균으로 라인 위치 계산하기

센서 배열에서 자주 사용하는 방법은 가중 평균입니다. 왼쪽 센서에는 음수 위치, 중앙 센서에는 0, 오른쪽 센서에는 양수 위치를 부여합니다. 예를 들어 5개 센서에 다음과 같은 위치값을 줄 수 있습니다.

센서S0S1S2S3S4
위치값-2-1012

라인이 중앙에 있으면 계산 결과가 0에 가까워집니다. 왼쪽에 있으면 음수가 되고, 오른쪽에 있으면 양수가 됩니다. 이 결과를 오차로 사용하면 됩니다. 오차가 음수이면 왼쪽으로 보정하고, 양수이면 오른쪽으로 보정합니다.

문제는 센서값의 방향입니다. 흰색에서 큰 값, 검은색에서 작은 값이 나오는 센서라면 검은 라인의 강도를 구하기 위해 값을 뒤집어야 합니다. 예를 들어 ADC 값이 0부터 1023까지라면 lineStrength = 1023 - value처럼 계산할 수 있습니다.

const int pins[5] = {A0, A1, A2, A3, A4};
const int weight[5] = {-2, -1, 0, 1, 2};

void setup() {
  Serial.begin(9600);
}

void loop() {
  long weightedSum = 0;
  long strengthSum = 0;

  for (int i = 0; i < 5; i++) {
    int value = analogRead(pins[i]);
    int strength = 1023 - value;  // 검은색에서 값이 작게 나오는 경우

    if (strength < 0) strength = 0;

    weightedSum += (long)strength * weight[i];
    strengthSum += strength;
  }

  if (strengthSum > 0) {
    float position = (float)weightedSum / strengthSum;
    Serial.println(position);
  } else {
    Serial.println("LINE LOST");
  }

  delay(30);
}

position이 -2에 가까우면 라인이 왼쪽 끝에 있다는 뜻이고, 2에 가까우면 오른쪽 끝에 있다는 뜻입니다. 중앙이면 0에 가깝습니다. 이 값을 모터 제어에 연결하면 라인트레이서가 훨씬 부드럽게 움직입니다.

예를 들어 기본 속도를 120으로 두고, 위치 오차에 비례해서 좌우 바퀴 속도를 조절할 수 있습니다.

int baseSpeed = 120;
float Kp = 35.0;

float error = position;        // 중앙 목표값은 0
int correction = Kp * error;

int leftSpeed  = baseSpeed + correction;
int rightSpeed = baseSpeed - correction;

라인이 오른쪽에 있으면 error가 양수가 됩니다. 그러면 왼쪽 바퀴가 빨라지고 오른쪽 바퀴가 느려져 로봇이 오른쪽으로 돌아갑니다. 이것이 뒤에서 자세히 다룰 P 제어의 기본 형태입니다. 지금은 "오차가 클수록 더 강하게 방향을 고친다" 정도로 이해하면 충분합니다.

임계값은 왜 계속 바뀌는가

라인 센서를 실습하다 보면 이상한 일이 생깁니다. 방금 전까지 잘 따라가던 로봇이 갑자기 라인을 놓치거나, 검은색도 아닌 바닥을 라인으로 착각합니다. 센서가 고장 난 것처럼 보이지만 대부분은 환경이 바뀐 것입니다.

라인 센서값에 영향을 주는 요인은 꽤 많습니다.

  • 주변 조명이 밝거나 어두운 정도

  • 햇빛, 형광등, LED 조명의 위치와 깜박임

  • 바닥의 재질, 광택, 색상, 먼지

  • 센서와 바닥 사이의 거리

  • 센서 장착 각도와 로봇 주행 중 진동

  • 배터리 전압 변화와 전원 노이즈

특히 광택이 있는 바닥은 골치 아픕니다. 검은 테이프보다 회색 바닥이 더 강하게 반사될 수도 있고, 센서가 살짝 기울어졌을 때 값이 크게 바뀔 수도 있습니다. 그래서 라인트레이서에서는 "실험실에서 정한 임계값"보다 "출발 직전에 현장에서 다시 잡은 임계값"이 더 믿을 만합니다.

가장 기본적인 보정 방법은 흰색 바닥값과 검은색 라인값을 각각 측정해 중간값을 임계값으로 정하는 것입니다.

int whiteValue = 850;
int blackValue = 220;
int threshold = (whiteValue + blackValue) / 2;

센서가 여러 개라면 임계값도 센서마다 따로 잡는 것이 좋습니다. 같은 모델의 센서라도 LED 밝기, 수광 소자 감도, 장착 높이가 조금씩 다르기 때문입니다. 센서 5개에 모두 같은 기준을 적용하면 왼쪽 센서는 민감하고 오른쪽 센서는 둔한 이상한 로봇이 될 수 있습니다.

자동 캘리브레이션 적용하기

캘리브레이션은 센서가 현재 환경에 맞게 기준을 다시 잡는 과정입니다. 라인트레이서에서는 출발 전에 로봇을 라인 위와 바닥 위에서 조금씩 흔들어 주며 각 센서의 최솟값과 최댓값을 기록하는 방식이 많이 쓰입니다.

아이디어는 간단합니다. 일정 시간 동안 센서값을 계속 읽으면서 가장 작은 값과 가장 큰 값을 저장합니다. 그 뒤 실제 주행에서는 현재값을 최솟값과 최댓값 사이의 비율로 바꿉니다. 이렇게 하면 센서마다 값 범위가 달라도 0부터 1000까지의 정규화된 값으로 비교할 수 있습니다.

const int sensorCount = 5;
const int pins[sensorCount] = {A0, A1, A2, A3, A4};

int minValue[sensorCount];
int maxValue[sensorCount];

void calibrate() {
  for (int i = 0; i < sensorCount; i++) {
    minValue[i] = 1023;
    maxValue[i] = 0;
  }

  unsigned long start = millis();
  while (millis() - start < 3000) {   // 3초 동안 캘리브레이션
    for (int i = 0; i < sensorCount; i++) {
      int value = analogRead(pins[i]);
      if (value < minValue[i]) minValue[i] = value;
      if (value > maxValue[i]) maxValue[i] = value;
    }
    delay(5);
  }
}

int normalizedRead(int index) {
  int value = analogRead(pins[index]);
  int range = maxValue[index] - minValue[index];

  if (range < 10) return 0;  // 값 차이가 너무 작으면 신뢰하기 어려움

  int norm = (long)(value - minValue[index]) * 1000 / range;
  if (norm < 0) norm = 0;
  if (norm > 1000) norm = 1000;
  return norm;
}

이 코드에서 normalizedRead()는 센서값을 0부터 1000 사이로 바꿉니다. 다만 검은색에서 값이 작은 센서라면 라인 강도를 구할 때 다시 뒤집어야 합니다.

int norm = normalizedRead(i);
int lineStrength = 1000 - norm;  // 검은 라인을 강한 값으로 만들기

자동 캘리브레이션을 적용하면 로봇은 훨씬 안정적으로 움직입니다. 바닥이 조금 누렇거나, 검은 테이프가 완전히 검지 않거나, 센서마다 감도가 달라도 어느 정도 적응할 수 있습니다. 실제 경기용 라인트레이서에서도 캘리브레이션은 거의 필수에 가깝습니다.

단, 캘리브레이션을 할 때 센서가 반드시 흰 바닥과 검은 라인을 모두 보아야 합니다. 로봇을 가만히 흰 바닥 위에만 두고 캘리브레이션하면 최솟값과 최댓값의 차이가 작아져 제대로 구분할 수 없습니다. 출발 전에 로봇을 좌우로 살짝 움직여 센서 배열 전체가 라인과 바닥을 모두 경험하게 만드는 것이 좋습니다.

라인 센서 장착에서 자주 생기는 문제

라인 센서는 회로보다 기구적인 장착이 더 중요할 때가 많습니다. 센서가 너무 높으면 반사광이 약해지고, 너무 낮으면 바닥에 닿거나 작은 요철에도 값이 크게 흔들립니다. 보통은 센서 모듈의 권장 거리 근처에서 시작하되, 실제 바닥에서 값을 보며 조정해야 합니다.

센서 배열은 로봇의 회전축보다 앞쪽에 다는 것이 일반적입니다. 자동차로 치면 앞바퀴보다 더 앞에 눈이 달린 느낌입니다. 그래야 로봇이 라인을 미리 보고 방향을 고칠 수 있습니다. 센서가 너무 뒤에 있으면 로봇은 이미 라인을 벗어난 뒤에야 알아차립니다.

또 하나 중요한 것은 외부 빛을 줄이는 것입니다. 센서 주변에 검은색 가림막을 달면 주변 조명의 영향을 줄일 수 있습니다. 아주 간단한 방법으로도 효과가 있습니다. 검은 종이나 3D 프린터로 만든 작은 커버를 센서 주변에 달아 바닥에서 반사된 빛만 보도록 해주는 것입니다.

모터 노이즈도 잊으면 안 됩니다. 라인 센서는 작은 전압 차이를 읽기 때문에 모터가 돌 때 값이 출렁일 수 있습니다. 보드 전원과 모터 전원을 안정적으로 구성하고, GND를 공통으로 묶고, 센서 선을 모터 전원선과 너무 가까이 붙이지 않는 것이 좋습니다. 이전 장들에서 반복해서 나온 전원 안정성은 센서에서도 그대로 중요합니다.

색상 센서는 무엇을 보는가

라인 센서가 밝고 어두운 정도를 본다면, 색상 센서는 빛을 색 성분으로 나누어 봅니다. 우리가 어떤 물체를 빨간색으로 보는 이유는 그 물체가 빨간빛을 상대적으로 많이 반사하기 때문입니다. 색상 센서는 이 반사광을 빨강, 초록, 파랑 성분으로 나누어 측정합니다.

많이 쓰이는 색상 센서 모듈은 방식에 따라 출력 형태가 다릅니다. 어떤 센서는 I2C 통신으로 R, G, B 값을 직접 읽게 해줍니다. 어떤 센서는 색 필터를 바꾸어가며 측정한 결과를 주파수 형태로 출력합니다. 주파수 출력 방식에서는 특정 색 성분이 강할수록 출력 펄스의 빈도가 달라집니다. 제어 보드는 일정 시간 동안 펄스를 세거나 펄스 폭을 측정해서 색의 강도를 계산합니다.

색상 센서가 읽는 RGB 값은 우리가 모니터에서 쓰는 RGB 색상값과 비슷해 보이지만 완전히 같지는 않습니다. 센서의 LED 밝기, 물체와의 거리, 주변 조명, 표면 광택에 따라 값이 크게 달라집니다. 따라서 "빨간색은 R이 255, G가 0, B가 0"처럼 기대하면 안 됩니다. 실제 센서값은 빨간 물체에서도 R 620, G 180, B 140처럼 나올 수 있습니다. 중요한 것은 절대 숫자가 아니라 상대적인 크기와 비율입니다.

RGB 값으로 색상 구분하기

가장 쉬운 색상 분류 방법은 세 값 중 가장 큰 값을 보는 것입니다. R이 가장 크면 빨간 계열, G가 가장 크면 초록 계열, B가 가장 크면 파란 계열로 판단합니다.

int r = 620;
int g = 180;
int b = 140;

if (r > g && r > b) {
  Serial.println("RED");
} else if (g > r && g > b) {
  Serial.println("GREEN");
} else if (b > r && b > g) {
  Serial.println("BLUE");
} else {
  Serial.println("UNKNOWN");
}

이 방식은 빨강, 초록, 파랑처럼 차이가 큰 색을 구분할 때 잘 동작합니다. 하지만 노란색, 주황색, 흰색, 회색처럼 여러 성분이 섞인 색에서는 부족합니다. 노란색은 보통 R과 G가 함께 크게 나오고 B가 작게 나옵니다. 흰색은 R, G, B가 모두 크게 나오고, 검은색은 모두 작게 나옵니다.

조금 더 나은 방법은 간단한 규칙을 추가하는 것입니다.

String classifyColor(int r, int g, int b) {
  int total = r + g + b;

  if (total < 150) {
    return "BLACK";
  }

  if (r > 400 && g > 400 && b > 400) {
    return "WHITE";
  }

  if (r > g * 1.4 && r > b * 1.4) {
    return "RED";
  }

  if (g > r * 1.3 && g > b * 1.3) {
    return "GREEN";
  }

  if (b > r * 1.3 && b > g * 1.3) {
    return "BLUE";
  }

  if (r > 300 && g > 250 && b < 200) {
    return "YELLOW";
  }

  return "UNKNOWN";
}

이 코드는 완벽한 색상 인식기가 아닙니다. 하지만 로봇이 빨간 표식을 만나면 멈추고, 파란 표식을 만나면 좌회전하고, 노란 표식을 만나면 속도를 줄이는 정도의 실습에는 충분히 쓸 수 있습니다.

색상 센서를 더 안정적으로 사용하려면 RGB 값을 그대로 비교하기보다 비율로 바꾸는 것이 좋습니다. 전체 밝기가 달라져도 비율은 상대적으로 덜 흔들리기 때문입니다.

float sum = r + g + b;
float rr = r / sum;
float gg = g / sum;
float bb = b / sum;

예를 들어 센서와 물체 사이의 거리가 조금 멀어지면 R, G, B 값이 모두 줄어들 수 있습니다. 이때 원래 값으로 판단하면 색이 바뀐 것처럼 보일 수 있습니다. 하지만 세 값을 합으로 나눈 비율을 보면 "전체적으로 어두워졌지만 빨간 성분이 가장 강하다"는 사실을 유지하기 쉽습니다.

주파수 출력 색상 센서의 생각법

일부 색상 센서는 R, G, B 값을 숫자로 바로 주지 않고 주파수로 출력합니다. 처음 보면 조금 낯설지만 원리는 어렵지 않습니다. 센서에 빨간 필터를 선택하면 빨간 성분에 해당하는 빛의 양에 따라 출력 펄스 속도가 달라집니다. 초록 필터를 선택하면 초록 성분에 해당하는 주파수가 나오고, 파란 필터도 마찬가지입니다.

제어 보드는 각 색 필터를 차례로 선택한 뒤, 일정 시간 동안 펄스가 얼마나 빠르게 들어오는지 측정합니다. 그 결과를 R, G, B 강도로 사용하면 됩니다.

이 방식의 핵심 절차는 다음과 같습니다.

  • 빨간 필터를 선택하고 출력 주파수를 측정한다.

  • 초록 필터를 선택하고 출력 주파수를 측정한다.

  • 파란 필터를 선택하고 출력 주파수를 측정한다.

  • 세 값을 비교하거나 비율로 바꾸어 색을 분류한다.

센서에 따라 주파수가 높을수록 색 성분이 강하다고 해석하는 경우도 있고, 측정 함수가 반환하는 펄스 폭이 짧을수록 강하다고 해석해야 하는 경우도 있습니다. 그래서 색상 센서도 라인 센서와 마찬가지로 처음에는 반드시 샘플 값을 출력해야 합니다. 빨간 종이, 초록 종이, 파란 종이, 흰 종이, 검은 종이를 차례로 대고 숫자가 어떻게 변하는지 보는 것이 가장 빠른 학습 방법입니다.

색상 센서 캘리브레이션

색상 센서 역시 캘리브레이션이 중요합니다. 같은 빨간색 물체라도 낮에는 다르게 보이고 밤에는 다르게 보입니다. 센서에 달린 흰색 LED를 사용하면 주변 조명의 영향을 줄일 수 있지만, 완전히 없앨 수는 없습니다.

간단한 색상 캘리브레이션은 기준 샘플을 읽는 방식으로 할 수 있습니다. 예를 들어 로봇이 구분해야 하는 색이 빨강, 초록, 파랑 세 가지라면 출발 전에 각 색상 카드의 RGB 평균값을 저장합니다. 이후 새로 읽은 색상값과 기준값 사이의 거리를 계산해서 가장 가까운 색으로 분류합니다.

색상 사이의 거리는 어렵게 생각할 필요가 없습니다. R 차이, G 차이, B 차이를 각각 구한 뒤 제곱해서 더하면 됩니다. 값이 작을수록 더 비슷한 색입니다.

long colorDistance(int r1, int g1, int b1, int r2, int g2, int b2) {
  long dr = r1 - r2;
  long dg = g1 - g2;
  long db = b1 - b2;
  return dr * dr + dg * dg + db * db;
}

이 방식은 규칙을 직접 만드는 것보다 유연합니다. 예를 들어 빨간색이라도 센서에서는 R만 큰 것이 아니라 G와 B도 어느 정도 나올 수 있습니다. 기준 샘플을 저장해 두면 그런 특성이 자연스럽게 반영됩니다.

다만 색상 분류에서는 센서와 물체 사이의 거리를 일정하게 유지하는 것이 매우 중요합니다. 거리가 바뀌면 전체 밝기가 변하고, 반사각이 바뀌면 특정 색 성분이 튈 수 있습니다. 그래서 색상 센서를 로봇에 달 때는 바닥이나 물체와의 거리가 일정하도록 가이드 구조를 만드는 것이 좋습니다.

라인과 색상을 함께 쓰는 로봇

라인 센서와 색상 센서를 함께 쓰면 재미있는 로봇을 만들 수 있습니다. 라인트레이서가 검은 선을 따라가다가 빨간 표식을 만나면 정지하고, 파란 표식을 만나면 속도를 높이고, 초록 표식을 만나면 지정된 동작을 수행하게 할 수 있습니다. 공장 자동화에서 바닥의 마커를 읽거나, 교육용 경기장에서 미션 지점을 구분하는 방식도 이와 비슷한 생각에서 출발합니다.

구조는 단순합니다. 라인 센서 배열은 주행 방향을 계속 보정하고, 색상 센서는 특별한 지점을 감지합니다. 라인 센서는 빠르게 자주 읽어야 하고, 색상 센서는 상대적으로 천천히 읽어도 됩니다. 라인트레이서의 방향 제어는 수십 밀리초 단위로 계속 바뀌지만, 색상 마커는 로봇이 그 위를 지나갈 때 한두 번만 정확히 읽어도 충분한 경우가 많습니다.

실전에서는 두 센서의 위치도 다르게 잡습니다. 라인 센서는 로봇 앞쪽 중앙에 낮게 배치하고, 색상 센서는 마커를 안정적으로 볼 수 있는 위치에 둡니다. 만약 색상 마커가 라인 옆에 있다면 센서를 라인 중앙이 아니라 옆쪽으로 달아야 할 수도 있습니다. 센서가 보는 위치와 로봇이 실제로 움직이는 위치를 함께 생각해야 합니다.

실습: 센서값 기록표 만들기

라인 센서와 색상 센서를 제대로 다루려면 먼저 값을 기록해야 합니다. 값 기록은 단순하지만 강력한 디버깅 도구입니다. "잘 안 된다"는 말은 너무 막연합니다. 흰색에서 몇이 나오고, 검은색에서 몇이 나오는지 알면 문제의 절반은 이미 해결된 것입니다.

다음과 같은 표를 직접 만들어 보는 것을 추천합니다.

실험 대상센서 1센서 2센서 3센서 4센서 5메모
흰 바닥조명 켠 상태
검은 라인검은 절연테이프
회색 바닥반사가 약함
햇빛 아래 흰 바닥창가
로봇 주행 중값 흔들림 확인

색상 센서는 다음처럼 기록할 수 있습니다.

색상 샘플RGB분류 결과메모
빨강거리 1cm
초록거리 1cm
파랑거리 1cm
노랑주변 조명 켬
흰색기준 밝기
검은색반사 약함

이 표를 채우다 보면 어떤 값이 안정적이고 어떤 값이 위험한지 보입니다. 예를 들어 흰 바닥과 검은 라인의 차이가 아주 작다면 센서 높이를 조정해야 합니다. 특정 센서 하나만 값이 이상하다면 납땜, 커넥터, 장착 각도, 센서 불량을 의심할 수 있습니다. 색상 센서에서 빨강과 주황이 자꾸 헷갈린다면 분류 규칙을 더 세밀하게 만들거나 조명을 일정하게 해야 합니다.

작은 숫자가 로봇의 방향이 된다

라인 센서와 색상 센서는 화려한 센서가 아닙니다. 카메라처럼 이미지를 보여주지도 않고, IMU처럼 자세한 움직임을 계산하지도 않습니다. 하지만 로봇을 실제로 움직이게 만들 때는 매우 강력합니다. 숫자 몇 개만으로 바닥의 선을 따라가고, 색상 표식을 읽고, 미션 지점을 판단할 수 있기 때문입니다.

이번 장에서 기억해야 할 핵심은 세 가지입니다. 첫째, 적외선 반사 센서는 표면의 반사율 차이를 읽습니다. 흰색과 검은색은 빛을 반사하는 정도가 다르기 때문에 센서값도 달라집니다. 둘째, 센서 하나보다 센서 배열이 훨씬 많은 정보를 줍니다. 배열을 사용하면 라인이 왼쪽인지 오른쪽인지 판단할 수 있고, 가중 평균을 쓰면 위치를 부드러운 숫자로 계산할 수 있습니다. 셋째, 센서값은 환경에 따라 바뀝니다. 임계값을 고정해 두기보다 자동 캘리브레이션으로 현재 바닥과 조명에 맞게 보정해야 합니다.

색상 센서도 같은 철학으로 다루면 됩니다. RGB 값이나 주파수 출력은 그 자체로 정답이 아닙니다. 여러 샘플을 읽고, 기준을 만들고, 비율이나 거리 계산으로 분류해야 합니다.

이제 로봇은 바퀴가 얼마나 돌았는지 알고, 몸이 얼마나 기울었는지 알고, 바닥의 선과 색상까지 읽을 수 있게 되었습니다. 다음 장에서는 로봇의 눈을 더 크게 확장합니다. 단순한 숫자 센서를 넘어, 카메라 모듈로 실제 이미지를 받아들이는 방법을 살펴보겠습니다.

책 목록

센서와 모터로 시작하는 실전 로봇 만들기

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기