5 분 소요

이 글에서는 SVD가 무엇인지 간단하게 설명하고, Computer Vision와 AI에서 언제 어떻게 쓰이는지 예시와 함께 다룰 예정입니다. (본인 공부 및 기록용)😁

SVD란?

SVD는 행렬을 분해하는 방법 중 하나입니다. ‘특이값 분해’라고도 불리는데, 복잡한 행렬을 더 간단한 형태로 나누어줍니다. 이를 통해 데이터의 패턴을 발견하거나 차원을 축소하는 등 다양한 응용이 가능합니다.

SVD는 다음과 같은 세 개의 행렬로 원래의 행렬을 분해합니다.

$ A = U \sum V^T $

A: m * n 행렬, U: m * n 직교 행렬, 열 벡터들은 A의 좌특이 벡터, $\sum$: m * n 대각 행렬, 대각선 성분들이 A의 특이값(Singular Values), $V^T$: n * n 직교 행렬, 행 벡터들은 A의 우특이 벡터,

컴퓨터 비전에서의 SVD 활용

컴퓨터 비전에서 SVD는 주로 이미지 압축, 노이즈 제거, 객체 인식 등 여러 작업에 사용됩니다.

이미지 압축

이미지 압축은 이미지를 저장하거나 전송할 때 필요한 공간을 줄이는 데 중요합니다. SVD를 사용하면 고차원의 이미지를 저차원으로 압축한 후, 필요한 경우 다시 복원할 수 있습니다. 아래 코드를 실행하면, 원본 이미지와 압축된 이미지를 비교할 수 있습니다. k 값을 조절하면서 압축률과 이미지 품질의 변화를 볼 수 있습니다.

import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color
from skimage.transform import resize

# 예제 이미지 불러오기
image = color.rgb2gray(data.astronaut())
image = resize(image, (256, 256))

# SVD 분해
U, Sigma, VT = np.linalg.svd(image)

# k 값을 통해 차원 축소
k = 50
compressed_image = np.dot(U[:, :k], np.dot(np.diag(Sigma[:k]), VT[:k, :]))

# 원본 이미지와 압축 이미지를 시각화
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(image, cmap='gray')
plt.axis('off')

plt.subplot(1, 2, 2)
plt.title('Compressed Image with k = {}'.format(k))
plt.imshow(compressed_image, cmap='gray')
plt.axis('off')

plt.show()

노이즈 제거

이미지에는 종종 노이즈가 포함되어 있을 수 있습니다. SVD를 사용하여 노이즈를 제거하고 더 깨끗한 이미지를 얻을 수 있습니다. 이는 특히 의료 이미지나 위성 이미지 처리에서 중요합니다.

import numpy as np
import matplotlib.pyplot as plt
from skimage import data, color, util

# 예제 이미지 불러오기
image = color.rgb2gray(data.astronaut())
image = resize(image, (256, 256))

# 노이즈 추가
noisy_image = util.random_noise(image, mode='gaussian', var=0.1)

# SVD 분해
U, Sigma, VT = np.linalg.svd(noisy_image)

# k 값을 통해 차원 축소 및 노이즈 제거
k = 50
denoised_image = np.dot(U[:, :k], np.dot(np.diag(Sigma[:k]), VT[:k, :]))

# 원본 이미지, 노이즈 이미지, 노이즈 제거 이미지를 시각화
plt.figure(figsize=(15, 5))
plt.subplot(1, 3, 1)
plt.title('Original Image')
plt.imshow(image, cmap='gray')
plt.axis('off')

plt.subplot(1, 3, 2)
plt.title('Noisy Image')
plt.imshow(noisy_image, cmap='gray')
plt.axis('off')

plt.subplot(1, 3, 3)
plt.title('Denoised Image with k = {}'.format(k))
plt.imshow(denoised_image, cmap='gray')
plt.axis('off')

plt.show()

포인트 클라우드 분산과 기하학적 구조 분석

포인트 클라우드 데이터에 SVD를 적용하면, 데이터의 주요 방향을 찾고 데이터의 분산을 분석할 수 있습니다. 예를 들어, 포인트 클라우드의 분산이 가장 큰 방향을 찾는 데 SVD를 사용할 수 있습니다. 이를 통해 포인트 클라우드의 주요 축을 결정할 수 있습니다.

C++ 코드에서 SVD의 적용 포인트 클라우드 데이터에서 SVD를 사용하여 키포인트 프레임을 선택하는 과정은 다음과 같습니다.

