---
title: "라즈베리 파이로 새소리 감지하고 실시간 콜라주까지 만드는 Avian Visitors"
published: 2026-05-28T23:23:52.000Z
canonical: https://jeff.news/article/3494
---
# 라즈베리 파이로 새소리 감지하고 실시간 콜라주까지 만드는 Avian Visitors

Avian Visitors는 BirdNET-Pi를 포크해 라즈베리 파이와 USB 마이크로 주변 새소리를 감지하고, 감지된 종을 실시간 콜라주 UI로 보여주는 개인 프로젝트다. Gemini로 만든 새 일러스트, eBird 지역 필터, Cloudflare Tunnel, Home Assistant, MQTT 연동까지 들어가 있어 취미 프로젝트 치고 꽤 알차다.

## 새소리 감지기를 예쁘게 만든 홈랩 프로젝트

- Avian Visitors는 BirdNET-Pi를 포크해서 만든 “새 방문자 시각화” 프로젝트임
  - BirdNET-Pi가 라즈베리 파이의 USB 마이크로 소리를 잡고, Cornell의 BirdNET 음향 분류기로 어떤 새인지 식별함
  - Avian Visitors는 그 위에 일본 화조화(kachō-e) 느낌의 콜라주 오버레이를 얹음
  - 결과물은 단순한 로그 페이지가 아니라, 최근 감지된 새들이 화면 위에 그림처럼 배치되는 사이트임

- 작성자는 처음엔 트윗 하나로 끝낼 생각이었는데, 반응이 커서 설치 글까지 썼다고 함
  - 프로젝트 저장소는 `github.com/Twarner491/AvianVisitors`
  - 실행 예시는 `bird.onethreenine.net`에서 볼 수 있다고 소개함

## 설치는 라즈베리 파이 + USB 마이크 조합

- 기본 구성은 꽤 현실적인 홈랩 수준임
  - Raspberry Pi OS Lite 64비트를 SD 카드에 굽고, 사용자명·와이파이·호스트명 `birdnet`·SSH 비밀번호 인증을 설정함
  - USB 마이크를 Pi에 꽂고 창가나 외부에 설치함
  - 작성자는 Pi는 실내에 두고, 작은 창문 방충망 쪽에 마이크를 붙였다고 함

- 설치 스크립트는 BirdNET-Pi와 AvianVisitors 오버레이를 한 번에 세팅함
  - BirdNET-Pi 설치, 오디오 캡처, 모델, 웹 UI, Caddy 웹 루트 심볼릭 링크까지 처리함
  - 설치 시간은 Pi 모델과 와이파이 속도에 따라 20-40분 정도
  - 재부팅 후 콜라주는 `http://birdnet.local/`, 기존 BirdNET-Pi UI는 `http://birdnet.local/index.php`에서 접근 가능함

- 관리 UI도 그냥 덤 수준이 아님
  - 오른쪽 위 메뉴에서 설정, 시스템 상태, 로그, 도구 패널을 열 수 있음
  - Pi 안의 작은 JSON facade를 호출해서 analyzer 튜닝, 서비스 확인, 로그 tail을 콜라주 화면에서 바로 처리함

## 집 밖 공개와 자동화 연동도 준비됨

- 기본 설치는 LAN 내부 전용이지만, 외부 공개 옵션도 3가지가 있음
  - Cloudflare Tunnel은 포트포워딩 없이 공개 HTTPS URL을 만들 수 있음
  - 작성자가 실제로 쓰는 방식이고, 무료 Cloudflare 계정으로 약 5분 세팅이라고 설명함
  - 공개 URL에 비밀번호를 걸고 싶으면 Cloudflare Access를 쓰거나 Caddy의 HTTP Basic auth 스니펫을 쓸 수 있음

- Home Assistant 연동은 최근 감지된 새를 센서로 노출함
  - REST sensor로 `sensor.latest_bird`를 만들고, 최근 1시간 감지 결과를 60초마다 읽음
  - 희귀종이 들리면 조명을 깜빡이거나 알림을 보내는 자동화를 붙일 수 있음

- MQTT bridge도 있음
  - 1분마다 최근 감지 endpoint를 polling하고, 새 종별로 `birdnet/<slug>` 아래 JSON을 publish함
  - dedup은 메모리 기반이라 서비스가 재시작되면 최근 1시간 감지를 다시 발행함
  - 그래서 downstream consumer는 idempotent하게 처리하는 게 좋다고 명시함

