3 분 소요

3D 포인트 클라우드 처리 분야에서 키포인트를 식별하는 것은 객체 인식, 정합, 장면 이해 등 다양한 응용 분야에서 중요합니다. 3D 포인트 클라우드에서 키포인트를 감지하는 데 사용되는 방법 중 하나인 Intrinsic Shape Signatures (ISS) 알고리즘이 무엇인지 간단하게 설명하고, Computer Vision와 AI에서 언제 어떻게 쓰이는지 예시와 함께 다룰 예정입니다. (본인 공부 및 기록용)😁

ISS(Intrinsic Shape Signatures)란?

ISS(Intrinsic Shape Signatures)3D 포인트 클라우드 데이터에서 중요한 포인트(키포인트)를 검출하기 위한 알고리즘입니다. 이 알고리즘은 포인트 클라우드의 각 포인트 주변의 형상을 분석하여, 해당 포인트가 중요한지 아닌지를 판단합니다. 쉽게 말해서, ISS는 3D 데이터에서 특별한 포인트들을 찾는 데 사용됩니다. Yu Zhong , “Intrinsic Shape Signatures: A Shape Descriptor for 3D Object Recognition”, 2009 에서 처음 제안됐습니다.

OpenCV ISS 참고

ISS 키포인트 검출 원리

ISS 키포인트 검출은 포인트 주변의 점 분포를 분석하여 중요한 포인트를 찾습니다. 이를 위해 주로 고유값 분해(Eigenvalue Decomposition, EVD)를 사용합니다. 다음은 ISS의 주요 단계들입니다.

1. 포인트 주변 영역 설정

각 포인트 $P_i$에 대해 반경 r을 설정하여 이 반경 내의 이웃 포인트들을 구합니다. 이웃 포인트 집합을 $N_i$라 합니다.

2. 공분산 행렬 계산

포인트 $P_i$의 이웃 포인트 집합 $N_i$에 대한 공분산 행렬을 계산합니다. 공분산 행렬 C는 다음과 같이 정의됩니다.

$ C = \frac{1}{|N_i|}\sum_{P_j \in N_i}(P_j - \mu _i)(P_j - \mu _i)^T $

여기서 $\mu _i$는 이웃 포인트들의 평균입니다.

3. 고유값 분해

공분산 행렬 C를 고유값 분해하여 고유값과 고유벡터를 얻습니다. 고유값은 $ \lambda _1 \lambda _2 \lambda _3$로 정렬됩니다.

단, $ \lambda _1 \leq \lambda _2 \leq \lambda _3 $

4. 고유값 비율 계산

고유값 비율을 사용하여 keypoint 여부를 결정합니다.

$ \frac{\lambda _2}{\lambda _3} : 낮은 값은 평평한 영역을 나타냅니다 \\ \frac{\lambda _1}{\lambda _2} : 낮은 값은 선형 구조를 나타냅니다. $

이 비율이 일정 임계값을 초과하면 해당 포인트를 keypoint로 간주합니다.

5. 비최소 억제

keypoint로 선택된 포인트들 중에서 더 중요한 포인트를 선택하기 위해서 비최소 억제(Non-Maximum Suppression)를 적용합니다. 이는 주변 포인트와 비교하여 고유값이 최대인 포인트만을 선택하는 과정입니다.

ISS 장점 과 단점

+강력한 특징점 검출: 3D 포인트 클라우드 데이터에서 강력하고 안정적인 특징 포인트를 검출할 수 있습니다. +회전 불변성: 포인트 클라우드 회전에 대해 불변성을 가집니다. +간단한 계산: 공분산 행렬 계산과 고유값 분해를 사용하여 비교적 간단하게 특징 포인트를 검출합니다.

-밀도 의존성: 포인트 클라우드의 밀도가 불균일할 경우 성능이 저하될 수 있습니다. -노이즈 민감성: 노이즈에 민감하여 정확한 키포인트 검출이 어려울 수 있습니다.

import numpy as np
from sklearn.neighbors import NearestNeighbors

def compute_covariance_matrix(points):
    """
    공분산 행렬 계산

    params points (np.array): 포인트들의 배열 (N x 3)

    Returns np.array: 주어진 포인트들의 공분산 행렬 (3 x 3)
    """
    mean = np.mean(points, axis=0) # 각 축에 대한 평균 계산
    centered_points = points - mean # 평균을 뺀 중심화된 포인트들
    covariance_matrix = np.dot(centered_points.T, centered_points) / len(points) # 공분산 행렬 계산
    return covariance_matrix

def iss_keypoints(points, radius, gamma21, gamma32):
    """
    ISS 키포인트들을 추출합니다.
    
    params points (np.array): 포인트들의 배열 (N x 3)
    params radius (float): 이웃 포인트를 찾기 위한 반경
    params gamma21 (float): 두 번째 고유값과 세 번째 고유값의 비율에 대한 임계값
    params gamma32 (float): 첫 번째 고유값과 두 번째 고유값의 비율에 대한 임계값
    
    Returns np.array: 검출된 ISS 키포인트들의 배열
    """
    keypoints = [] # 키포인트들을 저장할 리스트
    neighbors = NearestNeighbors(radius=radius) # radius 이내 이웃포인트들 검출
    neighbors.fit(points) # 포인트들로 NearestNeighbors 객체를 훈련시킴
    
    for point in points:
        indices = neighbors.radius_neighbors([point], return_distance=False)[0] # 반경 이내의 이웃 포인트 인덱스
        if len(indices) < 3: # 이웃 포인트가 3개 미만이면 건너뜀
            continue
        
        neighborhood = points[indices]
        covariance_matrix = compute_covariance_matrix(neighborhood) # 이웃 포인트들의 공분산 행렬 계산
        eigenvalues, _ = np.linalg.eigh(covariance_matrix) # 공분산 행렬의 고유값 계산
        eigenvalues = np.sort(eigenvalues) # 고유값을 오름차순으로 정렬
        
        # 고유값 비율을 사용하여 키포인트 여부 결정
        if eigenvalues[1] / eigenvalues[2] < gamma21 and eigenvalues[0] / eigenvalues[1] < gamma32:
            keypoints.append(point) # 조건을 만족하면 키포인트로 추가
    
    return np.array(keypoints) # 키포인트들을 배열로 반환

# 예제 포인트 클라우드 데이터
points = np.random.rand(1000, 3)

# ISS 파라미터
radius = 0.05
gamma21 = 0.8
gamma32 = 0.8

keypoints = iss_keypoints(points, radius, gamma21, gamma32)

이것저것 공부하면서 ISS 기법에 대해 새로 알게 되는 내용은 계속 추가할 예정입니다. 궁금한 것들이나 추가 및 수정했으면 좋겠는 거 말해주시면 좋을 거 같아요. 좋은 하루 보내시길 바래요 :)

댓글남기기