본문 바로가기
인공지능/컴퓨터비전

[OpenCV/Python] 악보 인식(디지털 악보 인식) - 9

by 이민훈 2021. 8. 6.

9. 인식 과정 - 음표(점)

이제 마지막 음표의 구성요소인 점만 인식하면 됩니다.

 

점은 크게 어려울 것은 없는데 오선의 범위를 넘은 음표를 그릴 때

 

오선에 대한 보조선(?)을 그어주는 경우가 있습니다.

 

해당 부분이 점을 탐색하는 구역과 겹칠 수 있어 유의해야 합니다.

 

그리고 8분음표나 16분음표와 같이 꼬리가 있는 음표들과도 겹칠 수가 있습니다.

 

그래서 탐색하는 영역을 정교하게 정해주어야 합니다.

 

recognize_note_dot 함수를 만들고 영역부터 확인해보겠습니다.

 

# recognition_modules.py
import functions as fs
import cv2

def recognize_note_dot(image, stem, direction):
    (x, y, w, h) = stem
    if direction:  # 정 방향 음표
        area_top = y + h - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + h + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(2)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(12)  # 음표 점을 탐색할 위치 (우측)
    else:  # 역 방향 음표
        area_top = y - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(14)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(24)  # 음표 점을 탐색할 위치 (우측)
    dot_rect = (
        area_left,
        area_top,
        area_right - area_left,
        area_bot - area_top
    )

    cv2.rectangle(image, dot_rect, (255, 0, 0), 1)
    
    pass

 

# recognition_modules.py
import functions as fs
import cv2

def recognize_note(image, staff, stats, stems, direction):
    x, y, w, h, area = stats
    notes = []
    pitches = []
    note_condition = (
        len(stems) and
        w >= fs.weighted(10) and  # 넓이 조건
        h >= fs.weighted(35) and  # 높이 조건
        area >= fs.weighted(95)  # 픽셀 갯수 조건
    )
    if note_condition:
        for i in range(len(stems)):
            stem = stems[i]
            head_exist, head_fill, head_center = recognize_note_head(image, stem, direction)
            if head_exist:
                tail_cnt = recognize_note_tail(image, i, stem, direction)
                recognize_note_dot(image, stem, direction)

    pass

 

 

얼추 맞는 것 같습니다.

 

# recognition_modules.py
import functions as fs
import cv2

def recognize_note_dot(image, stem, direction):
    (x, y, w, h) = stem
    if direction:  # 정 방향 음표
        area_top = y + h - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + h + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(2)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(12)  # 음표 점을 탐색할 위치 (우측)
    else:  # 역 방향 음표
        area_top = y - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(14)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(24)  # 음표 점을 탐색할 위치 (우측)
    dot_rect = (
        area_left,
        area_top,
        area_right - area_left,
        area_bot - area_top
    )

    pixels = fs.count_rect_pixels(image, dot_rect)
    fs.put_text(image, pixels, (x, y + h + fs.weighted(20)))
    cv2.rectangle(image, dot_rect, (255, 0, 0), 1)

    pass

 

count_rect_pixels 함수를 사용하여 픽셀의 개수를 찍어보겠습니다.

 

 

점음표 쪽에 픽셀들이 확실히 많은 것을 볼 수가 있습니다.

 

언뜻 봐도 13~15개씩은 존재하는 것 같습니다.

 

하지만 정방향 음표 중에 꼬리가 있는 음표들은 점이 없는데도 불구 픽셀이 많이 존재합니다.

 

이런 음표들에 한해 점이 있다고 판단하는 경곗값을 높여주도록 하겠습니다.

 

물론 빔 음표들은 꼬리가 내려와 있지 않기 때문에 제외하도록 하겠습니다.

 

그러기 위해선 음표 꼬리의 개수와 객체 안에 기둥이 몇 개 있는지 추가로 받아오도록 하겠습니다.

 

# recognition_modules.py
import functions as fs
import cv2

def recognize_note_dot(image, stem, direction, tail_cnt, stems_cnt):
    (x, y, w, h) = stem
    if direction:  # 정 방향 음표
        area_top = y + h - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + h + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(2)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(12)  # 음표 점을 탐색할 위치 (우측)
    else:  # 역 방향 음표
        area_top = y - fs.weighted(10)  # 음표 점을 탐색할 위치 (상단)
        area_bot = y + fs.weighted(5)  # 음표 점을 탐색할 위치 (하단)
        area_left = x + w + fs.weighted(14)  # 음표 점을 탐색할 위치 (좌측)
        area_right = x + w + fs.weighted(24)  # 음표 점을 탐색할 위치 (우측)
    dot_rect = (
        area_left,
        area_top,
        area_right - area_left,
        area_bot - area_top
    )

    pixels = fs.count_rect_pixels(image, dot_rect)

    threshold = (10, 15, 20, 30)
    if direction and stems_cnt == 1:
        return pixels >= fs.weighted(threshold[tail_cnt])
    else:
        return pixels >= fs.weighted(threshold[0])

 

경곗값에 따라 점음표인지 아닌지 반환해보겠습니다.

 

그런 다음 분류해주는 코드를 짜보면..

 

# recognition_modules.py
import functions as fs
import cv2

def recognize_note(image, staff, stats, stems, direction):
    (x, y, w, h, area) = stats
    notes = []
    pitches = []
    note_condition = (
        len(stems) and
        w >= fs.weighted(10) and  # 넓이 조건
        h >= fs.weighted(35) and  # 높이 조건
        area >= fs.weighted(95)  # 픽셀 갯수 조건
    )
    if note_condition:
        for i in range(len(stems)):
            stem = stems[i]
            head_exist, head_fill, head_center = recognize_note_head(image, stem, direction)
            if head_exist:
                tail_cnt = recognize_note_tail(image, i, stem, direction)
                dot_exist = recognize_note_dot(image, stem, direction, len(stems), tail_cnt)
                note_classification = (
                    ((not head_fill and tail_cnt == 0 and not dot_exist), 2),
                    ((not head_fill and tail_cnt == 0 and dot_exist), -2),
                    ((head_fill and tail_cnt == 0 and not dot_exist), 4),
                    ((head_fill and tail_cnt == 0 and dot_exist), -4),
                    ((head_fill and tail_cnt == 1 and not dot_exist), 8),
                    ((head_fill and tail_cnt == 1 and dot_exist), -8),
                    ((head_fill and tail_cnt == 2 and not dot_exist), 16),
                    ((head_fill and tail_cnt == 2 and dot_exist), -16),
                    ((head_fill and tail_cnt == 3 and not dot_exist), 32),
                    ((head_fill and tail_cnt == 3 and dot_exist), -32)
                )

                for j in range(len(note_classification)):
                    if note_classification[j][0]:
                        note = note_classification[j][1]
                        notes.append(note)
                        fs.put_text(image, note, (stem[0] - fs.weighted(10), stem[1] + stem[3] + fs.weighted(30)))
                        break

    return notes, pitches

 

 

모든 음표가 제대로 분류되었음을 볼 수 있습니다.

 

박자를 알아내었으니, 다음 챕터에서는 음정을 알아내는 알고리즘을 짜보도록 하겠습니다.

댓글