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

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

by 이민훈 2021. 8. 5.

5. 객체 분석 과정

모든 객체를 검출하고 최소 넓이와 높이 조건을 줌으로써 어느 정도 노이즈를 걷어냈습니다.

 

인식 단계 전 마지막 과정이 남았는데 객체에 직선 성분이 존재하는지

 

존재한다면 음표로 가정하고 음표의 방향은 어떻게 되는지 등

 

인식에 쓰일 특징점들을 추출해보는 과정입니다.

 

먼저 functions.py에 아래 함수를 정의해줍니다.

 

get_line 함수는 아래에 나올 stem_detection 함수뿐만 아니라, 객체를 분류할 때

 

여러 특징점을 찾아내기 위해 사용됩니다.

 

# functions.py
import cv2
import numpy as np

VERTICAL = True
HORIZONTAL = False

def get_line(image, axis, axis_value, start, end, length):
    if axis:
        points = [(i, axis_value) for i in range(start, end)]  # 수직 탐색
    else:
        points = [(axis_value, i) for i in range(start, end)]  # 수평 탐색
    pixels = 0
    for i in range(len(points)):
        (y, x) = points[i]
        pixels += (image[y][x] == 255)  # 흰색 픽셀의 개수를 셈
        next_point = image[y + 1][x] if axis else image[y][x + 1]  # 다음 탐색할 지점
        if next_point == 0 or i == len(points) - 1:  # 선이 끊기거나 마지막 탐색임
            if pixels >= weighted(length):
                break  # 찾는 길이의 직선을 찾았으므로 탐색을 중지함
            else:
                pixels = 0  # 찾는 길이에 도달하기 전에 선이 끊김 (남은 범위 다시 탐색)
    return y if axis else x, pixels

 

함수의 파라미터는 다음과 같은 의미를 가집니다.

 

image = 이미지, axis = 축, axis_value = 고정된 축의 값, start, end = 선을 검출할 범위, length = 선의 최소 길이 조건

 

axis가 false고 axis_value가 500이라고 가정하고

 

line은 (0, 100)으로 length는 50이라고 가정해봅시다.

 

이미지에서 [500, 0] ~ [500, 100] 좌표를 탐색하여 끊기지 않고 유지되는 50픽셀 이상의 선을 검출하는 함수입니다.

 

반환값은 끝 좌표와 길이입니다.

 

[500, 0] ~ [500, 100] 좌표를 탐색한 결과 20~80이 끊기지 않는 직선으로 검출되었다면 80, 60이 반환됩니다.

 

만약, axis가 true라면 [0, 500] ~ [100, 500] 좌표를 탐색하겠죠?

 

즉, axis가 true라면 세로로 된 직선을 검출하며, false라면 가로로 된 직선을 검출합니다.

 

아래 함수는 기둥을 검출하는 함수입니다.

 

# functions.py
import cv2
import numpy as np

def stem_detection(image, stats, length):
    (x, y, w, h, area) = stats
    stems = []  # 기둥 정보 (x, y, w, h)
    for col in range(x, x + w):
        end, pixels = get_line(image, VERTICAL, col, y, y + h, length)
        if pixels:
            if len(stems) == 0 or abs(stems[-1][0] + stems[-1][2] - col) >= 1:
                (x, y, w, h) = col, end - pixels + 1, 1, pixels
                stems.append([x, y, w, h])
            else:
                stems[-1][2] += 1
    return stems

 

음표 기둥에 해당하는 부분(세로 직선)이 객체에 존재하는지, 몇 개가 있는지 등을 검출하는 함수라고 할 수 있습니다.

 

x좌표를 돌며 해당 x좌표에 직선 성분이 있는지 get_line 함수를 호출해 검출합니다.

 

아랫부분은 오선을 검출할 때 보셨던 구조입니다.

 

음표 기둥이 1pixel로 이루어져 있을 수도 있지만 아닐 수도 있기 때문에 새로운 기둥인지 판단해야 합니다.

 

같은 기둥이라면 넓잇값만 업데이트해 줍니다.

 

stems에 추가되는 값들은 각 x좌표, y좌표, 넓이, 높이입니다.

 

stem_ditection 함수를 호출하여 직선들을 검출하고,

 