> [!TIP]
> 홈 자동화에 붙일 거면 MQTT 재시작 재발행을 전제로 설계하는 게 좋다. “새 이벤트 1번 = 알림 1번”으로 단순 가정하면 재시작 때 알림이 중복으로 날아갈 수 있음.

## Gemini로 만든 새 그림 450개

- 콜라주에 쓰는 그림은 Gemini의 `gemini-2.5-flash-image` 모델로 생성함
  - 북미에서 흔한 새 종 450개 일러스트가 번들로 들어 있음
  - 각 종마다 앉은 자세(perched)와 나는 자세(in-flight) 2개 포즈를 제공함
  - 프롬프트 템플릿에는 학명, common name, pose가 변수로 들어감

- eBird API로 지역 필터링도 가능함
  - `--ebird-region`을 넘기면 BirdNET 전체 종 목록과 해당 지역에서 관찰된 종 목록을 교차시킴
  - eBird 지역 코드는 `US-CA` 같은 주 단위나 `US-CA-085` 같은 카운티 단위까지 지원함
  - 전 세계 약 3000종을 다 렌더링하지 않고, 실제 자기 동네에서 날아다닐 가능성이 있는 종만 줄일 수 있음

- 생성형 이미지답게 오류도 꽤 있었음
  - 작성자는 Gemini가 해부학을 적지 않게 환각한다고 솔직히 적음
  - 감사 과정에서 앉은 자세는 약 3%, 나는 자세는 약 5%의 해부학적 결함을 잡았다고 함
  - 특히 나는 자세는 “날개 펼침”에 대한 모델의 강한 prior 때문에, 깃털 덩어리를 추가 날개처럼 그리는 일이 많았고 같은 박새(chickadee)를 5-6번 재생성해야 깨끗한 결과가 나오기도 했다고 함

## 콜라주 레이아웃이 생각보다 진심임

- 각 새 그림에는 alpha mask가 같이 들어감
  - 그림을 약 93px 폭으로 다운샘플링하고, 알파 채널을 thresholding한 뒤 base64 bit-array로 패킹함
  - 전체 mask registry는 `avian/frontend/masks.json`에 있고, 249종 기준 약 280KB라고 함
  - 프론트엔드는 이 마스크를 타일 배치와 hover hit-test에 둘 다 씀

- 박스가 아니라 실루엣 기준으로 겹침을 판단하는 게 포인트임
  - 이미지의 bounding box끼리는 겹쳐도, 실제 새 실루엣이 겹치지 않으면 배치 가능함
  - 여러 새 이미지 박스가 겹친 곳에서 마우스를 올려도, 실제 실루엣 기준으로 맞는 새가 highlight됨

- packing 알고리즘은 center-out spiral 방식임
  - 타일을 면적 내림차순으로 정렬하고, 가장 큰 타일을 중심에 둠
  - 이후 타일은 중심에서 바깥으로 나선형 탐색을 하며 기존 마스크와 충돌하지 않는 위치를 찾음
  - 비용 함수는 가로로 더 넓은 클러스터를 만들도록 `b = 2.1`의 ellipse aspect bias를 둠

- 타일 크기 계산도 단순 clamp에서 정규화 방식으로 바꿨음
  - 처음엔 감지 횟수 `n_i`에 대해 `n_i^1.2`로 면적을 키우고 최대값으로 clamp하려 했음
  - 하지만 일정 횟수 이상 감지된 종이 전부 같은 최대 크기로 눌리면서 시각적 계층이 망가짐
  - 최종 방식은 각 종 점수를 `n_i^0.65`로 계산하고, 전체 viewport 면적 예산 `B` 안에서 비례 배분함
  - 덕분에 400번 감지된 종은 30번 감지된 종보다 약 5배 면적으로 보이지만, 화면 크기에 따라 전체 레이아웃은 안정적으로 맞음

> [!IMPORTANT]
> 이 프로젝트에서 제일 인상적인 숫자는 “400회 감지 종이 30회 감지 종보다 약 5배 면적”으로 보이게 만든 부분이다. 단순히 많이 나온 새를 크게 그리는 게 아니라, 화면 예산 안에서 시각적 위계를 유지한 설계임.

## 실시간 업데이트와 상세 모달

