Vision & Inspection

[LensCal] 아직 끝나지 않은 회고 — 렌즈 캘리브레이션 작업 중간 정리

PixelMechanic 2026. 4. 13. 17:59

남은일들

시리즈 7/7.

시리즈 마지막 편입니다. 보통 마지막 편은 "이렇게 해서 성공적으로 마무리했습니다" 라고 쓰는 게 자연스러운데, 이번 시리즈는 그 모양으로 끝낼 수가 없네요. 솔직하게 말해서 작업은 여전히 진행 중이고, 제일 큰 결정 (검출기 선택) 은 아직 내려지지 않았습니다.

그래서 이번 편은 "완료 회고" 가 아니라 "중간 회고" 입니다. 지금까지 한 일을 정리하고, 아직 정해지지 않은 것들을 정해지지 않은 상태 그대로 적어두려 해요. 반년쯤 뒤에 이 글을 다시 읽었을 때 "아 그때 이런 고민을 했었지" 하고 스스로 피드백할 자료가 될 거라 생각합니다.

지금까지 한 일 — 완료된 것들

시리즈를 시작할 때만 해도 "OpenCV 단독 체커보드 검출기가 실장비에서 불안정하다" 는 고민에서 출발했었습니다. 지금은 그때보다 한참 나아간 상태예요. 구체적으로 보면 이렇습니다.

검출기 두 벌이 같은 인터페이스로 정리되었습니다. HWTester 에서 이식한 Cognex 기반 CCalibrationTarget 과, 새로 짠 OpenCV 기반 CCalibrationTargetCV완전히 똑같은 CALIBRATION_TARGET_RESULT 를 채워서 돌려줍니다. 엔진 쪽에서는 한 줄 수정으로 둘을 갈아끼울 수 있어요. 어느 쪽을 선택해도 그 위의 코드는 건드릴 필요가 없습니다.

그리드 인덱스 부여를 검출기 책임으로 통일했습니다. 자체 OpenCV 검출기 안에서 goodFeaturesToTrack → 4사분면 검증 → cornerSubPix → 피치 추정 → 축 결정 → BFS 로 인덱스까지 자동 부여합니다. 엔진은 검출기가 준 인덱스를 m_vGridMap[r][c] 2차원 배열로 재배치만 합니다. 빈 셀은 -1 로 허용하고요.

품질 분석 레이어 7종이 올라갔습니다. 재투영 오차 (Similarity Transform 기반), 격자 회전 각도, 직교성, 중심-주변 편차, 영역별 Michelson 콘트라스트, Sobel 기반 샤프니스, 방사 왜곡 k1·k2. 여기에 기본 지표 (해상도 H/V, 등방성, 9영역 균일도) 까지 합쳐서 총 12개 지표를 체커보드 한 장에서 뽑아냅니다.

대시보드를 4단 합성 구조로 만들었습니다. 히트맵 / 품질 지표 바 / 거리 분포 차트 / 요약 텍스트. 왼쪽 이미지 위에는 OverlayLevel 에 따라 단계별로 오버레이를 얹을 수 있게 했어요. MINIMAL 에서 DETAIL, HEATMAP 까지.

테스트가 135개 PASS 상태입니다. 중요한 건 이 테스트가 검출기 의존성 없이 돌아간다는 거예요. _UNIT_TEST 매크로 하위에 주입용 API (SetDetectedPointsForTest) 를 뚫어놔서, 이상 격자를 직접 만들어 엔진에 넣고 수학 검증을 할 수 있습니다. CI 가 매일 밤 이걸 돌립니다.

그 과정에서 배운 것 네 가지

작업 중에 머리에 박힌 것들을 정리해 둡니다.

1. "아직 결정 안 했다" 도 하나의 유효한 상태다

저는 원래 "양쪽을 다 유지하는 건 우유부단한 거다" 라고 생각했어요. 한쪽으로 결정하고 다른 쪽을 걷어내는 게 깔끔한 엔지니어링이라고요. 근데 이번에 해보니 꼭 그런 건 아니더군요.

핵심은 "결정을 미루는 것" 과 "결정을 내릴 수 있는 상태로 만들어두는 것" 은 다르다는 점입니다. 전자는 그냥 미루는 거고, 후자는 옵션 가치를 유지하는 전략입니다. 두 검출기를 드롭인 교체 가능한 형태로 만들어 두면, "이 데이터를 더 보고 결정할게" 라는 말이 코드 위에서 실제로 가능해져요. 결정을 미룬 건데 비용이 거의 안 듭니다. 이건 꽤 괜찮은 상태예요.