직선이 검출되면 음표로 가정, 방향을 알아낼 수 있습니다.

 

# Main.py
import cv2
import os
import numpy as np
import functions as fs
import modules

# 이미지 불러오기
resource_path = os.getcwd() + "/resource/"
image_0 = cv2.imread(resource_path + "music.jpg")

# 1. 보표 영역 추출 및 그 외 노이즈 제거
image_1 = modules.remove_noise(image_0)

# 2. 오선 제거
image_2, staves = modules.remove_staves(image_1)

# 3. 악보 이미지 정규화
image_3, staves = modules.normalization(image_2, staves, 10)

# 4. 객체 검출 과정
image_4, objects = modules.object_detection(image_3, staves)

# 5. 객체 분석 과정
for obj in objects:
    stats = obj[1]
    stems = fs.stem_detection(image_4, stats, 30)  # 객체 내의 모든 직선들을 검출함
    direction = None
    if len(stems) > 0:  # 직선이 1개 이상 존재함
        if stems[0][0] - stats[0] >= fs.weighted(5):  # 직선이 나중에 발견되면
            direction = True  # 정 방향 음표
        else:  # 직선이 일찍 발견되면
            direction = False  # 역 방향 음표
    obj.append(stems)  # 객체 리스트에 직선 리스트를 추가
    obj.append(direction)  # 객체 리스트에 음표 방향을 추가

# 이미지 띄우기
cv2.imshow('image', image_4)
k = cv2.waitKey(0)
if k == 27:
    cv2.destroyAllWindows()

 

객체의 x좌표와 직선의 x좌표의 거리를 재면 되는데, 그 이유는 음표 머리가 아래쪽에 있는 음표의 경우

 

머리가 먼저 나오기 때문에 기둥은 어느 정도 뒤쪽에 위치하고 있고

 

음표 머리가 위쪽에 있는 음표의 경우 기둥이 먼저 나오기 때문에 기둥이 객체의 앞쪽에 위치하고 있기 때문입니다.

 

stem_detection의 마지막 파라미터를 30으로 준 이유는 이전 챕터에서

 

음표객체의 높이를 찍어본 결과 40으로 나왔고 직선의 길이는 최소 30(오선 3칸)은 될 거라 판단했습니다.

 

직선이 제대로 검출되었는지 functions.py에 정의해둔 put_text 함수를 통해 찍어보도록 하겠습니다.

 

for obj in objects:
    (x, y, w, h, area) = obj[1]
    if len(obj[2]):
        fs.put_text(image_4, len(obj[2]), (x, y + h + 20))

 

 

# modules.py
import cv2
import numpy as np
import functions as fs

def object_analysis(image, objects):
    for obj in objects:
        stats = obj[1]
        stems = fs.stem_detection(image, stats, 30)  # 객체 내의 모든 직선들을 검출함
        direction = None
        if len(stems) > 0:  # 직선이 1개 이상 존재함
            if stems[0][0] - stats[0] >= fs.weighted(5):  # 직선이 나중에 발견되면
                direction = True  # 정 방향 음표
            else:  # 직선이 일찍 발견되면
                direction = False  # 역 방향 음표
        obj.append(stems)  # 객체 리스트에 직선 리스트를 추가
        obj.append(direction)  # 객체 리스트에 음표 방향을 추가

    return image, objects

 

# Main.py
import cv2
import os
import numpy as np
import functions as fs
import modules

# 이미지 불러오기
resource_path = os.getcwd() + "/resource/"
image_0 = cv2.imread(resource_path + "music.jpg")

# 1. 보표 영역 추출 및 그 외 노이즈 제거
image_1 = modules.remove_noise(image_0)

# 2. 오선 제거
image_2, staves = modules.remove_staves(image_1)

# 3. 악보 이미지 정규화
image_3, staves = modules.normalization(image_2, staves, 10)

# 4. 객체 검출 과정
image_4, objects = modules.object_detection(image_3, staves)

# 5. 객체 분석 과정
image_5, objects = modules.object_analysis(image_4, objects)

# 이미지 띄우기
cv2.imshow('image', image_5)
k = cv2.waitKey(0)
if k == 27:
    cv2.destroyAllWindows()

댓글