1572 단어
8 분
지문결제 ep.3 - 이진화가 간단한게 아니었다고? 내가 할 수 있을리가 없잖아! 무리무리!

이전 글에서 보드를 살렸으니 이제 펌웨어다.

데이터시트에 설명이 없다#

HF302GD 데이터시트를 폈는데 레지스터 테이블에 5글자짜리 줄임말레지스터 이름만 있고 설명이 없다. 각 레지스터가 뭘 하는지, 라이프사이클이 어떻게 되는지가 안 나와있다.

처음엔 이름만 보고 유추하면서 짰는데 당연히 잘 안 됐다. 진짜 제발 좀 알아들을 수 있게좀 제발

그래서 제조사가 제공한 AS608 칩이 올라간 테스트보드 소스를 보고 프로토콜을 유추/복구해서 펌웨어를 설계했다. 남의 레퍼런스 구현을 읽어서 스펙을 역으로 복원하는 작업이었는데, 이런 건 처음이라 꽤 오래 걸렸다.

통신 인터페이스#

BLE 쪽은 NUS 위에 소규모 커스텀 바이너리 프로토콜을 얹었다. 대략 이런 모양이다.

     Host (iPad)                      Sensor (nRF54L15)
         │                                   │
         │◀──────── 상태 / 이벤트 ───────────│   손가락 감지 등
         │                                   │
         │──────── 캡쳐 요청 ───────────────▶│
         │                                   │
         │◀──────── 이미지 헤더 ─────────────│   차원 / bpp
         │◀──────── 이미지 데이터 ───────────│   (분할 전송)
         │◀──────── 종료 플래그 ─────────────│
         │                                   │
         │◀──────── 에러 ───────────────────│   (실패 시)

설계하면서 챙긴 것 몇 가지만 적어두면,

우선 보안연결이 성립되지 않은 상태에서 들어온 캡쳐 요청은 그냥 거부한다.

에러도 몇 종류로 나눠놨다. 이미 캡쳐 중인데 또 요청이 온 경우, 정의되지 않은 명령이 온 경우, 호스트가 캡쳐를 끊은 경우, 데이터라인이 불안정해서 이미지가 깨진 경우 같은 것들이다. 매점에 나가있는 기기라 현장에서 원인을 좁힐 수 있어야 했다. 이 에러 코드는 OLED에도 같이 띄운다.

등록용으로는 별도의 캡쳐 모드를 하나 더 뒀다. 센서에서 3장을 연속으로 읽어와서 픽셀값을 평균낸 이미지를 만들어 반환한다. 이 센서가 방전용량 방식이라 노이즈에 민감한데, 평균만 내줘도 노이즈가 꽤 효과적으로 감쇄됐다.

8bpp를 1bpp로#

센서는 8bpp 이미지를 준다. 그런데 실제로 BLE로 보내는 건 260×320 1bpp다.

이유는 하나다. 전송속도. 8bpp를 그대로 보내면 데이터가 8배고, BLE 대역폭에서 그건 다시 V1 꼴이 난다.

문제는 8bpp에서 1bpp로 가는 게 곧 이진화라는 거고, 지문 이진화는 생각보다 훨씬 까다로웠다. 이 파이프라인은 한번에 나온 게 아니라 세 번에 걸쳐 개선된 결과다.

1세대 - 고정 Threshold#

가장 단순하게, 어떤 값보다 어두우면 1 밝으면 0.

망했다. 지문 융선의 생김새는 사람마다 다르다. 융선이 얕고 평평한 지문을 가진 사람은 패킹 과정에서 융선이 통째로 소실됐다. 이미지에 아무것도 안 남는다.

2세대 - 전역 동적 Threshold#

그럼 사람마다 기준을 다르게 잡으면 되지 않나. 그래서 융선이 전체 이미지의 40%를 차지하게 Threshold를 유동적으로 조절하는 알고리즘을 적용했다.

전체적으로 어두운 지문이든 밝은 지문이든 결과물의 융선 비율은 비슷하게 나온다. 1세대보단 훨씬 나았다.

근데 또 막혔다. 손가락을 완전히 접촉시키지 않아서 이미지 프레임 안에 지문이 꽉 차지 않는 사용자가 있었고, 애초에 지문을 찍으면 주로 가운데 부분은 강하게 접촉되고 테두리 부분은 약하게 접촉된다.

즉 한 장의 이미지 안에서 영역별로 적정 Threshold가 다르다. 이건 하나의 Global Threshold를 아무리 잘 잡아도 못 푼다. 가운데에 맞추면 테두리가 날아가고 테두리에 맞추면 가운데가 뭉갠다.

3세대 - Local window 표준편차#

그래서 전역 기준을 버렸다.

각 픽셀에 대해 local window를 만들어서 그 window 안에서 표준편차를 구하고, 표준편차의 3배 이상 차이가 나는 픽셀을 ridge로 판별하는 알고리즘을 작성했다.

이러면 “절대적으로 어두운가”가 아니라 “주변에 비해 튀는가”를 보게 된다. 접촉이 약해서 전체적으로 흐릿한 테두리 영역에서도 그 안에서의 상대적 대비만 살아있으면 융선이 검출된다. 접촉 강도 편차가 자동으로 흡수되는 거다.

근데 이게 또 부작용이 있었다. 지문이 아예 닿지 않은 흰 영역에서는 신호가 없으니 표준편차 자체가 매우 작고, 그러면 아주 작은 노이즈도 표준편차의 3배를 가볍게 넘어버린다. 흰 영역이 노이즈로 도배됐다.

해결은 단순했다. 전역 평균에 bias를 더한 값보다 밝은 픽셀은 그냥 죽였다. 상대적 기준으로 융선을 찾되, 절대적으로 너무 밝은 픽셀은 애초에 후보에서 빼는 노이즈 컷이다.

최종 파이프라인#

센서 8bpp 캡쳐 (256×320)


픽셀별 local window 표준편차 계산


표준편차 3배 초과 픽셀을 ridge로 판별


전역평균 + bias 보다 밝은 픽셀 제거


1bpp 패킹 후 BLE 전송

상대 기준으로 융선을 찾고 절대 기준으로 노이즈를 거른다. 결국 두 개를 같이 써야 했다.

참고로 이 이미지 처리가 최종 성능에서 630ms를 먹는다. 전체 1.38초 중에 제일 큰 조각이다. 그래도 BLE 전송이 150ms로 줄어든 걸 생각하면 남는 장사다. V1은 BLE에만 1초를 썼다.

다음 글에서는 인프라랑 DFU, 그리고 결과를 다룬다.

지문결제 ep.3 - 이진화가 간단한게 아니었다고? 내가 할 수 있을리가 없잖아! 무리무리!
https://yeonfish.dev/posts/fingerprint_payment/3_firmware/
저자
yeonfish6040
게시일
2026-09-02
라이선스
CC BY-NC-SA 4.0