물론 이 상태가 오래 유지되면 안 됩니다. 반년 이상 끌면 결국 "둘 다 쓸 수 있다" 가 "둘 다 디버깅해야 한다" 로 바뀝니다. 그래서 "언제까지는 결정한다" 는 데드라인이 필요하긴 해요. 저한테 이번 시리즈를 쓴 이유 중 하나가 이 데드라인 감각을 붙잡아 두는 겁니다.

2. 바꾸지 않아도 되는 걸 바꾸지 않는 게 가장 어렵다

리팩토링을 할 때 "이 김에 이것도 바꾸자" 가 정말 유혹적입니다. 결과 구조체가 옛날 스타일인 거, 포인터 메모리 소유권이 애매한 거, 이런 게 눈에 밟혀요. 그런데 이번엔 건드리지 않았습니다.

이유는 단순해요. 변경 범위가 부풀면 회귀 리스크가 부풀고, 롤백이 어려워집니다. 검출기 교체라는 리스크 큰 실험을 해보려면, 그 외의 변경을 최대한 눌러두는 게 맞습니다. 결과 구조체가 안 예쁜 건 나중에 따로 손보면 돼요. 지금은 아닙니다.

"안 해도 되는 일을 안 하는 감각" 은 사실 "해야 할 일을 하는 감각" 보다 어려운 것 같아요. 전자는 성장의 표시 같지 않거든요. 근데 이걸 못 하면 리팩토링이 끝도 없이 번집니다.

3. 검출기와 분석 레이어의 분리가 구조적 자유를 준다

이건 이번 작업에서 가장 크게 느낀 점이에요. 검출기 선택을 미뤄도 분석 레이어는 계속 쌓을 수 있었습니다. 왜냐면 분석 레이어는 검출기를 몰라요. 좌표와 그리드 인덱스만 있으면 돌아가거든요.

이 분리가 주는 자유는 예상보다 컸습니다. "검출기가 정해진 다음에 분석을 쌓는다" 는 순서가 아니라, 두 작업을 병렬로 할 수 있었어요. 검출기는 검출기대로 비교 검토하고, 분석은 분석대로 구현하고. 서로 기다리지 않습니다. 이게 가능했던 건 둘 사이의 인터페이스 (CALIBRATION_TARGET_RESULT) 가 이미 정립되어 있었기 때문이에요.

소프트웨어 설계에서 "경계를 잘 긋는 것" 이 왜 중요한가에 대한 설명이 많은데, 저는 이번에 "경계가 잘 그어져 있으면 양쪽을 병렬로 작업할 수 있다" 가 제일 피부에 와닿았습니다. 순차로만 가능하던 게 병렬이 되니까 체감 속도가 완전히 달라져요.

4. 숫자는 그림이 되어야 현장에서 쓰인다

12개 지표를 아무리 잘 뽑아도, 현장 운용자가 PropertyGrid 의 숫자 리스트를 뚫어져라 보고 있지 않는 한 아무 일도 안 일어납니다. 대시보드를 만들고 나서야 이 지표들이 "존재하기 시작했다" 는 느낌이 들었어요.

뒤집어 말하면, 아무리 좋은 지표도 잘못된 UI 에 얹으면 존재하지 않는 것과 같다는 뜻입니다. 지표 개발과 시각화 설계는 같은 비중으로 투자해야 하는데, 저는 지금까지 이 균형을 많이 놓쳤던 것 같아요. 이번에 시각화 쪽에 의식적으로 시간을 많이 썼고, 결과적으로 도구가 처음으로 "나 이외의 사람에게 유용한 상태" 에 가까워졌습니다.

아직 결정 못 한 것들 — 앞으로 해볼 것

가장 큰 질문: 검출기 최종 선택

1편에서 다룬 그대로입니다. Cognex 정식 vs 자체 OpenCV. 현재까지 수집한 데이터로는 정확도/속도 차이는 허용 범위 안이고, OpenCV 쪽이 속도는 소폭 유리합니다. 그런데도 결정을 못 하는 이유는 장기 신뢰성자체 코드의 유지보수 부담 이라는 두 개의 안 보이는 비용 때문이에요.

