원은 빙글빙글 돌기만 하는데 어떻게 모서리를 그릴까요?
회전하는 막대 끝에 펜을 달면 원이 그려집니다 그 끝에 또 다른 회전 막대를 연결하면 펜의 움직임은 조금 더 재미있어집니다 막대의 길이와 속도 시작 방향을 잘 조합하면 로켓 같은 모양도 그릴 수 있습니다
원은 그림을 배우지 않았습니다 좋은 지시를 받았을 뿐입니다
그림을 움직임으로 바꿔 보기
로켓은 잠시 잊고 작은 불빛 하나가 윤곽을 따라 한 바퀴 돈다고 생각해 보세요. 매 순간 불빛에는 가로 위치와 세로 위치가 있습니다. 이 두 값의 변화를 기록하면 전체 여정을 기록한 셈입니다.
푸리에 분석은 다른 질문을 던집니다 이 움직임을 더 단순한 움직임들의 합으로 표현할 수 있을까요. 여기서 단순한 움직임은 회전입니다. 회전하는 화살표들이 각각 가로와 세로 방향의 움직임을 조금씩 보태면 마지막 끝점이 그림의 한 점이 됩니다.
화음을 여러 음으로 나누는 것과 비슷합니다 다만 여기서는 소리가 아니라 경로를 나눕니다 그림이 다른 무언가로 변한 것은 아닙니다 같은 그림을 설명하는 방법이 달라진 것입니다.

각 원에 필요한 세 가지 지시
반지름은 움직임의 크기를 정합니다 주파수는 펜이 경로를 한 바퀴 도는 동안 몇 번 회전할지 정합니다 시작 각도는 어느 방향에서 출발할지 정합니다 시계 방향으로 도는 화살표도 있고 반시계 방향으로 도는 화살표도 있습니다.
화살표는 끝과 시작이 이어집니다 그래서 다음 원의 중심은 앞 화살표의 끝을 따라 움직입니다. 금색 점은 이 연결 전체의 마지막 끝점입니다. 숨겨 둔 로켓 선을 혼자 따라가는 점이 아닙니다.
연결 전체를 그림의 적절한 위치에 놓는 고정값도 하나 있습니다. 윤곽에서 뽑은 점들의 평균 위치이며 회전하지 않습니다. 이 글에서 말하는 32개의 원에는 이 고정 중심이 포함되지 않습니다.

여덟 개면 알아보고 서른두 개면 자세히 보입니다
이번 예제는 사진이 아니라 직접 설계한 다각형 로켓 윤곽으로 시작합니다 윤곽의 길이를 따라 같은 간격으로 1024개의 점을 뽑고 고속 푸리에 변환 FFT로 회전 성분을 계산합니다.
느리게 도는 원만 남기는 대신 기여하는 크기가 큰 성분부터 선택했습니다. 원이 여덟 개일 때도 로켓은 알아볼 수 있지만 날개는 둥글게 보입니다. 32개가 되면 주요 특징이 돌아오고 128개에서는 뾰족한 부분도 원본에 더 가까워집니다.
흥미로운 점은 원이 많을수록 자세해진다는 사실만이 아닙니다 작은 세부를 다 복원하기 전에도 전체 형태가 상당히 남아 있다는 점입니다. 큰 회전이 형태를 잡고 작은 회전들이 방향을 급하게 바꾸는 부분을 함께 다듬습니다.

어려운 부분은 모서리입니다
아래에서 밝은 노란색과 분홍색 부분은 32개의 원으로 복원했을 때 위치 차이가 큰 곳입니다. 로켓의 끝과 날개 꼬리를 보세요. 직선은 비교적 따라가기 쉽지만 방향이 갑자기 바뀌는 부분은 제한된 수의 매끄러운 회전만으로 표현하기 어렵습니다.
평균만 보면 이 차이를 놓칠 수 있습니다. 32개일 때 평균 거리는 원본 외접 사각형 대각선의 0.232퍼센트이고 가장 큰 거리는 1.314퍼센트입니다. 평균적으로 얼마나 다른지와 가장 크게 어긋난 곳이 얼마나 다른지는 서로 다른 질문입니다.
선택한 세 단계에서는 오차가 줄었지만 원을 하나 추가할 때마다 모든 점과 모든 오차 지표가 좋아진다는 뜻은 아닙니다 크기가 가장 큰 푸리에 성분을 남기면 같은 성분 수에서 표본 위치의 제곱 오차를 최소화합니다 평균 거리와 시각적 품질은 그와 다른 지표입니다.

| 원 개수 | 평균 오차 | 최대 오차 |
|---|---|---|
| 8 | 1.550% | 3.976% |
| 32 | 0.232% | 1.314% |
| 128 | 0.025% | 0.275% |
거리는 원본 윤곽 외접 사각형 대각선의 백분율이며 1024개 표본 위치에서 계산했습니다 고정 중심은 별도입니다
그림 아래에는 작은 아이디어가 있습니다
가로 좌표는 복소수의 실수부로 세로 좌표는 허수부로 표현합니다 갑자기 어려워 보이지만 두 좌표를 한 쌍으로 다루기 위한 편리한 방법입니다. 복소 지수는 회전을 간결하게 표현해 줍니다.
복원할 때는 각 순간에 선택한 회전 벡터들을 더하고 고정 중심을 다시 더합니다 아래는 계산의 핵심 부분이며 전체 그림 생성 프로그램은 아닙니다.
coefficients = np.fft.fft(points) / len(points)
frequencies = np.fft.fftfreq(len(points), d=1 / len(points))
selected = np.argsort(-np.abs(coefficients[1:]))[:32] + 1
rotations = coefficients[selected, None] * np.exp(
2j * np.pi * frequencies[selected, None] * time[None, :]
)
path = coefficients[0] + rotations.sum(axis=0)아름답지만 마법은 아닙니다
이것을 원 32개로 사진을 압축했다고 부르고 싶어질 수도 있습니다. 하지만 중요한 전제가 빠집니다 이 예제는 하나의 닫힌 윤곽을 다루고 수치 계수를 저장하며 실제 이미지 파일 크기를 비교하지 않습니다. JPEG나 SVG 또는 AI 이미지 모델을 대체하는 기술이 아닙니다.
유한한 표본으로 무한히 정밀한 다각형을 되살린 것도 아닙니다 모든 성분을 사용하면 1024개 표본 위치를 수치 정밀도 안에서 복원할 수 있습니다. 그 사이의 경로는 매끄러운 주기적 보간입니다.
제가 좋아하는 부분은 더 단순합니다 그림처럼 보이는 것을 정교하게 조합된 움직임으로 이해할 수 있다는 점입니다. 로켓은 어느 한 원 안에 숨어 있지 않았습니다. 원들이 함께 움직이는 방식 안에 있었습니다.
더 읽어 보기
NumPy · Discrete Fourier Transform ↗NumPy · numpy.fft.fft ↗NumPy 문서에서 변환 규약과 FFT를 확인할 수 있습니다 위 그림은 본문에서 설명한 계산으로 만들었습니다