1. 데이터 샘플링: 포인트 클라우드에서 일정 비율로 샘플링된 데이터를 선택합니다.
2. 중심 계산: 샘플링된 데이터의 중심을 계산합니다.
3. SVD 수행: 샘플링된 데이터에 대해 SVD를 수행합니다.
4. 특이값 비교: SVD의 결과로 얻어진 첫 번째 특이값을 임계값과 비교하여 키포인트 프레임을 선택합니다.
5. 변환 적용 및 저장: 선택된 포인트 클라우드에 변환을 적용하고 저장합니다.
void processPointCloudSVD(
    const std::vector<std::string>& ply_files,
    const std::vector<Matrix4f>& transforms,
    PointCloud::Ptr& merged_pointcloud,
    std::vector<Vector3f>& centers,
    std::vector<int>& used_indices,
    std::vector<std::pair<PointCloud::Ptr, std::string>>& save_queue,
    std::mutex& mtx,
    float sampling_ratio,
    float svd_threshold,
    int start_idx,
    int end_idx,
    const std::string& sampling_folder) {
        /*
        SVD를 사용하여 포인트 클라우드를 처리하는 함수입니다. 샘플링, SVD 수행, 첫 번째 특이값과 임계값 비교, 변환 및 저장의 과정을 수행합니다.
        */

    for (int i = start_idx; i < end_idx; ++i) {
        PointCloud::Ptr cloud(new PointCloud);
        if (!customPlyRead(ply_files[i], cloud)) {
            continue;
        }

        // 샘플링
        PointCloud::Ptr sampled_points = randomSample(cloud, sampling_ratio);
        Vector3f current_center = computeCenter(sampled_points);

        // 데이터 준비
        Eigen::MatrixXf data(sampled_points->size(), 3);
        for (size_t j = 0; j < sampled_points->size(); ++j) {
            data(j, 0) = sampled_points->points[j].x;
            data(j, 1) = sampled_points->points[j].y;
            data(j, 2) = sampled_points->points[j].z;
        }

        // SVD 수행
        Eigen::MatrixXf U, V;
        Eigen::VectorXf S;
        performSVD(data, U, S, V);

        // 첫 번째 특이값과 임계값 비교
        bool should_add = false;
        if (S(0) > svd_threshold) {
            should_add = true;
            std::lock_guard<std::mutex> lock(mtx);
            centers.push_back(current_center);
            used_indices.push_back(i);
        }

        // 선택된 포인트 클라우드 변환 및 저장
        if (should_add) {
            Matrix4f transform = transforms[i];
            PointCloud::Ptr transformed_points(new PointCloud);
            pcl::transformPointCloud(*sampled_points, *transformed_points, transform);

            {
                std::lock_guard<std::mutex> lock(mtx);
                *merged_pointcloud += *transformed_points;

                std::ostringstream oss;
                oss << sampling_folder << "/" << std::setw(4) << std::setfill('0') << used_indices.size() - 1 << ".ply";
                std::string save_file_path = oss.str();

                save_queue.emplace_back(transformed_points, save_file_path);
            }
        }
    }
}

void performSVD(const Eigen::MatrixXf& data, Eigen::MatrixXf& U, Eigen::VectorXf& S, Eigen::MatrixXf& V) {
    /*
    주어진 데이터 행렬에 대해 SVD를 수행하는 함수입니다. Eigen 라이브러리를 사용하여 SVD를 수행합니다.
    */
    Eigen::JacobiSVD<Eigen::MatrixXf> svd(data, Eigen::ComputeThinU | Eigen::ComputeThinV);
    U = svd.matrixU();
    S = svd.singularValues();
    V = svd.matrixV();
}

차원축소

고차원 데이터낮은 차원으로 줄이면서도 중요한 정보를 유지하는 기법입니다. SVD는 이 과정에서 주로 사용되며, 데이터의 패턴을 파악하고 계산 효율성을 높이는 데 도움을 줍니다.

import numpy as np
from sklearn.decomposition import TruncatedSVD
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt

# 데이터 로드
digits = load_digits()
X = digits.data
y = digits.target

# SVD를 사용한 차원 축소 #
svd = TruncatedSVD(n_components=2)
X_reduced = svd.fit_transform(X)

# 결과 시각화
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap='viridis')
plt.colorbar()
plt.title('Digits dataset reduced to 2D using SVD')
plt.show()

LSI(Latent Semantic Indexing)와 SVD

LSI는 텍스트 데이터를 분석해 문서 간의 유사성을 파악하는 데 사용됩니다. SVD는 텍스트 데이터의 단어-문서 행렬을 분해하여 숨겨진 의미적 구조를 파악하는 데 중요한 역할을 합니다.

from sklearn.decomposition import TruncatedSVD
from sklearn.feature_extraction.text import TfidfVectorizer

# 예제 문서 데이터
documents = [
    "The cat in the hat disabled his powers.",
    "A quick brown fox jumps over the lazy dog.",
    "The sun is shining in the blue sky."
]

# TF-IDF 행렬 생성
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(documents)

# SVD를 통한 LSI 수행
svd = TruncatedSVD(n_components=2)
X_lsi = svd.fit_transform(X)

print("LSI로 변환된 문서 벡터:")
print(X_lsi)

추천 시스템 (Recommendation Systems)와 SVD

추천 시스템에서는 사용자-아이템 행렬을 SVD로 분해하여 사용자와 아이템 간의 잠재력 관계를 파악합니다. 이를 통해 사용자가 좋아할 만한 아이템을 예측할 수 있습니다.

import numpy as np
from scipy.sparse.linalg import svds

# 예제 사용자-아이템 평점 행렬
ratings = np.array([
    [5, 4, 0, 0],
    [4, 0, 0, 2],
    [0, 0, 5, 4],
    [0, 3, 4, 0],
])

# SVD 수행
U, sigma, Vt = svds(ratings, k=2)
sigma = np.diag(sigma)

# 예측 행렬 계산
predicted_ratings = np.dot(np.dot(U, sigma), Vt)

print("예측된 평점 행렬:")
print(predicted_ratings)

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

댓글남기기