
최종 시연 영상 제작: 긴 복도 현장 이슈 수정
이번 주 목표
6주차까지 알고리즘 고도화와 회귀 28/28 PASS를 마쳤습니다. 발표는 사전에 촬영한 시연 영상을 재생하는 방식으로 진행하기로 했고, 촬영 장소는 랩실 앞 긴 복도로 정했습니다.
시뮬레이션 28종이 통과했어도 실제 복도에서 라즈베리파이와 LiDAR를 바닥에 놓고 영상을 뽑아내는 건 전혀 다른 문제였습니다. 이슈가 발생 순서대로 터졌고, 하나씩 수정하면서 최종 영상을 확보했습니다.
1. 반복 촬영 시 배경 재학습 대기 — SIGUSR1 리셋
증상
촬영 한 번에 50프레임(5초) 배경 학습이 완료되어야 감지가 시작됩니다. 테이크가 실패할 때마다 시연자가 복도 밖으로 나간 뒤 다시 5초를 기다려야 했습니다. 촬영 리듬이 계속 끊겼고, 기다리는 동안 Unity 화면에 warming_up 표시가 계속 떠 있어 영상 편집 시 매번 잘라내야 했습니다.
수정
SIGUSR1 시그널로 배경 즉시 리셋 기능을 추가했습니다. 시그널을 받으면 ScanProcessor와 BackgroundFilter의 학습 상태를 초기화하고 배경 학습 카운터를 0으로 리셋합니다.
// main.cpp
static std::atomic<bool> g_reset_bg{false};
void HandleSigUsr1(int) { g_reset_bg.store(true); }
// signal(SIGUSR1, HandleSigUsr1); — 초기화 시 등록
// 메인 루프
if (g_reset_bg.exchange(false)) {
processor.ResetBackground();
bg_filter.Reset();
operational_frame_count = 0;
std::cerr << "[RESET] 배경 학습 재시작\n";
}
두 번째 터미널에서 다음 명령 하나로 즉시 리셋했습니다.
kill -USR1 $(pgrep rplidar_app)
테이크 실패 → 복도 정리 → 리셋 명령 → 5초 대기 → 재촬영 흐름이 완성됐고, 서비스 재시작 없이 배경만 골라서 초기화할 수 있어 촬영 속도가 크게 올라갔습니다.
2. 서보 zone 경계에서 oscillation — Hysteresis 추가
증상
시연자가 복도 중앙보다 약간 오른쪽으로 걸어올 때 서보가 zone 2(45°)와 zone 3(90°) 사이에서 계속 오락가락했습니다. 촬영 영상에서 서보가 좌우로 미세하게 진동하는 모습이 그대로 찍혔습니다.
원인
5주차에서 구현한 SuspectToZone()은 LiDAR 좌표를 각도로 변환하여 가장 가까운 zone으로 스냅합니다. 시연자가 복도를 걸을 때 LiDAR가 측정하는 각도에 프레임마다 ±2~3° 노이즈가 섞이고, 이 노이즈가 45° 경계를 계속 넘나들면서 zone이 매 프레임 바뀌었습니다.
프레임 N: angle = 47° → zone 2 → 서보 45°로 이동
프레임 N+1: angle = 43° → zone 3 → 서보 90°로 이동
프레임 N+2: angle = 46° → zone 2 → 서보 45°로 이동 ...
수정
이전 zone에서 벗어나려면 ±10° hysteresis band를 초과해야만 zone이 바뀌도록 했습니다.
// servo_controller.cpp
int ServoController::ComputeTargetZone(double x_mm, double y_mm) {
int raw_zone = SuspectToZone(x_mm, y_mm, cfg_.servo_mirror);
if (raw_zone == current_zone_) return current_zone_;
// zone 경계에서 ±10° 이내면 현재 zone 유지
double angle = std::atan2(x_mm, std::max(std::abs(y_mm), 1.0))
* 180.0 / kPi + 90.0;
double zone_center = raw_zone * 45.0;
if (std::abs(angle - zone_center) < kZoneHysteresisDeg) {
return current_zone_; // 경계 구간, 현재 유지
}
return raw_zone;
}
Hysteresis 적용 후 서보가 경계 근방에서도 안정적으로 한 zone에 머물렀고, 영상에서 진동 없이 부드럽게 추적하는 모습이 나왔습니다.
3. 서보 오버슛 — 이동 속도 제한 (Slew Rate)
증상
suspect 위치가 복도 한쪽 끝에서 반대쪽으로 크게 이동할 때(예: zone 1 → zone 5, 0° → 180°) 서보가 한 번에 전각도를 회전하면서 관성으로 목표를 지나쳤다가 되돌아오는 오버슛이 발생했습니다. 정착하는 도중 서보가 흔들리는 모습이 영상에 찍혔습니다.
수정
MoveToAngle() 안에서 이동 각도를 최대 15°스텝으로 제한하는 슬루 레이트를 적용했습니다.
// servo_controller.cpp
void ServoController::MoveToAngle(int target_deg) {
constexpr int kMaxStepDeg = 15;
constexpr int kStepMs = 20;
while (current_angle_ != target_deg) {
int diff = target_deg - current_angle_;
current_angle_ += std::clamp(diff, -kMaxStepDeg, kMaxStepDeg);
SendAngleToArduino(current_angle_);
std::this_thread::sleep_for(std::chrono::milliseconds(kStepMs));
}
std::this_thread::sleep_for(std::chrono::milliseconds(60)); // 정착 대기
}
180° 전각도 이동 시 이동 시간이 약 80ms → 320ms로 늘었지만 오버슛이 완전히 사라졌고, 영상에서 서보가 부드럽게 회전하는 모습이 나왔습니다. Arduino 펌웨어 수정 없이 임베디드 측에서만 각도를 분할하여 보내는 방식으로 해결했습니다.
4. 원거리 진입 Tentative 구간 — Unity에 사람이 늦게 나타남
증상
시연 시나리오는 시연자가 복도 끝(약 12m 지점)에서 LiDAR 쪽으로 걸어오는 것이었습니다. 그런데 영상을 보면 시연자가 복도에 들어서 3~4걸음을 걷고 나서야 Unity 화면에 사람 오브젝트가 나타났습니다. 실제 사람이 먼저 움직이고 Unity가 뒤늦게 반응하는 것처럼 보여 시연 타이밍이 어색했습니다.
원인
5주차에서 도입한 Tentative → Confirmed 승격 게이트가 원인이었습니다. 신규 트랙은 tentative_confirm_frames(기본 8프레임) 동안 안정 관측이 확인돼야 Confirmed로 승격되어 Unity로 전송됩니다. 복도 끝 12m 거리에서는 LiDAR 점 밀도가 충분해 클러스터 자체는 잘 생성됐지만, 8프레임(0.8초) 동안 Unity에 아무것도 안 나타났습니다.
수정
ECOWARDEN_TENTATIVE_CONFIRM_FRAMES 환경변수를 추가했습니다. 시연 환경처럼 LiDAR 배치가 안정적이고 노이즈가 낮은 경우 프레임 수를 줄여 즉각적인 반응을 얻을 수 있게 했습니다.
# 시연용: 8프레임 → 4프레임으로 단축
sudo env ECOWARDEN_TENTATIVE_CONFIRM_FRAMES=4 ./build/rplidar_app
// cluster_tracker.cpp
if (tr.state == TrackState::Tentative) {
if (++tr.tentative_count >= params_.tentative_confirm_frames) {
tr.state = TrackState::Confirmed;
}
continue; // Confirmed 전까지 Unity 전송 제외
}
4프레임(0.4초)으로 단축한 결과 시연자가 복도에 들어서자마자 Unity에 사람이 나타나게 됐습니다. 회귀 시나리오에서는 기본값(8)을 유지하여 회귀 통과에 영향을 주지 않았습니다.
5. humanPrefab 잔상 — departure 이벤트 재전송
증상
촬영한 영상을 검토하니 투기 확정 후 Unity 화면에서 이전 시연자의 humanPrefab이 사라지지 않고 투기된 쓰레기 옆에 그대로 남아 있는 컷이 여러 군데 있었습니다. LiDAR 측에서는 트랙이 정상 삭제됐지만 Unity가 departure 이벤트를 받지 못한 것이었습니다.
원인
departure는 events[]에 담겨 단 한 번만 전송되는 단발성 메시지입니다. UDP 패킷 손실이 이 프레임에 겹치면 Unity가 이벤트를 영영 받지 못했습니다.
수정
departure 이벤트를 이후 5프레임 동안 매 FRAME 패킷에 포함하여 반복 전송했습니다.
// json_packet_sender.cpp
struct PendingDeparture { int track_id; int remaining; };
std::vector<PendingDeparture> pending_departures_;
void JsonPacketSender::Send(const Payload& payload) {
for (const auto& evt : payload.events)
if (evt.type == "departure")
pending_departures_.push_back({evt.track_id, 5});
Payload enriched = payload;
for (auto& pd : pending_departures_) {
enriched.events.push_back({.type = "departure", .track_id = pd.track_id});
pd.remaining--;
}
std::erase_if(pending_departures_, [](const auto& pd){ return pd.remaining <= 0; });
udp_.Send(Serialize(enriched));
}
Unity는 이미 삭제된 ID의 departure를 중복 수신해도 무시합니다. 수정 후 humanPrefab 잔상이 영상에서 사라졌습니다.
6. LiDAR USB 연결 끊김 — 재연결 루프
증상
촬영 중반에 LiDAR가 갑자기 점구름을 내보내지 않았습니다. Unity 화면도 멈추고, 터미널 로그에 grabScanDataHq 타임아웃 에러가 찍혔습니다. USB 케이블을 뽑았다 꽂으니 다시 동작했지만, 기존 코드는 초기화 시 한 번만 연결을 시도해서 끊기면 바로 프로세스가 종료됐습니다. systemd가 5초 후 재시작했지만 그 사이 배경 학습이 처음부터 다시 시작돼 또 대기가 생겼습니다.
수정
메인 루프 안에서 LiDAR 연결 상태를 확인하고, 끊기면 재연결을 시도하는 루프를 추가했습니다.
// main.cpp — 메인 루프 상단
if (!lidar.IsConnected()) {
std::cerr << "[LIDAR] 연결 끊김 — 재연결 시도\n";
lidar.Stop();
std::this_thread::sleep_for(std::chrono::seconds(2));
if (!lidar.Start()) {
std::this_thread::sleep_for(std::chrono::seconds(3));
continue; // 성공할 때까지 반복
}
std::cerr << "[LIDAR] 재연결 성공\n";
// 배경 스냅샷이 유효하면 복원, 아니면 재학습
operational_frame_count = 0;
continue;
}
RplidarS2::IsConnected()는 내부 드라이버 상태를 확인합니다.
// rplidar_s2.cpp
bool RplidarS2::IsConnected() const {
if (!driver_) return false;
sl_lidar_response_device_health_t health;
return SL_IS_OK(driver_->getHealth(health)) &&
health.status != SL_LIDAR_STATUS_ERROR;
}
재연결 성공 후 배경 재학습 없이 바로 이어서 촬영할 수 있어 끊김 후 복구 시간이 크게 줄었습니다.
7. 라즈베리파이 5 발열 — 촬영 후반 루프 느려짐
증상
촬영을 30분 이상 반복하다 보니 후반 테이크에서 Unity 화면이 눈에 띄게 뚝뚝 끊겼습니다. 로그를 보니 루프 주기가 안정적인 100ms에서 150~200ms로 늘어나 있었습니다. vcgencmd measure_temp 명령으로 CPU 온도를 확인하니 82°C — 라즈베리파이 5의 온도 제한(85°C)에 근접하여 CPU 클럭이 자동으로 낮아진 상태였습니다.
원인
촬영 장소가 복도여서 환기가 되기는 했지만, LiDAR 드라이버·DBSCAN·JSON 직렬화·서보 Slew Rate 스텝 루프가 모두 메인 루프에 집중되어 CPU를 지속적으로 점유했습니다. 라즈베리파이 5는 기본 CPU governor가 ondemand라서 상시 고부하에서 클럭을 올렸다가 열 제한에 걸리면 다운클럭하는 패턴이 반복됐습니다.
수정
① CPU governor를 performance로 고정
# setup.sh에 추가
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
ondemand는 부하에 반응하여 클럭을 올리는 과정 자체에 레이턴시가 생깁니다. performance로 고정하면 클럭이 항상 최대치에서 시작되어 오히려 발열 피크가 낮아집니다.
② 루프 주기 경고 로그 추가
// main.cpp — 루프 끝
auto loop_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - loop_start).count();
if (loop_ms > 120) {
std::cerr << "[WARN] 루프 지연 " << loop_ms << "ms (목표 100ms)\n";
}
경고 로그 덕분에 "언제부터 느려지기 시작했는지"를 촬영 중에 바로 알 수 있었습니다. 두 조치 이후 1시간 촬영에서도 루프 주기가 100±5ms로 안정됐습니다.
8. 최종 시연 영상 결과
7가지 이슈를 수정한 뒤 랩실 복도에서 최종 영상을 촬영했습니다.
| 반복 촬영 배경 대기 | 재시작 없이 리셋 수단 없음 | SIGUSR1 배경 즉시 리셋 |
| 서보 zone 경계 oscillation | 각도 노이즈가 zone 경계를 넘나듦 | Hysteresis ±10° |
| 서보 오버슛 흔들림 | 전각도 즉시 이동 | Slew Rate 15°/스텝 |
| Unity 사람 늦게 나타남 | Tentative 확정 8프레임 | 환경변수로 4프레임으로 단축 |
| humanPrefab 잔상 | departure 단발 전송 | 5프레임 반복 전송 |
| LiDAR USB 끊김 | 재연결 로직 없음 | 재연결 루프 + 상태 확인 |
| 후반 루프 느려짐 | RPi5 발열 다운클럭 | CPU governor performance 고정 |
시연 시나리오(시연자 진입 → 봉투 투기 → 서보 조준 → Unity 투기 확정 표시)가 처음부터 끝까지 끊김 없이 담긴 영상으로 발표를 마쳤습니다.
9. 이번 주 회고
잘된 점
- SIGUSR1 배경 리셋은 처음에 빠른 임시방편으로 만들었지만, 결과적으로 "촬영 현장에서 재시작 없이 상태를 초기화하는" 운영 도구로 계속 유용하게 쓰였습니다. 외부 시그널로 내부 상태를 제어하는 패턴은 이후 다른 파라미터 핫리로드에도 같은 방식을 쓸 수 있을 것 같습니다.
- 환경변수(ECOWARDEN_TENTATIVE_CONFIRM_FRAMES)로 Tentative 프레임을 조정한 것처럼, "시연 전용 튜닝"과 "회귀 기본값"을 코드 변경 없이 분리할 수 있어서 안전했습니다. 회귀를 건드리지 않고 영상 품질을 올릴 수 있었습니다.
- Hysteresis와 Slew Rate는 각각 작은 수정이었지만, 영상에서 서보의 시각적 완성도를 크게 끌어올렸습니다. 알고리즘 정확도만큼 "보이는 것"의 질도 중요하다는 것을 다시 확인했습니다.
어려웠던 점
- 회귀 28종이 전부 통과한 상태에서도 실제 촬영에 들어가니 새 이슈가 7개나 나왔습니다. 시뮬레이션은 데이터를 정해진 순서로 주입하기 때문에 "여러 번 재촬영", "USB 케이블 스트레스", "장시간 발열" 같은 운영 환경 이슈는 테스트 범위 밖이었습니다. 실제 하드웨어와 현장 환경을 포함한 E2E 테스트의 필요성을 체감했습니다.
- Tentative 프레임을 줄이면 빠른 반응을 얻는 대신 노이즈 클러스터가 잠깐 Unity에 나타날 수 있는 트레이드오프가 있었습니다. 복도 환경이 깨끗했기 때문에 4프레임이 안전했지만, 노이즈가 많은 환경에서는 다시 늘려야 합니다. 파라미터 하나의 적정값이 환경에 따라 크게 달라진다는 점을 다시 확인했습니다.
'ABC 미래내일 프로젝트' 카테고리의 다른 글
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 6주차 (1) | 2026.05.09 |
|---|---|
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 5주차 (0) | 2026.05.02 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 4주차 (0) | 2026.04.26 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 3주차 (0) | 2026.04.19 |
| [2026 ABC 프로젝트 멘토링 1기] 프로젝트 2주차 (0) | 2026.04.11 |








