본문 바로가기
공부(연습문제 정답)/컴퓨터 비전과 딥러닝

[5장]컴퓨터 비전과 딥러닝: 지역 특징

by lovedeveloping 2024. 10. 25.
반응형

교재 참고하기
컴퓨터 비전과 딥러닝



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가 속도 향상을 달성하는 원리를 설명하시오.

  1. 적분 이미지 사용: 이미지 전체의 합을 미리 계산
  2. BoxFilter를 이용해 헤이시안 행렬을 근사화
  3. 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번은 코드로 작성인데, 아직 이해가 좀 부족했는지 완벽하지 않아 추후에 완벽하게 구현하면 수정해서 올리도록 하겠습니다.

(까먹을지도 모르니 댓글로 재촉 해주셔도 됩니다.)

반응형