19주차에는 PID제어와 다투는 한 주였습니다.
지난 주에는 Laser 하드웨어 연결 구성,그리고 노이즈 해결 및 제어 기초 코드를 작성하였는데요 금주에는 영상에서의 레이저 포인트를 바탕으로 객체를 쫓아갈 수 있도록 하는 PID 제어를 구현했습니다 .
하지만 너무나도 쉽지 않은 task에 두손 두발을 다 들어버렸다는 ....
그럼 바로 고군분투 이야기를 찾아가보도록 하겠습니다.!!!
그러면 PID 제어가 무엇인지 한번 Gemini에게 물어봅시다 !
PID 제어는 제어 대상의 출력값을 목표값과 일치시키기 위해 사용하는 가장 대표적인 피드백 제어 알고리즘입니다.
P (Proportional): 비례 제어비례 제어는 현재 오차()의 크기에 비례하여 제어량을 조절합니다. 오차가 크면 크게 움직이고, 작으면 작게 움직이는 가장 직관적인 방식입니다.
I (Integral): 적분 제어D (Derivative): 미분 제어| 항목 | 명칭 | 중심 관점 | 주요 역할 | 부작용 |
|---|---|---|---|---|
| P | 비례 | 현재 | 빠른 응답성 확보 | 잔류 편차 발생 |
| I | 적분 | 과거 | 잔류 편차 제거 | 오버슈트 발생 가능 |
| D | 미분 | 미래 | 오버슈트 억제 (안정화) | 노이즈에 민감함 |
앞서 카메라 영상에서 레이저 포인터를 추출하였듯이 목표 객체와 픽셀간의 오차를 이용하여 제어를 시도하는 P제어 먼저 시도를 했습니다.
초기에는 영상 자체에서 오차를 구해 PID 항을 계산해 pwm 값만 esp8266으로 전송했지만 os 내부에서의 시간 엄격성과 계산량 분산을 위해 STM으로 PID 연산 loop를 넘겼습니다 .
위치형 PID
→ 매번 “현재 오차에 비례해서 중립 위치에서 얼마나 떨어져야 하는지”를 직접 계산
출력 = 중립값 + Kp×오차 + Ki×누적오차 + Kd×오차변화율
증분형 PID
→ 매번 “이번에 얼마나 더 움직여야 할지(변화량)”만 계산해서 이전 출력에 더함
이번 변화량 = Kp×오차 + Ki×오차 + Kd×(오차-이전오차)
출력 = 이전 출력 + 이번 변화량
프로젝트 초반에 증분형으로 구현했을 때 로그는 거의 이런 패턴이었습니다.
ex: 45.0 → out: 1430 → 1450 → 1470 → 1490 → … → 2200 (끝까지 감)
ex: -120.0 → out: 1250 → 1220 → 1190 → … → 800 (끝까지 감)
왔다 갔다 / 느린 추종 / 정확도 문제
기본 위치형 PID 출력
주요 원인
현상
오차가 5~20px 정도로 작아지면 PWM 변화가 거의 없어서 항상 그 오차를 남긴 채 멈춤
정상상태(steady-state)에서 오차가 일정
위치형 PID의 정상상태 출력:
주요 원인
요런 문제를 가지고 있다 ...

