동작 원리

ChordSticker는 악보를 어떻게 읽는가: 오선 탐지부터 다이어그램 합성까지

악보 사진 한 장이 코드 운지법이 붙은 이미지로 바뀌기까지 내부에서 벌어지는 일을 단계별로 공개합니다. 왜 어떤 악보는 잘 되고 어떤 악보는 실패하는지 이해할 수 있습니다.

작성 2026-07-05최종 수정 2026-08-03약 13분 분량동작 원리악보 인식YOLO영상 처리

ChordSticker에 악보 사진을 넣으면 몇십 초 뒤 코드 운지법이 붙은 이미지가 나옵니다. 그 사이에 무슨 일이 벌어지는지를 감추지 않고 공개합니다. 내부 동작을 알면 어떤 악보가 잘 되고 어떤 악보가 안 되는지를 스스로 예측할 수 있고, 실패했을 때 무엇을 바꿔야 하는지도 알 수 있기 때문입니다.

전체 흐름 한눈에 보기

눈여겨볼 점은 학습 모델이 쓰이는 구간이 4단계 하나뿐이라는 것입니다. 나머지는 전부 결정론적인 영상 처리와 규칙입니다. 이유는 뒤에서 설명합니다.

1단계: 오선 다섯 줄 찾기

모든 처리의 출발점은 보표를 찾는 것입니다. 보표 위치를 알아야 "코드가 적혀 있을 만한 영역"을 좁힐 수 있기 때문입니다.

흰색이 아닌 픽셀만 남긴다

먼저 이미지에서 거의 흰색인 픽셀(RGB 각 채널이 230 이상)을 배경으로 간주해 지웁니다. 남은 것이 잉크입니다. 이 방식은 단순하지만 인쇄 악보에는 잘 통합니다. 반대로 배경이 회색인 사진이나 어두운 조명에서 찍은 사진은 여기서부터 문제가 생깁니다.

가로 방향으로 투영한다

각 행마다 잉크 픽셀의 개수를 세어 세로 방향 히스토그램을 만듭니다. 오선은 이미지 폭 전체를 가로지르는 긴 직선이므로, 오선이 있는 행은 값이 크게 튑니다. 전체 폭의 일정 비율(기본 75%) 이상을 채우는 행만 "선 후보"로 남깁니다.

다섯 줄이 균등한 묶음만 인정한다

연속된 행은 하나의 선으로 묶고, 각 선의 중심 y좌표를 구합니다. 그 다음이 핵심입니다. 선 다섯 개를 연속으로 뽑았을 때, 네 개의 간격이 모두 평균 간격의 ±3픽셀 안에 들어오는 경우에만 보표로 인정합니다.

이 균등성 검사를 넣은 이유는 실전에서 오탐이 너무 많았기 때문입니다. 악보에는 오선 말고도 긴 가로선이 많습니다.

이런 선들은 간격이 불규칙합니다. 오선만이 인쇄 규격상 정확히 등간격입니다. 그래서 간격 균등성 하나로 대부분의 오탐이 걸러집니다.

여기서 실패하는 대표 사례

사진이 기울어져 있으면 오선이 가로 한 행에 깔끔하게 놓이지 않고 여러 행에 걸쳐 번집니다. 그러면 "전체 폭의 75% 이상" 조건을 만족하는 행이 사라지고, 보표를 하나도 못 찾게 됩니다. ChordSticker가 기울어진 사진에 특히 약한 근본 이유가 이것입니다.

2단계: 코드 표기 띠 잘라내기

보표를 찾았으면 그 위쪽을 봅니다. 일반적인 악보 관례상 코드 이름은 보표 상단에 인쇄되기 때문입니다.

보표 위 영역에서 다시 가로 투영을 하되, 이번에는 기준이 다릅니다. 오선처럼 폭 전체를 채우는 선이 아니라, 글자처럼 드문드문 잉크가 있는 행을 찾습니다. 잉크 픽셀이 일정 개수 이상인 행이 이어지면 그것을 "코드 띠"의 시작으로 보고, 빈 행이 충분히 이어지면 띠가 끝난 것으로 봅니다.

여기에 높이 상한을 겁니다. 띠의 세로 길이가 너무 길면 잘라냅니다. 상한이 없으면 위쪽 보표의 음표나 가사 줄이 통째로 딸려 들어와서, 이후 단계가 전부 망가집니다.

3단계: 글자 덩어리 분리하기

잘라낸 띠에서 개별 코드를 분리합니다. 두 가지 작업이 순서대로 일어납니다.

노이즈 제거

띠를 흑백으로 이진화한 뒤(밝기 210보다 어두우면 잉크로 간주) 연결 요소 분석을 돌립니다. 서로 붙어 있는 픽셀 덩어리를 하나의 후보로 묶고, 다음 조건에 걸리는 덩어리는 글자가 아니라고 보고 버립니다.