앞으로 해볼 것:

  • 더 다양한 조명/반사 조건의 이미지 스트레스 테스트. 며칠 돌려보고 괜찮았다고 해서 3개월 뒤에도 괜찮다는 보장은 없으니까요.
  • 검출 실패 발생 시 원인 로깅 강화. 자체 OpenCV 검출기에서 실패가 나면 어느 단계에서 났는지 (코너 후보 부족? 4사분면 검증 실패? 축 클러스터링 실패?) 로그로 남도록 고쳐야 합니다. 그래야 "원인 불명 실패" 가 사라져요.
  • 회귀 테스트용 이미지 아카이브 구축. 두 검출기를 동일 세트에 계속 돌려서 결과 드리프트를 모니터링하기.
  • 그리고 이 모든 것에 데드라인 설정. 언제까지는 결정한다, 라는 스스로와의 약속.

보조적인 미구현 지표들

검출기와 독립인 쪽은 이 몇 가지가 남아 있습니다.

  • FOV / 배율 — 스케일 × 센서 크기. 가장 싸게 붙일 수 있는 지표라 다음에 바로 넣을 예정
  • TV Distortion (%) — SMIA/EIA 기준 왜곡률. 보고서 쪽에서 요구하는 표준
  • 격자 직선성 — 같은 행/열 코너의 직선 피팅 잔차. 직교성과 교차 검증용
  • 커버리지 — 코너가 이미지를 얼마나 덮는지, Convex Hull 면적 비율

실장비 데이터로 임계값 튜닝

시리즈 중간중간에 "RMS 0.3 px 이하면 우수" 같은 숫자가 나왔는데, 이것들은 전부 교과서 값에 가깝습니다. 실제 장비에서 수집한 데이터로 재튜닝이 필요해요. 값 자체가 정확해도 기준이 틀어져 있으면 판정이 빗나가거든요.

컬러 카메라가 오면

이건 먼 얘기지만, 컬러 카메라로 가면 색수차와 화이트밸런스 균일도가 추가 가능합니다. 현재는 8bit grayscale 고정이라 불가능한 영역이에요.

시리즈를 마치며

체커보드 한 장으로 뽑아낼 수 있는 정보가 생각보다 훨씬 많다는 게 이번 작업의 가장 큰 교훈이었습니다. 저는 오랫동안 이 중 일부만 뽑고 나머지는 버리고 있었어요. "검출기가 좋은가" 에만 신경 쓰고 "좋은 검출기 위에서 뭘 더 뽑을 수 있는가" 는 안 보고 있었습니다.

이번 시리즈의 결론을 한 줄로 요약하면 이럴 것 같아요.

검출기 선택은 신중하게, 그 위의 분석 레이어는 과감하게, 시각화는 운용자 시선에서.

  • 검출기는 신중하게 — 이게 흔들리면 위가 다 흔들립니다. 급하게 결정하지 말고, 드롭인 교체 가능한 상태로 비교 데이터를 쌓아가며 결정하는 게 맞아요.
  • 분석은 과감하게 — 검출이 안정되면, 그 위에 쌓을 수 있는 지표는 생각보다 많습니다. 여기서 아끼지 마세요. 자료구조 하나 (m_vGridMap) 만 잘 설계해두면 분석 함수를 O(1) 인덱스 접근으로 줄줄이 쌓을 수 있습니다.
  • 시각화는 운용자 시선에서 — 아무리 정교한 지표도 PropertyGrid 숫자로만 있으면 안 쓰입니다. 히트맵, 바 차트, 임계선 — 눈이 1초 안에 판정할 수 있는 형태로 바꿔야 그제서야 지표가 "존재" 하기 시작해요.

그리고 "아직 결정 안 했다" 는 말을 부끄럽지 않게 적을 수 있는 것도 엔지니어링의 한 부분이라고 생각하게 됐습니다. 완결된 이야기만 기록하면 고민의 과정이 전부 사라지거든요. 결정의 결과보다 결정의 이유가 나중엔 더 값지더라구요.

검출기 선택이 결정되면 후속 포스트로 돌아오겠습니다. 그때는 "이렇게 갔고 그 이유는 이거였습니다" 라는 단정적인 이야기를 쓸 수 있을 거예요. 그 전까지는 이 중간보고로 대신합니다.

긴 시리즈 읽어 주셔서 감사합니다.


← 시리즈 목차: 00_Index.md

반응형