4. 객체 검출 과정
모든 전처리 과정이 끝났고 객체 검출 과정을 진행해보겠습니다.
먼저 정규화까지 진행된 이미지에 레이블링해보겠습니다.
잠시 그전에 functions.py에 함수 하나를 정의하겠습니다.
# functions.py
import cv2
import numpy as np
def weighted(value):
standard = 10
return int(value * (standard / 10))
weighted 함수는 standard에 따라 가중치가 곱해진 값을 리턴해주는 함수입니다.
이 함수가 왜 필요하냐면 오선 간격을 10이 아닌 20으로 정규화하였을 때 모든 구성요소들은
2배의 넓이와 높이를 가지게 됩니다.
그래서 코드 내에 상숫값을 적게 되면 standard 값을 바꿀 경우 제대로 코드가 작동하지 않게 됩니다.
앞서 정규화 함수에서 오선 간격인 standard를 10으로 정의하였고
해당 함수에 5라는 값을 넣으면 5라는 값이 리턴됩니다.
standard를 20으로 정의하였다면 5라는 값을 넣었을 때 10이 리턴됩니다.
우리가 정한 기준값에 따라 가중치를 곱해 리턴해주는 함수가 필요한 것이죠.
standard / 10 에서 10은 오선 간격을 상숫값 10으로 정의하겠다는 것을 의미합니다.
자 이제 fs.weighted(5)는 standard 값을 어떻게 지정해주든 오선 간격의 반 정도 되는 값을 반환해줍니다.
# 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. 객체 검출 과정
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(image_3) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
cv2.rectangle(image_3, (x, y, w, h), (255, 0, 0), 1)
# 이미지 띄우기
cv2.imshow('image', image_3)
k = cv2.waitKey(0)
if k == 27:
cv2.destroyAllWindows()
해당 악보는 비교적 함수 하나만으로 객체 검출이 잘되는 편인 것 같네요.
하지만 다른 악보를 대상으로 해보면..
분명 같은 객체임에도 픽셀이 미세하게 끊어져 있다거나 함으로인해 다른 객체로 검출되는 경우가 생깁니다.
위 악보에선 음자리표나 박자표 등이 여러 객체로 분할되어 검출되었네요.
이것을 방지하기위해 functions.py에 함수 하나를 정의하겠습니다.
# functions.py
import cv2
import numpy as np
def closing(image):
kernel = np.ones((weighted(5), weighted(5)), np.uint8)
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
return image
닫힘 연산이란 객체부분을 넓혔다가 다시 원래대로 돌림으로써 객체안에 존재하는 노이즈를 제거하는 연산입니다.
끊어진 객체 부분을 연결해주는 용도로 닫힘 연산을 사용하도록 하겠습니다.
# Main.py
import cv2
import os
import numpy as np
import functions as fs
import modules
# 4. 객체 검출 과정
closing_image = fs.closing(image_3)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
cv2.rectangle(closing_image, (x, y, w, h), (255, 0, 0), 1)
# 이미지 띄우기
cv2.imshow('image', closing_image)
k = cv2.waitKey(0)
if k == 27:
cv2.destroyAllWindows()
바로 아래와 같이 이미지가 변합니다.
객체안의 노이즈가 제거되고 분할된 객체들이 하나로 검출되는 것을 볼 수가 있습니다.
하지만 악보의 구성요소들이 변형되므로
객체 검출만 closing_image에서 해주고 다시 원본 이미지에 사각형을 그리도록 합시다.
# Main.py
import cv2
import os
import numpy as np
import functions as fs
import modules
# 4. 객체 검출 과정
closing_image = fs.closing(image_3)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
cv2.rectangle(image_3, (x, y, w, h), (255, 0, 0), 1)
# 이미지 띄우기
cv2.imshow('image', image_3)
k = cv2.waitKey(0)
if k == 27:
cv2.destroyAllWindows()
하지만 조그맣게 남은 노이즈들도 객체로 판별되는걸 볼 수 있습니다.
이를 방지하기 위해 악보의 구성요소들이 각 어느 정도의 크기를 가지는지, 어느 정도의 픽셀로 이루어져 있는지
이미지에 찍어보도록 하겠습니다.
functions.py 파일에 함수 하나를 정의하도록 하겠습니다.
# functions.py
import cv2
import numpy as np
def put_text(image, text, loc):
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, str(text), loc, font, 0.6, (255, 0, 0), 2)
이미지와 텍스트 좌표를 입력받아 흰색 픽셀로 텍스트를 적어주는 함수입니다.
closing_image = fs.closing(image_3)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
cv2.rectangle(image_3, (x, y, w, h), (255, 0, 0), 1)
fs.put_text(image_3, w, (x, y + h + 30))
fs.put_text(image_3, h, (x, y + h + 60))
다시 원래의 악보로 돌아와서..
점을 제외한 악보의 구성요소들은 최소 8의 넓이(플랫)와 6의 높이(온쉼표, 2분쉼표)를 가지고 있습니다.
그럼 넉넉히 넓이와 높이가 5 이상인 객체들만 검출하도록 하겠습니다.
closing_image = fs.closing(image_3)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
if w >= fs.weighted(5) and h >= fs.weighted(5): # 악보의 구성요소가 되기 위한 넓이, 높이 조건
cv2.rectangle(image_3, (x, y, w, h), (255, 0, 0), 1)
이제 이 검출된 객체들의 정보를 리스트에 담아봅시다.
먼저 functions.py에 get_center라는 함수를 만들어주도록 하겠습니다.
객체의 중간 y좌표를 반환해주는 함수입니다.
# functions.py
import cv2
import numpy as np
def get_center(y, h):
return (y + y + h) / 2
이후 각 객체가 어느 보표에 위치하는지 탐색해야 합니다.
해당 악보에는 4개의 보표가 있고 다음 코드로 속해있는 보표를 알 수 있습니다.
lines = int(len(staves) / 5) # 보표의 개수
objects = [] # 구성요소 정보가 저장될 리스트
closing_image = fs.closing(image_3)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
if w >= fs.weighted(5) and h >= fs.weighted(5): # 악보의 구성요소가 되기 위한 넓이, 높이 조건
center = fs.get_center(y, h)
for line in range(lines):
area_top = staves[line * 5] - fs.weighted(20) # 위치 조건 (상단)
area_bot = staves[(line + 1) * 5 - 1] + fs.weighted(20) # 위치 조건 (하단)
if area_top <= center <= area_bot:
objects.append([line, (x, y, w, h, area)]) # 객체 리스트에 보표 번호와 객체의 정보(위치, 크기)를 추가
objects.sort() # 보표 번호 → x 좌표 순으로 오름차순 정렬
보표에서 제일 위의 오선과 제일 아래의 오선에 fs.weighted(20) 즉, 오선 두 칸 정도 더해진 범위안에
중간 y좌표가 포함된다면 해당 보표에 속해있는 객체라고 볼 수 있습니다.
objects라는 리스트에 보표, 객체의 정보를 담아주도록 하고 보표 번호와 x 좌표의 순으로 정렬하겠습니다.
리스트에는 다음과 같은 리스트들이 저장되어 있습니다.
[[0, (100, 209, 26, 72, 863)], [0, (136, 227, 8, 25, 97)], [0, (146, 211, 18, 46, 207)] ... ]
첫 번째 리스트를 살펴보면 해당 객체는 0번째 즉, 첫 번째 보표에 속해있고 x좌표는 100, y좌표는 209,
넓이는 26, 높이는 72이고 픽셀의 개수는 863개인 객체라는 뜻입니다.
# modules.py
import cv2
import numpy as np
import functions as fs
def object_detection(image, staves):
lines = int(len(staves) / 5) # 보표의 개수
objects = [] # 구성요소 정보가 저장될 리스트
closing_image = fs.closing(image)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(closing_image) # 모든 객체 검출하기
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
if w >= fs.weighted(5) and h >= fs.weighted(5): # 악보의 구성요소가 되기 위한 넓이, 높이 조건
center = fs.get_center(y, h)
for line in range(lines):
area_top = staves[line * 5] - fs.weighted(20) # 위치 조건 (상단)
area_bot = staves[(line + 1) * 5 - 1] + fs.weighted(20) # 위치 조건 (하단)
if area_top <= center <= area_bot:
objects.append([line, (x, y, w, h, area)]) # 객체 리스트에 보표 번호와 객체의 정보(위치, 크기)를 추가
objects.sort() # 보표 번호 → x 좌표 순으로 오름차순 정렬
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)
# 이미지 띄우기
cv2.imshow('image', image_4)
k = cv2.waitKey(0)
if k == 27:
cv2.destroyAllWindows()
'인공지능 > 컴퓨터비전' 카테고리의 다른 글
[OpenCV/Python] 악보 인식(디지털 악보 인식) - 6 (2) | 2021.08.05 |
---|---|
[OpenCV/Python] 악보 인식(디지털 악보 인식) - 5 (0) | 2021.08.05 |
[OpenCV/Python] 악보 인식(디지털 악보 인식) - 3 (0) | 2021.08.04 |
[OpenCV/Python] 악보 인식(디지털 악보 인식) - 2 (0) | 2021.08.04 |
[OpenCV/Python] 악보 인식(디지털 악보 인식) - 1 (0) | 2021.08.04 |
댓글