조건버리는 이유
면적이 지나치게 작음스캔 먼지, 종이 얼룩
높이가 지나치게 낮음가로줄 조각, 밑줄
가로가 세로보다 4.5배 이상 김이음줄, 크레셴도 기호, 남은 오선 조각
높이가 지나치게 큼가사 글자, 딸려 들어온 음표

세로 공백으로 코드 나누기

남은 덩어리들을 세로 방향으로 투영해 잉크가 전혀 없는 열을 찾습니다. 그 공백이 일정 폭 이상 이어지면 "여기서 코드가 바뀐다"고 판단해 잘라냅니다.

여기서 발생하는 대표 오류: 코드가 둘로 쪼개짐

악보 서체나 자간에 따라 Am 사이 간격이 코드 경계로 오인될 만큼 넓을 때가 있습니다. 그러면 Am 하나가 Am 두 개로 잘립니다. 뒤에 이를 되돌리는 보정 규칙이 있지만 완전하지는 않습니다.

4단계: 문자 단위 인식과 조립

분리된 코드 조각을 하나씩 인식 모델에 넣습니다. 넣기 전에 정규화를 거칩니다. 조각을 흑백으로 바꾸고, 높이가 일정해지도록 확대·축소한 뒤, 흰 배경의 고정 크기 캔버스 가운데에 붙입니다. 조각마다 크기가 제각각이면 모델이 혼란스러워하기 때문입니다.

단어가 아니라 문자를 검출한다

이 모델은 일반적인 OCR처럼 "Am7"이라는 단어를 통째로 읽지 않습니다. YOLO 계열 객체 검출 모델로, 이미지 안에서 각 문자의 위치와 종류를 찾아냅니다. 인식 대상은 다음 25종입니다.

분류문자
근음A B C D E F G
임시표·구분자# b /
성질m M
숫자2 4 5 6 7 9 11 13
접미어sus dim add
괄호( )

여기서 두 가지를 알 수 있습니다. 첫째, 11과 13은 두 자리 숫자를 통째로 한 종류로 학습했습니다. 1이라는 개별 문자가 목록에 없기 때문입니다. 둘째, aug, dim7, maj 같은 표기는 개별 문자 조합으로 처리됩니다. maj는 목록에 없으므로 인식되지 않습니다.

대문자가 나오면 새 코드

검출된 문자들을 x좌표 순으로 정렬해 왼쪽부터 이어 붙입니다. 이때 대문자(A~G)가 나타나면 새 코드가 시작된 것으로 보고 끊습니다. 한 조각 안에 두 코드가 붙어 있는 경우를 처리하기 위한 규칙입니다.

단, 예외가 하나 있습니다. 바로 앞 문자가 슬래시(/)면 끊지 않습니다. G/B의 B는 새 코드가 아니라 베이스음이기 때문입니다.

5단계: 후처리 규칙

조립된 문자열을 그대로 믿지 않고 몇 가지 정리를 거칩니다.