그래서 I와 D항을 추가하여 overshoot을 해결하면서 settle time을 줄이기 위해 P항을 증가시키며 실험적 결과를 얻어내려고 노력했다 .
어쩌면 핑계일 수 있지만 음 P,I,D 최적의 값을 일일이 찾아내는 것은 유의미할지 모르지만 시간이 조금 부족하다고 느껴졌다.
그래서 우선은 라즈베리로 모터를 옮겼다. 😭😭
사용하는 servo motor는 절대적인 PWM 값으로 위치를 제어하기에 Raspi에서 수신하는 오차와 pwm값으로 최적의 P,I,D 계수를 찾기 위함이었다 .
pysysid“실제 로그 데이터(입력 PWM + 출력 오차)를 먹이면 시스템의 전달함수를 자동으로 추정해주고, 그 모델로 최적 PID 게인을 뽑아주는 Python 라이브러리”
왜 이걸 쓰면 편한가?
1. 로그만 주면 끝
STM32에서 찍은 PIDLOG (t, ex, out_x 등)를 txt로 떨구면 → pysysid가 자동으로 1차/2차 모델 fitting
2. 전달함수(G(s))를 먼저 알아줌
실제 서보 + 카메라 + 지연까지 포함된 전체 플랜트의 K(게인)와 τ(시간상수)를 뽑아줌
→ “우리 시스템은 실제로 이런 응답을 하네”를 숫자로 알 수 있음
3. 그 모델로 PID 자동 설계
control.pidtune()이나 Ziegler-Nichols, pole placement 등 여러 방법으로 Kp/Ki/Kd 후보를 바로 계산
→ “이론상 최적” 값이 뚝딱 나옴 (미세 조정은 필요)
이론 상 최적의 값이 뚝딱 나올 줄만 알았다 .
레이저 위치 값 오차 지연
현재 레이저의 위치는 들어오는 영상에서 HSV 변환과 Masking, Morphology 연산을 거쳐 레이저 포인터를 탐지하고 3프레임 당 한번씩 ESP혹은 라즈베리파이 ethernet 통신이 이루어졌다. 통신 과정에서 발생하는 지연은 적을지라도 전체 영상 처리, 그리고 수신 및 연산 과정은 더 길어질 수 있다.
레이저 탐지
실시간 탐지 중에서도 레이저 포인터가 가끔 오탐지가 되는 경우가 생겼다
이 때 갑자기 확 늘어난 오차가 전달되며이는 최적의 값을 찾는데 큰 noise가 되었다..
이런 연유로 Raspi-python 라이브러리를 활용한 최적값 탐지는 나에게 절망감을 선사했다. 동일한 환경을 구성하기 위해 wifi 연결부터 코드 이식까지 환경 구성에 하루를 소모했기 때문에 ..
log를 분석하며 제대로 된 값을 얻어낼 수 있었을 수도 있지만 우선은 앞으로 나아가는 길을 선택했다. PID 최적의 값을 얻어냈다면 어땠을까 하는 아쉬움을 남기며 기존의 P,I,D 값을 바탕으로 LUT를 만들었다
서보모터는 절대적인 pwm 값으로 각도가 결정된다.
이를 활용하여 화면을 11X19개의 그리드로 나누고 각 그리드의 중앙값을 PID 제어를 통해 레이저로 Pointing 하는 자동화 코드를 완성했다 .
이 과정에서도 화면의 가장자리로 갈 수록 레이저 탐지 확률이 줄어들어 코드 수정을 몇번이나 반복했다 .
[RTSP/카메라] → rtsp_laser_demo (e_u, e_v stdout)
↓
ubuntu_tcp_server (stdin → "EX=float,EY=float\n" TCP 전송)
↓
라즈베리 파이 pid_pwm_agent.py (TCP 클라이언트, 50Hz PID → pwm0/pwm1)
↓
GPIO12(pwm0)=X, GPIO13(pwm1)=Y → 서보/레이저 구동
Host: rtsp_laser_demo --lut-auto
그리드 정의
LUT_GRID_ROWS = 11, LUT_GRID_COLS = 19AutoLutCalibrator
idx → (gr(), gc())TargetROI 생성:currentTarget(W, H) 에서 (gc+0.5) * W/cols, (gr+0.5) * H/rows|e_u|,|e_v| ≤ SETTLE_THRESH_PX (기본 6px)settle_accum_s 에 누적SETTLE_TIME_SEC (예: 3초) 이상이면 “수렴”으로 간주PWM 요청 및 LUT 저장 흐름
lut.updateConvergence(e_u, e_v, dt) 가 true 를 반환REQUEST_PWM,GR=X,GC=Y 를 stdout 으로 송신ubuntu_tcp_server:REQUEST_PWM,... 라인은 가공 없이 TCP로 그대로 전달pid_pwm_agent.py:recv_loop 에서 REQUEST_PWM_RE 로 매치shared.last_ux_us, shared.last_uy_us 를 읽어PAN=xxxx,TILT=yyyy 형태로 TCP로 응답/tmp/lut_pwm_response FIFO 에서 PAN/TILT 응답을 읽음lut_data.json 에{"grid_r":r,"grid_c":c,"target_u":...,"target_v":...,"pan_us":...,"tilt_us":...}idx+1) 로 넘어감g_manual_laser_pending=true, g_manual_laser_pt 설정laser.found = true, laser.point = g_manual_laser_ptskip_periodic_send=true)====> 가장자리에서 레이저 포인터가 탐지되지 않는 경우를 해결 ❗❗❗
Raspi: pid_pwm_agent.py --lut-mode

이렇게 각 그리드에서 값을 탐지하고 이후 보간법을 이용해 화면 내에서 동일한 픽셀의 경우 빠른 추종이 가능하도록 만들었다.
하지만 추후 발생할 수 있는 문제로는 카메라와 객체의 거리가 달라질 경우 레이저의 각도가 미묘하게 달라질 필요가 있다.
이는 자동 캘리브레이션 코드와 LUT+PID 코드 구현으로 문제점을 해결해 나갈 생각이다.
🍎좋은 점
- 위치형 PID로 전환 후 → 오차=0 시 정확히 멈춤
- 시간이 지나면 제자리로 수렴 → 기본적인 closed-loop 동작 확인
☠️아직 부족한 점
- settling time (오차가 줄어드는 시간)이 1~2초 이상
→ 사람 0.5m/s만 움직여도 따라가지 못함
- 레이저 탐지 불안정 → 오차가 순간 0이 되거나 튀면 PID가 혼란
- 지연 (RTSP + TCP + 3프레임 전송) → PID가 과거 오차로 계산
실시간으로 객체를 추적하기 위해 위치 예측을 추가했다. 아직은 간단한 2D 칼만 필터를 추가했지만 추가로 고도화 할 계획이다.
공채 시즌이 시작되면서 더욱이 바쁜 나날을 보내고 있습니다 ..
죽는 느낌.. ALL RIGHT .!!
살자 ..