- 프론트엔드는 최근 감지 endpoint를 30초마다 polling함
  - 새 종이 현재 시간 창에 새로 들어오면 다음 refresh에서 레이아웃에 합류함
  - incremental insertion이 아니라 전체 repack을 수행함
  - 현재 grid stride 4px 기준으로 약 10종 repack이 Pi 4 클라이언트의 V8에서 20ms 미만이라고 함

- 시간 창은 `1H / 12H / 24H / 7D / ALL`로 바꿀 수 있음
  - 선택하면 `?hours=N`으로 refetch하고 화면을 다시 그림
  - 작성자는 페이지를 몇 시간 켜둬도 전환이 거슬리지 않을 정도라고 설명함

- 새 타일을 클릭하면 종 상세 모달이 열림
  - Wikipedia summary endpoint에서 설명을 가져옴
  - 앉은 자세와 나는 자세를 토글로 바꿔볼 수 있음
  - BirdNET-Pi가 아카이브한 mp3 녹음과 spectrogram도 같이 보여줌
  - 하단에는 Wikipedia와 eBird 외부 참조 chip이 붙음

---

## 기술 맥락

- 이 프로젝트의 선택은 “분류 모델을 새로 만들지 않고 BirdNET-Pi 위에 UX를 얹는다”는 쪽이에요. 새소리 분류 자체는 이미 잘 돌아가는 오픈소스가 있으니, 작성자는 그 시간을 시각화와 운영 경험에 쓴 거죠. 그래서 오디오 캡처, 모델 실행, 웹 UI라는 기반은 재사용하고, 콜라주·관리 패널·외부 연동이 차별점이 됐어요.

- 이미지 생성에도 현실적인 제약이 보여요. Gemini로 450개 종의 일러스트를 만들면 빠르게 에셋을 확보할 수 있지만, 새의 해부학 오류가 생기기 때문에 감사와 재생성이 필요했어요. 특히 나는 자세에서 오류율이 5%까지 나온 건, 생성형 이미지가 취미 프로젝트에서도 검수 비용을 만든다는 좋은 사례예요.

- 레이아웃 알고리즘은 단순 CSS grid로 풀기 어려운 문제라 직접 계산한 이유가 있어요. 새 그림은 투명 배경이 있는 불규칙한 실루엣이라 bounding box 기준 배치는 빈 공간을 많이 낭비하거든요. 그래서 alpha mask로 실제 형태를 계산하고, 충돌하지 않는 위치를 spiral search로 찾는 방식이 잘 맞아요.

- 크기 정규화도 중요한 결정이에요. 감지 횟수에 따라 면적을 키우되 최대값으로 자르면 인기 종들이 다 같은 크기가 돼서 데이터의 차이가 사라져요. viewport 면적 예산을 먼저 정하고 그 안에서 `n_i^0.65` 비율로 나누면, 작은 화면부터 2560px 디스플레이까지 같은 규칙으로 보기 좋은 결과를 만들 수 있어요.

- 외부 연동은 홈랩 프로젝트답게 운영 레이어를 잘 나눴어요. 공개는 Cloudflare Tunnel로 처리하고, 자동화는 Home Assistant REST sensor와 MQTT bridge로 내보내요. 이렇게 하면 콜라주 UI는 사람에게 보여주는 화면이고, 감지 이벤트는 다른 시스템이 소비할 수 있는 데이터가 돼요.

## 핵심 포인트

- BirdNET-Pi 기반으로 오디오 캡처와 조류 종 식별을 처리하고, 별도 콜라주 프론트엔드를 얹음
- 설치는 Raspberry Pi OS Lite 64비트, USB 마이크, SSH 설정 후 스크립트 실행으로 20-40분 정도 걸림
- Gemini 2.5 Flash Image로 북미 흔한 새 450개 일러스트를 만들고, 해부학 오류를 수작업 감사해 제거함
- 실루엣 마스크와 center-out spiral packing으로 겹치지 않는 실시간 조류 콜라주 레이아웃을 구현함
- Cloudflare Tunnel, Home Assistant REST sensor, MQTT bridge로 집 밖 공개와 자동화 연동까지 지원함

## 인사이트

이 글의 재미는 ‘새 감지기 만들었어요’가 아니라, 작은 홈랩 프로젝트 안에 ML 추론, 이미지 생성, 레이아웃 알고리즘, 홈 자동화, 터널링까지 다 들어간다는 점이다. 개인 프로젝트라도 UX와 시스템 설계를 제대로 고민하면 읽는 맛이 확 살아남.