규칙예시목적
맨 앞의 sus / add 제거susCC접미어가 앞 코드에서 잘못 넘어온 경우 정리
괄호 짝 맞추기C(sus4C(sus4)한쪽 괄호만 인식된 경우 보정
근음 없는 문자열 폐기sus, add → 버림코드로 성립하지 않음
첫 글자가 #, b, 숫자면 폐기7, #m → 버림근음 없이 시작할 수 없음

버려진 조각은 결과에 표시되지 않습니다. 확신할 수 없는 것을 억지로 보여주지 않는다는 원칙입니다.

쪼개진 조각 다시 붙이기

합성 단계 직전에 한 번 더 보정합니다. 어떤 코드 바로 뒤에 m, m7, 7처럼 혼자서는 코드가 될 수 없는 조각이 있고, 두 조각의 세로 위치 차이가 작고 가로 간격도 가까우면, 하나로 합칩니다.

거리 조건을 두는 이유는, 멀리 떨어진 A와 그 다음 마디의 m을 잘못 붙이면 오히려 결과가 나빠지기 때문입니다.

6단계: 사람의 검수

여기서 자동 처리가 멈추고 사용자에게 넘어갑니다. 이것은 성능 문제가 아니라 의도된 설계입니다.

인쇄 상태와 서체가 제각각인 실제 악보에서 100% 정확도는 현실적이지 않습니다. 그렇다면 선택지는 둘입니다. 틀린 결과를 그냥 내주거나, 사람이 고칠 기회를 주거나. 후자를 택했습니다.

편집 화면에서는 인식된 코드가 원본 이미지 위 실제 위치에 뱃지로 떠 있습니다. 목록만 보여주면 어느 코드가 어디 것인지 대조하기 어렵기 때문에, 좌표를 백분율로 계산해 이미지 위에 겹쳐 놓았습니다. 클릭해서 고치고, 필요 없는 것은 지웁니다.

7단계: 다이어그램 렌더링과 합성

확정된 코드 이름을 실제 그림으로 바꾸는 마지막 단계입니다.

코드 이름을 근음과 접미어로 쪼갠다

내장 코드 사전은 근음 12종을 C, C#, D, Eb, E, F, F#, G, Ab, A, Bb, B로 관리합니다. 코드 이름의 앞부분을 긴 것부터 매칭해 근음을 떼어냅니다. C#m7이면 C가 아니라 C#이 먼저 매칭되어야 하기 때문입니다.

사전에 없는 표기는 다음처럼 변환합니다.

다이어그램을 그린다

사전에서 찾은 운지 데이터에는 줄별 프렛 번호, 손가락 번호, 바레 위치, 시작 프렛이 들어 있습니다. 이 데이터로 6현 5프렛 격자를 그리고 다음을 얹습니다.

그린 뒤 주변 흰 여백을 잘라내고 정해진 크기로 축소해, 원래 코드 글자가 있던 좌표 중심에 붙입니다.

못 찾으면 물러선다

사전에서 운지를 못 찾았을 때의 동작은 두 단계입니다.

  1. 코드 이름에 슬래시가 있으면 앞부분만으로 다시 시도합니다. C/E가 없으면 C로.
  2. 그래도 없으면 그 코드는 건너뜁니다.

엉뚱한 운지를 자신 있게 붙이는 것보다 아무것도 안 붙이는 편이 낫다고 판단했습니다. 결과 이미지에 다이어그램이 빠진 코드가 있다면 대개 이 경우입니다.

이 구조에서 실패하는 악보의 조건

지금까지의 설명을 뒤집으면 실패 조건이 그대로 나옵니다.

실패하는 악보막히는 단계
기울어진 사진1단계 — 오선이 한 행에 정렬되지 않음
배경이 어둡거나 그림자가 진 사진1단계 — 배경을 잉크로 오인
오선이 없는 코드 시트1단계 — 기준으로 삼을 보표가 없음
코드가 보표 아래에 적힌 악보2단계 — 위쪽만 탐색함
코드와 가사가 붙어 있는 촘촘한 배치2단계 — 가사가 띠에 섞여 들어옴
손글씨 악보4단계 — 학습 범위 밖
해상도가 너무 낮은 사진3~4단계 — 글자가 노이즈 필터에 걸림
maj, aug 등 미학습 표기가 많은 악보4단계 — 문자 목록에 없음
드물게 쓰이는 텐션 코드가 많은 악보7단계 — 코드 사전에 없음

대부분은 촬영 방식만 바꿔도 해결됩니다. 구체적인 방법은 악보 사진 촬영 가이드에 정리했습니다.

왜 전부 딥러닝으로 하지 않았나

요즘이라면 이미지를 통째로 넣고 코드 목록을 뱉는 모델을 만드는 접근도 가능합니다. 그렇게 하지 않은 이유가 있습니다.

1. 오선은 규칙이 이미 완벽하다

오선은 다섯 줄 등간격이라는 인쇄 규격이 확고합니다. 이미 정답이 정해진 문제에 학습 데이터를 모으고 모델을 돌리는 것은 낭비입니다. 투영과 간격 검사로 충분하고, 오히려 더 안정적입니다.

2. 규칙 기반은 왜 틀렸는지 알 수 있다

사용자가 "이 악보가 안 됩니다"라고 제보했을 때, 규칙 기반 구간은 어느 조건에서 걸렸는지 바로 확인할 수 있습니다. 임계값을 조정하면 즉시 반영됩니다. 종단간 모델이었다면 "데이터를 더 모아서 재학습" 외에 할 수 있는 일이 없습니다.

3. 학습 데이터를 모으기 어렵다

악보는 대부분 저작물입니다. 종단간 모델을 학습시키려면 다양한 악보 이미지와 정답 레이블이 대량으로 필요한데, 이를 적법하게 확보하는 비용이 큽니다. 반면 코드 글자 조각만 인식하는 모델은 문자 단위 데이터로 학습할 수 있어 훨씬 현실적입니다.

4. 문자 검출은 학습이 이길 수밖에 없다

반대로 글자 인식은 규칙으로 풀기 어렵습니다. 서체, 크기, 인쇄 품질의 편차가 크고, b6, mM처럼 형태가 비슷한 문자가 많습니다. 이 구간만 학습 모델에 맡긴 것은 자연스러운 분업입니다.

정리

ChordSticker는 규칙으로 풀 수 있는 것은 규칙으로, 학습이 필요한 것만 모델로 처리하는 구조입니다. 오선과 코드 띠는 영상 처리로 찾고, 글자만 YOLO로 읽고, 최종 판단은 사람에게 맡깁니다.

이 구조를 알고 나면 결과를 해석하기가 쉬워집니다. 코드를 아예 못 찾았다면 1~2단계 문제이니 촬영을 다시 해야 하고, 이름이 틀렸다면 4단계 문제이니 편집 화면에서 고치면 되고, 이름은 맞는데 그림이 없다면 7단계 문제이니 표기를 바꿔 입력하면 됩니다.