Chapter 05 지역 특징
이번 시간엔 5장에 대해 작성해보겠습니다. 이 장은 이론과 계산 공식이 많아 이해하는 데 무척 어려운 것 같습니다.
저도 이해하면서 푸느라 실수가 있을 수 있으니, 참고하시고 조언은 언제나 감사합니다.
01 [그림 5-4(a)]에서 다음 두 점에 대해 [그림5-4(b)]의 S맵 식(5,2)의 C를 계산하시오.
(1) (y,x) = (6,3) 정답: 1
원본 영상 | S맵 | ||||
0 | 1 | 1 | 2 | 1 | 3 |
0 | 1 | 1 | 2 | 0 | 2 |
0 | 0 | 0 | 3 | 2 | 3 |
C = min(2,2,1,2) = 1
(2) (y,x) = (4,5) 정답: 1
원본 영상 | S맵 | ||||
1 | 0 | 0 | 3 | 2 | 1 |
1 | 1 | 0 | 2 | 0 | 2 |
1 | 1 | 1 | 1 | 1 | 2 |
C = min(2,2,2,1) = 1
02 문제 1의 두 점에 대해 표[5-1]의 2차 모멘트 행렬, 고윳값, 특징 가능성 값을 계산하시오.
이 문제는 P.169에 코드를 이용해서 구하면 됩니다.(공식 너무 어려워요..)
(1) (y,x) = (6,3)
2차 모멘트 행렬 | 0.53 -0.2 -0.2 0.53 |
고윳값 | A1 = 0.731, A2 = 0.323 |
특징 가능성 | 0.192 |
전체 코드
import cv2 as cv
import numpy as np
# 원본 영상
img = np.array([[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,0,0,0,0,0,0],
[0,0,0,1,1,0,0,0,0,0],
[0,0,0,1,1,1,0,0,0,0],
[0,0,0,1,1,1,1,0,0,0],
[0,0,0,1,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0]], dtype=np.float32)
# x, y방향 미분 필터와 가우시안 필터
ux = np.array([[-1, 0, 1]])
uy = np.array([-1, 0, 1]).transpose()
k = cv.getGaussianKernel(3,1)
g = np.outer(k, k.transpose())
# 기울기 계산
dy = cv.filter2D(img, cv.CV_32F, uy)
dx = cv.filter2D(img, cv.CV_32F, ux)
# 2차 모멘트 행렬 요소 계산
dyy = dy*dy
dxx = dx*dx
dyx = dy*dx
# 가우시안 필터링
gdyy = cv.filter2D(dyy, cv.CV_32F, g)
gdxx = cv.filter2D(dxx, cv.CV_32F, g)
gdyx = cv.filter2D(dyx, cv.CV_32F, g)
# (6,3) 위치의 2차 모멘트 행렬
M = np.array([[gdxx[6,3], gdyx[6,3]],
[gdyx[6,3], gdyy[6,3]]])
# 행렬 출력 형식 설정
np.set_printoptions(precision=2, suppress=True)
# 고윳값 계산
eigenvalues = np.linalg.eigvals(M)
# 특징 가능성 값 계산 (Harris response)
k = 0.04 # Harris 상수
R = np.linalg.det(M) - k * (np.trace(M)**2)
print("(6,3) 위치의 기울기:")
print(f"dx = {dx[6,3]:.3f}")
print(f"dy = {dy[6,3]:.3f}")
print("\n(6,3) 위치의 2차 모멘트 행렬:")
print(M)
print("\n고윳값:")
print(f"λ1 = {eigenvalues[0]:.3f}")
print(f"λ2 = {eigenvalues[1]:.3f}")
print("\n특징 가능성 값(R):")
print(f"R = {R:.3f}")
# 참고: 전체 영상의 2차 모멘트 행렬 요소들
print("\n전체 영상의 gdxx:")
print(gdxx)
print("\n전체 영상의 gdyy:")
print(gdyy)
print("\n전체 영상의 gdyx:")
print(gdyx)
(2) (y,x) = (4,5)
2차 모멘트 행렬 | 0.6 -0.2 -0.2 0.6 |
고윳값 | A1 = 1.204, A2 = 0 |
특징 가능성 | -0.058 |
전체 코드
import cv2 as cv
import numpy as np
# 원본 영상
img = np.array([[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,1,0,0,0,0,0,0],
[0,0,0,1,1,0,0,0,0,0],
[0,0,0,1,1,1,0,0,0,0],
[0,0,0,1,1,1,1,0,0,0],
[0,0,0,1,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0]], dtype=np.float32)
# x, y방향 미분 필터와 가우시안 필터
ux = np.array([[-1, 0, 1]])
uy = np.array([-1, 0, 1]).transpose()
k = cv.getGaussianKernel(3,1)
g = np.outer(k, k.transpose())
# 기울기 계산
dy = cv.filter2D(img, cv.CV_32F, uy)
dx = cv.filter2D(img, cv.CV_32F, ux)
# 2차 모멘트 행렬 요소 계산
dyy = dy*dy
dxx = dx*dx
dyx = dy*dx
# 가우시안 필터링
gdyy = cv.filter2D(dyy, cv.CV_32F, g)
gdxx = cv.filter2D(dxx, cv.CV_32F, g)
gdyx = cv.filter2D(dyx, cv.CV_32F, g)
# (6,3) 위치의 2차 모멘트 행렬
M = np.array([[gdxx[4,5], gdyx[4,5]],
[gdyx[4,5], gdyy[4,5]]])
# 행렬 출력 형식 설정
np.set_printoptions(precision=2, suppress=True)
# 고윳값 계산
eigenvalues = np.linalg.eigvals(M)
# 특징 가능성 값 계산 (Harris response)
k = 0.04 # Harris 상수
R = np.linalg.det(M) - k * (np.trace(M)**2)
print("(4,5) 위치의 기울기:")
print(f"dx = {dx[4,5]:.3f}")
print(f"dy = {dy[4,5]:.3f}")
print("\n(4, 5) 위치의 2차 모멘트 행렬:")
print(M)
print("\n고윳값:")
print(f"λ1 = {eigenvalues[0]:.3f}")
print(f"λ2 = {eigenvalues[1]:.3f}")
print("\n특징 가능성 값(R):")
print(f"R = {R:.3f}")
# 참고: 전체 영상의 2차 모멘트 행렬 요소들
print("\n전체 영상의 gdxx:")
print(gdxx)
print("\n전체 영상의 gdyy:")
print(gdyy)
print("\n전체 영상의 gdyx:")
print(gdyx)
03 [프로그램 5-2]의 06~07행은 가능한 모든 키포인트를 생성하고, 09행은 키포인트를 그린다.
(1) 키포인트 2개만 생성하도록 [프로그램 5-2]를 수정하시오.
import cv2 as cv
img = cv.imread('이미지 경로')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create(nfeatures = 2)
kp,des = sift.detectAndCompute(gray,None)
gray = cv.drawKeypoints(gray, kp,None, flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv.imshow('sift', gray)
k = cv.waitKey()
cv.destroyAllWindows()
(2) 2,4,6,8,...,512개의 키 포인트를 생성하고, 각 결과를 서로 다른 윈도우에 디스플레이하도록 프로그램을 확장하시오.
import cv2 as cv
import numpy as np
# 이미지 읽기
img = cv.imread('이미지 경로')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 키포인트 개수를 저장할 리스트 생성 (2부터 512까지 2배씩 증가)
keypoint_counts = [2 * (2 ** i) for i in range(9)] # [2, 4, 8, 16, 32, 64, 128, 256, 512]
# 각 키포인트 개수별로 SIFT 특징점 검출 및 시각화
for idx, n_features in enumerate(keypoint_counts):
# SIFT 검출기 생성
sift = cv.SIFT_create(nfeatures=n_features)
# 키포인트와 디스크립터 검출
kp, des = sift.detectAndCompute(gray, None)
# 결과 이미지 생성
result_img = cv.drawKeypoints(gray, kp, None,
flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# 윈도우 이름 설정
window_name = f'SIFT Keypoints (n={n_features})'
# 윈도우 위치 설정 (3x3 그리드로 배치)
window_width = img.shape[1]
window_height = img.shape[0]
x_position = (idx % 3) * window_width
y_position = (idx // 3) * 50 # 윈도우 제목 표시줄 고려하여 간격 조정
# 윈도우 생성 및 위치 설정
cv.namedWindow(window_name)
cv.moveWindow(window_name, x_position, y_position)
# 결과 표시
cv.imshow(window_name, result_img)
# 검출된 실제 키포인트 개수 출력
print(f'요청한 키포인트 개수: {n_features}, 실제 검출된 키포인트 개수: {len(kp)}')
# 키 입력 대기
k = cv.waitKey(0)
cv.destroyAllWindows()
위 코드에서 키포인트 개수 증가하는 코드와 단순 출력만 뽑아도 상관은 없습니다만. 좀 더 보기 좋게 다듬느라 코드가 많아졌네요.
(3)번은 여러분들이 분석해보시길 바랍니다! (절대 하기 싫어서 그런 거 아닐걸요.)
04 5.4.2항은 SIFT의 변종 부분에서 SURF가 SIFT에 비해 6배가량 빠르다고 설명한다. SURF가 속도 향상을 달성하는 원리를 설명하시오.
- 적분 이미지 사용: 이미지 전체의 합을 미리 계산
- BoxFilter를 이용해 헤이시안 행렬을 근사화
- 64차원으로 줄이고 단순화된 방향
05 SIFT는 에지에서 발생한 쓸모 없는 키포인트를 찾아 제거하는 후처리 연산을 적용한다. [Lowe2004]를 참조하여 이런 키포인트를 어떻게 찾는지 설명하시오.
정답: 가볍게 패스!!! 정답이 궁금하시다면 알아보시고 저도 알려주세요.
06 [그림5-9]의 옥타브 영상을 만드는 프로그램을 작성하시오. 예시 영상은 스스로 고르시오.
저는 이미지로 했습니다. 예시 보면 하나의 이미지로 한 게 아닌가 싶은데.. 흠 모르겠네요
(1) 그림 처럼 6개의 가우시안 영상과 5개의 DOG를 만들어 서로 다른 윈도우에 디스플레이하는 프로그램을 작성하시오.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def create_gaussian_pyramid(image, num_octaves=6):
"""
가우시안 피라미드를 생성하는 함수
"""
# 초기 시그마 값들 (이미지에 표시된 값들)
sigmas = [1.6000, 2.0159, 2.5398, 3.2000, 4.0317, 5.0797]
gaussian_images = []
# 각 시그마 값에 대해 가우시안 블러 적용
for sigma in sigmas:
# 커널 크기는 시그마의 6배로 설정 (반올림하여 홀수로)
ksize = int(6 * sigma)
if ksize % 2 == 0:
ksize += 1
blurred = cv2.GaussianBlur(image, (ksize, ksize), sigma)
gaussian_images.append(blurred)
return gaussian_images
def create_dog_pyramid(gaussian_images):
"""
DOG(Difference of Gaussian) 피라미드를 생성하는 함수
"""
dog_images = []
# 연속된 가우시안 이미지들의 차이를 계산
for i in range(len(gaussian_images) - 1):
dog = cv2.subtract(gaussian_images[i + 1], gaussian_images[i])
dog_images.append(dog)
return dog_images
def display_pyramids(gaussian_images, dog_images):
"""
가우시안 피라미드와 DOG 피라미드를 시각화하는 함수
"""
plt.figure(1, figsize=(10, 8))
plt.suptitle('Gaussian Pyramid')
for i, img in enumerate(gaussian_images):
plt.subplot(2, 3, i + 1)
plt.imshow(img, cmap='gray')
plt.title(f'σ = {[1.6000, 2.0159, 2.5398, 3.2000, 4.0317, 5.0797][i]:.4f}')
plt.axis('off')
plt.figure(2, figsize=(10, 8))
plt.suptitle('Difference of Gaussian (DOG)')
for i, img in enumerate(dog_images):
plt.subplot(2, 3, i + 1)
plt.imshow(img, cmap='gray')
plt.title(f'DOG {i + 1}')
plt.axis('off')
plt.show()
def main():
# 이미지 로드
image = cv2.imread('이미지 경로', cv2.IMREAD_GRAYSCALE)
# 이미지가 없는 경우 샘플 이미지 생성
if image is None:
image = np.zeros((300, 400), dtype=np.uint8)
cv2.circle(image, (200, 150), 50, 255, -1)
# 가우시안 피라미드 생성
gaussian_images = create_gaussian_pyramid(image)
# DOG 피라미드 생성
dog_images = create_dog_pyramid(gaussian_images)
# 결과 표시
display_pyramids(gaussian_images, dog_images)
if __name__ == "__main__":
main()
(2) 옥타브를 구성하는 가우시안 영상의 개수를 지정할 수 있도록 프로그램을 확장하시오.
import cv2
import numpy as np
import matplotlib.pyplot as plt
def compute_sigma_values(scales_per_octave, sigma0=1.6):
"""
옥타브 내의 스케일 수에 따른 시그마 값들을 계산하는 함수
Parameters:
scales_per_octave: 옥타브 당 스케일의 수
sigma0: 초기 시그마 값 (기본값 1.6)
Returns:
시그마 값들의 리스트
"""
k = 2 ** (1.0 / scales_per_octave) # 시그마 값 사이의 비율
sigmas = [sigma0 * (k ** i) for i in range(scales_per_octave + 3)] # +3은 DOG 계산을 위한 여분
return sigmas
def create_gaussian_pyramid(image, scales_per_octave=3, num_octaves=None, sigma0=1.6):
"""
가우시안 피라미드를 생성하는 함수
Parameters:
image: 입력 이미지
scales_per_octave: 옥타브 당 스케일의 수
num_octaves: 옥타브의 수 (None인 경우 이미지 크기에 따라 자동 결정)
sigma0: 초기 시그마 값
"""
if num_octaves is None:
num_octaves = int(np.log2(min(image.shape))) - 3
gaussian_images = []
sigmas = compute_sigma_values(scales_per_octave, sigma0)
# 각 옥타브에 대해
current_image = image.copy()
for octave in range(num_octaves):
octave_images = []
# 각 스케일에 대해
for sigma in sigmas:
# 커널 크기 계산 (시그마의 6배, 홀수)
ksize = int(6 * sigma)
if ksize % 2 == 0:
ksize += 1
blurred = cv2.GaussianBlur(current_image, (ksize, ksize), sigma)
octave_images.append(blurred)
gaussian_images.append(octave_images)
# 다음 옥타브를 위해 이미지 크기를 절반으로
current_image = cv2.resize(octave_images[-3], None, fx=0.5, fy=0.5,
interpolation=cv2.INTER_LINEAR)
return gaussian_images, sigmas
def create_dog_pyramid(gaussian_images):
"""
DOG(Difference of Gaussian) 피라미드를 생성하는 함수
"""
dog_images = []
# 각 옥타브에 대해
for octave_images in gaussian_images:
dogs_in_octave = []
# 연속된 가우시안 이미지들의 차이를 계산
for i in range(len(octave_images)-1):
dog = cv2.subtract(octave_images[i+1], octave_images[i])
dogs_in_octave.append(dog)
dog_images.append(dogs_in_octave)
return dog_images
def display_pyramids(gaussian_images, dog_images, sigmas):
"""
가우시안 피라미드와 DOG 피라미드를 시각화하는 함수
"""
num_octaves = len(gaussian_images)
scales_per_octave = len(gaussian_images[0]) - 3 # -3은 DOG를 위한 여분
# 가우시안 피라미드 표시
plt.figure(1, figsize=(15, 4*num_octaves))
plt.suptitle('Gaussian Pyramid')
for octave in range(num_octaves):
for scale in range(scales_per_octave + 3):
plt.subplot(num_octaves, scales_per_octave + 3,
octave*(scales_per_octave + 3) + scale + 1)
plt.imshow(gaussian_images[octave][scale], cmap='gray')
plt.title(f'Octave {octave+1}\nσ = {sigmas[scale]:.4f}')
plt.axis('off')
# DOG 피라미드 표시
plt.figure(2, figsize=(15, 4*num_octaves))
plt.suptitle('Difference of Gaussian (DOG)')
for octave in range(num_octaves):
for scale in range(len(dog_images[octave])):
plt.subplot(num_octaves, scales_per_octave + 2, # +2 because DOG has one less image
octave*(scales_per_octave + 2) + scale + 1)
plt.imshow(dog_images[octave][scale], cmap='gray')
plt.title(f'Octave {octave+1}\nDOG {scale+1}')
plt.axis('off')
plt.show()
def main():
# 이미지 로드
image = cv2.imread('input_image.jpg', cv2.IMREAD_GRAYSCALE)
# 이미지가 없는 경우 샘플 이미지 생성
if image is None:
image = np.zeros((300, 400), dtype=np.uint8)
cv2.circle(image, (200, 150), 50, 255, -1)
# 파라미터 설정
scales_per_octave = 3 # 옥타브 당 스케일 수
num_octaves = 2 # 옥타브 수
sigma0 = 1.6 # 초기 시그마 값
# 가우시안 피라미드 생성
gaussian_images, sigmas = create_gaussian_pyramid(
image,
scales_per_octave=scales_per_octave,
num_octaves=num_octaves,
sigma0=sigma0
)
# DOG 피라미드 생성
dog_images = create_dog_pyramid(gaussian_images)
# 결과 표시
display_pyramids(gaussian_images, dog_images, sigmas)
if __name__ == "__main__":
main()
메인 함수에서 옥타브 수를 수정할 수 있습니다. 구현 해보시고 그럴리는 없겠지만 오류 발생하시면 댓글 달아주세요.
07 ~ 08번은 넘기도록 하겠습니다.
08번은 코드로 작성인데, 아직 이해가 좀 부족했는지 완벽하지 않아 추후에 완벽하게 구현하면 수정해서 올리도록 하겠습니다.
(까먹을지도 모르니 댓글로 재촉 해주셔도 됩니다.)
'공부(연습문제 정답) > 컴퓨터 비전과 딥러닝' 카테고리의 다른 글
[4장]컴퓨터 비전과 딥러닝 : 에지와 영역 (4) | 2024.09.19 |
---|---|
[3장] 컴퓨터 비전과 딥러닝 : 영상처리 (5) | 2024.09.19 |
[2장]컴퓨터 비전과 딥러닝: OpenCV로 시작하는 컴퓨터 비전 (4) | 2024.09.18 |